Cmdargs is a Tcl extension that provides a command-line interface to any user-created proc.
Tcl makes heavy use of the "-switch" convention for specifying command-line options. For example,
set res [glob -nocomplain -directory "../dir" -types {f l} *]
Tcl users are familiar with this convention. This style of input is useful when a proc has several optional inputs or multiple parameters. Unfortunately, out of the box, user-created commands have only positional parmeters with (some) support for default values and no support for arbitrary-order, named parameters.
Say we've written a proc:
proc setdata {name {addr {}} {city {}} {age {}} {email {}}} {
# do something with inputs...
}
Instead of entering
setdata Sam "" "" "" sam@email.net
it would be better if this were possible:
setdata -name Sam -email sam@email.net
This extension cmdargs makes this kind of command-line easy to accomplish. For any practical number of command-line options cmdargs is also quite performant. It has a small amount of overhead, so it may not be optimum for procedures called in tight loops. However, for many or most purposes it's slight performance cost is insignificant.
Binary installation is straightforward. Download the archive in zip or tgz format. Use zip or tar to decompress into any convenient location.
The archive structure is as follows:
CmdArgs-ext
bin
config
config.exe
pkgNdx
pkgNdx.exe
doc
generic
tcl8
unix
cmdargs1.0
cmdargs10.so
pkgIndex.tcl
config.def
win-msys
(as in unix)
tcl9
unix
(as above)
win-msys
(as above)
tools
win
The extension is a shared library found in the cmdargs1.0 directory in each of the 4 directories: Tcl8/unix, Tcl8/win-msys, Tcl9/unix and Tcl9/win-msys. It's only necessary to copy the suitable cmdargs1.0 directory to the package directory used by your installed Tcl. (Usually that's /usr/lib, /usr/local/lib, or TclNNN/lib directory.)
Then put package require cmdargs
at the top of the Tcl file.
The use of the other directories is described below, see "Compiling".
cmdargs::chkArgs* <input (list)> <defaults (list[s])> ?<filter> {list}?
<input> : {-optionA ?valueA? ... ?--? ...}
# typically collected in <$args> variable
# <value> isn't required for boolean options
# special input "--" signals end of options
# everything after "--" is collected in variable <_CA_rest>
<defaults> : <type> <defaults-list> ...
# <one (or more) type/defaults pairs>
<type designator> : ?-?(dbool|dnum|dstr|denum)
# leading dash in type designator is optional
<defaults-list> : {optionNameA defaultA ...}
<filter> : ?-?dfilter {<nonulls | nozero | noneg> {option-names} ...}
The "cmdargs::" namespace provides 4 commands, chkArgs
, chkArgsCi
, chkArgsv
, and chkArgsvCi
. chkArgsCi is the case-insentive variant of chkArgs. The 'v' variants provide a variable, _CA_allvar in the caller. This variable has a list of all varnames created by chkArgs*
. The syntax:
cmdargs::chkArgs?vCi? <cmdline (list)> ?-dbool {...}? ?-dnum {...}? ?-dstr {...}? ?-denum {...}? ?-dfilter {...}?
cmdline is the input to the proc, typically using args. proc input is in -option value
format, except for boolean options which don't require input values. Typically chkArgs* is the first command in a procedure, like this:
proc test {args} {
cmdargs::chkArgs $args -dbool ....
...
}
Remainder of chkArgs invocation consists of paired parameters, one of <?-?dbool dnum dstr denum> followed by a list of <option value> pairs. For example, CmdArgs::chkArgs $args dbool {flagc 0 flagd 1} dnum {nlines 80 npages 23} ...
The d... identifiers denote the type of the values in the associated list.
ID | Value type | List example |
---|---|---|
dbool | boolean (0|1) | flagd 0 flagm 1 |
dnum | numeric (int|float) | elem 9.0 year 2011 |
dstr | string (Tcl string) | name "First Last" addr "" |
denum | enumerated list | food {fresh frozen canned} |
IOW using cmdargs provides a basic level of type-checking and validation of proc inputs. This not only reduces errors, it also can save programmers some work validating proc input values.
Note that in the lists the option names (even-numbered elements) will be names of variables created by cmdargs. Values (odd-numbered elements) are the defaults assigned to the named variables. Command-line options/values override these defaults.
<cmdname> -option value -option1 value1 -booloption -option2 -- <rest>...
Each -option sets a variable option with value. Some conditions apply:
Of course, as is standard practice the command-line is easily "shared" with preceding positional arguments:
proc myproc {a b args} {
chkArgs $args -dstr {tabby "HUGE cat"} -dnum {wt 23.5}
if {$a eq "BIG dog"} { ... }
...
}
# boolean
?-?dbool {flagx 0 flagy 1} ## sets flagx 0, flagy 1
cmdline: -flagx -option ... ## flagx is now 1
# numeric
?-?dnum {qty 12 area 234.5 ...}
cmdline: -qty 23
-qty abc -> error (non-numeric value)
-qtty 23 -> error (no such option)
# string
?-?dstr {item1 "green" ...} ## item1 -> green
cmdline: -item1 reddish ## item1 -> reddish
-item1 -itemX ... ## error (missing value)
# enumerated
?-?denum {food {veggie fruit legume}} ## food -> veggie
# first elem in list "veggie" is default value of var food
# enum list elems can be of any type
cmdline: -food legume -> sets food to "legume"
-food lamb -> error ("lamb" not member of enum)
The leading dash is optional for type designators.
At runtime, using "-opts?" or "-options?" prompts cmdargs to print a list of cmdargs-created variables and default values:
source testfile.tcl
myproc -opts?
----dstr----
kitchen: modern stuff
floors: linoleum
walls: off-white
auto: Honda
----dnum----
frac: 0.5
area: 2000
rooms: 12
doors: 4
---denum----
critter: cat dog racoon deer ants birds
food: vegetable fruit meat legume grain
---dbool----
flagd: 0
flagc: 1
Filters prevent certain input from being accepted. cmdargs has 3 filters:
Filters work on individual options as given in the defaults lists. Filter syntax is straightforward:
# define "myproc"
proc myproc {args} {
chkArgs $args -dstr {str0 cat str1 dog} \
-dnum {numA 202 numB 5.5} \
-dfilter {nonulls {str0} nozero {numA} noneg {numA numB}}
...
}
# call myproc with options...
myproc -str0 "" -> Error: empty string not allowed
myproc -str1 "" -> OK.
myproc -numA 0 -> Error: numeric value == 0 not allowed.
myproc -numB 0 -> OK.
myproc -numA -2 -> Error: numeric value < 0 not allowed
The "-dfilter {...}" pair can appear before or after other pairs following "$args". The set of type designator and associated list must be kept together but the order of sets doesn't matter.
Some benchmark results are listed in the table. Overall, the extension is ~ 7-9 times faster than the pure Tcl reference implementation (test/CmdArgs.tcl). Also, the chkArgs variant is fastest, chkArgsvCi slowest. The difference is ~25%.
test | cmdargs (μs) | CmdArgs (μs) | tC/tc |
---|---|---|---|
bool | 1.99 | 16.29 | 8.18 |
str | 2.01 | 18.40 | 9.15 |
num | 2.12 | 18.13 | 8.55 |
enum | 2.36 | 18.20 | 7.71 |
all | 5.72 | 39.98 | 6.98 |
Table. Benchmark times (μsec) for each implementation. tC/tc = time(CmdArgs)/time(cmdargs) Tests running on a laptop with I7, 16GB, Windows 11, WSL2/Ubuntu 22.04.
Tests performed using "tmtest" (tmtest -batch 7), in "test/cmdargs-test.tcl" (for cmdargs::chkArgs*) and "test/cmdargs-test-2.tcl" (for CmdArgs::chkArgs*)
While the supplied precompiled shared libraries should work on Linux and Windows systems, there's always a chance of problems.
The archive contains the full source code and a build system. The supplied Makefiles will probably need to be regenerated to suit individual operating systems and environments.
The included build system is simple but can be elaborated to adapt to situations as necessary. The bin directory contains two executables, config and pkgNdx (or config.exe, pkgNdx.exe). These two files can be copied to a directory on the PATH. These may also be run from the bin directory as well. On unix-like systems check that bin/config,pkgNdx have exec bits "on". If not, use chmod a+x config pkgNdx
to fix it.
The primary configuration file is config.def found in each Tcl8/9/unix/win directory. (The tools directory also has config.def in its unix and win subdirectories. The provided bin/ files will probably not need to be regenerated.)
In config.def change the "tcldir" default path near the top of the file. Change the installto list to reflect your system's Tcl package directories.
set Defaults {
tcldir /usr/local/opt/tcl87s <- (replace with location on YOUR system...)
installto {directories to cp <cmdargs1.0> to}
<other settings...>
}
Now run
../../bin/config
the Makefile will be regenerated. Then run make install
and it should be good to go.
As already noted downloaded archives contain pre-compiled binaries for Windows and Linux. Binaries include working config and pkgNdx (or config.exe and pkgNdx.exe).
Regenerating these programs isn't too difficult. Necessary tools are located in the tools directory. Therein should be the following directories/files:
> config8.vfs
> tcl_library
config.tcl
main.tcl
> config9.vfs
> tcl_library
config.tcl
main.tcl
> unix
config.def
Makefile
> win
config.def
Makefile
config.def
Makefile
mkconfig8.tcl
mkconfig9.tcl
mkpkgndx.c
In the tools directory is found a unique configuration system designed around creating Tcl extensions and projects. (But not limited to Tcl software.)
It has two components, config.tcl and config.def. Config.tcl takes configuration info in config.def and "translates" into a standard Gnu makefile. Note that these makefiles are intended for processing by Gnu make. That should never be a problem on Linux systems. Under Windows it's highly recommended to use msys2 which provides a Unix-like environment for producing Windows binaries. For *BSD and other systems, use the appropriate package utility or ports collection to install gmake or equivalent.
Config.tcl can be used in two ways. One is using a suitable tclsh* to run config.tcl:
cd /path/to/target-directory-with-config.def
/path/to/tclsh(.exe) config.tcl</code>.
This uses the target directory's config.def file to produce a Makefile. config.tcl is provided in the tools directory and can be copied as needed. However, it's not very convenient if used this way more than occasionally.
The other (arguably better) way is creating a config executable from one of the "mkconfig*.tcl" supplied. There are a few steps involved:
tclsh ../mkconfig(8/9).tcl.
to create config8(.exe) or config9(.exe). But which tclsh versions should be used?
config(.exe)
in tools/unix-or-win directory to generate a new Makefile.(g)make
to generate config8/9 and pkgNdx. Use ?sudo? make install
to install. make install-bin
to copy these binaries to the CmdArgs-ext/bin directory. Use make clean
to remove compiled files from unix or win.Config.def is designed as a per directory configuration resource. It consists of several sections where users enter the directives that config (config.tcl) needs to have in order to generate a proper Makefile for the application.
— Config.def sections:
Sections are Tcl lists with varying structures.
config -help
is run or an error occurs.— Built-in or predefined variables:
Certain variables are predefined by config.tcl.
Config.tcl also provides a command stdrecp that outputs a standard recipe for compiling a C object file: $(CC) $(CFLAGS) -c $< -o $@
As a special convience for Tcl programmers, config.tcl accepts input of a variable "tcldir". This built-in need only be listed in Defaults with the location of the desired Tcl installation directory. With no further work required of the programmer, config.tcl will process this to provide a number of useful variables. These include:
These variables are used in DEFINES like other built-ins. IOW the pair "tclinc $tclinc" expands to TCLINC = path-to-installed-tcl/include
.
— The config.tcl command-line:
The config
command-line is the simple and preferred way to configure and generate a Makefile. Currently, required config parameters include tcldir, tclsrc, and installto.
The syntax is simple: -variableName=value. This is a valid invocation:
config -tcldir=/usr/opt/tcl9
The variable name is preceded by one or two dashes and must a built-in like "cc", "exe", "libext" or declared in the config.def "Vars" list. The variable gets its value from the Defaults list, or overridden by a command-line value.
If a variable declared in "Vars" does not have a Default value, then the command-line must be used to assign a value. Empty value is signified by absent right-hand side:
config -myvar=
Since config.def is an ordinary Tcl file additional functionality can be included via sourcing new Tcl files into a local config.def. Another approach is modifying config.tcl to enhance capabilities. Programmers are invited to contribute their enhancements to the project (via the project website, fossil, etc.). If the config/config.def configuration is successfully used for non-Tcl projects that will be of particular interest in shaping its future development.
J Altfas
Sun 9 Apr 2023 00:23:01 -07:00
cmdargs-doc.md:v1.0.9B