12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073 |
- # 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.2
- # When this version number changes, update the pkgIndex.tcl file
- # and the install directory in the Makefiles.
- package provide opt 0.4.5
- 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 slaves"}
- {-nestedloading2 -boolean true "OK to load into nested slaves"}
- {-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 {[lsearch -exact $typeArgs $arg] < 0} {
- 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 [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 {}
- }
|