RubyLane / parse_args

A fast argument parser based on the patterns established by core Tcl commands like [lsort], [lsearch], [glob], [regex], etc.
Other
8 stars 5 forks source link

NAME

parse_args - Core-style argument parsing for scripts

SYNOPSIS

package require parse_args ?0.5?

parse_args::parse_args args argspec ?varname?

DESCRIPTION

The commands provided by the Tcl core follow a pattern of optional, named arguments (named with a leading “-” to indicate an option) in addition to positional arguments. They strongly prefer named arguments when the number of arguments and particularly optional arguments grows beyond about 3. Tcl scripts however have no access to standard argument parsing facilities to implement this pattern and have to build their named argument parsing machinery from scratch. This is slow to do in script, error prone, and creates noise in the code that distracts from the core task of the command implementation. This means that script-defined commands generally present half-baked argument handling APIs and tend to over-use positional arguments.

This package aims to address this weakness by providing a robust argument parsing mechanism to script code that can implement the full set of patterns established by the core Tcl commands, is terse and readable to specify the arguments, and is fast enough to use in any situation.

COMMANDS

ARGUMENT SPECIFICATION

The syntax of the argument specification (given in the argspec argument to the parse_args command) is a dictionary, with the keys corresponding to the names by which the arguments will be available to the caller, either as variables created by the parse_args command or keys in the dictionary written to varname if that was specified. If the first character of the key is a “-” character it defines a named option, otherwise a positional parameter. Like in proc argument definitions the name args is special, and will consume all remaining arguments.

The values of the dictionary contain the definition of the corresponding argument, such as -required, which marks that argument as being required (whether a named or positional argument). The options that are available to define the argument are:

EXAMPLES

Mimic the argument handling of the core glob command:

proc glob args {
    parse_args::parse_args $args {
        -directory  {}
        -join       {-boolean}
        -nocomplain {-boolean}
        -path       {}
        -tails      {-boolean}
        -types      {-default {}}
        args        {-name patterns}
    }

    if {$join} {
        set patterns    [list [file join {*}$patterns]]
    }

    if {[llength $patterns] == 0 && $nocomplain} return

    foreach pattern $patterns {
        if {[info exists directory]} {
            ...
        }
    }
    ...
}

Mimic regex:

proc regexp args {
    parse_args::parse_args $args {
        -about      {-boolean}
        -expanded   {-boolean}
        -indices    {-boolean}
        -line       {-boolean}
        -linestop   {-boolean}
        -lineanchor {-boolean}
        -nocase     {-boolean}
        -all        {-boolean}
        -inline     {-boolean}
        -start      {-default 0}
        exp         {-required}
        string      {-required}
        matchvar    {}
        args        {-name submatchvars}
    }
    ...
}

lsort:

proc lsort args {
    parse_args::parse_args $args {
        -ascii      {-name compare_as -multi -default ascii}
        -dictionary {-name compare_as -multi}
        -integer    {-name compare_as -multi}
        -real       {-name compare_as -multi}

        -command    {}

        -increasing {-name order -multi -default increasing}
        -decreasing {-name order -multi}

        -indices    {-boolean}
        -index      {}
        -stride     {-default 1}
        -nocase     {-boolean}
        -unique     {-boolean}
        list        {-required}
    }

    if {![info exists command]} {
        switch -- $compare_as {
            ascii {
                set command {string compare}
                if {$nocase} {
                    lappend command -nocase
                }
            }
            dictionary {
                ...
            }
            integer - real {
                set command tcl::mathop::-
            }
        }
    }
}

lsearch:

proc lsearch args {
    parse_args::parse_args $args {
        -exact      {-name matchtype -multi}
        -glob       {-name matchtype -multi -default glob}
        -regexp     {-name matchtype -multi}

        -sorted     {-boolean}
        -all        {-boolean}
        -inline     {-boolean}
        -not        {-boolean}
        -start      {-default 0}

        -ascii      {-name compare_as -multi -default ascii}
        -dictionary {-name compare_as -multi}
        -integer    {-name compare_as -multi}
        -real       {-name compare_as -multi}

        -nocase     {-boolean}

        -decreasing {-name order -multi}
        -increasing {-name order -multi -default increasing}
        -bisect     {-boolean}

        -index      {}
        -subindices {-boolean}
    }

    if {$sorted && $matchtype in {glob regexp}} {
        error "-sorted is mutually exclusive with -glob and -regexp"
    }
}

A Tk widget - entry:

proc entry {widget args} {
    parse_args::parse_args $args {
        -disabledbackground {-default {}}
        -disabledforeground {-default {}}
        -invalidcommand     {-default {}}
        -readonlybackground {-default {}}
        -show               {}
        -state              {-default normal -enum {
            normal disabled readonly
        }}
        -validate           {-default none -enum {
            none focus focusin focusout key all
        }}
        -validatecommand    {-default {}}
        -width              {-default 0}
        -textvariable       {}
    }
}

SEE ALSO

The paper presented at the 2016 Tcl Conference which discusses the approach and design choices made in this package in much greater depth: https://www.tcl-lang.org/community/tcl2016/assets/talk33/parse_args-paper.pdf

BUGS

Please open an issue on the github tracker for the project if you encounter any problems: https://github.com/RubyLane/parse_args/issues

LICENSE

This package is Copyright 2023 Cyan Ogilvie, and is made available under the same license terms as the Tcl Core