Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch amg-argparse Excluding Merge-Ins
This is equivalent to a diff from b99647b031 to eccc25a0c4
2019-11-26
| ||
02:53 | Integrated blowfish work. Updated local documentation. check-in: c230d5a347 user: aku tags: trunk | |
2019-11-25
| ||
22:28 | blowfish - blowfish - B - Tkt [d56da1abca] Version bumped to 1.0.5 (*). Updated documentation. Tweaked `Chunk` patch slightly (Early return for empty data). TODO: Add tests (padding, no padding). (*) Missed in patch for code, package index, docs. check-in: ad21f4eab3 user: andreask tags: tkt-d56da1abca | |
2019-11-24
| ||
21:25 | Merge amg-argparse Leaf check-in: b8b70870c9 user: andy tags: amg-argparse-validation | |
21:24 | Merge trunk Leaf check-in: e7b61937e1 user: andy tags: amg-pipeline | |
21:24 | Merge trunk Leaf check-in: eccc25a0c4 user: andy tags: amg-argparse | |
2019-11-23
| ||
05:02 | 1. Integrated markdown work. Updated local documentation. 2. Reworked the collection and post processing of per-test(suite) timings added in commit [6b2f59f4e4] for faster sorting. Further fixed an issue with the collection of the per-test timings in the face of variable-field data. 3. Switching the 8.6 series from 8.6.9 to 8.6.10 for testing caused failures (hook, string::token::shell) due to changes in command name reporting (FQN in a few places where plain names were reported before). This actually looks to be a bug fix, restoring 8.5 behaviour. These testsuite issues were fixed by extending the test code used to select the expected result by core version. Added a new test utility command `byConstraint` to help with that, allowing for easy multi-way selection. check-in: b99647b031 user: aku tags: trunk | |
2019-11-21
| ||
22:43 | Extended the sak test runner to collect and save per-test timing information (u-seconds). WIBNI for tcltest: Collect data separately for setup/body/cleanup scripts. check-in: 6b2f59f4e4 user: andreask tags: trunk | |
2019-11-20
| ||
07:09 | markdown - markdown - B - Tkt [52dfecac69] Tweaked rendering of code blocks. Extended testsuite. Version bumped to 1.1.1. Closed-Leaf check-in: d028774388 user: aku tags: tkt-52dfecac69 | |
2019-04-26
| ||
17:51 | Update to-do list check-in: 9064b9d02f user: andy tags: amg-argparse | |
Added modules/argparse/TODO.md.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | - Add switch to put parameters before switches - Some Tcl commands work this way - Examples: [chan copy], [zlib gunzip], [clock], [registry broadcast] - Remove `-boolean` - Instead, act as if `-boolean` were always being passed to [argparse] - Remove `-keep` as well - Add an `-unset` switch to selectively change the behavior to unsetting a variable or omitting a dict key when the switch is not present - Remove `-switch` and `-parameter` - Instead make `-` "shorthand" mandatory when declaring a switch - Change from a result dict to a result array - Makes more complex validation expressions possible - If `-inline` is used, use [array get] to get the return value - Allow validation expressions to see the result array, not just the switch or parameter being validated - Use [upvar] rather than [array get] and [array set] to expose the result array into the validation expression - Also use [upvar] to link the arg variable to the current value - Validation expressions can then be (ab)used to modify the results - Necessary to do validation expressions as a separate final pass - Still do enumeration immediately so their final result values will be reflected in the validation - Allow enumerations to have validation expressions - No real reason why these two have to conflict - Documentation - Instead of having a big comment, use doctools - Help text generation - Parameter and switch descriptions given by per-element `-help` switch - Need overall `-help` switch to provide narrative description - May need special grouping elements to organize switches and parameters - Text formatting and word wrapping - Automatic command usage error message - Takes the place of Tcl\_WrongNumArgs() - Single-character switch clustering - Requires use of `-long` for switches with multi-character names - Reimplement in C for performance reasons - No bytecoding needed - Continue to maintain Tcl version for reference and portability - Provide stubs-enabled C API - One function for parsing definition list - One function for parsing argument list against preparsed definition list - Custom Tcl\_Obj type for preparsed definition list - Test suite - Possibly the most daunting part of the project <!-- vim: set sts=4 sw=4 tw=80 et ft=markdown: --> |
Added modules/argparse/argparse.man.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 | [vset CATEGORY argparse] [vset ARGPARSE_VERSION 0.4] [manpage_begin argparse n [vset ARGPARSE_VERSION]] [keywords {argument processing}] [keywords argv] [keywords argv0] [keywords {cmdline processing}] [keywords {command line processing}] [see_also cmdline(n)] [moddesc {Command switch and parameter parsing}] [titledesc {Procedure for processing command switches and parameters.}] [category {Programming tools}] [require Tcl 8.6] [usage [cmd argparse]\ [opt [arg {-globalSwitch ...}]]\ [opt [option --]]\ [arg definition]\ [opt [arg arguments]]] [description] The [package argparse] package provides a powerful argument parser command named [cmd argparse] capable of flexibly processing and validating many varieties of switches and parameters. [para] Tcl commands requiring more advanced argument parsing than provided by the standard [cmd proc] command can be declared to accept [const args] (i.e. any number of arguments), then can call [cmd argparse] to perform the real argument parsing. [para] In addition to Tcl command argument parsing, [cmd argparse] is suitable for command line argument parsing, operating on the value of the [var ::argv] global variable. [para] [cmd argparse] may be applied to a variety of special purposes beyond standard argument parsing. For example, [cmd argparse] can parse custom variadic data structures formatted as lists of switches and/or parameters of a highly dynamic nature. Another example: by calling [cmd argparse] multiple times, it is possible to parse nested or multi-part argument lists in which arguments to subsystems are embedded in passthrough switches or parameters. [section {Quick Start}] The [cmd argparse] command may have many complex features, but it is not necessary to understand it in depth before using it for the most common tasks. Its syntax is reasonably intuitive, so the best thing to do is see it in action before reading the precise details on how it works. [para] Consider the following: [example_begin] % proc greet {args} { [cmd {argparse { {-salutation= -default hello} -modifier= -title subject }}] set msg [var {$salutation}] if {[lb]info exists [var title][rb]} { set msg [lb]string totitle $msg[rb] } if {[lb]info exists [var modifier][rb]} { append msg ", " [var {$modifier}] } append msg " " [var {$subject}] } % [cmd {greet world}] hello world % [cmd {greet -salutation howdy world}] howdy world % [cmd {greet -title -mod "my dear" world}] Hello, my dear world % [cmd {greet -title}] hello -title [example_end] This example demonstrates many of the [cmd argparse] core concepts and features. The [cmd greet] command is defined to accept [const args]. When not explicitly given an argument list to parse, [cmd argparse] parses the value of the [const args] variable and stores the results into local variables having the same [sectref Name names] as the various [sectref Element elements] of the [sectref Definition definition]. [para] Here, the definition is a list of four elements, named [var salutation], [var modifier], [var title], and [var subject]. Because their names are prefixed with "[option -]", [var salutation], [var modifier], and [var title] are [sectref Switch switches], whereas [var subject], lacking a "[option -]" prefix, is a [sectref Parameter parameter]. Two of the switches are given a "[option =]" suffix, which means they each take an [sectref Argument argument], whereas [var title] does not. In addition to these [sectref Flag flag] characters, the [var salutation] element is surrounded with braces because it contains more list words used to further customize how it is handled. Namely, it uses the [option -default] [sectref {Element Switches} {element switch}] to set its default value to [const hello], in case [option -salutation] is not present in the argument list. [para] By default, switches are [sectref Optional optional] and parameters are [sectref Required required]. [var salutation], [var modifier], and [var title], being switches, are all optional, but since [var salutation] has a default value, its variable's existence is therefore guaranteed. Likewise, [var subject], being a parameter, is required, and its variable's existence is also guaranteed. On the contrary, because [var modifier] and [var title] are optional and have no default value, it is necessary to use [cmd {info exists}] to confirm their variables' existence before attempting to read them. Because [var title] does not accept an argument, its variable's value (if the variable exists at all) is predefined to be empty string. [example_begin] % [cmd {greet world}] hello world [example_end] The first time [cmd greet] is called, it is given only one argument, which is bound to the [var subject] parameter. Normally, switch arguments appear to the left of parameter arguments, and parameter arguments are bound first. Thus, the final argument to [cmd greet] is always bound to the [var subject] parameter, even if it happens to resemble a switch, and is therefore stored in the [var subject] variable. Because their associated switches do not appear in the argument list, the [var salutation] variable is set to its default value ([const hello]), and the [var modifier] and [var title] variables are unset due to lacking a default. [example_begin] % [cmd {greet -salutation howdy world}] howdy world [example_end] The second time [cmd greet] is called, it is given three arguments. As discussed above, the final argument ([const world]) is immediately stored into the [var subject] variable before any switch processing occurs. Next, the remaining two arguments are examined and determined to be the name and value of the [var salutation] switch. Thus, the second argument ([const howdy]) is stored in the [var salutation] variable. The [var modifier] and [var title] variables are unset. [example_begin] % [cmd {greet -title -mod "my dear" world}] Hello, my dear world [example_end] The third time [cmd greet] is called, it is given four arguments. The first is [option -title], causing the [var title] variable to be set to empty string. The second is the name of the [var modifier] switch. More precisely, it is an unambiguous prefix thereof, i.e. an abbreviation. This causes the third argument ([const {my dear}]) to be stored in the [var modifier] variable, and the final argument ([const world]) is stored in the [var subject] variable. As for the [var salutation] variable, it is set to its default ([var hello]). [example_begin] % [cmd {greet -title}] hello -title [example_end] The fourth time [cmd greet] is called, it is given one argument. Because the final argument is always bound to the [var subject] parameter, [var subject] is set to [option -title] even though there happens to be a switch with the same name. There are no remaining arguments, so the switches are all handled according to defaults, just like in the first call to [cmd greet]. [section Concepts] This section lists and explains the concepts and terminology employed by the [package argparse] package. The concepts are numerous and interrelated, so it may be necessary to read this section multiple times and to refer back to it while reading the remainder of this document. [subsection Definition] The [term definition] determines how the [cmd argparse] command parses its arguments. Definitions are Tcl lists of any length, each word of which is an [sectref Element element]. [para] The following example definition may conceivably be used by a command that stores a sequence of numbers into a variable. [example { { # {Optional sequence control switches} {-from= -default 1} {-to= -default 10} {-step= -default 1} # {Required output list variable} listVar^ } }] [subsection Element] A [sectref Definition definition] contains any number of [term elements]. For the most part, each element defines either a [sectref Switch switch] or a [sectref Parameter parameter]. Elements may also be [sectref Comment comments] or [sectref {Global Switch} {global switches}]. An element is itself a Tcl list whose required first word is the [sectref Name name] (with optional shorthand [sectref Alias aliases] and [sectref Flag flags]) and whose optional subsequent words are [sectref {Element Switch} {element switches}]. [para] In addition to switches and parameters, elements may be [sectref Comment comments] or lists of [sectref {Global Switch} {global switches}]. [para] The example definition shown above contains one parameter element, three switch elements, two comment elements, and no global switches. All switch and parameter elements in the example make use of shorthand flags. [para] One element from the above example is repeated here so it may be examined more closely. [example { {-from= -default 1} }] The name of this element is [const from]. It uses the "[option -]" and "[option =]" flags, as well as the [option -default] element switch with argument [const 1]. The specific meaning of flags and element switches are described elsewhere in this document. [subsection Name] Aside from [sectref Alias aliases] and [sectref Flag flags], the first word of each [sectref Switch switch] or [sectref Parameter parameter] [sectref Element element] in the [sectref Definition definition] is the [term name]. [para] Switch and parameter element names may not be used more than once within a definition. [para] If the name is "[option #]", the element is a [sectref Comment comment] and is ignored. If the name is empty string, the element is neither a switch nor a parameter and is instead a list of [sectref {Global Switch} {global switches}]. [subsection Key] In addition to having a [sectref Name name], every [sectref Switch switch] and [sectref Parameter parameter] [sectref Definition definition] [sectref Element element] has a [term key]. Unlike names, multiple elements may share the same key, subject to restrictions. [para] By default, the key defaults to the name, but it is possible to use the [option -key] [sectref {Element Switch} {element switch}] to explicitly declare an element's key. [para] Keys determine the variable names or dict keys [cmd argparse] uses to return the argument value of the switch or parameter. [subsection Alias] [subsection Flag] [subsection {Global Switch}] A [term {global switch}] configures the overall operation of the [cmd argparse] command. Global switches are optional initial arguments to [cmd argparse] and may only appear before the [sectref Definition definition] argument. Global switches may also be embedded within special definition [sectref Element elements] whose [sectref Name name] is empty string. [subsection {Element Switch}] An [term {element switch}] configures a single [sectref Element element] in the [sectref Definition definition]. Element switches are listed following the the [sectref Name name] word of the definition element. [para] In the above example definition, each [sectref Switch switch] element explicitly uses the [option -default] element switch. Due to use of the "[option -]" and "[option =]" shorthand [sectref Flag flags], each switch element also implicitly uses the [option -switch] and [option -argument] element switches. [subsection Switch] [subsection Parameter] [subsection Comment] [subsection Argument] The actual values passed to the [cmd argparse] command to be parsed are known as arguments. [subsection Required] [subsection Optional] [section {Global Switches}] [list_begin options] [opt_def -inline] Return the result dict rather than setting caller variables [opt_def -exact] Require exact switch name matches, and do not accept prefixes [opt_def -mixed] Allow switches to appear after parameters [opt_def -long] Recognize "--switch" long option alternative syntax [opt_def -equalarg] Recognize "-switch=arg" inline argument alternative syntax [opt_def -normalize] Normalize switch syntax in passthrough result keys [opt_def -reciprocal] Every element's -require constraints are reciprocal [opt_def -level levelSpec] Every -upvar element's [lb]upvar[rb] level; defaults to 1 [opt_def -template templateString] Transform default element names using a substitution template [opt_def -pass passKey] Pass unrecognized elements through to a result key [opt_def -keep] Do not unset omitted element variables; conflicts with -inline [opt_def -boolean] Treat switches as having -boolean wherever possible [opt_def -validate validDef] Define named validation expressions to be used by elements [opt_def -enum enumDef] Define named enumeration lists to be used by elements [opt_def --] Force next argument to be interpreted as the definition list [list_end] [section {Element Switches}] [list_begin options] [opt_def -switch] Element is a switch; conflicts with -parameter [opt_def -parameter] Element is a parameter; conflicts with -switch [opt_def -alias aliasName] Alias name; requires -switch [opt_def -ignore] Element is omitted from result; conflicts with -key and -pass [opt_def -key keyName] Override key name; not affected by -template [opt_def -pass keyName] Pass through to result key; not affected by -template [opt_def -default value] Value if omitted; conflicts with -required and -keep [opt_def -keep] Do not unset if omitted; requires -optional; conflicts -inline [opt_def -value value] Value if present; requires -switch; conflicts with -argument [opt_def -boolean] Equivalent to "-default 0 -value 1" [opt_def -argument] Value is next argument following switch; requires -switch [opt_def -optional] Switch value is optional, or parameter is optional [opt_def -required] Switch is required, or stop -catchall from implying -optional [opt_def -catchall] Value is list of all otherwise unassigned arguments [opt_def -upvar] Links caller variable; conflicts with -inline and -catchall [opt_def -level levelSpec] This element's [lb]upvar[rb] level; requires -upvar [opt_def -standalone] If element is present, ignore -required, -require, and -forbid [opt_def -require nameList] If element is present, other elements that must be present [opt_def -forbid nameList] If element is present, other elements that must not be present [opt_def -imply argList] If element is present, extra switch arguments; requires -switch [opt_def -reciprocal] This element's -require is reciprocal; requires -require [opt_def -validate validNameOrDef] Name of validation expression, or inline validation definition [opt_def -enum enumNameOrDef] Name of enumeration list, or inline enumeration definition [list_end] [section Shorthand] [subsection Aliases] [subsection Flags] [section Validation] [section Passthrough] [section {Upvar Elements}] [section {Argument Processing Sequence}] [section {Return Value}] [include ../doctools2base/include/feedback.inc] [section Author] Andy Goth <[email protected]> [comment { vim: set sts=4 sw=4 tw=80 et ft=tcl: }] [manpage_end] |
Added modules/argparse/argparse.tcl.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 | # argparse.tcl -- # # Feature-heavy argument parsing package # # Copyright (C) 2019 Andy Goth <[email protected]> # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. package require Tcl 8.6 package provide argparse 0.5 # argparse -- # Parses an argument list according to a definition list. The result may be # stored into caller variables or returned as a dict. # # The [argparse] command accepts the following switches: # # -inline Return the result dict rather than setting caller variables # -exact Require exact switch name matches, and do not accept prefixes # -mixed Allow switches to appear after parameters # -long Recognize "--switch" long option alternative syntax # -equalarg Recognize "-switch=arg" inline argument alternative syntax # -normalize Normalize switch syntax in pass-through result keys # -reciprocal Every element's -require constraints are reciprocal # -level LEVEL Every -upvar element's [upvar] level; defaults to 1 # -template TMP Transform default element names using a substitution template # -pass KEY Pass unrecognized elements through to a result key # -keep Do not unset omitted element variables; conflicts with -inline # -boolean Treat switches as having -boolean wherever possible # -validate DEF Define named validation expressions to be used by elements # -enum DEF Define named enumeration lists to be used by elements # -- Force next argument to be interpreted as the definition list # # After the above switches comes the definition list argument, then finally the # optional argument list argument. If the argument list is omitted, it is taken # from the caller's args variable. # # A special syntax is provided allowing the above switches to appear within the # definition list argument. If an element of the definition list is a list # whose first element is empty string, its subsequent elements are treated as # additional overall switches to [argparse]. # # Each element of the definition list is itself a list containing a unique, # non-empty name element consisting of alphanumerics, underscores, and minus # (not as the first character), then zero or more of the following switches: # # -switch Element is a switch; conflicts with -parameter # -parameter Element is a parameter; conflicts with -switch # -alias ALIAS Alias name; requires -switch # -ignore Element is omitted from result; conflicts with -key and -pass # -key KEY Override key name; not affected by -template # -pass KEY Pass through to result key; not affected by -template # -default VAL Value if omitted; conflicts with -required and -keep # -keep Do not unset if omitted; requires -optional; conflicts -inline # -value VAL Value if present; requires -switch; conflicts with -argument # -boolean Equivalent to "-default 0 -value 1" # -argument Value is next argument following switch; requires -switch # -optional Switch value is optional, or parameter is optional # -required Switch is required, or stop -catchall from implying -optional # -catchall Value is list of all otherwise unassigned arguments # -upvar Links caller variable; conflicts with -inline and -catchall # -level LEVEL This element's [upvar] level; requires -upvar # -standalone If element is present, ignore -required, -require, and -forbid # -require LIST If element is present, other elements that must be present # -forbid LIST If element is present, other elements that must not be present # -imply LIST If element is present, extra switch arguments; requires -switch # -reciprocal This element's -require is reciprocal; requires -require # -validate DEF Name of validation expression, or inline validation definition # -enum DEF Name of enumeration list, or inline enumeration definition # # If the first (possibly only) word of a definition list element is the single # character "#", the element is ignored. If the definition list element is only # one word long, the following element is ignored as well. This may be used to # place comments directly within the definition list. # # If neither -switch nor -parameter are used, a shorthand form is permitted. If # the name is preceded by "-", it is a switch; otherwise, it is a parameter. An # alias may be written after "-", then followed by "|" and the switch name. The # element name may be followed by any number of flag characters: # # "=" Same as -argument; only valid for switches # "?" Same as -optional # "!" Same as -required # "*" Same as -catchall # "^" Same as -upvar # # -default specifies the value to assign to element keys when the element is # omitted. If -default is not used, keys for omitted switches and parameters # are omitted from the result, unless -catchall is used, in which case the # default value for -default is empty string. # # At most one parameter may use -catchall. # # Multiple elements may share the same -key value if they are switches, do not # use -argument or -catchall, and do not use -default for more than one element. # Such elements are automatically are given -forbid constraints to prevent them # from being used simultaneously. If such an element does not use -boolean or # -value, the element name is used as its default -value. # # -value specifies the value to assign to switch keys when the switch is # present. -value may not be used with -argument, -optional, -required, or # -catchall. -value normally defaults to empty string, except when -boolean is # used (1 is the default -value) or multiple switches share the same -key (the # element name is the default -value). # # -optional, -required, -catchall, and -upvar imply -argument when used with # -switch. Consequently, switches require an argument when any of the shorthand # flag characters defined above are used, and it is not necessary to explicitly # specify "=" if any of the other flag characters are used. # # If -argument is used, the value assigned to the switch's key is normally the # next argument following the switch. With -catchall, the value assigned to the # switch's key is instead the list of all remaining arguments. With -optional, # the following processing is applied: # # - If the switch is not present, the switch's key is omitted from the result. # - If the switch is not the final argument, its value is a two-element list # consisting of empty string and the argument following the switch. # - If the switch is the final argument, its value is empty string. # # By default, switches are optional and parameters are required. Switches can # be made required with -required, and parameters can be made optional with # -optional. -catchall also makes parameters optional, unless -required is # used, in which case at least one argument must be assigned to the parameter. # Otherwise, using -required with -parameter has no effect. -switch -optional # -required means the switch must be present but may be the final argument. # # When -switch and -optional are both used, -catchall, -default, and -upvar are # disallowed. -parameter -optional -required is also a disallowed combination. # # -validate and -enum provide element value validation. The overall -validate # and -enum switches declare named validation expressions and enumeration lists, # and the per-element -validate and -enum switches select which validation # expressions and enumeration lists are used on which elements. The argument to # the overall -validate and -enum switches is a dict mapping from validation or # enumeration name to validation expressions or enumeration lists. The argument # to a per-element -validate switch is a validation name or expression, and the # argument to a per-element -enum switch is an enumeration name or list. An # element may not use both -validate and -enum. # # A validation expression is an [expr] expression parameterized on a variable # named arg which is replaced with the argument. If the expression evaluates to # true, the argument is accepted. # # An enumeration list is a list of possible argument values. If the argument # appears in the enumeration list, the argument is accepted. Unless -exact is # used, if the argument is a prefix of exactly one element of the enumeration # list, the argument is replaced with the enumeration list element. # # Unambiguous prefixes of switch names are acceptable, unless the -exact switch # is used. Switches in the argument list normally begin with a single "-" but # can also begin with "--" if the -long switch is used. Arguments to switches # normally appear as the list element following the switch, but if -equalarg is # used, they may be supplied within the switch element itself, delimited with an # "=" character, e.g. "-switch=arg". # # The per-element -pass switch causes the element argument or arguments to be # appended to the value of the indicated pass-through result key. Many elements # may use the same pass-through key. If -normalize is used, switch arguments # are normalized to not use aliases, abbreviations, the "--" prefix, or the "=" # argument delimiter; otherwise, switches will be expressed the same way they # appear in the original input. Furthermore, -normalize causes omitted switches # that accept arguments and have default values, as well as omitted parameters # that have default values, to be explicitly included in the pass-through key. # If -mixed is used, pass-through keys will list all switches first before # listing any parameters. If the first parameter value for a pass-through key # starts with "-", its value will be preceded by "--" so that it will not appear # to be a switch. If no arguments are assigned to a pass-through key, its value # will be empty string. The intention is that the value of a pass-through key # can be parsed again to get the original data, and if -normalize is used, it # will not be necessary to use -mixed, -long, -equalarg, -alias, or -default to # get the correct result. However, pathological use of -default can conflict # with this goal. For example, if the first optional parameter has no -default # but the second one does, then parsing the result of -normalize can assign the # default value to the first parameter rather than the second. # # The [argparse] -pass switch may be used to collect unrecognized arguments into # a pass-through key, rather than failing with an error. Normalization and # unmixing will not be applied to these arguments because it is not possible to # reliably determine if they are switches or parameters. In particular, it is # not known if an undefined switch expects an argument. # # [argparse] produces a set of keys and values. The keys are the names of # caller variables into which the values are stored, unless -inline is used, in # which case the key-value pairs are returned as a dict. The element names # default to the key names, unless overridden by -key, -pass, or -template. If # both -key and -pass are used, two keys are defined: one having the element # value, the other having the pass-through elements. Unless -keep or -inline # are used, the caller variables for omitted switches and parameters are unset. # # -template applies to elements using neither -key nor -pass. Its value is a # substitution template applied to element names to determine key names. "%" in # the template is replaced with the element name. To protect "%" or "\" from # replacement, precede it with "\". One use for -template is to put the result # in an array, e.g. with "-template arrayName(%)". # # Elements with -upvar are special. Rather than having normal values, they are # bound to caller variables using the [upvar] command. -upvar conflicts with # -inline because it is not possible to map a dict value to a variable. Due to # limitations of arrays and [upvar], -upvar cannot be used with keys whose names # resemble array elements. -upvar conflicts with -catchall because the value # must be a variable name, not a list. The combination -switch -optional -upvar # is disallowed for the same reason. If -upvar is used with switches or with # optional parameters, [info exists KEY] returns 1 both when the element is not # present and when its value is the name of a nonexistent variable. To tell the # difference, check if [info vars KEY] returns an empty list; if so, the element # is not present. Note that the argument to [info vars] is a [string match] # pattern, so it may be necessary to precede *?[]\ characters with backslashes. # # Argument processing is performed in three stages: switch processing, parameter # allocation, and parameter assignment. Each argument processing stage and pass # is performed left-to-right. # # All switches must normally appear in the argument list before any parameters. # Switch processing terminates with the first argument (besides arguments to # switches) that does not start with "-" (or "--", if -long is used). The # special switch "--" can be used to force switch termination if the first # parameter happens to start with "-". If no switches are defined, the first # argument is known to be a parameter even if it starts with "-". # # When the -mixed switch is used, switch processing continues after encountering # arguments that do not start with "-" or "--". This is convenient but may be # ambiguous in cases where parameters look like switches. To resolve ambiguity, # the special "--" switch terminates switch processing and forces all remaining # arguments to be parameters. # # When -mixed is not used, the required parameters are counted, then that number # of arguments at the end of the argument list are treated as parameters even if # they begin with "-". This avoids the need for "--" in many cases. # # After switch processing, parameter allocation determines how many arguments to # assign to each parameter. Arguments assigned to switches are not used in # parameter processing. First, arguments are allocated to required parameters; # second, to optional, non-catchall parameters; and last to catchall parameters. # Finally, each parameter is assigned the allocated number of arguments. proc ::argparse {args} { # Common validation helper routine. set validateHelper {apply {{name opt args} { if {[dict exists $opt enum]} { set command [list tcl::prefix match -message "$name value"\ {*}[if {[uplevel 1 {info exists exact}]} {list -exact}]\ [dict get $opt enum]] set args [lmap arg $args {{*}$command $arg}] } elseif {[dict exists $opt validate]} { foreach arg $args [list if [dict get $opt validate] {} else { return -code error -level 2\ "$name value \"$arg\" fails [dict get $opt validateMsg]" }] } return $args }}} # Process arguments. set level 1 set enum {} set validate {} for {set i 0} {$i < [llength $args]} {incr i} { if {[catch { regsub {^-} [tcl::prefix match -message switch { -boolean -enum -equalarg -exact -inline -keep -level -long -mixed -normalize -pass -reciprocal -template -validate } [lindex $args $i]] {} switch } msg]} { # Do not allow "--" or definition lists nested within the special # empty-string element containing extra overall switches. if {[info exists reparse]} { return -code error $msg } # Stop after "--" or at the first non-switch argument. if {[lindex $args $i] eq "--"} { incr i } # Extract definition and args from the argument list, pulling from # the caller's args variable if the args parameter is omitted. switch [expr {[llength $args] - $i}] { 0 { break } 1 { set definition [lindex $args end] set argv [uplevel 1 {::set args}] } 2 { set definition [lindex $args end-1] set argv [lindex $args end] } default { return -code error "too many arguments" }} # Convert any definition list elements named empty string to instead # be overall switches, and arrange to reparse those switches. Also, # remove inline comments from the definition list. set args {} set reparse {} set i -1 foreach elem $definition[set definition {}] { if {[lindex $elem 0] eq "#"} { if {[llength $elem] == 1} { set comment {} } } elseif {[info exists comment]} { unset comment } elseif {[lindex $elem 0] eq {}} { lappend args {*}[lrange $elem 1 end] } else { lappend definition $elem } } } elseif {$switch ni {enum level pass template validate}} { # Process switches with no arguments. set $switch {} } elseif {$i == [llength $args] - 1} { return -code error "-$switch requires an argument" } else { # Process switches with arguments. set $switch [lindex $args [incr i]] } } # Fail if no definition argument was supplied. if {![info exists definition]} { return -code error "missing required parameter: definition" } # Forbid using -inline and -keep at the same time. if {[info exists inline] && [info exists keep]} { return -code error "-inline and -keep conflict" } # Parse element definition list. set def {} set aliases {} set order {} set switches {} set upvars {} set omitted {} foreach elem $definition { # Read element definition switches. set opt {} for {set i 1} {$i < [llength $elem]} {incr i} { if {[set switch [regsub {^-} [tcl::prefix match { -alias -argument -boolean -catchall -default -enum -forbid -ignore -imply -keep -key -level -optional -parameter -pass -reciprocal -require -required -standalone -switch -upvar -validate -value } [lindex $elem $i]] {}]] ni { alias default enum forbid imply key pass require validate value }} { # Process switches without arguments. dict set opt $switch {} } elseif {$i == [llength $elem] - 1} { return -code error "-$switch requires an argument" } else { # Process switches with arguments. incr i dict set opt $switch [lindex $elem $i] } } # Process the first element of the element definition. if {![llength $elem]} { return -code error "element definition cannot be empty" } elseif {[dict exists $opt switch] && [dict exists $opt parameter]} { return -code error "-switch and -parameter conflict" } elseif {[info exists inline] && [dict exists $opt keep]} { return -code error "-inline and -keep conflict" } elseif {![dict exists $opt switch] && ![dict exists $opt parameter]} { # If -switch and -parameter are not used, parse shorthand syntax. if {![regexp -expanded { ^(?:(-) # Leading switch "-" (?:(\w[\w-]*)\|)?)? # Optional switch alias (\w[\w-]*) # Switch or parameter name ([=?!*^]*)$ # Optional flags } [lindex $elem 0] _ minus alias name flags]} { return -code error "bad element shorthand: [lindex $elem 0]" } if {$minus ne {}} { dict set opt switch {} } else { dict set opt parameter {} } if {$alias ne {}} { dict set opt alias $alias } foreach flag [split $flags {}] { dict set opt [dict get { = argument ? optional ! required * catchall ^ upvar } $flag] {} } } elseif {![regexp {^\w[\w-]*$} [lindex $elem 0]]} { return -code error "bad element name: [lindex $elem 0]" } else { # If exactly one of -switch or -parameter is used, the first element # of the definition is the element name, with no processing applied. set name [lindex $elem 0] } # Check for collisions. if {[dict exists $def $name]} { return -code error "element name collision: $name" } if {[dict exists $opt switch]} { # -optional, -required, -catchall, and -upvar imply -argument when # used with switches. foreach switch {optional required catchall upvar} { if {[dict exists $opt $switch]} { dict set opt argument {} } } } else { # Parameters are required unless -catchall or -optional are used. if {([dict exists $opt catchall] || [dict exists $opt optional]) && ![dict exists $opt required]} { dict set opt optional {} } else { dict set opt required {} } } # Check requirements and conflicts. foreach {switch other} {reciprocal require level upvar} { if {[dict exists $opt $switch] && ![dict exists $opt $other]} { return -code error "-$switch requires -$other" } } foreach {switch others} { parameter {alias boolean value argument imply} ignore {key pass} required {boolean default} argument {boolean value} upvar {boolean inline catchall} boolean {default value} enum validate } { if {[dict exists $opt $switch]} { foreach other $others { if {[dict exists $opt $other]} { return -code error "-$switch and -$other conflict" } } } } if {[dict exists $opt upvar] && [info exists inline]} { return -code error "-upvar and -inline conflict" } # Check for disallowed combinations. foreach combination { {switch optional catchall} {switch optional upvar} {switch optional default} {switch optional boolean} {parameter optional required} } { foreach switch [list {*}$combination {}] { if {$switch eq {}} { return -code error "[join [lmap switch $combination { string cat - $switch }]] is a disallowed combination" } elseif {![dict exists $opt $switch]} { break } } } # Replace -boolean with "-default 0 -value 1". if {([info exists boolean] && [dict exists $opt switch] && ![dict exists $opt argument] && ![dict exists $opt upvar] && ![dict exists $opt default] && ![dict exists $opt value] && ![dict exists $opt required]) || [dict exists $opt boolean]} { dict set opt default 0 dict set opt value 1 } # Insert default -level if -upvar is used. if {[dict exists $opt upvar] && ![dict exists $opt level]} { dict set opt level $level } # Compute default output key if -ignore, -key, and -pass aren't used. if {![dict exists $opt ignore] && ![dict exists $opt key] && ![dict exists $opt pass]} { if {[info exists template]} { dict set opt key [string map\ [list \\\\ \\ \\% % % $name] $template] } else { dict set opt key $name } } if {[dict exists $opt parameter]} { # Keep track of parameter order. lappend order $name # Forbid more than one catchall parameter. if {[dict exists $opt catchall]} { if {[info exists catchall]} { return -code error "multiple catchall parameters:\ $catchall and $name" } else { set catchall $name } } } elseif {![dict exists $opt alias]} { # Build list of switches. lappend switches -$name } elseif {![regexp {^\w[\w-]*$} [dict get $opt alias]]} { return -code error "bad alias: [dict get $opt alias]" } elseif {[dict exists $aliases [dict get $opt alias]]} { return -code error "element alias collision: [dict get $opt alias]" } else { # Build list of switches (with aliases), and link switch aliases. dict set aliases [dict get $opt alias] $name lappend switches -[dict get $opt alias]|$name } # Map from upvar keys back to element names, and forbid collisions. if {[dict exists $opt upvar] && [dict exists $opt key]} { if {[dict exists $upvars [dict get $opt key]]} { return -code error "multiple upvars to the same variable:\ [dict get $upvars [dict get $opt key]] $name" } dict set upvars [dict get $opt key] $name } # Look up named enumeration lists and validation expressions. if {[dict exists $opt enum] && [dict exists $enum [dict get $opt enum]]} { dict set opt enum [dict get $enum [dict get $opt enum]] } elseif {[dict exists $opt validate]} { if {[dict exists $validate [dict get $opt validate]]} { dict set opt validateMsg "[dict get $opt validate] validation" dict set opt validate [dict get $validate\ [dict get $opt validate]] } else { dict set opt validateMsg "validation: [dict get $opt validate]" } } # Save element definition. dict set def $name $opt # Prepare to identify omitted elements. dict set omitted $name {} } # Process constraints and shared key logic. dict for {name opt} $def { # Verify constraint references. foreach constraint {require forbid} { if {[dict exists $opt $constraint]} { foreach otherName [dict get $opt $constraint] { if {![dict exists $def $otherName]} { return -code error "$name -$constraint references\ undefined element: $otherName" } } } } # Create reciprocal requirements. if {([info exists reciprocal] || [dict exists $opt reciprocal]) && [dict exists $opt require]} { foreach other [dict get $opt require] { dict update def $other otherOpt { dict lappend otherOpt require $name } } } # Perform shared key logic. if {[dict exists $opt key]} { dict for {otherName otherOpt} $def { if {$name ne $otherName && [dict exists $otherOpt key] && [dict get $otherOpt key] eq [dict get $opt key]} { # Limit when shared keys may be used. if {[dict exists $opt parameter]} { return -code error "$name cannot be a parameter because\ it shares a key with $otherName" } elseif {[dict exists $opt argument]} { return -code error "$name cannot use -argument because\ it shares a key with $otherName" } elseif {[dict exists $opt catchall]} { return -code error "$name cannot use -catchall because\ it shares a key with $otherName" } elseif {[dict exists $opt default] && [dict exists $otherOpt default]} { return -code error "$name and $otherName cannot both\ use -default because they share a key" } # Create forbid constraints on shared keys. if {![dict exists $otherOpt forbid] || $name ni [dict get $otherOpt forbid]} { dict update def $otherName otherOpt { dict lappend otherOpt forbid $name } } # Set default -value for shared keys. if {![dict exists $opt value]} { dict set def $name value $name } } } } } # Handle default pass-through switch by creating a dummy element. if {[info exists pass]} { dict set def {} pass $pass } # Force required parameters to bypass switch logic. set end [expr {[llength $argv] - 1}] if {![info exists mixed]} { foreach name $order { if {[dict exists $def $name required]} { incr end -1 } } } set force [lreplace $argv 0 $end] set argv [lrange $argv 0 $end] # Perform switch logic. set result {} set missing {} if {$switches ne {}} { # Build regular expression to match switches. set re ^- if {[info exists long]} { append re -? } append re {(\w[\w-]*)} if {[info exists equalarg]} { append re (?:(=)(.*))? } else { append re ()() } append re $ # Process switches, and build the list of parameter arguments. set params {} while {$argv ne {}} { # Check if this argument appears to be a switch. set argv [lassign $argv arg] if {[regexp $re $arg _ name equal val]} { # This appears to be a switch. Fall through to the handler. } elseif {$arg eq "--"} { # If this is the special "--" switch to end all switches, all # remaining arguments are parameters. set params $argv break } elseif {[info exists mixed]} { # If -mixed is used and this is not a switch, it is a parameter. # Add it to the parameter list, then go to the next argument. lappend params $arg continue } else { # If this is not a switch, terminate switch processing, and # process this and all remaining arguments as parameters. set params [linsert $argv 0 $arg] break } # Process switch aliases. if {[dict exists $aliases $name]} { set name [dict get $aliases $name] } # Preliminary guess for the normalized switch name. set normal -$name # Perform switch name lookup. if {[dict exists $def $name switch]} { # Exact match. No additional lookup needed. } elseif {![info exists exact] && ![catch { tcl::prefix match -message switch [lmap {key data} $def { if {[dict exists $data switch]} { set key } else { continue } }] $name } name]} { # Use the switch whose prefix unambiguously matches. set normal -$name } elseif {[dict exists $def {}]} { # Use default pass-through if defined. set name {} } else { # Fail if this is an invalid switch. set switches [lsort $switches] if {[llength $switches] > 1} { lset switches end "or [lindex $switches end]" } set switches [join $switches\ {*}[if {[llength $switches] > 2} {list ", "}]] return -code error "bad switch \"$arg\": must be $switches" } # If the switch is standalone, ignore all constraints. if {[dict exists $def $name standalone]} { foreach other [dict keys $def] { dict unset def $other required dict unset def $other require dict unset def $other forbid if {[dict exists $def $other parameter]} { dict set def $other optional {} } } } # Keep track of which elements are present. dict set def $name present {} # If the switch value was set using -switch=value notation, insert # the value into the argument list to be handled below. if {$equal eq "="} { set argv [linsert $argv 0 $val] } # Load key and pass into local variables for easy access. unset -nocomplain key pass foreach var {key pass} { if {[dict exists $def $name $var]} { set $var [dict get $def $name $var] } } # Keep track of which switches have been seen. dict unset omitted $name # Validate switch arguments and store values into the result dict. if {[dict exists $def $name catchall]} { # The switch is catchall, so store all remaining arguments. set argv [{*}$validateHelper $normal\ [dict get $def $name] {*}$argv] if {[info exists key]} { dict set result $key $argv } if {[info exists pass]} { if {[info exists normalize]} { dict lappend result $pass $normal {*}$argv } else { dict lappend result $pass $arg {*}$argv } } break } elseif {![dict exists $def $name argument]} { # The switch expects no arguments. if {$equal eq "="} { return -code error "$normal doesn't allow an argument" } if {[info exists key]} { if {[dict exists $def $name value]} { dict set result $key [dict get $def $name value] } else { dict set result $key {} } } if {[info exists pass]} { if {[info exists normalize]} { dict lappend result $pass $normal } else { dict lappend result $pass $arg } } } elseif {$argv ne {}} { # The switch was given the expected argument. set argv0 [lindex [{*}$validateHelper $normal\ [dict get $def $name] [lindex $argv 0]] 0] if {[info exists key]} { if {[dict exists $def $name optional]} { dict set result $key [list {} $argv0] } else { dict set result $key $argv0 } } if {[info exists pass]} { if {[info exists normalize]} { dict lappend result $pass $normal $argv0 } elseif {$equal eq "="} { dict lappend result $pass $arg } else { dict lappend result $pass $arg [lindex $argv 0] } } set argv [lrange $argv 1 end] } else { # The switch was not given the expected argument. if {![dict exists $def $name optional]} { return -code error "$normal requires an argument" } if {[info exists key]} { dict set result $key {} } if {[info exists pass]} { if {[info exists normalize]} { dict lappend result $pass $normal } else { dict lappend result $pass $arg } } } # Insert this switch's implied arguments into the argument list. if {[dict exists $def $name imply]} { set argv [concat [dict get $def $name imply] $argv] dict unset def $name imply } } # Build list of missing required switches. dict for {name opt} $def { if {[dict exists $opt switch] && ![dict exists $opt present] && [dict exists $opt required]} { if {[dict exists $opt alias]} { lappend missing -[dict get $opt alias]|$name } else { lappend missing -$name } } } # Fail if at least one required switch is missing. if {$missing ne {}} { set missing [lsort $missing] if {[llength $missing] > 1} { lset missing end "and [lindex $missing end]" } set missing [join $missing\ {*}[if {[llength $missing] > 2} {list ", "}]] return -code error [string cat "missing required switch"\ {*}[if {[llength $missing] > 1} {list es}] ": " $missing] } } else { # If no switches are defined, bypass the switch logic and process all # arguments using the parameter logic. set params $argv } # Allocate one argument to each required parameter, including catchalls. set alloc {} lappend params {*}$force set count [llength $params] set i 0 foreach name $order { if {[dict exists $def $name required]} { if {$count} { dict set alloc $name 1 dict unset omitted $name incr count -1 } else { lappend missing $name } } incr i } # Fail if at least one required parameter is missing. if {$missing ne {}} { if {[llength $missing] > 1} { lset missing end "and [lindex $missing end]" } return -code error [string cat "missing required parameter"\ {*}[if {[llength $missing] > 1} {list s}] ": "\ [join $missing {*}[if {[llength $missing] > 2} {list ", "}]]] } # Try to allocate one argument to each optional, non-catchall parameter, # until there are no arguments left. if {$count} { foreach name $order { if {![dict exists $def $name required] && ![dict exists $def $name catchall]} { dict set alloc $name 1 dict unset omitted $name if {![incr count -1]} { break } } } } # Process excess arguments. if {$count} { if {[info exists catchall]} { # Allocate remaining arguments to the catchall parameter. dict incr alloc $catchall $count dict unset omitted $catchall } elseif {[dict exists $def {}]} { # If there is no catchall parameter, instead allocate to the default # pass-through result key. lappend order {} dict set alloc {} $count } else { return -code error "too many arguments" } } # Check constraints. dict for {name opt} $def { if {[dict exists $opt present]} { foreach {match condition description} { 1 require requires 0 forbid "conflicts with" } { if {[dict exists $opt $condition]} { foreach otherName [dict get $opt $condition] { if {[dict exists $def $otherName present] != $match} { foreach var {name otherName} { if {[dict exists $def [set $var] switch]} { set $var -[set $var] } } return -code error "$name $description $otherName" } } } } } } # If normalization is enabled, explicitly store into the pass-through keys # all omitted switches that have a pass-through key, accept an argument, and # have a default value. if {[info exists normalize]} { dict for {name opt} $def { if {[dict exists $opt switch] && [dict exists $opt pass] && [dict exists $opt argument] && [dict exists $opt default] && [dict exists $omitted $name]} { dict lappend result [dict get $opt pass]\ -$name [dict get $opt default] } } } # Validate parameters and store in result dict. set i 0 foreach name $order { set opt [dict get $def $name] if {[dict exists $alloc $name]} { if {![dict exists $opt catchall] && $name ne {}} { set val [lindex [{*}$validateHelper $name\ $opt [lindex $params $i]] 0] if {[dict exists $opt pass]} { if {[string index $val 0] eq "-" && ![dict exists $result [dict get $opt pass]]} { dict lappend result [dict get $opt pass] -- } dict lappend result [dict get $opt pass] $val } incr i } else { set step [dict get $alloc $name] set val [lrange $params $i [expr {$i + $step - 1}]] if {$name ne {}} { set val [{*}$validateHelper $name $opt {*}$val] } if {[dict exists $opt pass]} { if {[string index [lindex $val 0] 0] eq "-" && ![dict exists $result [dict get $opt pass]]} { dict lappend result [dict get $opt pass] -- } dict lappend result [dict get $opt pass] {*}$val } incr i $step } if {[dict exists $opt key]} { dict set result [dict get $opt key] $val } } elseif {[info exists normalize] && [dict exists $opt default] && [dict exists $opt pass]} { # If normalization is enabled and this omitted parameter has both a # default value and a pass-through key, explicitly store the default # value in the pass-through key, located in the correct position so # that it can be recognized again later. if {[string index [dict get $opt default] 0] eq "-" && ![dict exists $result [dict get $opt pass]]} { dict lappend result [dict get $opt pass] -- } dict lappend result [dict get $opt pass] [dict get $opt default] } } # Create default values for missing elements. dict for {name opt} $def { if {[dict exists $opt key] && ![dict exists $result [dict get $opt key]]} { if {[dict exists $opt default]} { dict set result [dict get $opt key] [dict get $opt default] } elseif {[dict exists $opt catchall]} { dict set result [dict get $opt key] {} } } if {[dict exists $opt pass] && ![dict exists $result [dict get $opt pass]]} { dict set result [dict get $opt pass] {} } } if {[info exists inline]} { # Return result dict. return $result } else { # Unless -keep was used, unset caller variables for omitted elements. if {![info exists keep]} { dict for {name val} $omitted { set opt [dict get $def $name] if {![dict exists $opt keep] && [dict exists $opt key] && ![dict exists $result [dict get $opt key]]} { uplevel 1 [list ::unset -nocomplain [dict get $opt key]] } } } # Process results. dict for {key val} $result { if {[dict exists $upvars $key]} { # If this element uses -upvar, link to the named variable. uplevel 1 [list ::upvar\ [dict get $def [dict get $upvars $key] level] $val $key] } else { # Store result into caller variables. uplevel 1 [list ::set $key $val] } } } } # vim: set sts=4 sw=4 tw=80 et ft=tcl: |
Added modules/argparse/pkgIndex.tcl.
> | 1 | package ifneeded argparse 0.5 [list source [file join $dir argparse.tcl]] |
Changes to modules/cmdline/cmdline.man.
1 2 3 4 5 6 7 8 9 10 11 12 13 | [manpage_begin cmdline n 1.5] [keywords {argument processing}] [keywords argv] [keywords argv0] [keywords {cmdline processing}] [keywords {command line processing}] [moddesc {Command line and option processing}] [titledesc {Procedures to process command lines and options.}] [category {Programming tools}] [require Tcl 8.2] [require cmdline [opt 1.3.3]] [description] | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [manpage_begin cmdline n 1.5] [keywords {argument processing}] [keywords argv] [keywords argv0] [keywords {cmdline processing}] [keywords {command line processing}] [see_also argparse(n)] [moddesc {Command line and option processing}] [titledesc {Procedures to process command lines and options.}] [category {Programming tools}] [require Tcl 8.2] [require cmdline [opt 1.3.3]] [description] |
︙ | ︙ |
Changes to support/installation/modules.tcl.
︙ | ︙ | |||
37 38 39 40 41 42 43 44 45 46 47 48 49 50 | Module 0compatibility _tcl _null _null # Wrapper packages redirecting deprecated/moved packages to their old # implementations. See pkgIndex.tcl for more notes. # name pkg doc example Module aes _tcl _man _null Module amazon-s3 _tcl _man _null Module asn _tcl _man _null Module base32 _tcl _man _null Module base64 _tcl _man _null Module bee _tcl _man _null Module bench _tcl _null _null Module bibtex _tcl _man _exa Module blowfish _tcl _man _null | > | 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | Module 0compatibility _tcl _null _null # Wrapper packages redirecting deprecated/moved packages to their old # implementations. See pkgIndex.tcl for more notes. # name pkg doc example Module aes _tcl _man _null Module amazon-s3 _tcl _man _null Module argparse _tcl _man _null Module asn _tcl _man _null Module base32 _tcl _man _null Module base64 _tcl _man _null Module bee _tcl _man _null Module bench _tcl _null _null Module bibtex _tcl _man _exa Module blowfish _tcl _man _null |
︙ | ︙ |