[ Main Table Of Contents | Table Of Contents | Keyword Index ]

punkshell_module_punk::args(0) 0.1.0 doc "args to option-value dict and values dict"

Name

punkshell_module_punk::args - args parsing

Table Of Contents

Synopsis

Description

Utilities for parsing proc args

Overview

overview of punk::args

Concepts

There are 2 main conventions for parsing a proc args list

  1. leading option-value pairs followed by a list of values (Tcl style)

  2. leading list of values followed by option-value pairs (Tk style)

There are exceptions in both Tcl and Tk commands regarding this ordering

punk::args is focused on the 1st convention (Tcl style): parsing of the 'args' variable in leading option-value pair style

The proc can still contain some leading required values e.g

proc dostuff {arg1 arg2 args} {...}}

but having the core values elements at the end of args is arguably more generally useful - especially in cases where the number of trailing values is unknown and/or the proc is to be called in a functional 'pipeline' style.

The basic principle is that a call to punk::args::opts_vals is made near the beginning of the proc e.g

   proc dofilestuff {args} {
       lassign [dict values [punk::args {
           *proc -help "do some stuff with files e.g dofilestuff <file1> <file2> <file3>"
           *opts -type string
           #comment lines ok
           -directory   -default ""
           -translation -default binary
           #setting -type none indicates a flag that doesn't take a value (solo flag)
           -nocomplain -type none
           *values -min 1 -max -1
       } $args]] opts values
       puts "translation is [dict get $opts -translation]"
       foreach f [dict values $values] {
           puts "doing stuff with file: $f"
       }
   }

The lines beginning with * are optional in most cases and can be used to set defaults and some extra controls

- the above example would work just fine with only the -<optionname> lines, but would allow zero filenames to be supplied as no -min value is set for *values

valid * lines being with *proc *opts *values

lines beginning with a dash define options - a name can optionally be given to each trailing positional argument.

If no names are defined for positional arguments, they will end up in the values key of the dict with numerical keys starting at zero.

e.g the result from the punk::args call above may be something like:

opts {-translation binary -directory "" -nocomplain 0} values {0 file1.txt 1 file2.txt 2 file3.txt}

Here is an example that requires the number of values supplied to be exactly 2 and names the positional arguments

It also demonstrates an inital argument 'category' that is outside of the scope for punk::args processing - allowing leading and trailing positional arguments

   proc dofilestuff {category args} {
       lassign [dict values [punk::args {
           -directory   -default ""
           -translation -default binary
           -nocomplain -type none
           *values -min 2 -max 2
            fileA -existingfile 1
            fileB -existingfile 1
       } $args]] opts values
       puts "$category fileA: [dict get $values fileA]"
       puts "$category fileB: [dict get $values fileB]"
   }

By using standard tcl proc named arguments prior to args, and setting *values -min 0 -max 0

a Tk-style ordering can be acheived, where punk::args is only handling the trailing flags and the values element of the returned dict can be ignored

This use of leading positional arguments means the type validation features can't be applied to them. It can be done manually as usual,

or an additional call could be made to punk::args e.g

       punk::args {
           category                -choices {cat1 cat2 cat3}
           another_leading_arg     -type boolean
       } [list $category $another_leading_arg]

Notes

For internal functions not requiring features such as solo flags, prefix matching, type checking etc - a well crafted switch statement will be the fastest pure-tcl solution.

When functions are called often and/or in inner loops, a switch based solution generally makes the most sense. For functions that are part of an API a package may be more suitable.

The following example shows a switch-based solution that is highly performant (sub microsecond for the no-args case)

    proc test1_switch {args} {
        set opts [dict create\
            -return "object"\
            -frametype "heavy"\
            -show_edge  1\
            -show_seps  0\
            -x a\
            -y b\
            -z c\
            -1 1\
            -2 2\
            -3 3\
        ]
        foreach {k v} $args {
            switch -- $k {
                -return - -show_edge - -show_seps - -frametype - -x - -y - -z - -1 - -2 - -3 {
                    dict set opts $k $v
                }
                default {
                    error "unrecognised option '$k'. Known options [dict keys $opts]"
                }
            }
        }
        return $opts
    }

Note that the switch statement uses literals so that the compiler produces a jump-table for best performance.

Attempting to build the switch branch using the values from dict keys $opts will stop the jump table being built. To create the faster switch statement without repeating the key names, the proc body would need to be built using string map.

use punk::lib::show_jump_tables <procname> to verify that a jump table exists.

There are many alternative args parsing packages a few of which are listed here.

  1. argp (pure tcl)

  2. parse_args (c implementation)

  3. argparse (pure tcl *)

  4. cmdline (pure tcl)

  5. opt (pure tcl) distributed with Tcl but considered deprecated

  6. The tcllib set of TEPAM modules (pure tcl)

    TEPAM requires an alternative procedure declaration syntax instead of proc - but has support for Tk and documentation generation.

(* c implementation planned/proposed)

punk::args was designed initially without specific reference to TEPAM - and to handle some edge cases in specific projects where TEPAM wasn't suitable.

In subsequent revisions of punk::args - some features were made to operate in a way that is similar to TEPAM - to avoid gratuitous differences where possible, but of course there are differences

and those used TEPAM or mixing TEPAM and punk::args should take care to assess the differences.

TEPAM is a mature solution and is widely available as it is included in tcllib.

Serious consideration should be given to using TEPAM or one of the other packages, if suitable for your project.

punk::args is relatively performant for a pure-tcl solution - with the parsing of the argument specification block occuring only on the first run - after which a cached version of the spec is used.

punk::args is not limited to procs. It can be used in apply or coroutine situations for example.

dependencies

packages used by punk::args

  • Tcl 8.6-

API

Namespace punk::args::class

class definitions

Namespace punk::args

Core API functions for punk::args

get_dict optionspecs rawargs ?option value...?

Parse rawargs as a sequence of zero or more option-value pairs followed by zero or more values

Returns a dict of the form: opts <options_dict> values <values_dict>

ARGUMENTS:

multiline-string optionspecs

This a block of text with records delimited by newlines (lf or crlf) - but with multiline values allowed if properly quoted/braced

'info complete' is used to determine if a record spans multiple lines due to multiline values

Each optionspec line defining a flag must be of the form:

-optionname -key val -key2 val2...

where the valid keys for each option specification are: -default -type -range -choices -optional

Each optionspec line defining a positional argument is of the form:

argumentname -key val -ky2 val2...

where the valid keys for each option specification are: -default -type -range -choices

comment lines begining with # are ignored and can be placed anywhere except within a multiline value where it would become part of that value

lines beginning with *proc *opts or *values also take -key val pairs and can be used to set defaults and control settings.

*opts or *values lines can appear multiple times with defaults affecting flags/values that follow.

list rawargs

This is a list of the arguments to parse. Usually it will be the $args value from the containing proc, but it could be a manually constructed list of values made for example from positional args defined in the proc.

Namespace punk::args::lib

Secondary functions that are part of the API

Internal

Namespace punk::args::system

Internal functions that are not part of the API

Keywords

args, arguments, module, parse, proc