The command-line-parser Library#
The command-line-parser library provides a facility to parse the command line. It exports two modules:
command-line-parser - Main API module
option-parser-protocol - For extending the API
Overview#
Here’s a quick list of features to get a sense of what’s available:
Various option types
<flag-option>
for simple--boolean
flags.<parameter-option>
for--foo=bar
options.<repeated-parameter-option>
options may be repeated:-r a -r b
<optional-parameter-option>
in which the value is optional:--foo
or--foo bar
<keyed-option>
fills a table with key/value pairs, for example:-Dk1=v1 -Dk2=v2
<choice-option>
the value may be chosen from a predefined set.<positional-option>
to receive positional arguments.
Automatic and extensible conversion of arguments to other types. For example, an option with
type: <integer>
is automatically converted to integer by the parser.Subcommands
Automatic usage and
--help
generation.A convenient and readable syntax for creating the command line.
Extensibility. Add your own argument converters with
parse-option-value
or create entirely new option types.
Quick Start#
The best way to build a command line parser is with
command-line-definer
, since it is far more concise and readable than
building one “by hand” with make(<command-line-parser>, options: ...)
. It
also provides convenient methods to retrieve command-line option values rather
than accessing them by name, with strings.
Note
command-line-definer
does not currently support subcommands.
If you need subcommands you will have to build the parser by hand for
now.
Let’s say you want to parse a command line that looks like this:
frob --name=zoo --debug -r a -r b -r c --choice=foo one two three
The “frob” command accepts a --name
option that takes a value, a boolean
--debug
(or --nodebug
) a repeatable -r
option, a --choice
option that accepts one of several values, and then at least one positional
argument (here “one”, “two”, and “three”). Here’s what that parser looks like:
define command-line <frob-command-line> ()
option frob-name :: <string>,
names: #("name"),
help: "Name of the frob",
kind: <parameter-option>;
option frob-debug? :: <boolean>,
names: #("debug"),
negative-names: #("nodebug"),
help: "Enable or disable debugging",
kind: <flag-option>; // This is the default.
option frob-radicals :: <sequence>,
names: #("r"),
kind: <repeated-parameter-option>,
variable: "RAD", // Makes --help show "-r RAD"
help: "Free radicals";
option frob-choice :: <string>,
names: #("choice"),
choices: #("foo", "bar", "baz"),
default: "foo",
help: "Your choice";
option frob-filenames :: <sequence>,
names: #("filenames"),
kind: <positional-option>,
repeated?: #t,
help: "One or more filenames";
end command-line;
Now parse the command line:
block ()
let cmd = make(<frob-command-line>, help: "frob things");
parse-command-line(cmd, application-arguments());
// Now execute your main program code with cmd containing
// the parsed argument values.
frob(cmd);
exception (err :: <abort-command-error>)
// This condition is signaled by parse-command-line and also if
// your own code calls abort-command().
format-err("%s", err);
exit-application(err.exit-status);
end;
To access the option values simply read the <frob-command-line>
slot
values. Assuming cmd
is the command parsed above:
for (file in cmd.frob-filenames)
if (cmd.frob-debug?)
format-out(...);
end;
...more...
end;
Of course, it is also possible to make a command line parser without the macro
above, but doing so is much more verbose and requires accessing the option
values by calling get-option-value(cmd, "option-name")
. Briefly, just call
make
, like this:
let cmd
= make(<command-line-parser>,
help: "a most excellent program",
options: list(make(<flag-option>,
names: #("name"),
help: "provide a name"),
...,
make(<positional-option>,
names: #("filenames"),
repeated?: #t,
help: "one or more filenames")),
subcommands: list(make(<my-subcommand>, ...)));
parse-command-line(cmd, application-arguments());
let filenames = get-option-value(cmd, "filenames");
...etc...
Reference#
The command-line-parser Module#
- <command> Abstract Sealed Class#
Abstract superclass of
<command-line-parser>
and<subcommand>
.- Init-Keywords:
options – A sequence of
<option>
instances. Note that<positional-option>
instances must follow all other options, and there may be only a single<positional-option>
that specifiesrepeated?: #t
and it must be the last option in the sequence.help – Required. A
<string>
to display when help is requested via the--help
option or thehelp
subcommand.
- <subcommand> Open Abstract Class#
A named subcommand. Subcommands have their own set of command-line options. They may not contain other subcommands.
- Superclasses:
- Init-Keywords:
name – Required. The subcommand name, a
<string>
.
- Discussion:
Subclass this for each subcommand you need and implement a method on
execute-subcommand
for each subclass.
- <command-line-parser> Open Class#
Encapsulates a set of command-line options. May optionally contain a set of subcommands, each of which has its own set of options.
- Superclasses:
- Init-Keywords:
help-option? – A boolean specifying whether the parser should automatically add the default help option. By default, help can be requested via
--help
or-h
. If false, no help option will be added to the parser, and you must explicitly handle any request for help yourself. The default is true. See<help-option>
.help-subcommand? – A boolean specifying whether the parser should automatically add the default help subcommand. The default is true if the command-line has any subcommands. Set to false if you prefer to call the subcommand something else (e.g., non-English). See
<help-subcommand>
.subcommands – A sequence of
<subcommand>
.
- <help-subcommand> Class#
Implements the
help
subcommand. Normally there is no need to use this since the command line parser implements thehelp
subcommand itself. However, if you wanted to implement thehelp
subcommand differently, or just give it a different (or an additional) name, this is how to do it:define class <my-help-subcommand> (<help-subcommand>) end; let p = make(<command-line-parser>, help-subcommand?: #f, ... subcommands: list(make(<my-help-subcommand>, names: #("ayuda")) ...)); define method execute-subcommand (parser, sub :: <my-help-subcommand>) => (s :: false-or(<integer>)) ...etc... end
- <command-line-parser-error> Open Class#
Superclass of all errors signaled by this library.
- Superclasses:
<format-string-condition>
,<error>
- <abort-command-error> Sealed Class#
Provides a standard way for program code to indicate that the application should exit. Signaled by the command line parser itself after the standard
--help
option orhelp
subcommand have completed, so programs should always handle this at top level.- Superclasses:
- Init-Keywords:
status – Required. A status code to pass to
exit-application
. An<integer>
.
- Discussion:
This is commonly handled by calling
exit-application(err.exit-status)
after printing the error.
- exit-status Function#
Returns the exit status associated with an error.
- Parameters:
error – An instance of
<abort-command-error>
- Values:
status – An instance of
<integer>
- <usage-error> Open Class#
Signaled when a command-line cannot be parsed.
- Superclasses:
- Discussion:
This is commonly handled by calling
exit-application(err.exit-status)
. This condition need not be handled specially if the application already handles<abort-command-error>
.
- add-option Function#
Add an option to a command-line parser.
- Signature:
add-option (parser option) => ()
- Parameters:
parser – An instance of
<command-line-parser>
.option – An instance of
<option>
.
- Discussion:
If any of the option names specified are already used by other options then
<command-line-parser-error>
is signaled.
- parse-command-line Function#
Parses the command line in
argv
and side-effectsparser
accordingly.- Signature:
parse-command-line (parser argv) => ()
- Parameters:
parser – An instance of
<command-line-parser>
.argv – An instance of
<sequence>
. Normally the value returned byapplication-arguments
is passed here.
- Discussion:
By default, the
--help
flag and the “help” subcommand are handled automatically by callingprint-synopsis
and then signaling<abort-command-error>
, so the caller should handle that condition.If
argv
isn’t a valid set of options as described by theparser
then<usage-error>
.See Quick Start for an example.
- execute-command Generic function#
When using subcommands, call this to execute the parsed command line.
- Signature:
execute-command (parser) => (false-or(<integer>))
- Parameters:
parser – An instance of
<command-line-parser>
.
- Discussion:
Call this after calling
parse-command-line
, if your command-line has subcommands. (If not using subcommands there is no need to call this; just read option values from parser directly.) It determines the subcommand indicated by the user and invokesexecute-subcommand
on it. Thehelp
subcommand is handled specially.
- execute-subcommand Generic function#
Implement a method on this generic for each subclass of
<subcommand>
you create.- Signature:
execute-subcommand (parser subcommand) => (false-or(<integer>))
- Parameters:
parser – An instance of
<command-line-parser>
.subcommand – An instance of
<subcommand>
.
- Discussion:
This is the implementation of your subcommand. Read command-line options from
subcommand
and read global options (if any) fromparser
.
- print-synopsis Open Generic function#
Display a synopsis of the command line options.
- Signature:
print-synopsis (parser subcommand #key stream) => ()
- Parameters:
parser – An instance of
<command-line-parser>
.subcommand – An instance of
<subcommand>
or#f
.stream (#key) – An instance of
<stream>
.
- Discussion:
It is not normally necessary to call this function since the
--help
option andhelp
subcommand are handled automatically by the parser.
- option-present? Function#
Returns
#t
if this option was supplied on the command line.- Signature:
option-present? (option) => (present?)
- Parameters:
option – An instance of
<option>
.
- Values:
present? – An instance of
<boolean>
.
- Discussion:
Returns
#t
if this option was supplied on the command line. Returns#f
if called beforeparse-command-line
has been called on the associated parser, or if the option wasn’t supplied on the command line.
- option-value Function#
Returns the parsed value of the option supplied on the command line.
- Signature:
option-value (option) => (value)
- Parameters:
option – An instance of
<option>
.
- Values:
value – An instance of
<object>
.
- Discussion:
Returns the parsed value of the option supplied on the command line. If no value was supplied on the command line it returns the value specified with
default:
when the option was created, which in turn defaults to#f
.Note that the type of the return value is specified by the
type:
keyword argument when the option was created and the string supplied on the command line is converted to that type by a method onparse-option-value
.
- parse-option-value Open Generic function#
Convert a command line argument from a
<string>
to the<option>
type.- Signature:
parse-option-value (argument, type) => (value)
- Parameters:
- Values:
value – An instance of
<object>
.
- Discussion:
Convert a command-line argument (a string) to the type specified in the corresponding
<option>
instance. For example, given the following code, the “version” option’soption-value
slot would be set to an instance of<version>
by the command line parser so that you don’t have to manually do it in your application.make(<parameter-option>, names: #("version"), help: "A version specifier. Ex: v1.2.3", type: <version>) define method parse-option-value (arg :: <string>, type == <version>) => (v :: <version>) parse-version(arg) end;
There are predefined methods that convert to
<number>
,<boolean>
,<symbol>
, and<sequence>
. For<boolean>
, valid values are yes/no, on/off, true/false. For<sequence>
, strings are simply split around commas, without any attempt to be smart about quoting.
Option Classes#
- <option> Open Abstract Class#
Superclass of all other option types.
- Superclasses:
- Init-Keywords:
names –
Names for this option; a sequence of strings. For convenience a single string may be given if the option only has one name. Strings of length 1 are considered to be short options, i.e., they are prefixed by a single dash on the command line.
The first name in the list is considered the “canonical” name of the option and is used in various parts of the auto-generated
--help
message.type –
The kind of value represented by this option. That is, the string passed on the command line will be coerced to this type via
parse-option-value
if a relevant method exists. Clients may implement methods on that function for their own types to extend the parser.Predefined types include
<integer>
,subclass(<float>)
,subclass(<sequence>)
.help –
A string documenting the option. Displayed in
--help
output. Some automatic substitutions are performed:”%default%” => The string representation of the default value for the option.
”%app%” => The basename of the executable program.
For example, given this option specification:
make(<parameter-option>, names: #("v", "version"), help: "Package version [%default%]", default: "latest")
it will be displayed as:
--version V Package version [latest]
variable –
A string to stand in for the option value in
--help
output. For example, if the option name is--database
this might be “URL”, which would display as:--database URL A database URL.
When not specified, the first name of the option is used. For example:
make(<parameter-option>, names: #("n", "name"), help: "A name")
And on the command-line this will be displayed as:
-n, --name N A name
default – A default value for the option that will be used if the option isn’t specified by the user. The default value should be a member of the type specified with the
type:
keyword.#f
is the default default value.
- <flag-option> Sealed Class#
Defines a simple flag option, i.e., one that specifies a boolean value.
- Superclasses:
- Init-Keywords:
negative-names – Same as
names
, but specifies the negative forms.
- Discussion:
They default to
#f
and may exist in both positive and negative forms:--foo
and--no-foo
. In the case of conflicting options, the rightmost takes precedence to allow for abuse of the shell’s “alias” command.For example, a single instance of this class could be used to specify all of the following command-line options:
-q, -v, --quiet, --verbose
- <parameter-option> Sealed Class#
Defines an option that requires a value be specified.
- Superclasses:
- Discussion:
If the option appears more than once, the rightmost value takes precedence. If the option never appears, these will default to
#f
.Examples:
-cred, -c=red, -c = red, --color red, --color=red
- <optional-parameter-option> Sealed Class#
Similar to
<parameter-option>
, but the parameter is optional.- Superclasses:
- Discussion:
The parameter must directly follow the option with no intervening whitespace, or follow an “=” token. The value is
#f
if the option never appears,#t
if the option appears but the parameter does not, and the value of the parameter otherwise.Examples:
-z, -z3, -z=3, -z = 3, --zip, --zip=3, --zip = 3
Invalid examples:
-z 3, --zip 3, --zip3
- <repeated-parameter-option> Sealed Class#
Similar to
<parameter-option>
, but may appear more than once.- Superclasses:
- Discussion:
The final value is a deque of parameter values in the order they appeared on the command line. It defaults to the empty deque.
Examples:
-wall, -w=all, -w = all, --warnings all, --warnings=all
- <choice-option> Sealed Class#
Similar to
<parameter-option>
, but provides a restricted set of values to choose from.- Superclasses:
- Init-Keywords:
choices – A sequence of objects (usually strings). If the value supplied on the command line isn’t one of these objects then
<usage-error>
is signaled. If you supply a sequence of non-string choices you will also need to supply thetest:
init keyword since all command-line arguments are strings and won’t compare equal with the default test,=
.test – A function to test whether the value supplied on the command line is the same as one of the choices. The default is
=
. Another commonly used value isstring-equal-ic?
, to ignore case in the comparison.
- Discussion:
Example:
make(<choice-option>, names: #("foo"), help: "a or b", choices: #("a", "b"), test: string-equal-ic?)
- <keyed-option> Sealed Class#
Each occurrence of this type of option defines a key => value mapping.
- Superclasses:
- Discussion:
These are a bit obscure. The best example is gcc’s
-D
option. The final value is a<string-table>
containing each specified key, with one of the following values:#t
: The user specified “-Dkey”a string: The user specified “-Dkey=value”
You can read this with
element(table, key, default: #f)
to get a handy lookup table.Examples:
-Dkey, -Dkey=value, -D key = value, --define key = value
- <positional-option> Sealed Class#
Accepts an argument that is passed by possition on the command line.
- Superclasses:
- Discussion:
If you want your command-line parser to accept positional arguments you must add
<positional-option>
instances to it. Positional options must come after all non-positional options.By default, positional options are marked as required but you may pass
required?: #f
to make them optional. Note that it is an error to add a required positional option after an optional positional option since there is no way to parse that unambiguously.A
<positional-option>
may be marked as accepting any number of arguments by passingrepeated?: #t
when callingmake
. An option marked as repeated must be the last option in the parser’s list of options.In this example the command line requires one filename to be passed on the command line, and one or more words after that:
add-option(parser, make(<positional-option>, names: #("filename"), help: "A filename")); add-option(parser, make(<positional-option>, names: #("words"), help: "One or more words", repeated?: #t)); // Usage: app [options] FILENAME WORDS...
- <help-option> Open Class#
The standard
--help
option.- Superclasses:
- Discussion:
May be subclassed if you want to implement your own help option instead of the default one.
The option-parser-protocol Module#
This module exports an API that can be used to extend the existing command line parser without modifying the source in this library. It shouldn’t be common to need this and it is likely to be incomplete. See the source code for details.