Summary: 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 */
dest : std.option(byte[:]#) /* destination for option */
;;
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:
argdesc
-
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.
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
...
.For example, a program that takes an output and a list of inputs may provide the following for
argdesc
:"output inputs..."
When the help string is generated, the output would look like:
myprog [-h?] [-o option] output inputs...
minargs
-
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.
If set to 0, this value is ignored. This is the default value.
maxargs
-
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 1. Just like
minargs
, this does not count flags or the program name.If set to 0, this value is ignored. This is the default value.
noargs
-
This argument causes the program to reject any arguments at all.
opts
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.
type optdesc = struct
opt : char
arg : byte[:]
desc : byte[:]
optional : bool
dest : std.option(byte[:]#) /* destination for option */
;;
This is a description of a command line argument. It contains the following fields to be set by the user:
opt
-
This is a single unicode character that is used for the option flag.
arg
-
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
optional
flag is set. optional
-
This is a boolean that allows for the value
arg
to be optionally omitted when using the flag. It is disabled by default. desc
-
This is a short sentence describing
arg
. It has no semantic effect on the option parsing, and is only used in generating help output for the arguments. dest
If there is an arg parameter, and this is value is `Some ptr, then the pointer is initialized with the argument value.
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 : 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 : 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 : cmd.args
/* build stuff */
;;
}