autoopts

Difference between version 16 and 20 - Previous - Next
&| What          | '''autoopts''' |&
&| Where         | https://gitlab.com/dbohdan/autoopts |&
&| Prerequisites | Tcl 8.5-8.6 |&&| Updated       | 201720-089 |&
&| License       | MIT |&&| Contact       | [dbohdan] |&

** Description **
Autoopts is a Tcl module that automatically gives your program a command line interface.  It was inspired by [Perl 6], which does a similar thing [https://perl6advent.wordpress.com/2010/12/02/day-2-interacting-with-the-command-line-with-main-subs/%|%quite elegantly].  In short, autoopts generates a command line interface based on the arguments that your main proc takes.  It maps the proc's arguments whose names are prefixed with dashes (e.g., `-a` or `--arg`) to named command line arguments (options) or flags and other proc arguments to positional command line arguments.  E.g., `proc main {filename -a {--factor 2x}} ...` translates to `usage: yourscript.tcl -a A [--factor 2x] [--] filename`.  Proc arguments with default values are mapped to optional command line arguments, named or positional; other arguments are considered mandatory.  If some of the mandatory arguments are missing or unrecognized extra command line arguments are given, autoopts will output an informative error message, print an automatically generated usage note, and exit.  It will also output a usage message if the user gives the command line argument `-h` or `--help`.

To use autoopts, source it or `package require` it as a [module] and call `::autoopts::go ?description? ?your-main-proc?`
Download: `curl https://gitlab.com/dbohdan/autoopts/raw/v0.46.0/autoopts.tcl > autoopts-0.46.0.tm`

** Use example **

*** Code ***

======
#! /usr/bin/env tclsh
proc main {{input -} {--indent 4} {--reverse false}} {
    set ch [expr {$input eq "-" ? "stdin" : [open $input]}]
    while {[gets $ch line] > -1} {        if {${--reverse}} {
            regexp {^(\s*)(\S?.*)$} $line _ space line
            set line $space[string reverse $line]
        }

        puts [string repeat { } ${--indent}]$line
    }
}

source autoopts.tcl::autoopts::go {indenter pro v1.01.20 -- indents input with spaces}
======

*** Shell transcript ***

======
$ ./example.tcl --helpindenter pro v1.01.20 - indents input with spaces
usage: example.tcl [--reverse] [--indent 4] [--] [input]

$ ./example.tcl --wrong
unknown option: --wrongusage: example.tcl [--reverse] [--indent 4] [--] [input]

$ ./example.tcl file1 file2 file3
too many positional argumentsusage: example.tcl [--reverse] [--indent 4] [--] [input]

$ ./example.tcl --indent
missing value for option --indentusage: example.tcl [--reverse] [--indent 4] [--] [input]

$ ./example.tcl ./example.tcl --indent 11
           #! /usr/bin/env tclsh           
           proc main {{input -} {--indent 4} {--reverse false}} {
               set ch [expr {$input eq "-" ? "stdin" : [open $input]}]
               while {[gets $ch line] > -1} {                   if {${--reverse}} {
                       regexp {^(\s*)(\S?.*)$} $line _ space line
                       set line $space[string reverse $line]
                   }
           
                   puts [string repeat { } ${--indent}]$line
               }
           }
           
           source autoopts.tcl           ::autoopts::go {indenter pro v1.01.20 - indents input with spaces}

$ ./example.tcl --reverse ./example.tcl --indent 11
           hslct vne/nib/rsu/ !#
           
           { }}eslaf esrever--{ }4 tnedni--{ }- tupni{{ niam corp
               ]}]tupni$ nepo[ : "nidts" ? "-" qe tupni${ rpxe[ hc tes
               { }1- > ]enil hc$ steg[{ elihw
                   { }}esrever--{${ fi
                       enil ecaps _ enil$ }$)*.?S\()*s\(^{ pxeger
                       ]enil$ esrever gnirts[ecaps$ enil tes
                   }
           
                   enil$]}tnedni--{$ } { taeper gnirts[ stup
               }
           }
           
           lct.stpootua ecruos
           }secaps htiw tupni stnedni - 0.1.1v orp retnedni{ og::stpootua::
======

** Code **

See https://gitlab.com/dbohdan/autoopts/blob/master/autoopts.tcl.


** Discussion **

'''[arjen] - 2017-08-15 14:01:13'''

Just a few remarks:
   * Shouldn't invoke use [uplevel] to make sure that the procedure is called in the right namespace and that variables that are referred by name are correctly [upvar]'ed?
   * The test cases should include an example where the procedure lives in a different namespace.
   * It is a common convention to capitalise the names of private procedures. An easy way to see what you may and may not use.

[dbohdan] 2017-08-15: Thanks for the suggestions! I've implemented 1-2 (with an `uplevel 1` in both `::autoopts::go` and `::autoopts::invoke`). As for 3, consider the whole API unstable for now. I do not expect to change it in a major way, especially not `::autoopts::go`, but I can't promise I won't. When it stabilizes, I'll probably separate the public and the private procedures like in https://github.com/dbohdan/jimhttp/blob/master/json.tcl.

----

'''[rz] - 2017-08-16 06:00:34'''

Nice addition. Could you put all user visible texts in a message catalog?
[dbohdan] 2017-08-16: Thanks. There was only one natural language message in autoopts, which amounted to `usage: %s`. I put it in [msgcat] in v0.4.0.

<<categories>>Argument Processing | Package