| .flox | chore: add flox environment | |
| src | feat: add dash-name subcommand | |
| .gitignore | feat: add argument parser | |
| build.zig | feat: add argument parser | |
| build.zig.zon | feat: add argument parser | |
| LICENSE | Initial commit | |
| README.md | chore: update README | |
argparser
A modified version of the argparser contained in the TigerBeetle repository.
Guidelines
Here are the (slightly modified) guidelines from Tigerbeetle's flags.zig module:
The purpose of
argparseris to define standard behavior for parsing CLI arguments and provide a specific parsing library, implementing this behavior.These are TigerBeetle CLI guidelines:
- The main principle is robustness --- make operator errors harder to make.
- For production usage, avoid defaults.
- Thoroughly validate options.
- In particular, check that no options are repeated.
- Use only long options (
--addresses).- Exception:
-h/--helpis allowed.- Use
--key=valuesyntax for an option with an argument. Don't use--key value, as that can be ambiguous (e.g.,--key --verbose).- Use subcommand syntax when appropriate.
- Use positional arguments when appropriate.
Design choices for this particular
argparserlibrary:
Be a 80% solution. Parsing arguments is a surprisingly vast topic: auto-generated help, bash completions, typo correction. Rather than providing a definitive solution, this is just one possible option. It is ok to re-implement arg parsing in a different way, as long as the CLI guidelines are observed.
No auto-generated help. Zig doesn't expose doc comments through
@typeInfo, so it's hard to implement auto-help nicely. Additionally, fully hand-crafted--helpmessage can be of higher quality.Concise DSL. Most cli parsing is done for ad-hoc tools like benchmarking, where the ability to quickly add a new argument is valuable. As this is a 80% solution, production code may use more verbose approach if it gives better UX.
Changes to Tigerbeetle's flags.zig
- Updated to be compatible with Zig 0.16.0.
- Errors are propagated upwards rather than exiting the process immediately.
- I changed this because it makes testing easier.
- Parameterized by a
std.Io.Writerfor each ofstdoutandstderr.- I also changed this to make testing easier.
- I also want to be able to capture and inspect output in tests of more than just the argument parser in some upcoming projects, so this is the first step towards that.
- Uses
ohsnapfor snapshot testing instead ofsnaptest.zigin the Tigerbeetle repository.ohsnaphas better support for pretty-printing arbitrary data structures.- I also didn't want to maintain ports of Tigerbeetle's
stdx.zig.
- Updated the function names to follow Zig naming conventions e.g. camel case for function names.
- Allow
-h/--helpon leaf-node subcommands.- Previously this was only allowed on subcommands that had also had subcommands as children.
- The restriction here is that
-h/--helpmust be the first argument.
Usage
There is an included demo CLI in main.zig. You can test it out via zig build run -- <args>.
If there's question as to whether something should work one way or another, consult the tests at the end of argparser.zig, they cover a ton of cases (thank you Tigerbeetle).
Most of this is spelled out in the Tigerbeetle guidelines above, but just to remind you:
- Only long flags are allowed.
- Flag arguments use
--flag=<arg>syntax, not--flag <arg>. - Prefer subcommands to flags.
- Subcommand names can't contain underscores (
_). - For subcommand names that contain dashes, use
@"my-cmd"as the enum tag. - Help text is written verbatim by the developer.
When parsing:
- Call
Args.parseto parse[]const []const u8. - Call
Args.parseZto parse[]const [*:0]const u8. - Errors are written to
stderranderror.CliParseErroris bubbled up. nullis returned when a user passes-h/--help.- You get your actual args type in all other cases.
For the lazy, this is what it looks like to define your arguments:
constCLI=union(enum){pubconsthelp=\\Usage:\\\\ demo [-h | --help]\\ demo subcmd\\ demo flags [--foo] [--bar=<u32>] [--baz=<str>]\\ demo positional <str> [<str>]\\ demo subcmd-varargs [--foo=<u32>] [-- <str> [<str>]]\\ demo dash-name\\\\Description:\\\\ A very simple CLI that demonstrates how to use this argument parser.\\\\Commands:\\\\ subcmd\\ A subcommand that takes no arguments.\\\\ flags\\ A subcommand with optional arguments.\\\\ positional\\ A subcommand that takes positional arguments.\\\\ varargs\\ A subcommand that takes both optional arguments\\ and trailing "variadic" arguments.\\\\ dash-name\\ A subcommand showing that it's possible for command names\\ to contain dashes.\\\\Options:\\\\ -h, --help\\ Print this help message and exit.\\;subcmd,flags:struct{pubconsthelp=\\Usage:\\\\ demo flags [--foo] [--bar=<u32>] [--baz=<str>]\\\\Description:\\\\ A subcommand that demonstrates how to use optional arguments.\\\\Options:\\\\ --foo [default: false]\\ An optional boolean flag.\\\\ --bar=<u32> [default: 0]\\ An optional flag that takes a single unsigned integer\\ as an argument.\\\\ --baz=<str> [default: null]\\ An optional flag that takes a string argument.\\;foo:bool=false,bar:u32=0,baz:?[]constu8=null,},positional:struct{pubconsthelp=\\Usage:\\\\ demo positional <str> [<str>]\\\\Description:\\\\ A subcommand that takes one required positional argument, and a\\ second optional positional argument. Prints the arguments that\\ are provided.\\;@"--":void,first:[]constu8,second:?[]constu8=null,},varargs:struct{pubconsthelp=\\Usage:\\\\ demo varargs [--flag=<str>] [-- <str> ...]\\\\Description:\\\\ A subcommand that takes an optional flag and optional trailing\\ "variadic" arguments like you would use to collect arguments to\\ another command to be invoked.\\\\Options:\\\\ --flag\\ An optional flag that takes a string argument. This solely\\ exists to show you that you can mix optional, positional,\\ and variadic arguments together in the same command.\\;flag:?[]constu8=null,@"--":void,// NOTE: This can't be optional, it will just be empty when no// trailing arguments are provided.trailing:[]const[]constu8,},@"dash-name",};