Spaces:
Runtime error
Runtime error
| # optparse.tcl -- | |
| # | |
| # (private) Option parsing package | |
| # Primarily used internally by the safe:: code. | |
| # | |
| # WARNING: This code will go away in a future release | |
| # of Tcl. It is NOT supported and you should not rely | |
| # on it. If your code does rely on this package you | |
| # may directly incorporate this code into your application. | |
| package require Tcl 8.5- | |
| # When this version number changes, update the pkgIndex.tcl file | |
| # and the install directory in the Makefiles. | |
| package provide opt 0.4.8 | |
| namespace eval ::tcl { | |
| # Exported APIs | |
| namespace export OptKeyRegister OptKeyDelete OptKeyError OptKeyParse \ | |
| OptProc OptProcArgGiven OptParse \ | |
| Lempty Lget \ | |
| Lassign Lvarpop Lvarpop1 Lvarset Lvarincr \ | |
| SetMax SetMin | |
| ################# Example of use / 'user documentation' ################### | |
| proc OptCreateTestProc {} { | |
| # Defines ::tcl::OptParseTest as a test proc with parsed arguments | |
| # (can't be defined before the code below is loaded (before "OptProc")) | |
| # Every OptProc give usage information on "procname -help". | |
| # Try "tcl::OptParseTest -help" and "tcl::OptParseTest -a" and | |
| # then other arguments. | |
| # | |
| # example of 'valid' call: | |
| # ::tcl::OptParseTest save -4 -pr 23 -libsok SybTcl\ | |
| # -nostatics false ch1 | |
| OptProc OptParseTest { | |
| {subcommand -choice {save print} "sub command"} | |
| {arg1 3 "some number"} | |
| {-aflag} | |
| {-intflag 7} | |
| {-weirdflag "help string"} | |
| {-noStatics "Not ok to load static packages"} | |
| {-nestedloading1 true "OK to load into nested children"} | |
| {-nestedloading2 -boolean true "OK to load into nested children"} | |
| {-libsOK -choice {Tk SybTcl} | |
| "List of packages that can be loaded"} | |
| {-precision -int 12 "Number of digits of precision"} | |
| {-intval 7 "An integer"} | |
| {-scale -float 1.0 "Scale factor"} | |
| {-zoom 1.0 "Zoom factor"} | |
| {-arbitrary foobar "Arbitrary string"} | |
| {-random -string 12 "Random string"} | |
| {-listval -list {} "List value"} | |
| {-blahflag -blah abc "Funny type"} | |
| {arg2 -boolean "a boolean"} | |
| {arg3 -choice "ch1 ch2"} | |
| {?optarg? -list {} "optional argument"} | |
| } { | |
| foreach v [info locals] { | |
| puts stderr [format "%14s : %s" $v [set $v]] | |
| } | |
| } | |
| } | |
| ################### No User serviceable part below ! ############### | |
| # Array storing the parsed descriptions | |
| variable OptDesc | |
| array set OptDesc {} | |
| # Next potentially free key id (numeric) | |
| variable OptDescN 0 | |
| # Inside algorithm/mechanism description: | |
| # (not for the faint hearted ;-) | |
| # | |
| # The argument description is parsed into a "program tree" | |
| # It is called a "program" because it is the program used by | |
| # the state machine interpreter that use that program to | |
| # actually parse the arguments at run time. | |
| # | |
| # The general structure of a "program" is | |
| # notation (pseudo bnf like) | |
| # name :== definition defines "name" as being "definition" | |
| # { x y z } means list of x, y, and z | |
| # x* means x repeated 0 or more time | |
| # x+ means "x x*" | |
| # x? means optionally x | |
| # x | y means x or y | |
| # "cccc" means the literal string | |
| # | |
| # program :== { programCounter programStep* } | |
| # | |
| # programStep :== program | singleStep | |
| # | |
| # programCounter :== {"P" integer+ } | |
| # | |
| # singleStep :== { instruction parameters* } | |
| # | |
| # instruction :== single element list | |
| # | |
| # (the difference between singleStep and program is that \ | |
| # llength [lindex $program 0] >= 2 | |
| # while | |
| # llength [lindex $singleStep 0] == 1 | |
| # ) | |
| # | |
| # And for this application: | |
| # | |
| # singleStep :== { instruction varname {hasBeenSet currentValue} type | |
| # typeArgs help } | |
| # instruction :== "flags" | "value" | |
| # type :== knowType | anyword | |
| # knowType :== "string" | "int" | "boolean" | "boolflag" | "float" | |
| # | "choice" | |
| # | |
| # for type "choice" typeArgs is a list of possible choices, the first one | |
| # is the default value. for all other types the typeArgs is the default value | |
| # | |
| # a "boolflag" is the type for a flag whose presence or absence, without | |
| # additional arguments means respectively true or false (default flag type). | |
| # | |
| # programCounter is the index in the list of the currently processed | |
| # programStep (thus starting at 1 (0 is {"P" prgCounterValue}). | |
| # If it is a list it points toward each currently selected programStep. | |
| # (like for "flags", as they are optional, form a set and programStep). | |
| # Performance/Implementation issues | |
| # --------------------------------- | |
| # We use tcl lists instead of arrays because with tcl8.0 | |
| # they should start to be much faster. | |
| # But this code use a lot of helper procs (like Lvarset) | |
| # which are quite slow and would be helpfully optimized | |
| # for instance by being written in C. Also our struture | |
| # is complex and there is maybe some places where the | |
| # string rep might be calculated at great exense. to be checked. | |
| # | |
| # Parse a given description and saves it here under the given key | |
| # generate a unused keyid if not given | |
| # | |
| proc ::tcl::OptKeyRegister {desc {key ""}} { | |
| variable OptDesc | |
| variable OptDescN | |
| if {[string equal $key ""]} { | |
| # in case a key given to us as a parameter was a number | |
| while {[info exists OptDesc($OptDescN)]} {incr OptDescN} | |
| set key $OptDescN | |
| incr OptDescN | |
| } | |
| # program counter | |
| set program [list [list "P" 1]] | |
| # are we processing flags (which makes a single program step) | |
| set inflags 0 | |
| set state {} | |
| # flag used to detect that we just have a single (flags set) subprogram. | |
| set empty 1 | |
| foreach item $desc { | |
| if {$state == "args"} { | |
| # more items after 'args'... | |
| return -code error "'args' special argument must be the last one" | |
| } | |
| set res [OptNormalizeOne $item] | |
| set state [lindex $res 0] | |
| if {$inflags} { | |
| if {$state == "flags"} { | |
| # add to 'subprogram' | |
| lappend flagsprg $res | |
| } else { | |
| # put in the flags | |
| # structure for flag programs items is a list of | |
| # {subprgcounter {prg flag 1} {prg flag 2} {...}} | |
| lappend program $flagsprg | |
| # put the other regular stuff | |
| lappend program $res | |
| set inflags 0 | |
| set empty 0 | |
| } | |
| } else { | |
| if {$state == "flags"} { | |
| set inflags 1 | |
| # sub program counter + first sub program | |
| set flagsprg [list [list "P" 1] $res] | |
| } else { | |
| lappend program $res | |
| set empty 0 | |
| } | |
| } | |
| } | |
| if {$inflags} { | |
| if {$empty} { | |
| # We just have the subprogram, optimize and remove | |
| # unneeded level: | |
| set program $flagsprg | |
| } else { | |
| lappend program $flagsprg | |
| } | |
| } | |
| set OptDesc($key) $program | |
| return $key | |
| } | |
| # | |
| # Free the storage for that given key | |
| # | |
| proc ::tcl::OptKeyDelete {key} { | |
| variable OptDesc | |
| unset OptDesc($key) | |
| } | |
| # Get the parsed description stored under the given key. | |
| proc OptKeyGetDesc {descKey} { | |
| variable OptDesc | |
| if {![info exists OptDesc($descKey)]} { | |
| return -code error "Unknown option description key \"$descKey\"" | |
| } | |
| set OptDesc($descKey) | |
| } | |
| # Parse entry point for ppl who don't want to register with a key, | |
| # for instance because the description changes dynamically. | |
| # (otherwise one should really use OptKeyRegister once + OptKeyParse | |
| # as it is way faster or simply OptProc which does it all) | |
| # Assign a temporary key, call OptKeyParse and then free the storage | |
| proc ::tcl::OptParse {desc arglist} { | |
| set tempkey [OptKeyRegister $desc] | |
| set ret [catch {uplevel 1 [list ::tcl::OptKeyParse $tempkey $arglist]} res] | |
| OptKeyDelete $tempkey | |
| return -code $ret $res | |
| } | |
| # Helper function, replacement for proc that both | |
| # register the description under a key which is the name of the proc | |
| # (and thus unique to that code) | |
| # and add a first line to the code to call the OptKeyParse proc | |
| # Stores the list of variables that have been actually given by the user | |
| # (the other will be sets to their default value) | |
| # into local variable named "Args". | |
| proc ::tcl::OptProc {name desc body} { | |
| set namespace [uplevel 1 [list ::namespace current]] | |
| if {[string match "::*" $name] || [string equal $namespace "::"]} { | |
| # absolute name or global namespace, name is the key | |
| set key $name | |
| } else { | |
| # we are relative to some non top level namespace: | |
| set key "${namespace}::${name}" | |
| } | |
| OptKeyRegister $desc $key | |
| uplevel 1 [list ::proc $name args "set Args \[::tcl::OptKeyParse $key \$args\]\n$body"] | |
| return $key | |
| } | |
| # Check that a argument has been given | |
| # assumes that "OptProc" has been used as it will check in "Args" list | |
| proc ::tcl::OptProcArgGiven {argname} { | |
| upvar Args alist | |
| expr {[lsearch $alist $argname] >=0} | |
| } | |
| ####### | |
| # Programs/Descriptions manipulation | |
| # Return the instruction word/list of a given step/(sub)program | |
| proc OptInstr {lst} { | |
| lindex $lst 0 | |
| } | |
| # Is a (sub) program or a plain instruction ? | |
| proc OptIsPrg {lst} { | |
| expr {[llength [OptInstr $lst]]>=2} | |
| } | |
| # Is this instruction a program counter or a real instr | |
| proc OptIsCounter {item} { | |
| expr {[lindex $item 0]=="P"} | |
| } | |
| # Current program counter (2nd word of first word) | |
| proc OptGetPrgCounter {lst} { | |
| Lget $lst {0 1} | |
| } | |
| # Current program counter (2nd word of first word) | |
| proc OptSetPrgCounter {lstName newValue} { | |
| upvar $lstName lst | |
| set lst [lreplace $lst 0 0 [concat "P" $newValue]] | |
| } | |
| # returns a list of currently selected items. | |
| proc OptSelection {lst} { | |
| set res {} | |
| foreach idx [lrange [lindex $lst 0] 1 end] { | |
| lappend res [Lget $lst $idx] | |
| } | |
| return $res | |
| } | |
| # Advance to next description | |
| proc OptNextDesc {descName} { | |
| uplevel 1 [list Lvarincr $descName {0 1}] | |
| } | |
| # Get the current description, eventually descend | |
| proc OptCurDesc {descriptions} { | |
| lindex $descriptions [OptGetPrgCounter $descriptions] | |
| } | |
| # get the current description, eventually descend | |
| # through sub programs as needed. | |
| proc OptCurDescFinal {descriptions} { | |
| set item [OptCurDesc $descriptions] | |
| # Descend untill we get the actual item and not a sub program | |
| while {[OptIsPrg $item]} { | |
| set item [OptCurDesc $item] | |
| } | |
| return $item | |
| } | |
| # Current final instruction adress | |
| proc OptCurAddr {descriptions {start {}}} { | |
| set adress [OptGetPrgCounter $descriptions] | |
| lappend start $adress | |
| set item [lindex $descriptions $adress] | |
| if {[OptIsPrg $item]} { | |
| return [OptCurAddr $item $start] | |
| } else { | |
| return $start | |
| } | |
| } | |
| # Set the value field of the current instruction | |
| proc OptCurSetValue {descriptionsName value} { | |
| upvar $descriptionsName descriptions | |
| # get the current item full adress | |
| set adress [OptCurAddr $descriptions] | |
| # use the 3th field of the item (see OptValue / OptNewInst) | |
| lappend adress 2 | |
| Lvarset descriptions $adress [list 1 $value] | |
| # ^hasBeenSet flag | |
| } | |
| # empty state means done/paste the end of the program | |
| proc OptState {item} { | |
| lindex $item 0 | |
| } | |
| # current state | |
| proc OptCurState {descriptions} { | |
| OptState [OptCurDesc $descriptions] | |
| } | |
| ####### | |
| # Arguments manipulation | |
| # Returns the argument that has to be processed now | |
| proc OptCurrentArg {lst} { | |
| lindex $lst 0 | |
| } | |
| # Advance to next argument | |
| proc OptNextArg {argsName} { | |
| uplevel 1 [list Lvarpop1 $argsName] | |
| } | |
| ####### | |
| # Loop over all descriptions, calling OptDoOne which will | |
| # eventually eat all the arguments. | |
| proc OptDoAll {descriptionsName argumentsName} { | |
| upvar $descriptionsName descriptions | |
| upvar $argumentsName arguments | |
| # puts "entered DoAll" | |
| # Nb: the places where "state" can be set are tricky to figure | |
| # because DoOne sets the state to flagsValue and return -continue | |
| # when needed... | |
| set state [OptCurState $descriptions] | |
| # We'll exit the loop in "OptDoOne" or when state is empty. | |
| while 1 { | |
| set curitem [OptCurDesc $descriptions] | |
| # Do subprograms if needed, call ourselves on the sub branch | |
| while {[OptIsPrg $curitem]} { | |
| OptDoAll curitem arguments | |
| # puts "done DoAll sub" | |
| # Insert back the results in current tree | |
| Lvarset1nc descriptions [OptGetPrgCounter $descriptions]\ | |
| $curitem | |
| OptNextDesc descriptions | |
| set curitem [OptCurDesc $descriptions] | |
| set state [OptCurState $descriptions] | |
| } | |
| # puts "state = \"$state\" - arguments=($arguments)" | |
| if {[Lempty $state]} { | |
| # Nothing left to do, we are done in this branch: | |
| break | |
| } | |
| # The following statement can make us terminate/continue | |
| # as it use return -code {break, continue, return and error} | |
| # codes | |
| OptDoOne descriptions state arguments | |
| # If we are here, no special return code where issued, | |
| # we'll step to next instruction : | |
| # puts "new state = \"$state\"" | |
| OptNextDesc descriptions | |
| set state [OptCurState $descriptions] | |
| } | |
| } | |
| # Process one step for the state machine, | |
| # eventually consuming the current argument. | |
| proc OptDoOne {descriptionsName stateName argumentsName} { | |
| upvar $argumentsName arguments | |
| upvar $descriptionsName descriptions | |
| upvar $stateName state | |
| # the special state/instruction "args" eats all | |
| # the remaining args (if any) | |
| if {($state == "args")} { | |
| if {![Lempty $arguments]} { | |
| # If there is no additional arguments, leave the default value | |
| # in. | |
| OptCurSetValue descriptions $arguments | |
| set arguments {} | |
| } | |
| # puts "breaking out ('args' state: consuming every reminding args)" | |
| return -code break | |
| } | |
| if {[Lempty $arguments]} { | |
| if {$state == "flags"} { | |
| # no argument and no flags : we're done | |
| # puts "returning to previous (sub)prg (no more args)" | |
| return -code return | |
| } elseif {$state == "optValue"} { | |
| set state next; # not used, for debug only | |
| # go to next state | |
| return | |
| } else { | |
| return -code error [OptMissingValue $descriptions] | |
| } | |
| } else { | |
| set arg [OptCurrentArg $arguments] | |
| } | |
| switch $state { | |
| flags { | |
| # A non-dash argument terminates the options, as does -- | |
| # Still a flag ? | |
| if {![OptIsFlag $arg]} { | |
| # don't consume the argument, return to previous prg | |
| return -code return | |
| } | |
| # consume the flag | |
| OptNextArg arguments | |
| if {[string equal "--" $arg]} { | |
| # return from 'flags' state | |
| return -code return | |
| } | |
| set hits [OptHits descriptions $arg] | |
| if {$hits > 1} { | |
| return -code error [OptAmbigous $descriptions $arg] | |
| } elseif {$hits == 0} { | |
| return -code error [OptFlagUsage $descriptions $arg] | |
| } | |
| set item [OptCurDesc $descriptions] | |
| if {[OptNeedValue $item]} { | |
| # we need a value, next state is | |
| set state flagValue | |
| } else { | |
| OptCurSetValue descriptions 1 | |
| } | |
| # continue | |
| return -code continue | |
| } | |
| flagValue - | |
| value { | |
| set item [OptCurDesc $descriptions] | |
| # Test the values against their required type | |
| if {[catch {OptCheckType $arg\ | |
| [OptType $item] [OptTypeArgs $item]} val]} { | |
| return -code error [OptBadValue $item $arg $val] | |
| } | |
| # consume the value | |
| OptNextArg arguments | |
| # set the value | |
| OptCurSetValue descriptions $val | |
| # go to next state | |
| if {$state == "flagValue"} { | |
| set state flags | |
| return -code continue | |
| } else { | |
| set state next; # not used, for debug only | |
| return ; # will go on next step | |
| } | |
| } | |
| optValue { | |
| set item [OptCurDesc $descriptions] | |
| # Test the values against their required type | |
| if {![catch {OptCheckType $arg\ | |
| [OptType $item] [OptTypeArgs $item]} val]} { | |
| # right type, so : | |
| # consume the value | |
| OptNextArg arguments | |
| # set the value | |
| OptCurSetValue descriptions $val | |
| } | |
| # go to next state | |
| set state next; # not used, for debug only | |
| return ; # will go on next step | |
| } | |
| } | |
| # If we reach this point: an unknown | |
| # state as been entered ! | |
| return -code error "Bug! unknown state in DoOne \"$state\"\ | |
| (prg counter [OptGetPrgCounter $descriptions]:\ | |
| [OptCurDesc $descriptions])" | |
| } | |
| # Parse the options given the key to previously registered description | |
| # and arguments list | |
| proc ::tcl::OptKeyParse {descKey arglist} { | |
| set desc [OptKeyGetDesc $descKey] | |
| # make sure -help always give usage | |
| if {[string equal -nocase "-help" $arglist]} { | |
| return -code error [OptError "Usage information:" $desc 1] | |
| } | |
| OptDoAll desc arglist | |
| if {![Lempty $arglist]} { | |
| return -code error [OptTooManyArgs $desc $arglist] | |
| } | |
| # Analyse the result | |
| # Walk through the tree: | |
| OptTreeVars $desc "#[expr {[info level]-1}]" | |
| } | |
| # determine string length for nice tabulated output | |
| proc OptTreeVars {desc level {vnamesLst {}}} { | |
| foreach item $desc { | |
| if {[OptIsCounter $item]} continue | |
| if {[OptIsPrg $item]} { | |
| set vnamesLst [OptTreeVars $item $level $vnamesLst] | |
| } else { | |
| set vname [OptVarName $item] | |
| upvar $level $vname var | |
| if {[OptHasBeenSet $item]} { | |
| # puts "adding $vname" | |
| # lets use the input name for the returned list | |
| # it is more usefull, for instance you can check that | |
| # no flags at all was given with expr | |
| # {![string match "*-*" $Args]} | |
| lappend vnamesLst [OptName $item] | |
| set var [OptValue $item] | |
| } else { | |
| set var [OptDefaultValue $item] | |
| } | |
| } | |
| } | |
| return $vnamesLst | |
| } | |
| # Check the type of a value | |
| # and emit an error if arg is not of the correct type | |
| # otherwise returns the canonical value of that arg (ie 0/1 for booleans) | |
| proc ::tcl::OptCheckType {arg type {typeArgs ""}} { | |
| # puts "checking '$arg' against '$type' ($typeArgs)" | |
| # only types "any", "choice", and numbers can have leading "-" | |
| switch -exact -- $type { | |
| int { | |
| if {![string is integer -strict $arg]} { | |
| error "not an integer" | |
| } | |
| return $arg | |
| } | |
| float { | |
| return [expr {double($arg)}] | |
| } | |
| script - | |
| list { | |
| # if llength fail : malformed list | |
| if {[llength $arg]==0 && [OptIsFlag $arg]} { | |
| error "no values with leading -" | |
| } | |
| return $arg | |
| } | |
| boolean { | |
| if {![string is boolean -strict $arg]} { | |
| error "non canonic boolean" | |
| } | |
| # convert true/false because expr/if is broken with "!,... | |
| return [expr {$arg ? 1 : 0}] | |
| } | |
| choice { | |
| if {$arg ni $typeArgs} { | |
| error "invalid choice" | |
| } | |
| return $arg | |
| } | |
| any { | |
| return $arg | |
| } | |
| string - | |
| default { | |
| if {[OptIsFlag $arg]} { | |
| error "no values with leading -" | |
| } | |
| return $arg | |
| } | |
| } | |
| return neverReached | |
| } | |
| # internal utilities | |
| # returns the number of flags matching the given arg | |
| # sets the (local) prg counter to the list of matches | |
| proc OptHits {descName arg} { | |
| upvar $descName desc | |
| set hits 0 | |
| set hitems {} | |
| set i 1 | |
| set larg [string tolower $arg] | |
| set len [string length $larg] | |
| set last [expr {$len-1}] | |
| foreach item [lrange $desc 1 end] { | |
| set flag [OptName $item] | |
| # lets try to match case insensitively | |
| # (string length ought to be cheap) | |
| set lflag [string tolower $flag] | |
| if {$len == [string length $lflag]} { | |
| if {[string equal $larg $lflag]} { | |
| # Exact match case | |
| OptSetPrgCounter desc $i | |
| return 1 | |
| } | |
| } elseif {[string equal $larg [string range $lflag 0 $last]]} { | |
| lappend hitems $i | |
| incr hits | |
| } | |
| incr i | |
| } | |
| if {$hits} { | |
| OptSetPrgCounter desc $hitems | |
| } | |
| return $hits | |
| } | |
| # Extract fields from the list structure: | |
| proc OptName {item} { | |
| lindex $item 1 | |
| } | |
| proc OptHasBeenSet {item} { | |
| Lget $item {2 0} | |
| } | |
| proc OptValue {item} { | |
| Lget $item {2 1} | |
| } | |
| proc OptIsFlag {name} { | |
| string match "-*" $name | |
| } | |
| proc OptIsOpt {name} { | |
| string match {\?*} $name | |
| } | |
| proc OptVarName {item} { | |
| set name [OptName $item] | |
| if {[OptIsFlag $name]} { | |
| return [string range $name 1 end] | |
| } elseif {[OptIsOpt $name]} { | |
| return [string trim $name "?"] | |
| } else { | |
| return $name | |
| } | |
| } | |
| proc OptType {item} { | |
| lindex $item 3 | |
| } | |
| proc OptTypeArgs {item} { | |
| lindex $item 4 | |
| } | |
| proc OptHelp {item} { | |
| lindex $item 5 | |
| } | |
| proc OptNeedValue {item} { | |
| expr {![string equal [OptType $item] boolflag]} | |
| } | |
| proc OptDefaultValue {item} { | |
| set val [OptTypeArgs $item] | |
| switch -exact -- [OptType $item] { | |
| choice {return [lindex $val 0]} | |
| boolean - | |
| boolflag { | |
| # convert back false/true to 0/1 because expr !$bool | |
| # is broken.. | |
| if {$val} { | |
| return 1 | |
| } else { | |
| return 0 | |
| } | |
| } | |
| } | |
| return $val | |
| } | |
| # Description format error helper | |
| proc OptOptUsage {item {what ""}} { | |
| return -code error "invalid description format$what: $item\n\ | |
| should be a list of {varname|-flagname ?-type? ?defaultvalue?\ | |
| ?helpstring?}" | |
| } | |
| # Generate a canonical form single instruction | |
| proc OptNewInst {state varname type typeArgs help} { | |
| list $state $varname [list 0 {}] $type $typeArgs $help | |
| # ^ ^ | |
| # | | | |
| # hasBeenSet=+ +=currentValue | |
| } | |
| # Translate one item to canonical form | |
| proc OptNormalizeOne {item} { | |
| set lg [Lassign $item varname arg1 arg2 arg3] | |
| # puts "called optnormalizeone '$item' v=($varname), lg=$lg" | |
| set isflag [OptIsFlag $varname] | |
| set isopt [OptIsOpt $varname] | |
| if {$isflag} { | |
| set state "flags" | |
| } elseif {$isopt} { | |
| set state "optValue" | |
| } elseif {![string equal $varname "args"]} { | |
| set state "value" | |
| } else { | |
| set state "args" | |
| } | |
| # apply 'smart' 'fuzzy' logic to try to make | |
| # description writer's life easy, and our's difficult : | |
| # let's guess the missing arguments :-) | |
| switch $lg { | |
| 1 { | |
| if {$isflag} { | |
| return [OptNewInst $state $varname boolflag false ""] | |
| } else { | |
| return [OptNewInst $state $varname any "" ""] | |
| } | |
| } | |
| 2 { | |
| # varname default | |
| # varname help | |
| set type [OptGuessType $arg1] | |
| if {[string equal $type "string"]} { | |
| if {$isflag} { | |
| set type boolflag | |
| set def false | |
| } else { | |
| set type any | |
| set def "" | |
| } | |
| set help $arg1 | |
| } else { | |
| set help "" | |
| set def $arg1 | |
| } | |
| return [OptNewInst $state $varname $type $def $help] | |
| } | |
| 3 { | |
| # varname type value | |
| # varname value comment | |
| if {[regexp {^-(.+)$} $arg1 x type]} { | |
| # flags/optValue as they are optional, need a "value", | |
| # on the contrary, for a variable (non optional), | |
| # default value is pointless, 'cept for choices : | |
| if {$isflag || $isopt || ($type == "choice")} { | |
| return [OptNewInst $state $varname $type $arg2 ""] | |
| } else { | |
| return [OptNewInst $state $varname $type "" $arg2] | |
| } | |
| } else { | |
| return [OptNewInst $state $varname\ | |
| [OptGuessType $arg1] $arg1 $arg2] | |
| } | |
| } | |
| 4 { | |
| if {[regexp {^-(.+)$} $arg1 x type]} { | |
| return [OptNewInst $state $varname $type $arg2 $arg3] | |
| } else { | |
| return -code error [OptOptUsage $item] | |
| } | |
| } | |
| default { | |
| return -code error [OptOptUsage $item] | |
| } | |
| } | |
| } | |
| # Auto magic lazy type determination | |
| proc OptGuessType {arg} { | |
| if { $arg == "true" || $arg == "false" } { | |
| return boolean | |
| } | |
| if {[string is integer -strict $arg]} { | |
| return int | |
| } | |
| if {[string is double -strict $arg]} { | |
| return float | |
| } | |
| return string | |
| } | |
| # Error messages front ends | |
| proc OptAmbigous {desc arg} { | |
| OptError "ambigous option \"$arg\", choose from:" [OptSelection $desc] | |
| } | |
| proc OptFlagUsage {desc arg} { | |
| OptError "bad flag \"$arg\", must be one of" $desc | |
| } | |
| proc OptTooManyArgs {desc arguments} { | |
| OptError "too many arguments (unexpected argument(s): $arguments),\ | |
| usage:"\ | |
| $desc 1 | |
| } | |
| proc OptParamType {item} { | |
| if {[OptIsFlag $item]} { | |
| return "flag" | |
| } else { | |
| return "parameter" | |
| } | |
| } | |
| proc OptBadValue {item arg {err {}}} { | |
| # puts "bad val err = \"$err\"" | |
| OptError "bad value \"$arg\" for [OptParamType $item]"\ | |
| [list $item] | |
| } | |
| proc OptMissingValue {descriptions} { | |
| # set item [OptCurDescFinal $descriptions] | |
| set item [OptCurDesc $descriptions] | |
| OptError "no value given for [OptParamType $item] \"[OptName $item]\"\ | |
| (use -help for full usage) :"\ | |
| [list $item] | |
| } | |
| proc ::tcl::OptKeyError {prefix descKey {header 0}} { | |
| OptError $prefix [OptKeyGetDesc $descKey] $header | |
| } | |
| # determine string length for nice tabulated output | |
| proc OptLengths {desc nlName tlName dlName} { | |
| upvar $nlName nl | |
| upvar $tlName tl | |
| upvar $dlName dl | |
| foreach item $desc { | |
| if {[OptIsCounter $item]} continue | |
| if {[OptIsPrg $item]} { | |
| OptLengths $item nl tl dl | |
| } else { | |
| SetMax nl [string length [OptName $item]] | |
| SetMax tl [string length [OptType $item]] | |
| set dv [OptTypeArgs $item] | |
| if {[OptState $item] != "header"} { | |
| set dv "($dv)" | |
| } | |
| set l [string length $dv] | |
| # limit the space allocated to potentially big "choices" | |
| if {([OptType $item] != "choice") || ($l<=12)} { | |
| SetMax dl $l | |
| } else { | |
| if {![info exists dl]} { | |
| set dl 0 | |
| } | |
| } | |
| } | |
| } | |
| } | |
| # output the tree | |
| proc OptTree {desc nl tl dl} { | |
| set res "" | |
| foreach item $desc { | |
| if {[OptIsCounter $item]} continue | |
| if {[OptIsPrg $item]} { | |
| append res [OptTree $item $nl $tl $dl] | |
| } else { | |
| set dv [OptTypeArgs $item] | |
| if {[OptState $item] != "header"} { | |
| set dv "($dv)" | |
| } | |
| append res [string trimright [format "\n %-*s %-*s %-*s %s" \ | |
| $nl [OptName $item] $tl [OptType $item] \ | |
| $dl $dv [OptHelp $item]]] | |
| } | |
| } | |
| return $res | |
| } | |
| # Give nice usage string | |
| proc ::tcl::OptError {prefix desc {header 0}} { | |
| # determine length | |
| if {$header} { | |
| # add faked instruction | |
| set h [list [OptNewInst header Var/FlagName Type Value Help]] | |
| lappend h [OptNewInst header ------------ ---- ----- ----] | |
| lappend h [OptNewInst header {(-help} "" "" {gives this help)}] | |
| set desc [concat $h $desc] | |
| } | |
| OptLengths $desc nl tl dl | |
| # actually output | |
| return "$prefix[OptTree $desc $nl $tl $dl]" | |
| } | |
| ################ General Utility functions ####################### | |
| # | |
| # List utility functions | |
| # Naming convention: | |
| # "Lvarxxx" take the list VARiable name as argument | |
| # "Lxxxx" take the list value as argument | |
| # (which is not costly with Tcl8 objects system | |
| # as it's still a reference and not a copy of the values) | |
| # | |
| # Is that list empty ? | |
| proc ::tcl::Lempty {list} { | |
| expr {[llength $list]==0} | |
| } | |
| # Gets the value of one leaf of a lists tree | |
| proc ::tcl::Lget {list indexLst} { | |
| if {[llength $indexLst] <= 1} { | |
| return [lindex $list $indexLst] | |
| } | |
| Lget [lindex $list [lindex $indexLst 0]] [lrange $indexLst 1 end] | |
| } | |
| # Sets the value of one leaf of a lists tree | |
| # (we use the version that does not create the elements because | |
| # it would be even slower... needs to be written in C !) | |
| # (nb: there is a non trivial recursive problem with indexes 0, | |
| # which appear because there is no difference between a list | |
| # of 1 element and 1 element alone : [list "a"] == "a" while | |
| # it should be {a} and [listp a] should be 0 while [listp {a b}] would be 1 | |
| # and [listp "a b"] maybe 0. listp does not exist either...) | |
| proc ::tcl::Lvarset {listName indexLst newValue} { | |
| upvar $listName list | |
| if {[llength $indexLst] <= 1} { | |
| Lvarset1nc list $indexLst $newValue | |
| } else { | |
| set idx [lindex $indexLst 0] | |
| set targetList [lindex $list $idx] | |
| # reduce refcount on targetList (not really usefull now, | |
| # could be with optimizing compiler) | |
| # Lvarset1 list $idx {} | |
| # recursively replace in targetList | |
| Lvarset targetList [lrange $indexLst 1 end] $newValue | |
| # put updated sub list back in the tree | |
| Lvarset1nc list $idx $targetList | |
| } | |
| } | |
| # Set one cell to a value, eventually create all the needed elements | |
| # (on level-1 of lists) | |
| variable emptyList {} | |
| proc ::tcl::Lvarset1 {listName index newValue} { | |
| upvar $listName list | |
| if {$index < 0} {return -code error "invalid negative index"} | |
| set lg [llength $list] | |
| if {$index >= $lg} { | |
| variable emptyList | |
| for {set i $lg} {$i<$index} {incr i} { | |
| lappend list $emptyList | |
| } | |
| lappend list $newValue | |
| } else { | |
| set list [lreplace $list $index $index $newValue] | |
| } | |
| } | |
| # same as Lvarset1 but no bound checking / creation | |
| proc ::tcl::Lvarset1nc {listName index newValue} { | |
| upvar $listName list | |
| set list [lreplace $list $index $index $newValue] | |
| } | |
| # Increments the value of one leaf of a lists tree | |
| # (which must exists) | |
| proc ::tcl::Lvarincr {listName indexLst {howMuch 1}} { | |
| upvar $listName list | |
| if {[llength $indexLst] <= 1} { | |
| Lvarincr1 list $indexLst $howMuch | |
| } else { | |
| set idx [lindex $indexLst 0] | |
| set targetList [lindex $list $idx] | |
| # reduce refcount on targetList | |
| Lvarset1nc list $idx {} | |
| # recursively replace in targetList | |
| Lvarincr targetList [lrange $indexLst 1 end] $howMuch | |
| # put updated sub list back in the tree | |
| Lvarset1nc list $idx $targetList | |
| } | |
| } | |
| # Increments the value of one cell of a list | |
| proc ::tcl::Lvarincr1 {listName index {howMuch 1}} { | |
| upvar $listName list | |
| set newValue [expr {[lindex $list $index]+$howMuch}] | |
| set list [lreplace $list $index $index $newValue] | |
| return $newValue | |
| } | |
| # Removes the first element of a list | |
| # and returns the new list value | |
| proc ::tcl::Lvarpop1 {listName} { | |
| upvar $listName list | |
| set list [lrange $list 1 end] | |
| } | |
| # Same but returns the removed element | |
| # (Like the tclX version) | |
| proc ::tcl::Lvarpop {listName} { | |
| upvar $listName list | |
| set el [lindex $list 0] | |
| set list [lrange $list 1 end] | |
| return $el | |
| } | |
| # Assign list elements to variables and return the length of the list | |
| proc ::tcl::Lassign {list args} { | |
| # faster than direct blown foreach (which does not byte compile) | |
| set i 0 | |
| set lg [llength $list] | |
| foreach vname $args { | |
| if {$i>=$lg} break | |
| uplevel 1 [list ::set $vname [lindex $list $i]] | |
| incr i | |
| } | |
| return $lg | |
| } | |
| # Misc utilities | |
| # Set the varname to value if value is greater than varname's current value | |
| # or if varname is undefined | |
| proc ::tcl::SetMax {varname value} { | |
| upvar 1 $varname var | |
| if {![info exists var] || $value > $var} { | |
| set var $value | |
| } | |
| } | |
| # Set the varname to value if value is smaller than varname's current value | |
| # or if varname is undefined | |
| proc ::tcl::SetMin {varname value} { | |
| upvar 1 $varname var | |
| if {![info exists var] || $value < $var} { | |
| set var $value | |
| } | |
| } | |
| # everything loaded fine, lets create the test proc: | |
| # OptCreateTestProc | |
| # Don't need the create temp proc anymore: | |
| # rename OptCreateTestProc {} | |
| } | |