argparse

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 [L1 ].

Examples

 basic example
proc ex1 {args} {
   puts [argparse -inline {
       {-debug -boolean}
       {-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}
        {-integer    -key sort}
        {-real       -key sort}
        {-command=   -forbid {ascii dictionary integer real}}
        {-increasing -key order -default increasing}
        {-decreasing -key order}
        -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}
        {-glob       -key match -default glob}
        {-regexp     -key match}
        {-sorted     -forbid {glob regexp}}
        -all
        -inline
        -not
        -start=
        {-ascii      -key format -value text -default text}
        {-dictionary -key format}
        {-integer    -key format}
        {-nocase     -key format}
        {-real       -key format}
        {-increasing -key order -require sorted -default increasing}
        {-decreasing -key order -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 lambdas, 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
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 <value>, 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:

For Tcl 8.4, you will need:

Code

Filename View Download
argparse.tcl View Download
pkgIndex.tcl View Download