shithub: mc

ref: d0ffb958c7247028223c7e53253249d52a47a324
dir: /doc/api/libstd/cli.txt/

View raw version
{
        title:  CLI Parsing
        description:  libstd: CLI Parsing
}


Command Line Parsing
--------------------

Command line parsing is something that nearly every program needs. This
section of libstd provides simple command line parsing with autogenerated
help.

    pkg std =
            type optdef = struct
                    argdesc	: byte[:]	/* the description for the usage */
                    minargs	: std.size	/* the minimum number of positional args */
                    maxargs	: std.size	/* the maximum number of positional args (0 = unlimited) */
                    noargs	: std.bool	/* whether we accept args at all */
                    opts	: optdesc[:]	/* the description of the options */
            ;;
            type optdesc = struct
                    opt	: char
                    arg	: byte[:]
                    desc	: byte[:]
                    optional	: bool
            ;;
            type optparsed = struct
                    opts	: (char, byte[:])[:]
                    args	: byte[:][:]
            ;;

            const optparse	: (optargs : byte[:][:], def : optdef# -> optparsed)
            const optusage	: (prog : byte[:], def : optdef# -> void)
    ;;

Syntax
-------

A command line is composed of a list of words, known as. These arguments may
be options that the program can act on, known as "flags". These flags may take
up to one argument. To avoid confusing with the top level arguments, this
document will refer to them as "values". Anything that is not a flag is a
"positional argument", or simply an "argument".

In general, the POSIX syntax for arguments is followed, with a few minor
enhancements. Myrddin program will use the following semantics for command
line options:

 - Arguments are groupls of flags if they follow a '-'. A flag is any
   single unicode character, potentially followed by a single value. This
   value may be optional.
 - Flags that take values will consume the remainder of the argument as
   the value. If the remainder of the argument is empty, then the next
   argument is consumed as the value. For example, `-foo` and `-f oo`
   are equivalent.
 - Any flags that do not take arguments may be placed before other
   flags within the same argument. For example, `-x -y -z` is equivalent
   to `-xyz`, as long as `-x` and `-y` have no optional arguments.
 - The first '--' stops flags from being recognized, and treats them
   as arguments.
 - Flags may be supplied in any order, intermingled with arguments,
   and repeated as many times as desired. An unrecognized flag is an
   error, and will trigger a usage message.

Types
------

The API provided for command line parsing is relatively declarative, with the
options specified in a struct passed to the parsing.

    type optdef = struct
            argdesc	: byte[:]
            minargs	: std.size
            maxargs	: std.size
            noargs	: std.bool
            opts	: optdesc[:]
    ;;

The optdef is the top level structure describing the command line arguments.
It contains the following fields:

<dl>
    <dt><code>argdesc</code></dt>
    <dd>
    <p>Argdesc is a string describing the positional arguments passed to the
    program. It doesn't change the way that the arguments are parsed, but is
    used to document the arguments to the user.</p>

    <p>In general, this should be a summary of any expected argument. If a
    variable number of them are expected, the argument should be followed
    with a <code>...</code>.</p>

    <p>For example, a program that takes an output and a list of inputs may
    provide the following for <code>argdesc</code>:</p>

    <p><code>"output inputs..."</code></p>

    <p>When the help string is generated, the output would look like:</p>
    <p><code>myprog [-o option] output inputs...</code></p>
    </dd>

    <dt><code>minargs</code></dt>
    <dd>
    <p>This argument limits the minimum number of arguments that the
    program will accept without error. If at minimum 3 inputs are needed, for
    example, then this value should be set to 3. This does not count flags,
    nor does it count the program name.</p>
    <p> If set to 0, this value is ignored. This is the default value.</p>
    </dd>

    <dt><code>maxargs</code></dt>
    <dd>
    <p>This argument limits the maximum number of arguments that the program
    will accept without error. If the program takes at most 1 argument, for
    example, example, then this value should be set to 3. Just like
    <code>maxargs</code>, this does not count flags or the program name.
    </p>
    <p> If set to 0, this value is ignored. This is the default value.</p>
    </dd>

    <dt><code>noargs</code></dt>
    <dd>
    <p>This argument causes the program to reject any arguments at all.</p>
    </dd>
    <dt><code>opts</code></dt>
    <dd><p>This is a list of descriptions of the options that this program
    takes. This list may be empty, at which point this api still provides a
    good way of checking that no invalid arguments are passed.</p>
    </dd>
</dl>
    
    
    type optdesc = struct
            opt	: char
            arg	: byte[:]
            desc	: byte[:]
            optional	: bool
    ;;

This is a description of a command line argument. It contains the following
fields to be set by the user:

<dl>
    <dt><code>opt</code></dt>
    <dd>
    <p>This is a single unicode character that is used for the option
    flag.</p>
    </dd>
    <dt><code>arg</code></dt>
    <dd>
    <p>This is a single word description of the argument. If it is not present
    or has zero length, this indicates that the flag takes no value.
    Otherwise, the value is mandatory, unless the <code>optional</code> flag
    is set.</p>
    </dd>
    <dt><code>optional</code></dt>
    <dd>
    <p>This is a boolean that allows for the value <code>arg</code> to be
    optionally omitted when using the flag. It is disabled by default.
    </p>
    </dd>
    <dt><code>desc</code></dt>
    <dd>
    <p>This is a short sentence describing <code>arg</code>. It has no
    semantic effect on the option parsing, and is only used in generating
    help output for the arguments.
    </p>
    </dd>
</dl>


    type optparsed = struct
            opts	: (char, byte[:])[:]
            args	: byte[:][:]
            prog        : byte[:]
    ;;

This is the final result of parsing the options. The `opts` member contains a
list of options in the form of `(opt, val)` pairs. The option `opt` will be
repeated once for every time that the flag `opt` is seen within the command
line.

If there is no value passed with the flag, then the string will be the empty
string. Otherwise, it will contain the string passed.

The `args` member contains the arguments, collected for easy iteration, and the
`prog` member contains the binary name.

Functions
----------

    const optparse	: (optargs : byte[:][:], def : optdef# -> optparsed)

Optparse takes an array `optargs` containing the command line arguments passed
to the program, as well as an `optdef` pointer describing the expected
arguments, and spits out out an `optparsed`. The arguments `optargs` are
expected to contain the program name.

    const optusage	: (prog : byte[:], def : optdef# -> void)


Optusage takes the string `prog` containing the program name, and an `def`
containing an `optdef` which describes the arguments to provide help for. It
prints these out on `stderr` (fd 1), and returns.


Examples:
--------

This example is a trivial one, which parses no flags, and merely
errors if given any.

    const main = {args
        var cmd
        
        cmd = std.optparse(args, &[
                .argdesc = "vals",
        ])
        for arg in cmd.args
                std.put("arg: {}\n", arg)
        ;;
    }

This example shows some more advanced usage, and is extracted from
mbld.

    const main = {args
	var dumponly
	var targname
	var bintarg
	var cmd 
	var libpath
	
	cmd = std.optparse(args, &[
		.argdesc = "[inputs...]",
		.opts = [
			[.opt='t', .desc="list all available targets"],
			[.opt='T', .arg="tag", .desc="build with specified systag"],
			[.opt='S', .desc="generate assembly when building"],
			[.opt='d', .desc="dump debugging information for mbld"],
			[.opt='I', .arg="inc", .desc="add 'inc' to your include path"],
			[.opt='R', .arg="root", .desc="install into 'root'"],
			[.opt='b', .arg="bin", .desc="compile binary named 'bin' from inputs"],
			[.opt='l', .arg="lib", .desc="compile lib named 'lib' from inputs"],
			[.opt='r', .arg="rt", .desc="link against runtime 'rt' instead of default"],
			[.opt='C', .arg="mc", .desc="compile with 'mc' instead of the default compiler"],
			[.opt='M', .arg="mu", .desc="merge uses with 'mu' instead of the default muse"],
		][:]
	])
	targname = ""
	tags = [][:]
	for opt in cmd.opts
		match opt
		| ('t', ""):	dumponly = true
		| ('S', ""):	bld.opt_genasm = true
		| ('I', arg):	bld.opt_incpaths = std.slpush(bld.opt_incpaths, arg)
		| ('R', arg):	bld.opt_instroot = arg
		| ('T', tag):	tags = std.slpush(tags, tag)
		| ('b', arg):
			targname = arg
			bintarg = true
		| ('l', arg):
			targname = arg
			bintarg = false
		| ('r', arg):
			if std.sleq(arg, "none")
				bld.opt_runtime = ""
			else
				bld.opt_runtime = arg
			;;
		/*
		internal undocumented args; used by compiler suite for
		building with an uninstalled compiler.
		*/
		| ('d', arg): bld.opt_debug = true
		| ('C', arg): bld.opt_mc = arg
		| ('M', arg): bld.opt_muse = arg
		| _:	std.die("unreachable\n")

		;;
	;;

        for arg in cmd.args
                /* build stuff */
        ;;
    }