optparse.tcl 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  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.5
  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 [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. }