Command-line options
The cli module parses a program’s command line. Declare each option and
positional argument on a cli.Parser, call parse, then read the values
through the handles the declarations returned, or declare them all at once
as the fields of a struct. The help, --version and
the error messages all come from the declarations. Bring it in with
import cli; the full API is in the reference.
# Command-line parsing: declare options and arguments, then parse.
import cli;
main(args: String[]): int {
var parser = cli.Parser("greet", "Greets someone", version = "1.0");
var greeting = parser.option("greeting", "g", "what to say", default_value = "hello");
var loud = parser.flag("loud", "l", "shout it");
var times = parser.option<int>("times", "n", "how many times to say it", value_name = "COUNT", default_value = "1");
var names = parser.positionals("names", "who to greet");
# `parse` prints --help and --version and exits; on a mistake it writes
# the error and the usage to standard error and returns false.
if (!parser.parse(args)) {
return 2;
}
var people = names.is_set ? names.values() : ["world"];
for (var person in people) {
var line = $"{greeting.value()}, {person}";
for (var _ in 0..times.value()) {
print(loud.is_set ? line.upper() : line);
}
}
return 0;
}
greet --help prints:
Greets someone
usage: greet [OPTIONS] [names]...
Arguments:
<names>... who to greet
Options:
-g, --greeting <GREETING> what to say [default: hello]
-l, --loud shout it
-n, --times <COUNT> how many times to say it [default: 1]
-h, --help show this help
-V, --version show the version
The raw arguments stay where they were: main(args: String[]) receives
them, and process.arguments() returns them. parse takes them as main
receives them, program name first.
Declaring
| Declaration | Handle | Reads |
|---|---|---|
flag(name, short_name, help, global) | Flag | is_set and occurrences (properties) |
option<T>(name, short_name, help, value_name, default_value, required, global, implicit_value) | Argument<T> | value(), value_or(fallback), is_set |
multiple<T>(name, short_name, help, value_name, required, global) | ArgumentList<T> | values(), is_set |
positional<T>(name, help, required, default_value) | Argument<T> | value(), value_or(fallback), is_set |
positionals<T>(name, help, required) | ArgumentList<T> | values(), is_set |
T is the type of the value (see Typed values); leave it
out for a String: option("output") is option<String>("output").
- An option’s
nameis its long name without--;short_nameis one character without-, or empty for none. One of the two may be empty:option("", "o")is an option spelled only-o, as a C compiler’s-I,-Dand-oare. Pass the rest by name:option("output", "o", "the executable to write", default_value = "a.out"). value()returns the value given, else the default; it panics when the argument was left out and has no default, so usevalue_oror checkis_setfor an optional one.is_set(a property, likeoccurrencesandis_selected) is false for a value that came from the default.- Positional arguments fill in declaration order. A required one cannot
follow an optional one, and
positionalstakes every value that is left, so it comes last. -h/--helpis always there, and-V/--versionwhen the parser has aversion. Declaring-hor-Vyourself takes the short name; the long forms keep working.- A mistake in the declarations, such as a name declared twice or a short name longer than one character, panics: it is a bug in the program, not in its input.
On the command line
- A value follows its option as
--output a.out,--output=a.out,-o a.out,-oa.outor-o=a.out. It is taken as it is, even when it starts with-. - Short flags combine:
-vqis-v -q, and-vo a.outgives-vand then-o a.out.-vvvcounts threeoccurrences. - Options and positional arguments mix in any order.
--ends the options, sorm -- -fileremoves a file named-file, and-alone is a positional argument (by custom, standard input). - An option that takes one value keeps the last one given.
- An option declared with an
implicit_valuetakes a value only when it is attached (--emit-ast=json,-O3); given alone, it has the implicit value, and the argument after it is not its value. The help shows it as--emit-ast[=<FORMAT>]or-O[<LEVEL>].
Typed values
A declaration with a type argument reads its values as that type, and a value that is not one fails the parse:
greet: error: invalid value 'x' for '--times': expected an integer
T | A value is | An error expects |
|---|---|---|
String | any text | |
int, long | an integer, as int.parse reads it | an integer |
uint, ulong | an integer that is not negative | a non-negative integer |
byte, short, ushort | an integer in the type’s range | an integer from 0 to 255 |
float, double | a number, as double.parse reads it | a number |
io.Path | any text, as a path | |
| an enum whose cases carry no values | a case’s name in kebab case: DebugInfo is debug-info | 'fast' or 'debug-info' |
- An enum’s help lists its cases:
[possible values: fast, debug-info]. default_valueis written as on the command line, so the help shows it as it is:option<int>("jobs", default_value = "4"). A default that is not a value of the type panics.- Any other type is a compile error at the call.
Errors
parse writes a clang-style error, the usage and a hint to standard error
and returns false, so the program can exit with 2:
greet: error: unknown option '--lound'; did you mean '--loud'?
usage: greet [OPTIONS] [names]...
Run 'greet --help' for more information.
The messages:
unknown option '--x'(withdid you mean '--y'?for a near miss) andunknown option '-x'missing value for '-o'option '--loud' takes no value(for--loud=yes)invalid value 'x' for '--times': expected an integermissing required option '--target',missing required argument '<source>'unexpected argument 'x'when there is no positional argument left for itunknown command 'x'andmissing command: expected 'add' or 'update'
A mistake parse cannot see, such as two options that cannot be used
together, is reported the same way with usage_error, which returns 2:
if (test.is_set and library.is_set) {
return parser.usage_error("'--test' cannot be used with '--lib'");
}
try_parse does the same parse and writes nothing. It returns a
Result<String?>: an error holding the message, some text when --help or
--version asked for it (print it and exit with 0), or none when the
arguments were parsed. Tests use it, and so can a program that reports
errors its own way.
Declaring options as a struct
add_options declares an option or positional argument for each public
field of a struct, and every successful parse stores the values in that
struct’s fields. The attributes [cli.Option] and [cli.Positional] on
the fields carry what the builder calls take as arguments:
# Command-line parsing: declare the options as a struct's fields.
import cli;
struct Options {
[cli.Option("g", "what to say")]
var greeting: String = "hello";
[cli.Option("l", "shout it")]
var loud: bool;
[cli.Option("n", "how many times to say it", value_name = "COUNT")]
var times: int = 1;
[cli.Positional("who to greet")]
var names: String[];
}
main(args: String[]): int {
var options = Options();
var parser = cli.Parser("greet", "Greets someone", version = "1.0");
parser.add_options(options);
if (!parser.parse(args)) {
return 2;
}
var people = options.names.is_empty ? ["world"] : options.names;
for (var person in people) {
var line = $"{options.greeting}, {person}";
for (var _ in 0..options.times) {
print(options.loud ? line.upper() : line);
}
}
return 0;
}
Its help and errors are the same as the builder example’s above: the declarations are the same ones.
| Field type | Declares | After a parse |
|---|---|---|
bool | flag | whether it was given |
T | option<T> | the value given, else the field’s starting value |
T? | option<T> | the value given, or Empty |
T[] | multiple<T> | the values given, in order |
Tis any type of Typed values:String, a number type,io.Pathor an enum. A field’s starting value is the default the help shows, except an emptyStringor a nullio.Path, which means none.- An option is named after its field, with each
_written-:import_pathis--import-path. Any other field type is a compile error at the call:'add_options' cannot declare field 'table' of type 'Map<String, int>'; use bool, String, a number type, io.Path or an enum, or one of those as T? or T[]. [cli.Option(short_name, help, name, value_name, required, global)]sets the rest, every argument optional:[cli.Option("o", "the executable to write")],[cli.Option(required = true)].[cli.Positional(help, name, required)]makes the field the next positional argument. ATone is required when it has no default orrequiredis set; aT?one may be left out; aT[]one takes every value left (positionals), andrequiredasks for at least one.- Each parse sets every field again, so a flag left out is false even if the field started true.
- A parse that fails, or that asks for
--helpor--version, leaves the fields as they were. - The two styles mix: call
flagandoptionon the same parser, andadd_optionson a command’s parser for that command’s options. A command’s struct is filled only when that command is given.
Subcommands
command(name, about) declares a subcommand, as in git add or
pep-package update, and returns its own Parser to declare its options
and arguments on. The first argument that is not an option names the
command; after parse, is_selected tells which command was given.
# Subcommands, and handling the outcome yourself with try_parse.
import cli;
main(): int {
var parser = cli.Parser("pkg", "Manages packages");
var verbose = parser.flag("verbose", "v", "say more");
var add = parser.command("add", "Adds a dependency");
var name = add.positional("name", "the package to add");
var dev = add.flag("dev", "d", "only for development");
var update = parser.command("update", "Updates dependencies");
var packages = update.positionals("packages", "what to update; everything when none");
# The arguments as `main(args: String[])` would receive them.
var parsed = parser.try_parse(["pkg", "-v", "add", "--dev", "yaml"]);
assert(parsed.is_ok() && parsed.value().is_empty(), "parsed, no help asked for");
assert(add.is_selected && !update.is_selected, "the add command");
assert(verbose.is_set && dev.is_set && name.value() == "yaml", "its values");
# A mistake is an error with a clang-style message.
parsed = parser.try_parse(["pkg", "updat"]);
assert(parsed.error().message == "unknown command 'updat'; did you mean 'update'?", "a suggestion");
# --help gives the text to print, for the command it follows.
parsed = parser.try_parse(["pkg", "update", "--help"]);
print(parsed.value().value());
return 0;
}
- A parser with commands needs one, and cannot have positional arguments of its own.
- Options before the command’s name belong to the parent, and everything
after it to the command, so
pkg -v add xworks andpkg add -v xdoes not, unless-vis global. - A global option (
flag("verbose", "v", global = true), and the same argument onoptionandmultiple) may also be given after the name of any command below its parser, at any depth, and each command’s help lists it. A command cannot declare an option with a global option’s name, and a required global option may be given anywhere on the line. --helpafter a command shows that command’s help, and errors show its usage.- Commands nest: a command’s parser can declare commands too.
Shell completions
completions(shell) returns a script that completes the program’s
command line in Bash, Zsh, fish or PowerShell (cli.Shell), from the
declarations: options (with the global ones in each command), commands,
an enum’s cases, and file names for a value that is a String or an
io.Path. A program prints it on request, often from a command of its
own, as pkg completions fish does here:
# Shell completion scripts, printed by a command of the program's own.
import cli;
main(): int {
var parser = cli.Parser("pkg", "Manages packages");
parser.flag("verbose", "v", "say more", global = true);
var add = parser.command("add", "Adds a dependency");
add.positional("name", "the package");
var completions = parser.command("completions", "Prints a shell completion script");
var shell = completions.positional<cli.Shell>("shell", "bash, zsh, fish or powershell");
# The arguments as `main(args: String[])` would receive them.
var parsed = parser.try_parse(["pkg", "completions", "fish"]);
assert(parsed.is_ok() && completions.is_selected, "the completions command");
print(parser.completions(shell.value()));
return 0;
}
The user installs what it prints where the shell looks for it:
| Shell | Install the script as |
|---|---|
bash | ~/.local/share/bash-completion/completions/pkg, or source it from ~/.bashrc |
zsh | _pkg in a directory on $fpath |
fish | ~/.config/fish/completions/pkg.fish |
powershell | dot-sourced from $PROFILE |
Not yet
- Windows-style
/optionforms: every platform uses-and--.