Module cli/arg_parser
Command-line argument parsing.
Supports flags (--verbose), options (--output file), short aliases (-v),
and positional arguments. Automatically generates help text.
Example
{ ArgParser } :: import "std/cli/arg_parser";
{ args } :: import "std/env";
parser := ArgParser.new(`myapp`, `A sample CLI tool`);
parser.add_flag(`--verbose`, `-v`, `Enable verbose output`);
parser.add_option(`--output`, `-o`, `Output file`, `out.txt`);
parsed := parser.parse(args()).unwrap();
verbose := parsed.get_flag(`--verbose`);
output := parsed.get_option(`--output`);
Stability
unstable — parse returns Result(ParsedArgs, String), and a
stringly-typed error is a D1 violation (plans/STD_API_STABILIZATION.md
§1 counts five of them; this one is not on that list, so it has not been
scheduled). Freezing the module would freeze the error channel, and with
it the thing that makes the channel wrong: --help is delivered as an
.Err whose payload is the help screen, so "the user asked for help" and
"the user made a mistake" are the same value and the same exit path. A
typed ArgError with a HelpRequested arm is what a caller needs, and it
cannot be added without changing parse's signature.
Two gaps sit behind that one: add_positional records a _required flag
nothing enforces, and the lexer has no -- end-of-options separator, so
no negative number or bare - can reach a positional slot. Both are
fixable additively; the error type is not. Freezing follows the typed
error, not a release count.
Types
Result of parsing command-line arguments.
Fields
| Name | Type | Description |
|---|---|---|
_set_flags | ArrayList(String) | |
_option_names | ArrayList(String) | |
_option_values | ArrayList(String) | |
_positionals | ArrayList(String) | |
_positional_names | ArrayList(String) | |
_subcommand | Option(String) | Name of the chosen subcommand ( |
_subcommand_args | Option(<struct:struct_decl_250177_file____home_runner_work_Yo_Yo_std_cli_arg_parser_yo>) | Parsed arguments for the chosen subcommand ( |
impl(ParsedArgs, ...)
new : (ParsedArgs) fn() -> ParsedArgsA parser with nothing registered — the starting point for the
add_* calls.
name and description are used only by help_text, where they
become the Usage: line and the paragraph under it. Neither is
matched against anything, so name need not be argv[0]; it is only
the name shown to the user.
Returns: ParsedArgs
get_subcommand : (ParsedArgs) fn(self : ParsedArgs) -> Option(String)get_subcommand_args : (ParsedArgs) fn(self : ParsedArgs) -> Option(<struct:struct_decl_250177_file____home_runner_work_Yo_Yo_std_cli_arg_parser_yo>)Returns the parsed arguments of the chosen subcommand, if any.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ParsedArgs |
Returns: Option(<struct:struct_decl_250177_file____home_runner_work_Yo_Yo_std_cli_arg_parser_yo>)
get_flag : (ParsedArgs) fn(self : ParsedArgs, name : String) -> boolWhether the flag registered under name appeared on the command
line.
name is the LONG name as registered, dashes included —
get_flag(--verbose), not `verbose`. Passing the short
alias answers false even when -v is what the user typed,
because parse records _long_name whichever spelling it matched.
There is no third answer: a flag that was never registered and a
registered flag the user omitted both give false. Rust's clap
separates those with ArgMatches::try_get_one, which errors on an
unknown id; this returns a bare bool, so a typo'd name reads as
"the user did not pass it".
Parameters
| Name | Type | Notes |
|---|---|---|
self | ParsedArgs | |
name | String |
Returns: bool
_lookup : (ParsedArgs) fn(self : ParsedArgs, names : ArrayList(String), values : ArrayList(String), name : String) -> Option(String)get_option : (ParsedArgs) fn(self : ParsedArgs, name : String) -> Option(String)The value given for the option registered under name, or its
registered default.
Keyed on the long name with its dashes (get_option(--output)),
same as get_flag. parse writes non-empty defaults into the
result after the scan, so a .Some here does not mean the user
typed it — and .None covers three cases a caller may want to tell
apart: the option was never registered, it was registered with an
EMPTY default and not passed, or the name is misspelled. O(n) in
the number of options seen; this is a linear scan of two parallel
lists, not a map.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ParsedArgs | |
name | String |
get_positional : (ParsedArgs) fn(self : ParsedArgs, name : String) -> Option(String)The positional argument registered under name (bare, no dashes —
get_positional(input)).
Positionals are matched to their registrations by ARRIVAL ORDER,
not by content: the first non-flag token that is not a subcommand
takes the name of the first add_positional, the second takes the
second, and so on. Tokens beyond the number registered are still
collected but get no name, so they are reachable only through
get_positional_at. O(n) in the positionals seen.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ParsedArgs | |
name | String |
get_positional_at : (ParsedArgs) fn(self : ParsedArgs, index : usize) -> Option(String)The index-th positional argument in command-line order, 0-based,
.None past the end.
This sees every positional, including the unregistered trailing
ones get_positional cannot name — it is the way to read a
variadic tail. Subcommand names are NOT in this list; they and
everything after them belong to get_subcommand_args.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ParsedArgs | |
index | usize |
Command-line argument parser.
Fields
| Name | Type | Description |
|---|---|---|
_name | String | Application name shown in help text. |
_description | String | Application description shown in help text. |
_args | ArrayList(ArgDef) | Registered argument definitions. |
_subcommand_names | ArrayList(String) | Subcommand names (parallel to |
_subcommand_descriptions | ArrayList(String) | Subcommand descriptions (parallel to |
_subcommand_parsers | ArrayList(<struct:struct_decl_250216_file____home_runner_work_Yo_Yo_std_cli_arg_parser_yo>) | Subcommand parsers (parallel to |
impl(ArgParser, ...)
new : (ArgParser) fn(name : String, description : String) -> ArgParserA parser with nothing registered — the starting point for the
add_* calls.
name and description are used only by help_text, where they
become the Usage: line and the paragraph under it. Neither is
matched against anything, so name need not be argv[0]; it is only
the name shown to the user.
Parameters
| Name | Type | Notes |
|---|---|---|
name | String | |
description | String |
Returns: ArgParser
add_subcommand : (ArgParser) fn(self : ArgParser, name : String, description : String) -> ArgParser_find_subcommand : (ArgParser) fn(self : ArgParser, name : String) -> Option(usize)add_flag : (ArgParser) fn(self : ArgParser, long_name : String, short_name : String, description : String) -> unitRegisters a boolean flag: present on the command line or not, and consuming no following argument.
Both names are matched VERBATIM, so they must be written with their
dashes — add_flag(--verbose, -v, …). short_name may be
empty to declare no alias, in which case help_text shows the long
name alone. --verbose=1 is not a spelling this parser knows; only
the exact token matches.
Returns unit and mutates the parser in place (ArgParser is a
ref struct), so the add_* calls are statements rather than a
chain — deliberately unlike clap's Command::arg, which returns
Self.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ArgParser | |
long_name | String | |
short_name | String | |
description | String |
Returns: unit
add_option : (ArgParser) fn(self : ArgParser, long_name : String, short_name : String, description : String, default_value : String) -> unitRegisters an option that takes its value from the NEXT argv token
(--output out.txt), matched verbatim on either name with its
dashes.
The value is taken positionally, not parsed out of the token, so
--output=out.txt is read as an unknown argument and
--output --verbose quietly takes --verbose as the value. If
there is no next token, parse fails with
Missing value for option: <long_name>.
A non-empty default_value is written into the result for any run
that did not pass the option, so get_option answers .Some
either way. An EMPTY default_value means "no default" — it is
never recorded, so an option defaulted to the empty string is
indistinguishable from one with no default at all.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ArgParser | |
long_name | String | |
short_name | String | |
description | String | |
default_value | String |
Returns: unit
add_positional : (ArgParser) fn(self : ArgParser, name : String, description : String) -> unitNames the next positional slot, so that the value arriving in it
can be read back as get_positional(name).
name is bare — no dashes — because it is a label for help text
and lookup, never a token matched on the command line. Slots are
filled in registration order from the non-flag tokens.
It does NOT make the argument mandatory, despite setting the
internal _required flag: nothing reads that flag, and parse
succeeds with the slot empty. Check for the .None from
get_positional yourself.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ArgParser | |
name | String | |
description | String |
Returns: unit
_find_arg_index : (ArgParser) fn(self : ArgParser, name : String) -> Option(usize)help_text : (ArgParser) fn(self : ArgParser) -> StringRenders the --help screen: a Usage: line, the description,
then Arguments:, Options: and Subcommands: sections, each
emitted only if it has entries.
Columns are separated by a literal TAB, not padded — alignment is left to whatever displays the text, which is why two descriptions of different name lengths will not line up in a pager that treats tabs as one space.
--help, -h is always listed even though it is not a registered
argument, because parse handles it itself. The Usage: line
carries the program name only — it does not synthesize a synopsis
of the positionals the way clap does.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ArgParser |
Returns: String
impl(ArgParser, ...)
parse : (ArgParser) fn(self : ArgParser, args : ArrayList(String)) -> Result(ParsedArgs, String)Scans args against the registered arguments and returns the
result. Synchronous and pure — it does no I/O, prints nothing and
never exits the process.
args is the WHOLE command line including the program name;
scanning starts at index 1, so passing a pre-stripped list silently
loses the first real argument.
--help comes back as an .Err
--help or -h anywhere on the line stops the scan and returns
.Err(help_text()). The help screen IS the error payload, so a
caller that prints the .Err and exits non-zero prints the right
text with the wrong exit status, and cannot tell a help request
from a usage mistake. Compare the .Err against help_text() if
that distinction matters. This is deliberate today only in the
sense that nothing better was written — see the module's Stability
note.
What else fails
- a token starting with
-that matches no registered name →Unknown argument: <token>. That includes tokens a caller may have meant as values: there is no--end-of-options separator, so a negative number or a bare-for stdin cannot be passed as a positional. - a registered option with nothing after it →
Missing value for option: <long_name>.
A missing positional is NOT a failure — see add_positional.
Subcommands consume the remainder
A non-flag token equal to a registered subcommand name hands
everything after it to that sub-parser (re-prepending the program
name, so the sub-parser's own index-1 skip lines up) and ends the
parent scan. Flags written after the subcommand name therefore
belong to the SUBCOMMAND, not the parent, and an .Err from the
sub-parser is returned unchanged — including its help text. The
match is tried on every non-flag token until one hits, not only the
first, so a positional value that happens to equal a subcommand
name is taken as the subcommand.
Option defaults are applied last, after the scan succeeds, and the parent's defaults are applied even when a subcommand ran.
Parameters
| Name | Type | Notes |
|---|---|---|
self | ArgParser | |
args | ArrayList(String) |
Returns: Result(ParsedArgs, String)