jpayne@68
|
1 # optparse.tcl --
|
jpayne@68
|
2 #
|
jpayne@68
|
3 # (private) Option parsing package
|
jpayne@68
|
4 # Primarily used internally by the safe:: code.
|
jpayne@68
|
5 #
|
jpayne@68
|
6 # WARNING: This code will go away in a future release
|
jpayne@68
|
7 # of Tcl. It is NOT supported and you should not rely
|
jpayne@68
|
8 # on it. If your code does rely on this package you
|
jpayne@68
|
9 # may directly incorporate this code into your application.
|
jpayne@68
|
10
|
jpayne@68
|
11 package require Tcl 8.5-
|
jpayne@68
|
12 # When this version number changes, update the pkgIndex.tcl file
|
jpayne@68
|
13 # and the install directory in the Makefiles.
|
jpayne@68
|
14 package provide opt 0.4.8
|
jpayne@68
|
15
|
jpayne@68
|
16 namespace eval ::tcl {
|
jpayne@68
|
17
|
jpayne@68
|
18 # Exported APIs
|
jpayne@68
|
19 namespace export OptKeyRegister OptKeyDelete OptKeyError OptKeyParse \
|
jpayne@68
|
20 OptProc OptProcArgGiven OptParse \
|
jpayne@68
|
21 Lempty Lget \
|
jpayne@68
|
22 Lassign Lvarpop Lvarpop1 Lvarset Lvarincr \
|
jpayne@68
|
23 SetMax SetMin
|
jpayne@68
|
24
|
jpayne@68
|
25
|
jpayne@68
|
26 ################# Example of use / 'user documentation' ###################
|
jpayne@68
|
27
|
jpayne@68
|
28 proc OptCreateTestProc {} {
|
jpayne@68
|
29
|
jpayne@68
|
30 # Defines ::tcl::OptParseTest as a test proc with parsed arguments
|
jpayne@68
|
31 # (can't be defined before the code below is loaded (before "OptProc"))
|
jpayne@68
|
32
|
jpayne@68
|
33 # Every OptProc give usage information on "procname -help".
|
jpayne@68
|
34 # Try "tcl::OptParseTest -help" and "tcl::OptParseTest -a" and
|
jpayne@68
|
35 # then other arguments.
|
jpayne@68
|
36 #
|
jpayne@68
|
37 # example of 'valid' call:
|
jpayne@68
|
38 # ::tcl::OptParseTest save -4 -pr 23 -libsok SybTcl\
|
jpayne@68
|
39 # -nostatics false ch1
|
jpayne@68
|
40 OptProc OptParseTest {
|
jpayne@68
|
41 {subcommand -choice {save print} "sub command"}
|
jpayne@68
|
42 {arg1 3 "some number"}
|
jpayne@68
|
43 {-aflag}
|
jpayne@68
|
44 {-intflag 7}
|
jpayne@68
|
45 {-weirdflag "help string"}
|
jpayne@68
|
46 {-noStatics "Not ok to load static packages"}
|
jpayne@68
|
47 {-nestedloading1 true "OK to load into nested children"}
|
jpayne@68
|
48 {-nestedloading2 -boolean true "OK to load into nested children"}
|
jpayne@68
|
49 {-libsOK -choice {Tk SybTcl}
|
jpayne@68
|
50 "List of packages that can be loaded"}
|
jpayne@68
|
51 {-precision -int 12 "Number of digits of precision"}
|
jpayne@68
|
52 {-intval 7 "An integer"}
|
jpayne@68
|
53 {-scale -float 1.0 "Scale factor"}
|
jpayne@68
|
54 {-zoom 1.0 "Zoom factor"}
|
jpayne@68
|
55 {-arbitrary foobar "Arbitrary string"}
|
jpayne@68
|
56 {-random -string 12 "Random string"}
|
jpayne@68
|
57 {-listval -list {} "List value"}
|
jpayne@68
|
58 {-blahflag -blah abc "Funny type"}
|
jpayne@68
|
59 {arg2 -boolean "a boolean"}
|
jpayne@68
|
60 {arg3 -choice "ch1 ch2"}
|
jpayne@68
|
61 {?optarg? -list {} "optional argument"}
|
jpayne@68
|
62 } {
|
jpayne@68
|
63 foreach v [info locals] {
|
jpayne@68
|
64 puts stderr [format "%14s : %s" $v [set $v]]
|
jpayne@68
|
65 }
|
jpayne@68
|
66 }
|
jpayne@68
|
67 }
|
jpayne@68
|
68
|
jpayne@68
|
69 ################### No User serviceable part below ! ###############
|
jpayne@68
|
70
|
jpayne@68
|
71 # Array storing the parsed descriptions
|
jpayne@68
|
72 variable OptDesc
|
jpayne@68
|
73 array set OptDesc {}
|
jpayne@68
|
74 # Next potentially free key id (numeric)
|
jpayne@68
|
75 variable OptDescN 0
|
jpayne@68
|
76
|
jpayne@68
|
77 # Inside algorithm/mechanism description:
|
jpayne@68
|
78 # (not for the faint hearted ;-)
|
jpayne@68
|
79 #
|
jpayne@68
|
80 # The argument description is parsed into a "program tree"
|
jpayne@68
|
81 # It is called a "program" because it is the program used by
|
jpayne@68
|
82 # the state machine interpreter that use that program to
|
jpayne@68
|
83 # actually parse the arguments at run time.
|
jpayne@68
|
84 #
|
jpayne@68
|
85 # The general structure of a "program" is
|
jpayne@68
|
86 # notation (pseudo bnf like)
|
jpayne@68
|
87 # name :== definition defines "name" as being "definition"
|
jpayne@68
|
88 # { x y z } means list of x, y, and z
|
jpayne@68
|
89 # x* means x repeated 0 or more time
|
jpayne@68
|
90 # x+ means "x x*"
|
jpayne@68
|
91 # x? means optionally x
|
jpayne@68
|
92 # x | y means x or y
|
jpayne@68
|
93 # "cccc" means the literal string
|
jpayne@68
|
94 #
|
jpayne@68
|
95 # program :== { programCounter programStep* }
|
jpayne@68
|
96 #
|
jpayne@68
|
97 # programStep :== program | singleStep
|
jpayne@68
|
98 #
|
jpayne@68
|
99 # programCounter :== {"P" integer+ }
|
jpayne@68
|
100 #
|
jpayne@68
|
101 # singleStep :== { instruction parameters* }
|
jpayne@68
|
102 #
|
jpayne@68
|
103 # instruction :== single element list
|
jpayne@68
|
104 #
|
jpayne@68
|
105 # (the difference between singleStep and program is that \
|
jpayne@68
|
106 # llength [lindex $program 0] >= 2
|
jpayne@68
|
107 # while
|
jpayne@68
|
108 # llength [lindex $singleStep 0] == 1
|
jpayne@68
|
109 # )
|
jpayne@68
|
110 #
|
jpayne@68
|
111 # And for this application:
|
jpayne@68
|
112 #
|
jpayne@68
|
113 # singleStep :== { instruction varname {hasBeenSet currentValue} type
|
jpayne@68
|
114 # typeArgs help }
|
jpayne@68
|
115 # instruction :== "flags" | "value"
|
jpayne@68
|
116 # type :== knowType | anyword
|
jpayne@68
|
117 # knowType :== "string" | "int" | "boolean" | "boolflag" | "float"
|
jpayne@68
|
118 # | "choice"
|
jpayne@68
|
119 #
|
jpayne@68
|
120 # for type "choice" typeArgs is a list of possible choices, the first one
|
jpayne@68
|
121 # is the default value. for all other types the typeArgs is the default value
|
jpayne@68
|
122 #
|
jpayne@68
|
123 # a "boolflag" is the type for a flag whose presence or absence, without
|
jpayne@68
|
124 # additional arguments means respectively true or false (default flag type).
|
jpayne@68
|
125 #
|
jpayne@68
|
126 # programCounter is the index in the list of the currently processed
|
jpayne@68
|
127 # programStep (thus starting at 1 (0 is {"P" prgCounterValue}).
|
jpayne@68
|
128 # If it is a list it points toward each currently selected programStep.
|
jpayne@68
|
129 # (like for "flags", as they are optional, form a set and programStep).
|
jpayne@68
|
130
|
jpayne@68
|
131 # Performance/Implementation issues
|
jpayne@68
|
132 # ---------------------------------
|
jpayne@68
|
133 # We use tcl lists instead of arrays because with tcl8.0
|
jpayne@68
|
134 # they should start to be much faster.
|
jpayne@68
|
135 # But this code use a lot of helper procs (like Lvarset)
|
jpayne@68
|
136 # which are quite slow and would be helpfully optimized
|
jpayne@68
|
137 # for instance by being written in C. Also our struture
|
jpayne@68
|
138 # is complex and there is maybe some places where the
|
jpayne@68
|
139 # string rep might be calculated at great exense. to be checked.
|
jpayne@68
|
140
|
jpayne@68
|
141 #
|
jpayne@68
|
142 # Parse a given description and saves it here under the given key
|
jpayne@68
|
143 # generate a unused keyid if not given
|
jpayne@68
|
144 #
|
jpayne@68
|
145 proc ::tcl::OptKeyRegister {desc {key ""}} {
|
jpayne@68
|
146 variable OptDesc
|
jpayne@68
|
147 variable OptDescN
|
jpayne@68
|
148 if {[string equal $key ""]} {
|
jpayne@68
|
149 # in case a key given to us as a parameter was a number
|
jpayne@68
|
150 while {[info exists OptDesc($OptDescN)]} {incr OptDescN}
|
jpayne@68
|
151 set key $OptDescN
|
jpayne@68
|
152 incr OptDescN
|
jpayne@68
|
153 }
|
jpayne@68
|
154 # program counter
|
jpayne@68
|
155 set program [list [list "P" 1]]
|
jpayne@68
|
156
|
jpayne@68
|
157 # are we processing flags (which makes a single program step)
|
jpayne@68
|
158 set inflags 0
|
jpayne@68
|
159
|
jpayne@68
|
160 set state {}
|
jpayne@68
|
161
|
jpayne@68
|
162 # flag used to detect that we just have a single (flags set) subprogram.
|
jpayne@68
|
163 set empty 1
|
jpayne@68
|
164
|
jpayne@68
|
165 foreach item $desc {
|
jpayne@68
|
166 if {$state == "args"} {
|
jpayne@68
|
167 # more items after 'args'...
|
jpayne@68
|
168 return -code error "'args' special argument must be the last one"
|
jpayne@68
|
169 }
|
jpayne@68
|
170 set res [OptNormalizeOne $item]
|
jpayne@68
|
171 set state [lindex $res 0]
|
jpayne@68
|
172 if {$inflags} {
|
jpayne@68
|
173 if {$state == "flags"} {
|
jpayne@68
|
174 # add to 'subprogram'
|
jpayne@68
|
175 lappend flagsprg $res
|
jpayne@68
|
176 } else {
|
jpayne@68
|
177 # put in the flags
|
jpayne@68
|
178 # structure for flag programs items is a list of
|
jpayne@68
|
179 # {subprgcounter {prg flag 1} {prg flag 2} {...}}
|
jpayne@68
|
180 lappend program $flagsprg
|
jpayne@68
|
181 # put the other regular stuff
|
jpayne@68
|
182 lappend program $res
|
jpayne@68
|
183 set inflags 0
|
jpayne@68
|
184 set empty 0
|
jpayne@68
|
185 }
|
jpayne@68
|
186 } else {
|
jpayne@68
|
187 if {$state == "flags"} {
|
jpayne@68
|
188 set inflags 1
|
jpayne@68
|
189 # sub program counter + first sub program
|
jpayne@68
|
190 set flagsprg [list [list "P" 1] $res]
|
jpayne@68
|
191 } else {
|
jpayne@68
|
192 lappend program $res
|
jpayne@68
|
193 set empty 0
|
jpayne@68
|
194 }
|
jpayne@68
|
195 }
|
jpayne@68
|
196 }
|
jpayne@68
|
197 if {$inflags} {
|
jpayne@68
|
198 if {$empty} {
|
jpayne@68
|
199 # We just have the subprogram, optimize and remove
|
jpayne@68
|
200 # unneeded level:
|
jpayne@68
|
201 set program $flagsprg
|
jpayne@68
|
202 } else {
|
jpayne@68
|
203 lappend program $flagsprg
|
jpayne@68
|
204 }
|
jpayne@68
|
205 }
|
jpayne@68
|
206
|
jpayne@68
|
207 set OptDesc($key) $program
|
jpayne@68
|
208
|
jpayne@68
|
209 return $key
|
jpayne@68
|
210 }
|
jpayne@68
|
211
|
jpayne@68
|
212 #
|
jpayne@68
|
213 # Free the storage for that given key
|
jpayne@68
|
214 #
|
jpayne@68
|
215 proc ::tcl::OptKeyDelete {key} {
|
jpayne@68
|
216 variable OptDesc
|
jpayne@68
|
217 unset OptDesc($key)
|
jpayne@68
|
218 }
|
jpayne@68
|
219
|
jpayne@68
|
220 # Get the parsed description stored under the given key.
|
jpayne@68
|
221 proc OptKeyGetDesc {descKey} {
|
jpayne@68
|
222 variable OptDesc
|
jpayne@68
|
223 if {![info exists OptDesc($descKey)]} {
|
jpayne@68
|
224 return -code error "Unknown option description key \"$descKey\""
|
jpayne@68
|
225 }
|
jpayne@68
|
226 set OptDesc($descKey)
|
jpayne@68
|
227 }
|
jpayne@68
|
228
|
jpayne@68
|
229 # Parse entry point for ppl who don't want to register with a key,
|
jpayne@68
|
230 # for instance because the description changes dynamically.
|
jpayne@68
|
231 # (otherwise one should really use OptKeyRegister once + OptKeyParse
|
jpayne@68
|
232 # as it is way faster or simply OptProc which does it all)
|
jpayne@68
|
233 # Assign a temporary key, call OptKeyParse and then free the storage
|
jpayne@68
|
234 proc ::tcl::OptParse {desc arglist} {
|
jpayne@68
|
235 set tempkey [OptKeyRegister $desc]
|
jpayne@68
|
236 set ret [catch {uplevel 1 [list ::tcl::OptKeyParse $tempkey $arglist]} res]
|
jpayne@68
|
237 OptKeyDelete $tempkey
|
jpayne@68
|
238 return -code $ret $res
|
jpayne@68
|
239 }
|
jpayne@68
|
240
|
jpayne@68
|
241 # Helper function, replacement for proc that both
|
jpayne@68
|
242 # register the description under a key which is the name of the proc
|
jpayne@68
|
243 # (and thus unique to that code)
|
jpayne@68
|
244 # and add a first line to the code to call the OptKeyParse proc
|
jpayne@68
|
245 # Stores the list of variables that have been actually given by the user
|
jpayne@68
|
246 # (the other will be sets to their default value)
|
jpayne@68
|
247 # into local variable named "Args".
|
jpayne@68
|
248 proc ::tcl::OptProc {name desc body} {
|
jpayne@68
|
249 set namespace [uplevel 1 [list ::namespace current]]
|
jpayne@68
|
250 if {[string match "::*" $name] || [string equal $namespace "::"]} {
|
jpayne@68
|
251 # absolute name or global namespace, name is the key
|
jpayne@68
|
252 set key $name
|
jpayne@68
|
253 } else {
|
jpayne@68
|
254 # we are relative to some non top level namespace:
|
jpayne@68
|
255 set key "${namespace}::${name}"
|
jpayne@68
|
256 }
|
jpayne@68
|
257 OptKeyRegister $desc $key
|
jpayne@68
|
258 uplevel 1 [list ::proc $name args "set Args \[::tcl::OptKeyParse $key \$args\]\n$body"]
|
jpayne@68
|
259 return $key
|
jpayne@68
|
260 }
|
jpayne@68
|
261 # Check that a argument has been given
|
jpayne@68
|
262 # assumes that "OptProc" has been used as it will check in "Args" list
|
jpayne@68
|
263 proc ::tcl::OptProcArgGiven {argname} {
|
jpayne@68
|
264 upvar Args alist
|
jpayne@68
|
265 expr {[lsearch $alist $argname] >=0}
|
jpayne@68
|
266 }
|
jpayne@68
|
267
|
jpayne@68
|
268 #######
|
jpayne@68
|
269 # Programs/Descriptions manipulation
|
jpayne@68
|
270
|
jpayne@68
|
271 # Return the instruction word/list of a given step/(sub)program
|
jpayne@68
|
272 proc OptInstr {lst} {
|
jpayne@68
|
273 lindex $lst 0
|
jpayne@68
|
274 }
|
jpayne@68
|
275 # Is a (sub) program or a plain instruction ?
|
jpayne@68
|
276 proc OptIsPrg {lst} {
|
jpayne@68
|
277 expr {[llength [OptInstr $lst]]>=2}
|
jpayne@68
|
278 }
|
jpayne@68
|
279 # Is this instruction a program counter or a real instr
|
jpayne@68
|
280 proc OptIsCounter {item} {
|
jpayne@68
|
281 expr {[lindex $item 0]=="P"}
|
jpayne@68
|
282 }
|
jpayne@68
|
283 # Current program counter (2nd word of first word)
|
jpayne@68
|
284 proc OptGetPrgCounter {lst} {
|
jpayne@68
|
285 Lget $lst {0 1}
|
jpayne@68
|
286 }
|
jpayne@68
|
287 # Current program counter (2nd word of first word)
|
jpayne@68
|
288 proc OptSetPrgCounter {lstName newValue} {
|
jpayne@68
|
289 upvar $lstName lst
|
jpayne@68
|
290 set lst [lreplace $lst 0 0 [concat "P" $newValue]]
|
jpayne@68
|
291 }
|
jpayne@68
|
292 # returns a list of currently selected items.
|
jpayne@68
|
293 proc OptSelection {lst} {
|
jpayne@68
|
294 set res {}
|
jpayne@68
|
295 foreach idx [lrange [lindex $lst 0] 1 end] {
|
jpayne@68
|
296 lappend res [Lget $lst $idx]
|
jpayne@68
|
297 }
|
jpayne@68
|
298 return $res
|
jpayne@68
|
299 }
|
jpayne@68
|
300
|
jpayne@68
|
301 # Advance to next description
|
jpayne@68
|
302 proc OptNextDesc {descName} {
|
jpayne@68
|
303 uplevel 1 [list Lvarincr $descName {0 1}]
|
jpayne@68
|
304 }
|
jpayne@68
|
305
|
jpayne@68
|
306 # Get the current description, eventually descend
|
jpayne@68
|
307 proc OptCurDesc {descriptions} {
|
jpayne@68
|
308 lindex $descriptions [OptGetPrgCounter $descriptions]
|
jpayne@68
|
309 }
|
jpayne@68
|
310 # get the current description, eventually descend
|
jpayne@68
|
311 # through sub programs as needed.
|
jpayne@68
|
312 proc OptCurDescFinal {descriptions} {
|
jpayne@68
|
313 set item [OptCurDesc $descriptions]
|
jpayne@68
|
314 # Descend untill we get the actual item and not a sub program
|
jpayne@68
|
315 while {[OptIsPrg $item]} {
|
jpayne@68
|
316 set item [OptCurDesc $item]
|
jpayne@68
|
317 }
|
jpayne@68
|
318 return $item
|
jpayne@68
|
319 }
|
jpayne@68
|
320 # Current final instruction adress
|
jpayne@68
|
321 proc OptCurAddr {descriptions {start {}}} {
|
jpayne@68
|
322 set adress [OptGetPrgCounter $descriptions]
|
jpayne@68
|
323 lappend start $adress
|
jpayne@68
|
324 set item [lindex $descriptions $adress]
|
jpayne@68
|
325 if {[OptIsPrg $item]} {
|
jpayne@68
|
326 return [OptCurAddr $item $start]
|
jpayne@68
|
327 } else {
|
jpayne@68
|
328 return $start
|
jpayne@68
|
329 }
|
jpayne@68
|
330 }
|
jpayne@68
|
331 # Set the value field of the current instruction
|
jpayne@68
|
332 proc OptCurSetValue {descriptionsName value} {
|
jpayne@68
|
333 upvar $descriptionsName descriptions
|
jpayne@68
|
334 # get the current item full adress
|
jpayne@68
|
335 set adress [OptCurAddr $descriptions]
|
jpayne@68
|
336 # use the 3th field of the item (see OptValue / OptNewInst)
|
jpayne@68
|
337 lappend adress 2
|
jpayne@68
|
338 Lvarset descriptions $adress [list 1 $value]
|
jpayne@68
|
339 # ^hasBeenSet flag
|
jpayne@68
|
340 }
|
jpayne@68
|
341
|
jpayne@68
|
342 # empty state means done/paste the end of the program
|
jpayne@68
|
343 proc OptState {item} {
|
jpayne@68
|
344 lindex $item 0
|
jpayne@68
|
345 }
|
jpayne@68
|
346
|
jpayne@68
|
347 # current state
|
jpayne@68
|
348 proc OptCurState {descriptions} {
|
jpayne@68
|
349 OptState [OptCurDesc $descriptions]
|
jpayne@68
|
350 }
|
jpayne@68
|
351
|
jpayne@68
|
352 #######
|
jpayne@68
|
353 # Arguments manipulation
|
jpayne@68
|
354
|
jpayne@68
|
355 # Returns the argument that has to be processed now
|
jpayne@68
|
356 proc OptCurrentArg {lst} {
|
jpayne@68
|
357 lindex $lst 0
|
jpayne@68
|
358 }
|
jpayne@68
|
359 # Advance to next argument
|
jpayne@68
|
360 proc OptNextArg {argsName} {
|
jpayne@68
|
361 uplevel 1 [list Lvarpop1 $argsName]
|
jpayne@68
|
362 }
|
jpayne@68
|
363 #######
|
jpayne@68
|
364
|
jpayne@68
|
365
|
jpayne@68
|
366
|
jpayne@68
|
367
|
jpayne@68
|
368
|
jpayne@68
|
369 # Loop over all descriptions, calling OptDoOne which will
|
jpayne@68
|
370 # eventually eat all the arguments.
|
jpayne@68
|
371 proc OptDoAll {descriptionsName argumentsName} {
|
jpayne@68
|
372 upvar $descriptionsName descriptions
|
jpayne@68
|
373 upvar $argumentsName arguments
|
jpayne@68
|
374 # puts "entered DoAll"
|
jpayne@68
|
375 # Nb: the places where "state" can be set are tricky to figure
|
jpayne@68
|
376 # because DoOne sets the state to flagsValue and return -continue
|
jpayne@68
|
377 # when needed...
|
jpayne@68
|
378 set state [OptCurState $descriptions]
|
jpayne@68
|
379 # We'll exit the loop in "OptDoOne" or when state is empty.
|
jpayne@68
|
380 while 1 {
|
jpayne@68
|
381 set curitem [OptCurDesc $descriptions]
|
jpayne@68
|
382 # Do subprograms if needed, call ourselves on the sub branch
|
jpayne@68
|
383 while {[OptIsPrg $curitem]} {
|
jpayne@68
|
384 OptDoAll curitem arguments
|
jpayne@68
|
385 # puts "done DoAll sub"
|
jpayne@68
|
386 # Insert back the results in current tree
|
jpayne@68
|
387 Lvarset1nc descriptions [OptGetPrgCounter $descriptions]\
|
jpayne@68
|
388 $curitem
|
jpayne@68
|
389 OptNextDesc descriptions
|
jpayne@68
|
390 set curitem [OptCurDesc $descriptions]
|
jpayne@68
|
391 set state [OptCurState $descriptions]
|
jpayne@68
|
392 }
|
jpayne@68
|
393 # puts "state = \"$state\" - arguments=($arguments)"
|
jpayne@68
|
394 if {[Lempty $state]} {
|
jpayne@68
|
395 # Nothing left to do, we are done in this branch:
|
jpayne@68
|
396 break
|
jpayne@68
|
397 }
|
jpayne@68
|
398 # The following statement can make us terminate/continue
|
jpayne@68
|
399 # as it use return -code {break, continue, return and error}
|
jpayne@68
|
400 # codes
|
jpayne@68
|
401 OptDoOne descriptions state arguments
|
jpayne@68
|
402 # If we are here, no special return code where issued,
|
jpayne@68
|
403 # we'll step to next instruction :
|
jpayne@68
|
404 # puts "new state = \"$state\""
|
jpayne@68
|
405 OptNextDesc descriptions
|
jpayne@68
|
406 set state [OptCurState $descriptions]
|
jpayne@68
|
407 }
|
jpayne@68
|
408 }
|
jpayne@68
|
409
|
jpayne@68
|
410 # Process one step for the state machine,
|
jpayne@68
|
411 # eventually consuming the current argument.
|
jpayne@68
|
412 proc OptDoOne {descriptionsName stateName argumentsName} {
|
jpayne@68
|
413 upvar $argumentsName arguments
|
jpayne@68
|
414 upvar $descriptionsName descriptions
|
jpayne@68
|
415 upvar $stateName state
|
jpayne@68
|
416
|
jpayne@68
|
417 # the special state/instruction "args" eats all
|
jpayne@68
|
418 # the remaining args (if any)
|
jpayne@68
|
419 if {($state == "args")} {
|
jpayne@68
|
420 if {![Lempty $arguments]} {
|
jpayne@68
|
421 # If there is no additional arguments, leave the default value
|
jpayne@68
|
422 # in.
|
jpayne@68
|
423 OptCurSetValue descriptions $arguments
|
jpayne@68
|
424 set arguments {}
|
jpayne@68
|
425 }
|
jpayne@68
|
426 # puts "breaking out ('args' state: consuming every reminding args)"
|
jpayne@68
|
427 return -code break
|
jpayne@68
|
428 }
|
jpayne@68
|
429
|
jpayne@68
|
430 if {[Lempty $arguments]} {
|
jpayne@68
|
431 if {$state == "flags"} {
|
jpayne@68
|
432 # no argument and no flags : we're done
|
jpayne@68
|
433 # puts "returning to previous (sub)prg (no more args)"
|
jpayne@68
|
434 return -code return
|
jpayne@68
|
435 } elseif {$state == "optValue"} {
|
jpayne@68
|
436 set state next; # not used, for debug only
|
jpayne@68
|
437 # go to next state
|
jpayne@68
|
438 return
|
jpayne@68
|
439 } else {
|
jpayne@68
|
440 return -code error [OptMissingValue $descriptions]
|
jpayne@68
|
441 }
|
jpayne@68
|
442 } else {
|
jpayne@68
|
443 set arg [OptCurrentArg $arguments]
|
jpayne@68
|
444 }
|
jpayne@68
|
445
|
jpayne@68
|
446 switch $state {
|
jpayne@68
|
447 flags {
|
jpayne@68
|
448 # A non-dash argument terminates the options, as does --
|
jpayne@68
|
449
|
jpayne@68
|
450 # Still a flag ?
|
jpayne@68
|
451 if {![OptIsFlag $arg]} {
|
jpayne@68
|
452 # don't consume the argument, return to previous prg
|
jpayne@68
|
453 return -code return
|
jpayne@68
|
454 }
|
jpayne@68
|
455 # consume the flag
|
jpayne@68
|
456 OptNextArg arguments
|
jpayne@68
|
457 if {[string equal "--" $arg]} {
|
jpayne@68
|
458 # return from 'flags' state
|
jpayne@68
|
459 return -code return
|
jpayne@68
|
460 }
|
jpayne@68
|
461
|
jpayne@68
|
462 set hits [OptHits descriptions $arg]
|
jpayne@68
|
463 if {$hits > 1} {
|
jpayne@68
|
464 return -code error [OptAmbigous $descriptions $arg]
|
jpayne@68
|
465 } elseif {$hits == 0} {
|
jpayne@68
|
466 return -code error [OptFlagUsage $descriptions $arg]
|
jpayne@68
|
467 }
|
jpayne@68
|
468 set item [OptCurDesc $descriptions]
|
jpayne@68
|
469 if {[OptNeedValue $item]} {
|
jpayne@68
|
470 # we need a value, next state is
|
jpayne@68
|
471 set state flagValue
|
jpayne@68
|
472 } else {
|
jpayne@68
|
473 OptCurSetValue descriptions 1
|
jpayne@68
|
474 }
|
jpayne@68
|
475 # continue
|
jpayne@68
|
476 return -code continue
|
jpayne@68
|
477 }
|
jpayne@68
|
478 flagValue -
|
jpayne@68
|
479 value {
|
jpayne@68
|
480 set item [OptCurDesc $descriptions]
|
jpayne@68
|
481 # Test the values against their required type
|
jpayne@68
|
482 if {[catch {OptCheckType $arg\
|
jpayne@68
|
483 [OptType $item] [OptTypeArgs $item]} val]} {
|
jpayne@68
|
484 return -code error [OptBadValue $item $arg $val]
|
jpayne@68
|
485 }
|
jpayne@68
|
486 # consume the value
|
jpayne@68
|
487 OptNextArg arguments
|
jpayne@68
|
488 # set the value
|
jpayne@68
|
489 OptCurSetValue descriptions $val
|
jpayne@68
|
490 # go to next state
|
jpayne@68
|
491 if {$state == "flagValue"} {
|
jpayne@68
|
492 set state flags
|
jpayne@68
|
493 return -code continue
|
jpayne@68
|
494 } else {
|
jpayne@68
|
495 set state next; # not used, for debug only
|
jpayne@68
|
496 return ; # will go on next step
|
jpayne@68
|
497 }
|
jpayne@68
|
498 }
|
jpayne@68
|
499 optValue {
|
jpayne@68
|
500 set item [OptCurDesc $descriptions]
|
jpayne@68
|
501 # Test the values against their required type
|
jpayne@68
|
502 if {![catch {OptCheckType $arg\
|
jpayne@68
|
503 [OptType $item] [OptTypeArgs $item]} val]} {
|
jpayne@68
|
504 # right type, so :
|
jpayne@68
|
505 # consume the value
|
jpayne@68
|
506 OptNextArg arguments
|
jpayne@68
|
507 # set the value
|
jpayne@68
|
508 OptCurSetValue descriptions $val
|
jpayne@68
|
509 }
|
jpayne@68
|
510 # go to next state
|
jpayne@68
|
511 set state next; # not used, for debug only
|
jpayne@68
|
512 return ; # will go on next step
|
jpayne@68
|
513 }
|
jpayne@68
|
514 }
|
jpayne@68
|
515 # If we reach this point: an unknown
|
jpayne@68
|
516 # state as been entered !
|
jpayne@68
|
517 return -code error "Bug! unknown state in DoOne \"$state\"\
|
jpayne@68
|
518 (prg counter [OptGetPrgCounter $descriptions]:\
|
jpayne@68
|
519 [OptCurDesc $descriptions])"
|
jpayne@68
|
520 }
|
jpayne@68
|
521
|
jpayne@68
|
522 # Parse the options given the key to previously registered description
|
jpayne@68
|
523 # and arguments list
|
jpayne@68
|
524 proc ::tcl::OptKeyParse {descKey arglist} {
|
jpayne@68
|
525
|
jpayne@68
|
526 set desc [OptKeyGetDesc $descKey]
|
jpayne@68
|
527
|
jpayne@68
|
528 # make sure -help always give usage
|
jpayne@68
|
529 if {[string equal -nocase "-help" $arglist]} {
|
jpayne@68
|
530 return -code error [OptError "Usage information:" $desc 1]
|
jpayne@68
|
531 }
|
jpayne@68
|
532
|
jpayne@68
|
533 OptDoAll desc arglist
|
jpayne@68
|
534
|
jpayne@68
|
535 if {![Lempty $arglist]} {
|
jpayne@68
|
536 return -code error [OptTooManyArgs $desc $arglist]
|
jpayne@68
|
537 }
|
jpayne@68
|
538
|
jpayne@68
|
539 # Analyse the result
|
jpayne@68
|
540 # Walk through the tree:
|
jpayne@68
|
541 OptTreeVars $desc "#[expr {[info level]-1}]"
|
jpayne@68
|
542 }
|
jpayne@68
|
543
|
jpayne@68
|
544 # determine string length for nice tabulated output
|
jpayne@68
|
545 proc OptTreeVars {desc level {vnamesLst {}}} {
|
jpayne@68
|
546 foreach item $desc {
|
jpayne@68
|
547 if {[OptIsCounter $item]} continue
|
jpayne@68
|
548 if {[OptIsPrg $item]} {
|
jpayne@68
|
549 set vnamesLst [OptTreeVars $item $level $vnamesLst]
|
jpayne@68
|
550 } else {
|
jpayne@68
|
551 set vname [OptVarName $item]
|
jpayne@68
|
552 upvar $level $vname var
|
jpayne@68
|
553 if {[OptHasBeenSet $item]} {
|
jpayne@68
|
554 # puts "adding $vname"
|
jpayne@68
|
555 # lets use the input name for the returned list
|
jpayne@68
|
556 # it is more usefull, for instance you can check that
|
jpayne@68
|
557 # no flags at all was given with expr
|
jpayne@68
|
558 # {![string match "*-*" $Args]}
|
jpayne@68
|
559 lappend vnamesLst [OptName $item]
|
jpayne@68
|
560 set var [OptValue $item]
|
jpayne@68
|
561 } else {
|
jpayne@68
|
562 set var [OptDefaultValue $item]
|
jpayne@68
|
563 }
|
jpayne@68
|
564 }
|
jpayne@68
|
565 }
|
jpayne@68
|
566 return $vnamesLst
|
jpayne@68
|
567 }
|
jpayne@68
|
568
|
jpayne@68
|
569
|
jpayne@68
|
570 # Check the type of a value
|
jpayne@68
|
571 # and emit an error if arg is not of the correct type
|
jpayne@68
|
572 # otherwise returns the canonical value of that arg (ie 0/1 for booleans)
|
jpayne@68
|
573 proc ::tcl::OptCheckType {arg type {typeArgs ""}} {
|
jpayne@68
|
574 # puts "checking '$arg' against '$type' ($typeArgs)"
|
jpayne@68
|
575
|
jpayne@68
|
576 # only types "any", "choice", and numbers can have leading "-"
|
jpayne@68
|
577
|
jpayne@68
|
578 switch -exact -- $type {
|
jpayne@68
|
579 int {
|
jpayne@68
|
580 if {![string is integer -strict $arg]} {
|
jpayne@68
|
581 error "not an integer"
|
jpayne@68
|
582 }
|
jpayne@68
|
583 return $arg
|
jpayne@68
|
584 }
|
jpayne@68
|
585 float {
|
jpayne@68
|
586 return [expr {double($arg)}]
|
jpayne@68
|
587 }
|
jpayne@68
|
588 script -
|
jpayne@68
|
589 list {
|
jpayne@68
|
590 # if llength fail : malformed list
|
jpayne@68
|
591 if {[llength $arg]==0 && [OptIsFlag $arg]} {
|
jpayne@68
|
592 error "no values with leading -"
|
jpayne@68
|
593 }
|
jpayne@68
|
594 return $arg
|
jpayne@68
|
595 }
|
jpayne@68
|
596 boolean {
|
jpayne@68
|
597 if {![string is boolean -strict $arg]} {
|
jpayne@68
|
598 error "non canonic boolean"
|
jpayne@68
|
599 }
|
jpayne@68
|
600 # convert true/false because expr/if is broken with "!,...
|
jpayne@68
|
601 return [expr {$arg ? 1 : 0}]
|
jpayne@68
|
602 }
|
jpayne@68
|
603 choice {
|
jpayne@68
|
604 if {$arg ni $typeArgs} {
|
jpayne@68
|
605 error "invalid choice"
|
jpayne@68
|
606 }
|
jpayne@68
|
607 return $arg
|
jpayne@68
|
608 }
|
jpayne@68
|
609 any {
|
jpayne@68
|
610 return $arg
|
jpayne@68
|
611 }
|
jpayne@68
|
612 string -
|
jpayne@68
|
613 default {
|
jpayne@68
|
614 if {[OptIsFlag $arg]} {
|
jpayne@68
|
615 error "no values with leading -"
|
jpayne@68
|
616 }
|
jpayne@68
|
617 return $arg
|
jpayne@68
|
618 }
|
jpayne@68
|
619 }
|
jpayne@68
|
620 return neverReached
|
jpayne@68
|
621 }
|
jpayne@68
|
622
|
jpayne@68
|
623 # internal utilities
|
jpayne@68
|
624
|
jpayne@68
|
625 # returns the number of flags matching the given arg
|
jpayne@68
|
626 # sets the (local) prg counter to the list of matches
|
jpayne@68
|
627 proc OptHits {descName arg} {
|
jpayne@68
|
628 upvar $descName desc
|
jpayne@68
|
629 set hits 0
|
jpayne@68
|
630 set hitems {}
|
jpayne@68
|
631 set i 1
|
jpayne@68
|
632
|
jpayne@68
|
633 set larg [string tolower $arg]
|
jpayne@68
|
634 set len [string length $larg]
|
jpayne@68
|
635 set last [expr {$len-1}]
|
jpayne@68
|
636
|
jpayne@68
|
637 foreach item [lrange $desc 1 end] {
|
jpayne@68
|
638 set flag [OptName $item]
|
jpayne@68
|
639 # lets try to match case insensitively
|
jpayne@68
|
640 # (string length ought to be cheap)
|
jpayne@68
|
641 set lflag [string tolower $flag]
|
jpayne@68
|
642 if {$len == [string length $lflag]} {
|
jpayne@68
|
643 if {[string equal $larg $lflag]} {
|
jpayne@68
|
644 # Exact match case
|
jpayne@68
|
645 OptSetPrgCounter desc $i
|
jpayne@68
|
646 return 1
|
jpayne@68
|
647 }
|
jpayne@68
|
648 } elseif {[string equal $larg [string range $lflag 0 $last]]} {
|
jpayne@68
|
649 lappend hitems $i
|
jpayne@68
|
650 incr hits
|
jpayne@68
|
651 }
|
jpayne@68
|
652 incr i
|
jpayne@68
|
653 }
|
jpayne@68
|
654 if {$hits} {
|
jpayne@68
|
655 OptSetPrgCounter desc $hitems
|
jpayne@68
|
656 }
|
jpayne@68
|
657 return $hits
|
jpayne@68
|
658 }
|
jpayne@68
|
659
|
jpayne@68
|
660 # Extract fields from the list structure:
|
jpayne@68
|
661
|
jpayne@68
|
662 proc OptName {item} {
|
jpayne@68
|
663 lindex $item 1
|
jpayne@68
|
664 }
|
jpayne@68
|
665 proc OptHasBeenSet {item} {
|
jpayne@68
|
666 Lget $item {2 0}
|
jpayne@68
|
667 }
|
jpayne@68
|
668 proc OptValue {item} {
|
jpayne@68
|
669 Lget $item {2 1}
|
jpayne@68
|
670 }
|
jpayne@68
|
671
|
jpayne@68
|
672 proc OptIsFlag {name} {
|
jpayne@68
|
673 string match "-*" $name
|
jpayne@68
|
674 }
|
jpayne@68
|
675 proc OptIsOpt {name} {
|
jpayne@68
|
676 string match {\?*} $name
|
jpayne@68
|
677 }
|
jpayne@68
|
678 proc OptVarName {item} {
|
jpayne@68
|
679 set name [OptName $item]
|
jpayne@68
|
680 if {[OptIsFlag $name]} {
|
jpayne@68
|
681 return [string range $name 1 end]
|
jpayne@68
|
682 } elseif {[OptIsOpt $name]} {
|
jpayne@68
|
683 return [string trim $name "?"]
|
jpayne@68
|
684 } else {
|
jpayne@68
|
685 return $name
|
jpayne@68
|
686 }
|
jpayne@68
|
687 }
|
jpayne@68
|
688 proc OptType {item} {
|
jpayne@68
|
689 lindex $item 3
|
jpayne@68
|
690 }
|
jpayne@68
|
691 proc OptTypeArgs {item} {
|
jpayne@68
|
692 lindex $item 4
|
jpayne@68
|
693 }
|
jpayne@68
|
694 proc OptHelp {item} {
|
jpayne@68
|
695 lindex $item 5
|
jpayne@68
|
696 }
|
jpayne@68
|
697 proc OptNeedValue {item} {
|
jpayne@68
|
698 expr {![string equal [OptType $item] boolflag]}
|
jpayne@68
|
699 }
|
jpayne@68
|
700 proc OptDefaultValue {item} {
|
jpayne@68
|
701 set val [OptTypeArgs $item]
|
jpayne@68
|
702 switch -exact -- [OptType $item] {
|
jpayne@68
|
703 choice {return [lindex $val 0]}
|
jpayne@68
|
704 boolean -
|
jpayne@68
|
705 boolflag {
|
jpayne@68
|
706 # convert back false/true to 0/1 because expr !$bool
|
jpayne@68
|
707 # is broken..
|
jpayne@68
|
708 if {$val} {
|
jpayne@68
|
709 return 1
|
jpayne@68
|
710 } else {
|
jpayne@68
|
711 return 0
|
jpayne@68
|
712 }
|
jpayne@68
|
713 }
|
jpayne@68
|
714 }
|
jpayne@68
|
715 return $val
|
jpayne@68
|
716 }
|
jpayne@68
|
717
|
jpayne@68
|
718 # Description format error helper
|
jpayne@68
|
719 proc OptOptUsage {item {what ""}} {
|
jpayne@68
|
720 return -code error "invalid description format$what: $item\n\
|
jpayne@68
|
721 should be a list of {varname|-flagname ?-type? ?defaultvalue?\
|
jpayne@68
|
722 ?helpstring?}"
|
jpayne@68
|
723 }
|
jpayne@68
|
724
|
jpayne@68
|
725
|
jpayne@68
|
726 # Generate a canonical form single instruction
|
jpayne@68
|
727 proc OptNewInst {state varname type typeArgs help} {
|
jpayne@68
|
728 list $state $varname [list 0 {}] $type $typeArgs $help
|
jpayne@68
|
729 # ^ ^
|
jpayne@68
|
730 # | |
|
jpayne@68
|
731 # hasBeenSet=+ +=currentValue
|
jpayne@68
|
732 }
|
jpayne@68
|
733
|
jpayne@68
|
734 # Translate one item to canonical form
|
jpayne@68
|
735 proc OptNormalizeOne {item} {
|
jpayne@68
|
736 set lg [Lassign $item varname arg1 arg2 arg3]
|
jpayne@68
|
737 # puts "called optnormalizeone '$item' v=($varname), lg=$lg"
|
jpayne@68
|
738 set isflag [OptIsFlag $varname]
|
jpayne@68
|
739 set isopt [OptIsOpt $varname]
|
jpayne@68
|
740 if {$isflag} {
|
jpayne@68
|
741 set state "flags"
|
jpayne@68
|
742 } elseif {$isopt} {
|
jpayne@68
|
743 set state "optValue"
|
jpayne@68
|
744 } elseif {![string equal $varname "args"]} {
|
jpayne@68
|
745 set state "value"
|
jpayne@68
|
746 } else {
|
jpayne@68
|
747 set state "args"
|
jpayne@68
|
748 }
|
jpayne@68
|
749
|
jpayne@68
|
750 # apply 'smart' 'fuzzy' logic to try to make
|
jpayne@68
|
751 # description writer's life easy, and our's difficult :
|
jpayne@68
|
752 # let's guess the missing arguments :-)
|
jpayne@68
|
753
|
jpayne@68
|
754 switch $lg {
|
jpayne@68
|
755 1 {
|
jpayne@68
|
756 if {$isflag} {
|
jpayne@68
|
757 return [OptNewInst $state $varname boolflag false ""]
|
jpayne@68
|
758 } else {
|
jpayne@68
|
759 return [OptNewInst $state $varname any "" ""]
|
jpayne@68
|
760 }
|
jpayne@68
|
761 }
|
jpayne@68
|
762 2 {
|
jpayne@68
|
763 # varname default
|
jpayne@68
|
764 # varname help
|
jpayne@68
|
765 set type [OptGuessType $arg1]
|
jpayne@68
|
766 if {[string equal $type "string"]} {
|
jpayne@68
|
767 if {$isflag} {
|
jpayne@68
|
768 set type boolflag
|
jpayne@68
|
769 set def false
|
jpayne@68
|
770 } else {
|
jpayne@68
|
771 set type any
|
jpayne@68
|
772 set def ""
|
jpayne@68
|
773 }
|
jpayne@68
|
774 set help $arg1
|
jpayne@68
|
775 } else {
|
jpayne@68
|
776 set help ""
|
jpayne@68
|
777 set def $arg1
|
jpayne@68
|
778 }
|
jpayne@68
|
779 return [OptNewInst $state $varname $type $def $help]
|
jpayne@68
|
780 }
|
jpayne@68
|
781 3 {
|
jpayne@68
|
782 # varname type value
|
jpayne@68
|
783 # varname value comment
|
jpayne@68
|
784
|
jpayne@68
|
785 if {[regexp {^-(.+)$} $arg1 x type]} {
|
jpayne@68
|
786 # flags/optValue as they are optional, need a "value",
|
jpayne@68
|
787 # on the contrary, for a variable (non optional),
|
jpayne@68
|
788 # default value is pointless, 'cept for choices :
|
jpayne@68
|
789 if {$isflag || $isopt || ($type == "choice")} {
|
jpayne@68
|
790 return [OptNewInst $state $varname $type $arg2 ""]
|
jpayne@68
|
791 } else {
|
jpayne@68
|
792 return [OptNewInst $state $varname $type "" $arg2]
|
jpayne@68
|
793 }
|
jpayne@68
|
794 } else {
|
jpayne@68
|
795 return [OptNewInst $state $varname\
|
jpayne@68
|
796 [OptGuessType $arg1] $arg1 $arg2]
|
jpayne@68
|
797 }
|
jpayne@68
|
798 }
|
jpayne@68
|
799 4 {
|
jpayne@68
|
800 if {[regexp {^-(.+)$} $arg1 x type]} {
|
jpayne@68
|
801 return [OptNewInst $state $varname $type $arg2 $arg3]
|
jpayne@68
|
802 } else {
|
jpayne@68
|
803 return -code error [OptOptUsage $item]
|
jpayne@68
|
804 }
|
jpayne@68
|
805 }
|
jpayne@68
|
806 default {
|
jpayne@68
|
807 return -code error [OptOptUsage $item]
|
jpayne@68
|
808 }
|
jpayne@68
|
809 }
|
jpayne@68
|
810 }
|
jpayne@68
|
811
|
jpayne@68
|
812 # Auto magic lazy type determination
|
jpayne@68
|
813 proc OptGuessType {arg} {
|
jpayne@68
|
814 if { $arg == "true" || $arg == "false" } {
|
jpayne@68
|
815 return boolean
|
jpayne@68
|
816 }
|
jpayne@68
|
817 if {[string is integer -strict $arg]} {
|
jpayne@68
|
818 return int
|
jpayne@68
|
819 }
|
jpayne@68
|
820 if {[string is double -strict $arg]} {
|
jpayne@68
|
821 return float
|
jpayne@68
|
822 }
|
jpayne@68
|
823 return string
|
jpayne@68
|
824 }
|
jpayne@68
|
825
|
jpayne@68
|
826 # Error messages front ends
|
jpayne@68
|
827
|
jpayne@68
|
828 proc OptAmbigous {desc arg} {
|
jpayne@68
|
829 OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc]
|
jpayne@68
|
830 }
|
jpayne@68
|
831 proc OptFlagUsage {desc arg} {
|
jpayne@68
|
832 OptError "bad flag \"$arg\", must be one of" $desc
|
jpayne@68
|
833 }
|
jpayne@68
|
834 proc OptTooManyArgs {desc arguments} {
|
jpayne@68
|
835 OptError "too many arguments (unexpected argument(s): $arguments),\
|
jpayne@68
|
836 usage:"\
|
jpayne@68
|
837 $desc 1
|
jpayne@68
|
838 }
|
jpayne@68
|
839 proc OptParamType {item} {
|
jpayne@68
|
840 if {[OptIsFlag $item]} {
|
jpayne@68
|
841 return "flag"
|
jpayne@68
|
842 } else {
|
jpayne@68
|
843 return "parameter"
|
jpayne@68
|
844 }
|
jpayne@68
|
845 }
|
jpayne@68
|
846 proc OptBadValue {item arg {err {}}} {
|
jpayne@68
|
847 # puts "bad val err = \"$err\""
|
jpayne@68
|
848 OptError "bad value \"$arg\" for [OptParamType $item]"\
|
jpayne@68
|
849 [list $item]
|
jpayne@68
|
850 }
|
jpayne@68
|
851 proc OptMissingValue {descriptions} {
|
jpayne@68
|
852 # set item [OptCurDescFinal $descriptions]
|
jpayne@68
|
853 set item [OptCurDesc $descriptions]
|
jpayne@68
|
854 OptError "no value given for [OptParamType $item] \"[OptName $item]\"\
|
jpayne@68
|
855 (use -help for full usage) :"\
|
jpayne@68
|
856 [list $item]
|
jpayne@68
|
857 }
|
jpayne@68
|
858
|
jpayne@68
|
859 proc ::tcl::OptKeyError {prefix descKey {header 0}} {
|
jpayne@68
|
860 OptError $prefix [OptKeyGetDesc $descKey] $header
|
jpayne@68
|
861 }
|
jpayne@68
|
862
|
jpayne@68
|
863 # determine string length for nice tabulated output
|
jpayne@68
|
864 proc OptLengths {desc nlName tlName dlName} {
|
jpayne@68
|
865 upvar $nlName nl
|
jpayne@68
|
866 upvar $tlName tl
|
jpayne@68
|
867 upvar $dlName dl
|
jpayne@68
|
868 foreach item $desc {
|
jpayne@68
|
869 if {[OptIsCounter $item]} continue
|
jpayne@68
|
870 if {[OptIsPrg $item]} {
|
jpayne@68
|
871 OptLengths $item nl tl dl
|
jpayne@68
|
872 } else {
|
jpayne@68
|
873 SetMax nl [string length [OptName $item]]
|
jpayne@68
|
874 SetMax tl [string length [OptType $item]]
|
jpayne@68
|
875 set dv [OptTypeArgs $item]
|
jpayne@68
|
876 if {[OptState $item] != "header"} {
|
jpayne@68
|
877 set dv "($dv)"
|
jpayne@68
|
878 }
|
jpayne@68
|
879 set l [string length $dv]
|
jpayne@68
|
880 # limit the space allocated to potentially big "choices"
|
jpayne@68
|
881 if {([OptType $item] != "choice") || ($l<=12)} {
|
jpayne@68
|
882 SetMax dl $l
|
jpayne@68
|
883 } else {
|
jpayne@68
|
884 if {![info exists dl]} {
|
jpayne@68
|
885 set dl 0
|
jpayne@68
|
886 }
|
jpayne@68
|
887 }
|
jpayne@68
|
888 }
|
jpayne@68
|
889 }
|
jpayne@68
|
890 }
|
jpayne@68
|
891 # output the tree
|
jpayne@68
|
892 proc OptTree {desc nl tl dl} {
|
jpayne@68
|
893 set res ""
|
jpayne@68
|
894 foreach item $desc {
|
jpayne@68
|
895 if {[OptIsCounter $item]} continue
|
jpayne@68
|
896 if {[OptIsPrg $item]} {
|
jpayne@68
|
897 append res [OptTree $item $nl $tl $dl]
|
jpayne@68
|
898 } else {
|
jpayne@68
|
899 set dv [OptTypeArgs $item]
|
jpayne@68
|
900 if {[OptState $item] != "header"} {
|
jpayne@68
|
901 set dv "($dv)"
|
jpayne@68
|
902 }
|
jpayne@68
|
903 append res [string trimright [format "\n %-*s %-*s %-*s %s" \
|
jpayne@68
|
904 $nl [OptName $item] $tl [OptType $item] \
|
jpayne@68
|
905 $dl $dv [OptHelp $item]]]
|
jpayne@68
|
906 }
|
jpayne@68
|
907 }
|
jpayne@68
|
908 return $res
|
jpayne@68
|
909 }
|
jpayne@68
|
910
|
jpayne@68
|
911 # Give nice usage string
|
jpayne@68
|
912 proc ::tcl::OptError {prefix desc {header 0}} {
|
jpayne@68
|
913 # determine length
|
jpayne@68
|
914 if {$header} {
|
jpayne@68
|
915 # add faked instruction
|
jpayne@68
|
916 set h [list [OptNewInst header Var/FlagName Type Value Help]]
|
jpayne@68
|
917 lappend h [OptNewInst header ------------ ---- ----- ----]
|
jpayne@68
|
918 lappend h [OptNewInst header {(-help} "" "" {gives this help)}]
|
jpayne@68
|
919 set desc [concat $h $desc]
|
jpayne@68
|
920 }
|
jpayne@68
|
921 OptLengths $desc nl tl dl
|
jpayne@68
|
922 # actually output
|
jpayne@68
|
923 return "$prefix[OptTree $desc $nl $tl $dl]"
|
jpayne@68
|
924 }
|
jpayne@68
|
925
|
jpayne@68
|
926
|
jpayne@68
|
927 ################ General Utility functions #######################
|
jpayne@68
|
928
|
jpayne@68
|
929 #
|
jpayne@68
|
930 # List utility functions
|
jpayne@68
|
931 # Naming convention:
|
jpayne@68
|
932 # "Lvarxxx" take the list VARiable name as argument
|
jpayne@68
|
933 # "Lxxxx" take the list value as argument
|
jpayne@68
|
934 # (which is not costly with Tcl8 objects system
|
jpayne@68
|
935 # as it's still a reference and not a copy of the values)
|
jpayne@68
|
936 #
|
jpayne@68
|
937
|
jpayne@68
|
938 # Is that list empty ?
|
jpayne@68
|
939 proc ::tcl::Lempty {list} {
|
jpayne@68
|
940 expr {[llength $list]==0}
|
jpayne@68
|
941 }
|
jpayne@68
|
942
|
jpayne@68
|
943 # Gets the value of one leaf of a lists tree
|
jpayne@68
|
944 proc ::tcl::Lget {list indexLst} {
|
jpayne@68
|
945 if {[llength $indexLst] <= 1} {
|
jpayne@68
|
946 return [lindex $list $indexLst]
|
jpayne@68
|
947 }
|
jpayne@68
|
948 Lget [lindex $list [lindex $indexLst 0]] [lrange $indexLst 1 end]
|
jpayne@68
|
949 }
|
jpayne@68
|
950 # Sets the value of one leaf of a lists tree
|
jpayne@68
|
951 # (we use the version that does not create the elements because
|
jpayne@68
|
952 # it would be even slower... needs to be written in C !)
|
jpayne@68
|
953 # (nb: there is a non trivial recursive problem with indexes 0,
|
jpayne@68
|
954 # which appear because there is no difference between a list
|
jpayne@68
|
955 # of 1 element and 1 element alone : [list "a"] == "a" while
|
jpayne@68
|
956 # it should be {a} and [listp a] should be 0 while [listp {a b}] would be 1
|
jpayne@68
|
957 # and [listp "a b"] maybe 0. listp does not exist either...)
|
jpayne@68
|
958 proc ::tcl::Lvarset {listName indexLst newValue} {
|
jpayne@68
|
959 upvar $listName list
|
jpayne@68
|
960 if {[llength $indexLst] <= 1} {
|
jpayne@68
|
961 Lvarset1nc list $indexLst $newValue
|
jpayne@68
|
962 } else {
|
jpayne@68
|
963 set idx [lindex $indexLst 0]
|
jpayne@68
|
964 set targetList [lindex $list $idx]
|
jpayne@68
|
965 # reduce refcount on targetList (not really usefull now,
|
jpayne@68
|
966 # could be with optimizing compiler)
|
jpayne@68
|
967 # Lvarset1 list $idx {}
|
jpayne@68
|
968 # recursively replace in targetList
|
jpayne@68
|
969 Lvarset targetList [lrange $indexLst 1 end] $newValue
|
jpayne@68
|
970 # put updated sub list back in the tree
|
jpayne@68
|
971 Lvarset1nc list $idx $targetList
|
jpayne@68
|
972 }
|
jpayne@68
|
973 }
|
jpayne@68
|
974 # Set one cell to a value, eventually create all the needed elements
|
jpayne@68
|
975 # (on level-1 of lists)
|
jpayne@68
|
976 variable emptyList {}
|
jpayne@68
|
977 proc ::tcl::Lvarset1 {listName index newValue} {
|
jpayne@68
|
978 upvar $listName list
|
jpayne@68
|
979 if {$index < 0} {return -code error "invalid negative index"}
|
jpayne@68
|
980 set lg [llength $list]
|
jpayne@68
|
981 if {$index >= $lg} {
|
jpayne@68
|
982 variable emptyList
|
jpayne@68
|
983 for {set i $lg} {$i<$index} {incr i} {
|
jpayne@68
|
984 lappend list $emptyList
|
jpayne@68
|
985 }
|
jpayne@68
|
986 lappend list $newValue
|
jpayne@68
|
987 } else {
|
jpayne@68
|
988 set list [lreplace $list $index $index $newValue]
|
jpayne@68
|
989 }
|
jpayne@68
|
990 }
|
jpayne@68
|
991 # same as Lvarset1 but no bound checking / creation
|
jpayne@68
|
992 proc ::tcl::Lvarset1nc {listName index newValue} {
|
jpayne@68
|
993 upvar $listName list
|
jpayne@68
|
994 set list [lreplace $list $index $index $newValue]
|
jpayne@68
|
995 }
|
jpayne@68
|
996 # Increments the value of one leaf of a lists tree
|
jpayne@68
|
997 # (which must exists)
|
jpayne@68
|
998 proc ::tcl::Lvarincr {listName indexLst {howMuch 1}} {
|
jpayne@68
|
999 upvar $listName list
|
jpayne@68
|
1000 if {[llength $indexLst] <= 1} {
|
jpayne@68
|
1001 Lvarincr1 list $indexLst $howMuch
|
jpayne@68
|
1002 } else {
|
jpayne@68
|
1003 set idx [lindex $indexLst 0]
|
jpayne@68
|
1004 set targetList [lindex $list $idx]
|
jpayne@68
|
1005 # reduce refcount on targetList
|
jpayne@68
|
1006 Lvarset1nc list $idx {}
|
jpayne@68
|
1007 # recursively replace in targetList
|
jpayne@68
|
1008 Lvarincr targetList [lrange $indexLst 1 end] $howMuch
|
jpayne@68
|
1009 # put updated sub list back in the tree
|
jpayne@68
|
1010 Lvarset1nc list $idx $targetList
|
jpayne@68
|
1011 }
|
jpayne@68
|
1012 }
|
jpayne@68
|
1013 # Increments the value of one cell of a list
|
jpayne@68
|
1014 proc ::tcl::Lvarincr1 {listName index {howMuch 1}} {
|
jpayne@68
|
1015 upvar $listName list
|
jpayne@68
|
1016 set newValue [expr {[lindex $list $index]+$howMuch}]
|
jpayne@68
|
1017 set list [lreplace $list $index $index $newValue]
|
jpayne@68
|
1018 return $newValue
|
jpayne@68
|
1019 }
|
jpayne@68
|
1020 # Removes the first element of a list
|
jpayne@68
|
1021 # and returns the new list value
|
jpayne@68
|
1022 proc ::tcl::Lvarpop1 {listName} {
|
jpayne@68
|
1023 upvar $listName list
|
jpayne@68
|
1024 set list [lrange $list 1 end]
|
jpayne@68
|
1025 }
|
jpayne@68
|
1026 # Same but returns the removed element
|
jpayne@68
|
1027 # (Like the tclX version)
|
jpayne@68
|
1028 proc ::tcl::Lvarpop {listName} {
|
jpayne@68
|
1029 upvar $listName list
|
jpayne@68
|
1030 set el [lindex $list 0]
|
jpayne@68
|
1031 set list [lrange $list 1 end]
|
jpayne@68
|
1032 return $el
|
jpayne@68
|
1033 }
|
jpayne@68
|
1034 # Assign list elements to variables and return the length of the list
|
jpayne@68
|
1035 proc ::tcl::Lassign {list args} {
|
jpayne@68
|
1036 # faster than direct blown foreach (which does not byte compile)
|
jpayne@68
|
1037 set i 0
|
jpayne@68
|
1038 set lg [llength $list]
|
jpayne@68
|
1039 foreach vname $args {
|
jpayne@68
|
1040 if {$i>=$lg} break
|
jpayne@68
|
1041 uplevel 1 [list ::set $vname [lindex $list $i]]
|
jpayne@68
|
1042 incr i
|
jpayne@68
|
1043 }
|
jpayne@68
|
1044 return $lg
|
jpayne@68
|
1045 }
|
jpayne@68
|
1046
|
jpayne@68
|
1047 # Misc utilities
|
jpayne@68
|
1048
|
jpayne@68
|
1049 # Set the varname to value if value is greater than varname's current value
|
jpayne@68
|
1050 # or if varname is undefined
|
jpayne@68
|
1051 proc ::tcl::SetMax {varname value} {
|
jpayne@68
|
1052 upvar 1 $varname var
|
jpayne@68
|
1053 if {![info exists var] || $value > $var} {
|
jpayne@68
|
1054 set var $value
|
jpayne@68
|
1055 }
|
jpayne@68
|
1056 }
|
jpayne@68
|
1057
|
jpayne@68
|
1058 # Set the varname to value if value is smaller than varname's current value
|
jpayne@68
|
1059 # or if varname is undefined
|
jpayne@68
|
1060 proc ::tcl::SetMin {varname value} {
|
jpayne@68
|
1061 upvar 1 $varname var
|
jpayne@68
|
1062 if {![info exists var] || $value < $var} {
|
jpayne@68
|
1063 set var $value
|
jpayne@68
|
1064 }
|
jpayne@68
|
1065 }
|
jpayne@68
|
1066
|
jpayne@68
|
1067
|
jpayne@68
|
1068 # everything loaded fine, lets create the test proc:
|
jpayne@68
|
1069 # OptCreateTestProc
|
jpayne@68
|
1070 # Don't need the create temp proc anymore:
|
jpayne@68
|
1071 # rename OptCreateTestProc {}
|
jpayne@68
|
1072 }
|