cli
import cli from "cli";
import io from "io";
import os from "os";
pub fn main() -> i32 {
let spec: cli.Spec = cli.Spec {
name: "grep";
about: "Print lines that contain a pattern.";
opts: [
cli.Opt { long: "ignore-case"; short: "i"; help: "Match upper and lower case alike"; },
cli.Opt { long: "max"; short: "m"; help: "Stop after this many lines"; takes_value: true; },
];
positionals: ["pattern"];
rest: "files";
};
let args: []const []const u8 = os.arg_list();
let p: cli.Parsed = cli.parse(spec, args) or {
if (err == cli.CliError.HelpRequested) { io.print(cli.usage(spec)); return 0; }
io.eprintln("grep: {cli.message(spec, args)}");
return 2;
};
let pattern: []const u8 = p.positional(0)!;
let max: []const u8 = p.value_or("max", "100");
let ignore_case: bool = p.flag("ignore-case");
// ... search the files named by p.positional(1), p.positional(2), ...
return 0;
}You describe the options and arguments your program takes; cli reads a
command line against that description and you ask the result for what was
given. cli is pure: it prints nothing and never exits, so what happens on
--help or a mistake is up to you.
The spec
pub struct Opt {
long: []const u8; // "max" for --max
short: []const u8 = ""; // "m" for -m, or "" for none
help: []const u8 = ""; // one line for the usage text
takes_value: bool = false; // --max 5, --max=5, -m 5, -m5
}
pub struct Spec {
name: []const u8; // the program, for the usage text
about: []const u8 = ""; // a sentence under the usage line
opts: []const Opt = [];
positionals: []const []const u8 = []; // required arguments, in order
rest: []const u8 = ""; // name of any further arguments, or ""
}Leave out the fields you don’t need: every field but long and name
declares an empty default. Write the arrays right in the literal when you
build the spec in the function that parses (usually main): those arrays
live in that function, so don’t return a
spec or a Parsed built that way. To share a spec, put its options in a
const table, which lives as long as the program, and return a spec built
from it:
const OPTS: [2]cli.Opt = [
cli.Opt { long: "jobs"; short: "j"; help: "Run this many at once"; takes_value: true; },
cli.Opt { long: "dry-run"; short: "n"; help: "Only print"; },
];
fn spec() -> cli.Spec {
return cli.Spec { name: "make"; opts: OPTS[0..]; rest: "targets"; };
}A mistake in the spec itself (an empty or duplicate name, a short longer
than one letter or digit, an option called help) stops the program with a
trap: it’s a bug in the program, not something its user did.
Parsing
| Function | Returns |
|---|---|
parse(spec, args) -> Parsed or CliError | The parsed command line; args[0] (the program) is skipped |
What parse understands:
--max 5and--max=5;-m 5and-m5for an option that takes a value. A value is taken as it is, even-or-5.- Grouped short flags:
-ivis-i -v;-im5is-i -m 5. --ends the options: everything after it is an argument. A lone-is an argument too.- Options anywhere: before, between or after the arguments, and as often as you like.
--help, and-hunless one of your options usesh, giveHelpRequested, even when the line has other mistakes. Printcli.usage(spec)then.
Errors:
CliError | When |
|---|---|
UnknownOption | An option not in the spec |
MissingValue | An option that takes a value is last |
UnexpectedValue | --flag=x for an option without a value |
UnexpectedArgument | More arguments than positionals and no rest |
MissingArgument | Fewer arguments than positionals names |
HelpRequested | --help or -h |
cli.message(spec, args) says what is wrong in words, for your error
report: unknown option '--frob', option '--max' needs a value,
missing argument <pattern>.
Asking for what was given
| Method | Returns |
|---|---|
flag(long) -> bool | Whether --long was given |
count(long) -> i64 | How many times (-vvv is 3) |
value(long) -> ?[]const u8 | Its value (the last one), or null |
value_or(long, default) -> []const u8 | Its value, or default |
values(long) -> ValueIter | Every value, in order: for (dir in p.values("include")) |
positional(i) -> ?[]const u8 | Argument i (the named ones, then the rest), or null |
positional_count() -> i64 | How many arguments there are |
Options are named by their long name without dashes. Asking for a name
the spec doesn’t have traps, so a typo shows up the first time the code
runs. Values are text: turn them into numbers with
conv:
let n: i64 = conv.parse_i64(p.value_or("max", "100")) or {
io.eprintln("grep: --max: {err}");
return 2;
};Nothing allocates: values and arguments are views of args.
Usage text
cli.usage(spec) returns the help text:
Usage: grep [options] <pattern> [<files>...]
Print lines that contain a pattern.
Options:
-i, --ignore-case Match upper and lower case alike
-m, --max <max> Stop after this many lines
-h, --help Print this helpSubcommands
cli has no subcommands of its own. Look at the first argument yourself
and parse the rest with that command’s spec. parse skips the first
element of what you pass, so args[1..] puts the command in the program’s
place:
let args: []const []const u8 = os.arg_list();
if (args.len > 1 && bytes.eq(args[1], "commit")) {
let p: cli.Parsed = cli.parse(commit_spec, args[1..]) or { ... };
}Last modified: