arguments guide

← arguments · API reference · source

arguments is GNU-style command-line parsing: you declare what your program accepts, hand it argv, and then ask. It understands --name value, --name=value, -n value, glued -nvalue, bundled -abc, unambiguous prefix abbreviation (--verb for --verbose), -- to end option parsing, and options interspersed with positionals.

It is not a subcommand framework — there is no git commit-shaped dispatch — and it does not read the environment, a config file or a completion script. It parses one argv and answers questions about it.

Three steps, always in this order: declare, parse, query. Declaring after parsing is not an error and simply comes too late.

use arguments::*;
fn main() {
  a = arguments::create("greet", "1.2", "Greet somebody");
  a.flag("v", "verbose", "Say more");
  a.option("n", "name", "WHO", "Who to greet");
  ok = a.parse(["--name", "Ada", "-v", "extra.txt"]);
  println("parsed  : {ok}");
  println("name    : {a.get(\"name\")}");
  println("verbose : {a.has(\"verbose\")}");
  println("left    : {a.positionals[0]}");
  assert(ok, "the line parses");
  assert(a.get("name") == "Ada", "a value option carries its value");
  assert(a.has("verbose"), "a flag is present or it is not");
  assert(a.positionals[0] == "extra.txt", "anything that is not an option is a positional");

Every section below is a function, and main calls each one. A section main does not call still COMPILES — so a renamed API is caught, and the assertions that check the example is RIGHT never run.

  help_is_yours_to_print();
  the_four_kinds_of_option();
  a_failed_parse_says_why();
  what_the_syntax_accepts();
}

That prints:

  parsed  : true
  name    : Ada
  verbose : true
  left    : extra.txt

The one that bites: enable_help() prints nothing

enable_help() registers --help / -h and --version / -V so they parse instead of being rejected as unknown. It does not print anything, and it does not exit. So a program that calls it and stops there answers --help with silence, and reads as broken.

After parse, ASK — wants_help() and wants_version() — and render help() or version_text() yourself. That is deliberate: where the text goes, whether it pages, and what exit code follows are the program's decisions, not the parser's.

A --help line also short-circuits the required-option check, so prog --help works on a program whose options are all mandatory — which is what a user asking for help needs.

fn help_is_yours_to_print() {
  a = arguments::create("greet", "1.2", "Greet somebody");
  a.enable_help();
  a.parse(["--help"]);
  assert(a.wants_help(), "the flag parsed, and the ANSWER is a question you ask");
  assert(a.help().contains("Usage: greet"), "help() is text — you decide where it goes");
  assert(a.version_text() == "greet 1.2", "and so is the version line");

The short-circuit: a required option is not missed when help was asked for.

  b = arguments::create("greet", "1.2", "Greet somebody");
  b.required("n", "name", "WHO", "Who to greet");
  b.enable_help();
  assert(b.parse(["--help"]), "--help parses even though --name is required");
  println("help    : registered, never printed — you print it");
}

The four kinds, and the one that surprises

- `flag(short, long, desc)` — present or absent, never takes a value
- `option(short, long, ARG, desc)` — always takes a value
- `required(short, long, ARG, desc)` — the same, and `parse` FAILS without it
- `optional(short, long, ARG, desc)` — takes a value only when it is GLUED

That last rule is the one to read twice. --color=auto and -cauto carry a value; bare `--color` is present with an EMPTY value and does not eat the next word. It is not an error either, so a program that expects --color auto to work gets an empty colour and an unexpected positional, with nothing reported.

fn the_four_kinds_of_option() {
  a = arguments::create("ls", "1.0", "List files");
  a.optional("c", "color", "WHEN", "Colorize the output");
  assert(a.parse(["--color", "file.txt"]), "bare optional parses — it is not an error");
  assert(a.has("color"), "…and is present");
  assert(a.get("color") == "", "…with an empty value");
  assert(a.positionals[0] == "file.txt", "…and the next word stays a positional");
  b = arguments::create("ls", "1.0", "List files");
  b.optional("c", "color", "WHEN", "Colorize the output");
  assert(b.parse(["--color=always"]), "glued parses");
  assert(b.get("color") == "always", "…and carries the value");
  println("kinds   : optional takes a value only when GLUED");
}

A failed parse says why, and get tells absent from empty

parse answers a boolean. On false, error_msg() is the sentence to show — it names the option and what was wrong with it. ok() is the same fact asked the other way.

For reading values: get answers text?, so a missing option is null and an option given an empty value is "" — two different things the query keeps apart. get_or folds them into one when you have a default, and get_int parses.

fn a_failed_parse_says_why() {
  a = arguments::create("greet", "1.2", "Greet somebody");
  a.required("n", "name", "WHO", "Who to greet");
  assert(!a.parse([]), "a missing required option fails the parse");
  assert(!a.ok(), "…and ok() agrees");
  assert(a.error_msg() == "option '--name' is required", "…with the sentence to show");
  b = arguments::create("greet", "1.2", "Greet somebody");
  b.option("n", "name", "WHO", "Who to greet");
  assert(b.parse([]), "an ordinary option may be absent");
  assert(b.get("name") == null, "absent is null…");
  assert(b.get_or("name", "world") == "world", "…which is what get_or is for");
  println("errors  : parse answers false, error_msg says which option");
}

What the syntax accepts

Prefix abbreviation resolves --verb to --verbose while it is unambiguous, and reports an error when two declared options share the prefix. Everything after -- is a positional whatever it looks like, and a lone - is a positional too — the stdin convention.

fn what_the_syntax_accepts() {
  a = arguments::create("greet", "1.2", "Greet somebody");
  a.flag("v", "verbose", "Say more");
  assert(a.parse(["--verb"]), "an unambiguous prefix parses");
  assert(a.has("verbose"), "…as the option it abbreviates");
  b = arguments::create("greet", "1.2", "Greet somebody");
  b.flag("v", "verbose", "Say more");
  assert(b.parse(["--", "--verbose"]), "-- ends option parsing");
  assert(!b.has("verbose"), "…so this one is not the flag");
  assert(b.positionals[0] == "--verbose", "…it is a positional that looks like a flag");
  println("syntax  : --verb abbreviates, -- ends the options");
}

Where to go next