[Peter Lewerin] 2014-01-20: after replying about this on Stackoverflow, I just wanted to jot down some notes on using `[dict]`ionaries to handle command line options. ***Basics*** The options need to be passed as an even-sized list of option names and option values. Passing options together with non-option arguments or with flag options (options that don't take a value) is discussed below. By convention, option names have a "-" prefix, but `dict` doesn't really care if they have them or not (see at the bottom of the page). ***Handling options*** An options dictionary can be initialized using a syntax similar to (but in this case not the same as) that of `[cmdline]`: ====== # options and optUsage need to be unset or empty at this point foreach {option default usage} { r "" "use time from ref_file" t -1 "use specified time" } { dict set options -$option $default dict set optUsage -$option $usage } % set options # => -r {} -t -1 % set optUsage # => -r {use time from ref_file} -t {use specified time} ====== (A slightly more complicated procedure could be used to deal with flag options or value options without default values, as `cmdline` can.) At this point, something like this will print a handy usage message: ====== dict for {option usage} $optUsage { puts "$option $usage" } ====== Now, if the argument `[args]` contains the list of option names and option values passed to the command, a dictionary of actual options for use inside the command can be created like this: ====== % set args # => -t 99 set actualOptions [dict merge $::options [dict create {*}$args]] # => -r {} -t 99 ====== (If you're really close to the deadline, you can save a few characters by doing it this way:) ====== set actualOptions [dict merge $::options $args] ====== This works because any even-sized list seems to be equivalent to a dictionary value. You can query the options dictionary like this: ====== dict get $actualOptions -t # => 99 ====== Or you can evaluate your code with the option names as variables (note that those variables are still linked to the option values): ====== dict with actualOptions { incr -t puts ${-t} } % set actualOptions # => -r {} -t 100 ====== Or use any other kind of `dict` operation on the options. If you dislike the "-" prefix for options (which forces one to use braces in the previous example), that can be remedied, see below. ***Passing options*** The options can be put in a literal list: ====== proc foo {bar} { # handles options inside $bar } % foo [list -optA valA -optB valB] # or % foo {-optA valA -optB valB} ====== but using the `[args]` special argument name, they can be passed in sequence: ====== proc foo {args} { # handles options inside $args } % foo -optA valA -optB valB ====== If you use `args`, any arguments that aren't options with values need to be passed in front of the options: ====== proc foo {bar baz args} { # handles options inside $args } % foo 99 "tomato juice" -optA valA -optB valB ====== ***Flag options*** Options that are just flags, i.e. they have no following value but instead passing or not passing them is in itself a boolean value, can't be handled with `dict`. If you use them, you must separate them from the other options, or insert a value like 1 after them, before processing the options. Examples: ****Removing the flags**** ====== # $args holds all options set flaglist {-flagA -flagB -flagC} # set each flag option to true or false foreach flag $flaglist { # "set $name val" is usually an error, but here it's intentional set $flag [expr {$flag in $args}] } # purge the flag options (the ldiff command is non-standard but can be found on this wiki) set args [ldiff $args $flaglist] # flag option names (e.g. -flagB) are now variables with boolean values # now dict can handle the remaining options in $args ====== (See `[ldiff]`.) ****Giving the flags values**** ====== # $args holds all options set flaglist {-flagA -flagB -flagC} # set each flag option to true or false foreach flag $flaglist { # add the value 1 after each flag, if it occurs in $args set index [lsearch -exact $args $flag] if {$index >= 0} { set args [lreplace $args $index $index $flag 1] } } # now dict can handle all options, because $args is now an even-sized list of names and values ====== ***Undashed option names*** If you don't want the "-" prefix, you can of course just define your option dictionary without dashed names (making them look more like named arguments, which is basically the same anyway). If you want the dashes in the invocation of the command but want to get rid of them inside the command, that's easy too: ====== % set actualOptions # => -optA valA -optB valB -optC valC % dict for {name value} $actualOptions { dict set undashedOptions [string range $name 1 end] $value } % set undashedOptions # => optA valA optB valB optC valC ====== <> Argument Processing | Arts and crafts of Tcl-Tk programming