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

  • 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 specifies repeated?: #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 the help subcommand.

<subcommand> Open Abstract Class#

A named subcommand. Subcommands have their own set of command-line options. They may not contain other subcommands.

Superclasses:

<command>

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:

<command>

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 the help subcommand itself. However, if you wanted to implement the help 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 or help subcommand have completed, so programs should always handle this at top level.

Superclasses:

<command-line-parser-error>

Init-Keywords:
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:
Values:
<usage-error> Open Class#

Signaled when a command-line cannot be parsed.

Superclasses:

<abort-command-error>

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:
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-effects parser accordingly.

Signature:

parse-command-line (parser argv) => ()

Parameters:
Discussion:

By default, the --help flag and the “help” subcommand are handled automatically by calling print-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 the parser 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:
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 invokes execute-subcommand on it. The help 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:
Discussion:

This is the implementation of your subcommand. Read command-line options from subcommand and read global options (if any) from parser.

print-synopsis Open Generic function#

Display a synopsis of the command line options.

Signature:

print-synopsis (parser subcommand #key stream) => ()

Parameters:
Discussion:

It is not normally necessary to call this function since the --help option and help 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:
Values:
Discussion:

Returns #t if this option was supplied on the command line. Returns #f if called before parse-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:
Values:
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 on parse-option-value.

get-option-value Function#

Retrieves an option from a <command> by name.

Signature:

get-option-value (parser name) => (value)

Parameters:
Values:
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:
  • argument – An instance of <string>.

  • type – An instance of <type>.

Values:
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’s option-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:

<object>

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:

    1. ”%default%” => The string representation of the default value for the option.

    2. ”%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:

<option>

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:

<option>

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:

<option>

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:

<option>

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:

<parameter-option>

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 the test: 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 is string-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:

<option>

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:

<option>

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 passing repeated?: #t when calling make. 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:

<flag-option>

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.