chiark / gitweb /
Missed a paren. Un-`toys'-ify.
[misc] / splitconf
1 #! /usr/bin/tclsh
2
3 #----- Miscellaneous utilities ----------------------------------------------
4
5 # die MSG
6 #
7 # Something didn't work.  Exit right now.
8
9 proc die {msg} {
10   global argv0
11   puts stderr "$argv0: $msg"
12   exit 1
13 }
14
15 # usage FILE
16 #
17 # Write a usage message to FILE, which is a file handle.
18
19 proc usage {file} {
20   global argv0
21   puts $file "Usage: \n\
22         $argv0 [-s] FILE\n
23         $argv0 -u OUTPUT FILE FILE ..."
24 }
25
26 # clear-arrays ARRAY ...
27 #
28 # Make each named ARRAY exist and be empty.
29
30 proc clear-arrays {args} {
31   foreach i $args {
32     upvar 1 $i v
33     unset i
34     array set v {}
35   }
36 }
37
38 #------ Write-safe ----------------------------------------------------------
39
40 # write-safe STUFF [TIDY]
41 #
42 # Do some safe I/O.  If STUFF succeeds, do TIDY and commit the modifications;
43 # otherwise, do TIDY and back out all the changes.  See also write-safe-open,
44 # write-safe-file and write-safe-delete.
45
46 proc write-safe {stuff {tidy {}}} {
47   global _ws_close _ws_del _ws_new
48   clear-arrays _ws_del _ws_new
49   set _ws_close {}
50
51   if {[set rc [catch {
52     uplevel 1 $stuff
53   } err]]} {
54     foreach f $_ws_close { catch { close $f } }
55     foreach f [array names _ws_new] { catch { file delete -- $f.new } }
56     catch { uplevel 1 $tidy }
57     return -code $rc $err
58   }
59   foreach f $_ws_close { catch { close $f } }
60   clear-arrays all
61   foreach f [concat [array names _ws_old] [array names _ws_del]] {
62     set all($f) 0
63   }
64   if {[set rc [catch {
65     foreach f [array names all] {
66       if {[file exists $f]} {
67         file delete -- $f.old
68         file copy -force -- $f $f.old
69       }
70       set old($f) 0
71     }
72     foreach f [array names _ws_new] { file rename -force -- $f.new $f }
73     foreach f [array names _ws_del] { file delete -- $f }
74   } err]]} {
75     foreach f [array names _ws_new] { catch { file delete -- $f.new } }
76     foreach f [array names old] { file rename -force -- $f.old $f }
77     catch { uplevel 1 $tidy }
78     return -code $rc $err
79   }
80   foreach i [array names all] { catch { file delete -- $i.old } }
81   catch { uplevel 1 $tidy }
82   return {}
83 }
84
85 # write-safe-open NAME [TRANS]
86 #
87 # Open file NAME for writing, with the translation mode TRANS (default is
88 # `auto'); return the file handle.  The file NAME is not destroyed until the
89 # changes are committed by an enclosing write-safe completing.  You can close
90 # the file handle if you like; write-safe will close it automatically anyway.
91
92 proc write-safe-open {name {trans auto}} {
93   global _ws_close _ws_new
94   if {[file isdirectory $name]} { error "`$name' is a directory" }
95   set f [open $name.new w]
96   fconfigure $f -translation $trans
97   lappend _ws_close $f
98   set _ws_new($name) 0
99   return $f
100 }
101
102 # write-safe-delete NAME
103 #
104 # Delete file NAME.  The file isn't actually removed until the enclosing
105 # write-safe completes.
106
107 proc write-safe-delete {name} {
108   global _ws_del
109   set _ws_del($name) 0
110 }
111
112 # write-safe-file NAME CONTENTS [TRANS]
113 #
114 # Write CONTENTS to FILE, using translation mode TRANS (default `auto').  The
115 # file isn't actually replaced until the changes are committed by an
116 # enclosing write-safe completing.
117
118 proc write-safe-file {name contents {trans auto}} {
119   set f [write-safe-open $name $trans]
120   puts -nonewline $f $contents
121   close $f
122 }
123
124 # read-file NAME [TRANS]
125 #
126 # Evaluates to the contents of the file NAME under translation mode TRANS
127 # (default `auto').
128
129 proc read-file {name {trans auto}} {
130   set f [open $name]
131   fconfigure $f -translation $trans
132   set c [read $f]
133   close $f
134   return $c
135 }
136
137 #----- Splitconf-specific stuff ---------------------------------------------
138
139 # write-safe-manifest F L
140 #
141 # Writes the list of filenames L to the manifest file associated with config
142 # file F.
143
144 proc write-safe-manifest {f l} {
145   set f [write-safe-open $f.files]
146   foreach i $l { puts $f $i }
147   close $f
148 }
149
150 # old-files CONF
151 #
152 # Returns the filenames in the current manifest of the config file CONF.
153
154 proc old-files {conf} {
155   set old {}
156   if {[file exists $conf.files]} {
157     set f [open $conf.files]
158     while {[gets $f line] >= 0} { lappend old $line }
159     close $f
160   }
161   return $old
162 }
163
164 #----- Main code ------------------------------------------------------------
165
166 set job "split"
167 while {[llength $argv]} {
168   switch -glob -- [lindex $argv 0] {
169     "-u" - "--unsplit" {
170       set job "unsplit"
171       if {[llength $argv] < 2} { die "option `-u' needs an argument" }
172       set output [lindex $argv 1]
173       set argv [lrange $argv 1 end]
174     }
175     "-d" - "--delete" { set job "delete" }
176     "-s" - "--split" { set job "split" }
177     "-h" - "--help" { usage stdout; exit 0 }
178     "-" { break }
179     "--" { set argv [lrange $argv 1 end]; break }
180     "-*" { die "unknown option `[lindex $argv 0]'"; exit 1 }
181     default { break }
182   }
183   set argv [lrange $argv 1 end]
184 }
185
186 set rc 0
187 clear-arrays opt
188 array set opt {
189   prefix ""
190   before ""
191   after ""
192 }
193 switch $job {
194   "unsplit" {
195     set f "\#\# automatically generated by splitconf\n\n"
196     set ff {}
197     foreach i $argv {
198       if {[catch {
199         set c [read-file $i]
200         append f "\[$i\]\n$c\n"
201         lappend ff $i
202       } msg]} {
203         set rc 1
204       }
205     }
206     write-safe {
207       write-safe-file $output $f
208       write-safe-manifest $output $ff
209     }
210   }
211   "delete" {
212     if {[llength $argv] != 1} { die "need exactly one filename" }
213     set conf [lindex $argv 0]
214     set old [old-files $conf]
215     write-safe {
216       foreach i $old { write-safe-delete $i }
217       write-safe-delete $conf.files
218     }
219   }
220   "split" {
221     if {[llength $argv] != 1} { die "need exactly one filename" }
222     set conf [lindex $argv 0]
223     set old [old-files $conf]
224     set c [open $conf r]
225     catch { unset o }
226     set spill ""
227     set donebefore 0
228     array set new {}
229     write-safe {
230       while {[gets $c line] >= 0} {
231         if {[regexp -- {^\[(.*)\]\s*$} $line . name]} {
232           if {[info exists o]} {
233             close $o
234           } elseif {!$donebefore} {
235             exec "sh" "-c" $opt(before) <@ stdin >@ stdout 2>@ stderr
236             set donebefore 1
237           }
238           if {[string equal $name ""]} {
239             catch { unset o }
240           } else {
241             set name "$opt(prefix)$name"
242             set o [write-safe-open $name]
243             set new($name) 1
244             set spill ""
245           }
246         } elseif {[info exists o]} {
247           switch -regexp -- $line {
248             {^\s*$} { append spill "$line\n" }
249             {^\#\#} { }
250             {^\!} {
251               puts -nonewline $o "$spill[string range $line 1 end]\n"
252               set spill ""
253             }
254             default { puts -nonewline $o "$spill$line\n"; set spill "" }
255           }
256         } elseif {[regexp -- {^\s*(\#|$)} $line]} {
257           continue
258         } elseif {[regexp -- \
259                        {^\s*([-./\w]+)\s*=\s*(.*\S|)\s*$} $line . k v]} {
260           if {![info exists opt($k)]} {
261             error "unknown configuration option `$k'"
262           } else {
263             set opt($k) $v
264           }
265         } elseif {[regexp -- \
266                        {^\s*([-./\w]+)\s*:\s*(.*\S|)\s*$} $line . name d]} {
267           if {!$donebefore} {
268             exec "sh" "-c" $opt(before) <@ stdin >@ stdout 2>@ stderr
269             set donebefore 1
270           }
271           set name "$opt(prefix)$name"
272           set new($name) 1
273           write-safe-file $name "$d\n"
274         } else {
275           error "unknown preamble directive"
276         }
277       }
278       if {[info exists o]} {
279         close $o
280       }
281       close $c
282       foreach i $old {
283         if {![info exists new($i)]} { write-safe-delete $i }
284       }
285       write-safe-manifest $conf [array names new]
286     } {
287       exec "sh" "-c" $opt(after) <@ stdin >@ stdout 2>@ stderr
288     }
289   }
290 }
291
292 #----- That's all, folks ----------------------------------------------------