[AMG]: [[argparse]] is a feature-heavy argument parser. Documentation and test suite to come. For now, look at the big comment at the start of the implementation. **Usage summary** 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. 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 |& As a special case, a definition list element may be the single character "`#`", which will cause the following element to be completely ignored. 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` |& For more information, see the long header comment preceding the code [https://wiki.tcl-lang.org/page/argparse#340f463033e0fd5ddeabb922df4d4f1b5747494d0f5ed9894f13b6e13ca831f5]. **Examples** <>basic example ====== proc ex1 {args} { puts [argparse -inline { {-debug -default 0 -value 1} {-increment= -default 1} {-from= -default 1} {-to= -default 10} }] } % ex1 -debug debug 1 increment 1 from 1 to 10 % ex1 -from 1.0 -to 100.0 -increment 0.1 from 1.0 to 100.0 increment 0.1 debug 0 ====== <> <>another basic example ====== proc cmdline {args} { puts [argparse -inline -long -equalarg { {-debug -value true} {-h|help} {-type= -required} {-i|in=} {outfile -required} }] } % cmdline --type mp3 xyzzy type mp3 outfile xyzzy % cmdline --type flac --in waltz.flac missing required parameter: outfile % cmdline --type flac --in waltz.flac waltz.ogg type flac in waltz.flac outfile waltz.ogg % cmdline --type flac --in waltz.flac waltz.ogg type flac in waltz.flac outfile waltz.ogg % cmdline --type flac --in=waltz.flac waltz.ogg type flac in waltz.flac outfile waltz.ogg ====== <> <>lsort ====== proc lsort_ {args} { puts [argparse -inline { {-ascii -key sort -value text -default text} {-dictionary -key sort -value dictionary} {-integer -key sort -value integer} {-real -key sort -value real} {-command= -forbid {ascii dictionary integer real}} {-increasing -key order -value increasing -default increasing} {-decreasing -key order -value decreasing} -indices -index= -stride= -nocase -unique list }] } % lsort_ -increasing -real {2.0 1.0} order increasing sort real list {2.0 1.0} ====== <> <>lsearch ====== proc lsearch_ {args} { puts [argparse -inline { {-exact -key match -value exact} {-glob -key match -value glob -default glob} {-regexp -key match -value regexp} {-sorted -forbid {glob regexp}} -all -inline -not -start= {-ascii -key format -value text -default text} {-dictionary -key format -value dictionary} {-integer -key format -value integer} {-nocase -key format -value nocase} {-real -key format -value real} {-increasing -key order -value increasing -require sorted -default increasing} {-decreasing -key order -value decreasing -require sorted} {-bisect -imply -sorted -forbid {all not}} -index= {-subindices -require index} list pattern }] } % lsearch_ -inline -start 1 -exact -bisect {a b c} b inline {} start 1 match exact bisect {} sorted {} list {a b c} pattern b ====== <> <>example with a catchall and optional arguments ====== proc dummy {args} { puts [argparse -inline {a? b c* d e?}] } # note the interaction between the optional arguments and the catchall argument. # the optional arguments are assigned first before the catchall argument. % dummy 1 2 b 1 d 2 c {} % dummy 1 2 3 a 1 b 2 d 3 c {} % dummy 1 2 3 4 a 1 b 2 d 3 e 4 c {} % dummy 1 2 3 4 5 a 1 b 2 c 3 d 4 e 5 % dummy 1 2 3 4 5 6 a 1 b 2 c {3 4} d 5 e 6 ====== <> One feature not shown by the examples is setting or linking variables. I'm just using the -inline mode for display purposes. **Ideas** ***Enabling -boolean by default*** I'm using -boolean often enough now that I want it to be enabled by default for every compatible switch, as if -boolean were passed to the [[argparse]] command as a global configuration switch. To make this work, I would have to remove -keep and instead accept -unset as its converse. ***Automatic command usage error message*** At least when not given an explicit argument list and instead relying on the caller's args variable, I'd like for this command to generate the usage error message for the procedure that's calling it. To make this work, it will need to check the name and argument list of its calling procedure. This won't give the right result in every case though; for example, the caller might be applying logic of its own before calling [[argparse]]. Therefore, additional switches may be needed to enable, disable, or fine-tune the help text. Basically, [[argparse]] would take the place of [Tcl_WrongNumArgs]() which otherwise would never be called when the proc's parameter list is "args". ***Help text generation*** A -help or -usage switch could be added to the element definition syntax to supply usage information for each switch or parameter. Using this text, nicely-formatted help text can be produced, suitable for display by a command-line program. There's more to it than this though. Some overall help text would need to be supplied to describe the behavior of the program at a higher level, as opposed to simply describing each argument. Plus there's the question of knowing how and when to display this text. ***Header comment parsing*** (Suggested by [tjk] via email.) Like [docstring] or [Ruff!], [[argparse]] could use [info body] to fetch the implementation of its calling procedure then parse an extended header comment at the beginning. From this header comment it could extract not only the list of switches and parameters but also description text explaining their use, as well as an overall synopsis of the procedure. Naturally, this can't be the only mode of operation for [[argparse]]. One idea is that it could do this if both its definition and argument list arguments are omitted. My inclination is to say this creates ambiguity if [[argparse]] is supplied with overall switches such as -inline or -exact: ====== argparse -inline -exact ====== Here, -inline is a perfectly good definition list, and -exact is a perfectly good argument list. But they also would work just as well as overall switches if [[argparse]] is updated to allow the definition and argument lists to be omitted in favor of looking at the header comment and the [args] variable. At present, [[argparse]] resolves the ambiguity by treating the first argument it doesn't recognize as a known switch as the definition list, and if that's not good enough, -- may be used to force it. This heuristic should be good enough, so I think we're okay. I could also make an explicit switch to tell [[argparse]] to get its configuration from the header comment, but I'm disinclined to do this since I don't have an analogous switch to tell it to get its argument list from the args variable. It should be able to autodetect both situations just fine. I have some work ahead of me defining a nice, human-readable comment format that also doubles as an input to [[argparse]]. However, there is a performance pitfall that needs to be addressed. If there is no definition list argument, where would a high-performance [[argparse]] C implementation cache the parsed definition list data? The obvious solution is to piggyback on the [Tcl_Obj] containing the script body. The script body itself is not where the bytecodes are stored, and it has a pure string type, so it is a reasonable candidate to cache data derived from that self-same string. But there is another trap, one I did not expect. Every time I call [[info body]], it returns a new Tcl_Obj, as shown by [tcl::unsupported::representation] reporting a different object pointer each time. This spells trouble. I need a way to associate metadata with procs, and if I can't just shove it in the script body's [intrep], then things get messy. I probably can maintain a mapping from Tcl_CmdInfo pointers to my cached parsed definition lists, but I will also have to set the Tcl_CmdDeleteProc to a cleanup routine to deallocate my cache. I am not sure if Tcl_CmdDeleteProc even works for procs. But if it does, I will also need to worry about chaining to the original Tcl_CmdDeleteProc if one came predefined. Plus if another extension comes along and tries to do the same thing I'm doing, I had better hope it doesn't assume it's the only one using Tcl_CmdDeleteProc. I will also have to worry about [apply] [lambda]s, namespace commands, and ensemble commands too. See [info body] for details on how to get the current body even when called from a lambda or a namespace command. Though, at this point it's more important to get the name than the body. For the case of lambdas, as much as I'd like to cache the parsed definition list in the lambda body, attempting to treat the overall lambda as a list in order to get its body will likely shimmer away the bytecode representation. I will have to investigate further to see if there is a way to bypass this limitation and directly access the text of the lambda body. This is a problem even for simply reading the body in order to parse the header comment, so there could be constant recompilation even when not trying to cache the parsed definition list. Update: I'm having second thoughts about this feature. There seem to be too many implementation details such as key name to adequately capture inside a formatted header comment without making the comment be difficult to read. It might just be better to intersperse -help switches throughout the definition list to capture the documentation information while still incorporating the implementation details. Furthermore, this constitutes creating yet another language that needs to be parsed, and since it's one designed to resemble human language, it's going to be extra difficult to parse and debug. Tcl's a halfway decent language, so maybe we should use it. ***Test suite*** Yup, definitely need to write one. There are a lot of complicated features and corner cases to exercise. I have a partial test suite for [[argparse]], though it only covers parameters and not switches. I'll have to update it for new syntax, expanded capability, and the features I didn't test in the first place. ***C API*** I plan to rewrite this code in C and provide a stubs-enabled C API, then create a script binding for same. I doubt bytecode optimization will provide any benefit if it's already all rolled up in a single function. Though, more likely it will be several functions: one to parse the element definition list, another to parse an argument list using a pre-parsed element definition list. One thing that will be needed alongside the script binding is a new internal representation type to cache the parsed element definition list. This should provide a major performance boost by avoiding the need to repeatedly parse and validate the element definition list. ***Support switch clustering*** Elsewhere on this page, [bll] mentioned switch clustering, e.g. "`[ls] -lA`". I can add support for this, though it will preclude using a single `-` for switch names longer than a single character. Instead, only the `-long` form (`--`) would be allowed for longer names. **Bugs** In updating an existing application to use this code, I found and fixed a few bugs and feature gaps, but it's quite possible more issues remain. Please report anything you find right here. **References** [Command Option Parsing]: Has more references and links to discussion [extending the notation of proc args]: Backward-compatible proposal to incorporate similar functionality into the [proc] parameter list [parse_args]: Very similar functionality to [[argparse]], implemented in C [https://wiki.tcl-lang.org/page/dispatch#f295cc474c2cd9ae047b9eaf124c94d134bc153bfd4f32b9c14b9459953c9bdd%|%dispatch%|%]: [switch]-like command that uses [[argparse]] to let each script accept arguments **Discussion** <>Discussion ***Use of argv*** [bll] 2019-2-28: I just noticed that argparse is modifying argv. Is there a reason for this? It seems to me that argv should not be touched. If the user wants argv modified, e.g. with ''mycmd -p1 -p2=stuff -- -pb1 -pb2'', and the user wants argv to end up with ''-pb1 -pb2'', then this could be an option to argparse. [AMG]: [[argparse]] has a local variable named argv that it modifies, but this is not tied to any caller or global variables that may have the same name. To make [[argparse]] modify a caller variable named argv, use argv as a key or pass name and don't use -inline, or use a -template the somehow permutes to argv (e.g. -template argv(%)). For example: ====== argparse -equalarg {-p1 -p2= argv*} $argv ====== ***Arguments in many places*** [bll] 2018-8-23: Arguments can appear in many places: The command line, in an environment variable, global configuration files, user configuration files. (1) The argument processing should have the flexibility of doing: ====== # overly simplistic pseudo-code argparse $dataFromGlobalConfig argparse $dataFromLocalConfig argparse $::env(MYPROG_ENV) argparse $::argv ====== and end up with a single set of options. [AMG]: These all would work if you simply inserted the element definition as the initial argument. For command line parsing, consider using `-long`, `-equalarg`, and `-mixed` to more closely resemble the switch syntax supported by most common Unix-like commands. ***Counting duplicate arguments*** [bll]: (2) I would always like to see some sort of ability to supply duplicate arguments that increment a value. e.g. -v -v -v is often seen on a command line to increase a verbosity level. [AMG]: At present, the only support for duplicate arguments is via `-pass`, then the caller can do secondary parsing of the pass-through variable or dict value. ====== argparse {{-v -pass v}} set v [llength $v] ====== I've considered adding special support for this usage, but the above isn't so bad. ***Option aliases*** [bll]: I'm just looking at some option code I wrote (not for Tcl) and what else would be nice. (3) Option aliases, I think you can support already with: `-D -imply debug` or `-D -require debug`. [AMG]: There's `-alias` or its `|` shorthand. [[`argparse {-D|debug}`]] will recognize `-D`, `-d`, `-de`, `-deb`, `-debu`, `-debug`. ([[`argparse {-D|debug -define}`]] will not recognize `-d` or `-de` as ways of writing `-debug`.) Try this in combination with `-pass` and `-normalize`, by the way. [bll]: I hate prefixes and would always use `-exact`. Ok, figured out the syntax. I find it rather confusing. `-D= -alias debug` specifies that debug is an alias of it, not that `-D` is an alias of `-debug`. This is backwards in my mind. I think I would use: `-D= -hasalias debug` or `-debug -alias D`. Well, this one will drive me crazy if I need it. ====== % set args [list -D=5] -D=5 % argparse -equalarg -exact -template apopts(%) {{-debug=} {-D= -alias debug}} % parray apopts apopts(D) = 5 ====== [AMG]: I'm not sure I understand the complaint here. You say you would prefer "`-debug -alias D`" but that is exactly what is expected and supported. Saying "`-D -alias debug`" is indeed backwards. As you point out, the key will default to D rather than debug. The way it works is the first element is the switch or parameter name, then subsequent elements modify the definition. I recommend the shorthand: "`-D|debug=`" means the same as "`-debug= -alias D`". The longest possible form is "`debug -switch -alias D -argument`". But use the shorthand. Shorthand is good. The long form exists solely to regularize the internals. The previous version of this code only supported shorthand, and it meant the internals were searching strings for single-character flags, plus had no room for expansion. But obviously the long form is too verbose for typical use. Thus I allow both syntaxes. Why do I list the alias first in the shorthand? Because the alias is almost always a single character, and this results in a neater display. Personal preference. Sorry if this contributes to confusion. [bll]: Our minds work differently. I read `-alias` as is-an-alias-of. [AMG]: Here, have a real-life example, modified a bit from the production form: ====== argparse -mixed { {-s|slocs -key fileMode -value slocs} {-t|total -key fileMode -value total} {-d|density -key fileMode -value density} {-l|language -key fileMode -value language} {-f|filename -key fileMode -value filename -default filename} {-r|reverse -key fileReverse} {-o|omit -key fileMode -value omit} {-S|summarySlocs -key summaryMode -value slocs} {-T|summaryTotal -key summaryMode -value total} {-D|summaryDensity -key summaryMode -value density} {-L|summaryLanguage -key summaryMode -value language -default language} {-C|summaryCount -key summaryMode -value count} {-R|summaryReverse -key summaryReverse} {-O|summaryOmit -key summaryMode -value omit} -n|noRecurse -x|exclude= -g|debug argv* } $argv ====== Hopefully this should clarify how aliases are intended to be written. Basically, don't use `-alias`, rather use the shorthand. ***Clustering*** [bll]: (4) Possibly a legacy mode, where `-ab` = `-a -b` instead of `-a b`. I don't know if this is necessary. [AMG]: I believe you're talking about switch clustering, which is a nice feature for compactness and command line parsing, but it collides very badly with long options. The existence of switch clustering is the whole reason we have `--switch` long option syntax. I could create a `-cluster` switch which would imply `-long` and make single `-` support only single-character unique prefixes (presumably from aliases) but allow multiple per argument. I don't know what you mean by `-a b`. In context I would guess "the switch whose name is the two-character sequence `ab`" but it's not clear. [bll]: If there is no clustering, `-ab` is the same as `-a=b` or `-a b` (old unix style). I believe the programming community is moving away from switch clustering. I would definitely lump it into the legacy category. I cannot recall if the community is moving away from the `-ab` no-space syntax (I think so). [AMG]: Ah, I completely forgot about having the argument immediately follow the single-character switch name. I'm not keen on supporting that, but it does turn up in a lot of places. Example: `-I/usr/local/include`. ***Ignoring options*** [bll]: (5) Be able to specify an option that is just ignored. [AMG]: Use `-ignore`: [[`argparse {{-foo -ignore} -bar}`]] will parse both `-foo` and `-bar` but will only set the variable `bar`. ***Switch and parameter arrays*** [bll] 2018-8-23: In [extending the notation of proc args] an example is given where the options are stored in local variables: e.g. ${-start}. I do not like this at all. I would very much prefer to specify and access an array: $apopts(-start). Then I can check the options from other procedures in my program. [AMG]: There are numerous ways to fine-tune where the switch and parameter values are stored. `argparse -template apopts(%) {-start= -exact}`: Switches go in apopts array, having keys "start" and "exact" `argparse -template apopts(-%) {-start= -exact}`: Switches go in apopts array, having keys "-start" and "-exact" `argparse {{-start= -key apopts(-start)} {-exact -key apopts(-exact)}}`: Switches go in apopts array, having keys "-start" and "-exact" `set apopts [[argparse -inline {-start= -exact}]]`: Switches go in apopts dict, having keys "start" and "exact" While arrays and dicts work, they do have the drawback of not being compatible with `-upvar` because Tcl does not allow for an array element or dict value to be a link to another variable. Saying `-template apopts(-%)` is cheating because it prefixes all array element names with `-`, both switches and parameters. But if you're only using switches, and you really want that `-` in there, it's fine. There is not the ability to set a different template for switches than for parameters, but you are able to individually set the `-key` of each element. [bll]: With my simple option parser I use, I often specify some parameters as `-parameter `, so that's not a big issue. I sort of like the `-` in front, but that's not an issue. ***Auto defaulting of simple switches*** [bll]: RFE: I would like an option to auto-default simple switches. If the switch is not specified, a false value would always be returned. ====== # hypothetical example % set args [list] % unset -nocomplain apopts % argparse -template apopts(%) -simple false {-test1 -stride=} % parray aptopts apopts(test1) = false ====== If you add `-boolean` as stated from [parse_args], this would not be needed. [AMG]: Yeah, `-boolean` is probably what you want since you're only addressing half the issue. The "default -default" is for the array element (or whatever) to not even be created in the first place, but the "default -value" (i.e. the value used for a switch lacking an argument) is empty string. It would be very strange to have the choices be false and empty string. I think I would have `-boolean` be permitted both as a switch modifying individual switches and as a switch applying to the entire [[argparse]] command. In the latter case it would change the default `-default` and `-value` to 0 and 1 for switches that lack `-argument` (or `-optional`, `-required`, and `-catchall`, all of which imply `-argument` when used with `-switch`; also note that these all have shorthand syntaxes you're more likely to use). [AMG]: `-boolean` is now implemented, both overall (as a leading argument to [[argparse]]) and per-switch (in the same way as other modifier switches). It works the same as `-default 0 -value 1`. If you use `-boolean` overall, it will automatically apply to every element for which it would be legal to specify it. That is, it overall `-boolean` applies to every element that: * is a switch (uses `-switch` or starts with "`-`") * does not accept an argument (no `-argument`, does not end with "`=`" or "`!`") * does not link to a caller variable (no `-upvar`, does not end with "`^`") * does not have a default value (no `-default`) * does not specify a value if set (no `-value`) * is not required (no `-required`) Oh yes, I made another change worth mentioning in the context of your example. You do [[unset -nocomplain apopts]], but that is no longer necessary because variables for omitted elements are now automatically unset. To get the old behavior back, use the `-keep` switch, which may either be specified overall or for an individual element (which, unlike `-boolean`, is legal for parameters as well as switches). Rewriting your example: ====== % argparse -template apopts(%) -boolean {-test1 -stride=} {} % parray aptopts apopts(test1) = 0 ====== However, I do not support changing the way false and true are expressed. I chose to go with their canonical representations, being 0 and 1. This will make no difference when actually testing logic values and will only impact display, which is typically a development/debug task only rather than something visible to an application's end user. ***Easy testing if a parameter was specified*** [bll]: Another RFE: I also have in mine a very simple way of testing whether a switch was specified at all, so instead of many `[[info exists opts(-weburl)]]`, I simply do `if { $opts(-weburl) } ...` and access the argument with `$opts(-weburl.arg)`. This could be reversed: ====== # hypothetical example and I may have wrong syntax set args {} puts [argparse -inline -specifyexists -stride=] stride.exists false set args -stride=5 puts [argparse -inline -specifyexists -stride=] stride.exists true stride 5 ====== [AMG]: So you're suggesting having separate keys to track existence and value. I don't have a problem with [[info exists]] and frequently use the presence or absence of a variable to signal a boolean, particularly if there's additional detail attached to a "true" condition, most especially if there is no value I can reserve to indicate "false". But if you prefer another style then that can be accommodated. This is interesting in combination with "`-optional`" switches (i.e. tri-state switches that can either be omitted altogether, be supplied as the final argument and have no argument of their own, or be present and given an argument). Currently, such switches are either omitted from the result, have empty string as their value, or have a single-element dict mapping from empty string to the real value. With `-inline` this becomes a bit more natural: %| Script | Return value |% &| `argparse -inline {-foo?} {}` | `{}` |& &| `argparse -inline {-foo?} {-foo}` | `foo {}` |& &| `argparse -inline {-foo?} {-foo bar}` | `foo {{} bar}` |& This allows: ====== set opt [argparse -inline {-foo?}] dict exists $opt foo ;# Checks if -foo was specified dict exists $opt foo {} ;# Checks if -foo was specified and given a value dict get $opt foo {} ;# Returns the value of -foo or throws an error if not given one ====== With your proposal, the above would instead be: %| Script | Return value |% &| `argparse -inline -specifyexists {-foo?} {}` | `foo 0` |& &| `argparse -inline -specifyexists {-foo?} {-foo}` | `foo 1` |& &| `argparse -inline -specifyexists {-foo?} {-foo bar}` | `foo 1 foo.arg bar` |& ====== set opt [argparse -inline {-foo?}] dict get $opt foo ;# Checks if -foo was specified dict exists $opt foo.arg ;# Checks if -foo was specified and given a value dict get $opt foo.arg ;# Returns the value of -foo or throws an error if not given one ====== [bll]: I have to think about this. Whereas the utility is nice, I don't want to create more code mess to support something that might only be used by one person. Let me roll this around in the back of my head for a few days. ***Mixing - and --*** [bll]: Now this surprised me: ====== cmdline -type flac --in waltz.flac waltz.ogg ====== I have never seen -option and --option mixed on the command line before. Slightly unusual. If you do end up supporting options with no space, e.g. `-I/usr/include/local`, this will probably go away. [AMG]: Having both in the same command line is admittedly weird, but rejecting it would be arbitrary and useless. Possibly the command line was built up in parts coming from different places. Without `-long`, both would have to be `-type` and `-in`. With `-long`, the above is valid, though the expectation is that the user would pick one style and stick with it. With `-cluster` (doesn't exist yet), the caller would have to use `--type` and `--in`, or single-character aliases/prefixes, e.g. `-t` and `-i`. Related: I'm considering adding an `-appendarg` switch to go along with `-equalarg` to allow the argument to be directly appended to single-character switch names. This would make `-I/usr/local/include` possible. ***Biased testimonial*** [AMG]: Take it for what it's worth, since I'm just talking about my own code, but lately I found that having this functionality (a more limited predecessor version) has transformed the way I program in Tcl. I wish I had written this code years ago, but I've only had it for a month or two. I'm now free to make more flexible procedures that take many arguments, no longer having to worry about the complexity of argument parsing or the nightmare of long argument lists. I don't have to make contorted syntaxes that always put the "`args`" parameter at the end if an alternative would be more natural (e.g. a list of switches up at the front) since [[argparse]] lets optional, defaulted, and catchall arguments appear anywhere. In addition to defaulted arguments, I have optional arguments for which I don't need to worry about picking some default value that will never show up in normal usage; just check [[[info exists]]] to see if they were passed or not. It's now much easier to link to caller variables: just tack `^` on the end of the switch or parameter name. I'm now even using [[argparse]] for variadic data structures. [[dict]] would have sufficed for that purpose, except [[dict]] is a more rigid format. Because of how much I'm using it these days for professional work, I'm definitely interested in making this code faster. [bll]: My apologies for not reading everything thoroughly. Though the clarifications help. I am looking forward to seeing it as a package and in C form. I definitely agree with you. I made a very simple option parser for my application, and it has helped a lot with making the code clearer and like you I wish I had written it a little sooner. Unfortunately, everybody has their own way of parsing their command lines, and unless every possible ability is supported, it is hard for an option/argument parser to gain traction. [AMG]: Agreed. I feel it's a losing proposition no matter what. If the parser is too simplistic, it won't be useful enough to even bother publishing. If it supports too much stuff, it's too daunting and people either don't read the whole documentation or give up without trying. And if it's somewhere in the middle, it's still not flexible enough for many cases since there are so many possible syntaxes in the wild. Thus, I'm pretty well forced to implement the kitchen sink and accept the consequences of bloat. Because it's not merely my code that's bloated; it's the problem space that's bloated, and I'm just dealing with it. One thing I think will be helpful is a gentle introduction, one I have utterly failed to provide. Accompanying my code I wrote a very long yet dense comment giving a complete reference but not real examples. Then I dumped this code on the wiki and didn't spend much additional time writing tutorial material. All I did was write a few complex examples showing [[[lsort]]] and [[[lsearch]]]. And now I'm afraid to add more material to this page because it's so long already. If I port this to C, it will be accompanied by both an introduction and a reference. <> **Compatibility** This code is written for Tcl 8.6 and newer. If you want to use this with Tcl 8.5, you will need: * [lmap forward compatibility] * [string cat] forward compatibility * [Forward-compatible tcl::prefix] * [Forward-compatible try and throw] For Tcl 8.4, you will need: * [Forward-compatible dict] * [https://wiki.tcl.tk/1530#pagetocaa2ba245%|%Forward-compatible lassign] * [[[eval]]] instead of [{*}] (invasive change) <> **Code** %| Filename | View | Download |% &| argparse.tcl | [https://core.tcl.tk/tcllib/artifact?filename=modules/argparse/argparse.tcl&ci=amg-argparse%|%View] | [https://core.tcl.tk/tcllib/raw/argparse.tcl?filename=modules/argparse/argparse.tcl&ci=amg-argparse%|%Download] |& &| pkgIndex.tcl | [https://core.tcl.tk/tcllib/artifact?filename=modules/argparse/pkgIndex.tcl&ci=amg-argparse%|%View] | [https://core.tcl.tk/tcllib/raw/pkgIndex.tcl?filename=modules/argparse/pkgIndex.tcl&ci=amg-argparse%|%Download] |& <> Command | Argument Processing