optparse.tcl 32 KB

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