zig argparse
Easy declarative argument parsing in zig
const parseArgs = @import("argparse.zig").parseArgs;
// All you need to provide is a type defining what arguments you expect / support.
// This struct is the result of parsing, so you can just use these values in your code
const MyArgs = struct {
// Any field of this struct is an argument. Optional args will be `null` if not provided by the user.
name: ?[]const u8,
// `--foo` sets this to `true`, otherwise it's set to `false`
foo: ?bool,
// required because type is not optional, parsing will fail if not provided.
// This gets parsed as a number, with bounds checks.
age: u32,
};
pub fn main() void {
const parsed = parseArgs(MyArgs) catch return;
std.debug.warn("{}\n", parsed.age);
if (parsed.name) |name| {
std.debug.warn("{}\n", name);
}
}Note that you can catch errors instead of just returning. You could return -1 for example.
If you want to support subcommands or otherwise preprocess arguments, you could use parseArgsList and provide a slice of strings.
Use parseArgsOpt / parseArgsListOpt if you want to provide options like a custom allocator.
Features
Parse with = or additional arguments
./a --foo 123 is the same as ./a --foo=123
Number bounds checks
Since numbers are parsed with zig's std lib, they get bounds checked
const MyArgs = struct {
age: ?u32,
small: ?i8,
};
// ./a --number -42
// Expected u32 for 'age', found '-42'
// ./a --small 10000
// Expected i8 for 'small', found '10000'Float parsing when expected
If you declare your arg as an int, it won't validate floats.
If you declare your arg as a float, it will allow floats or integers.
const MyArgs = struct {
height: ?f32,
age: ?u32,
};
// ./a --height 1.85
// ./a --age 1.85 Expected u32 for 'age', found '1.85' Array types
const MyArgs = struct {
resolution: [2]u32,
};
// ./a --resolution 1920 1080Passing more or fewer values is an error, and values are typechecked.
Slice types
Slices are allocated for you and consume any number of arguments until an invalid one is found. Allocated using options.allocator.
const MyArgs = struct {
names: [][]const u8,
values: []u32,
};
// ./a --names Alice Bob Carol --values 1 100Future...
Default values
If zig issue #2937 is done,
arguments can provide default values as part of the struct!
const MyArgs = struct {
foo: i32 = 20,
};
// foo is 20 if not provided, instead of null. Type doesn't need to be optional if a default is given.Unimplemented Workaround idea: provide a constant declaration value
that matches argument name
const MyArgs = struct {
foo: i32,
const foo__DEFAULT = 33;
};Documentation
If zig issue #2573 is done,
doc strings can be shown when --help is used!
const MyArgs = struct {
/// This comment would show up if on `--help` or when used incorrectly
foo: i32 = 20,
};Unimplemented Workaround idea: provide a constant declaration string
that matches argument name
const MyArgs = struct {
foo: i32,
const foo__DOC = "This string would show in `--help`";
};Collect extra arguments
(TODO)
Any arguments not starting with -- are errors right now, but these could be accumulated and put somewhere. There could be a special signal value:
const MyArgs = struct {
foo: bool,
files: ExtraArguments,
};
// ./a file1.txt file2.txtWhere ExtraArguments would be a type provided by the library, which is basically just an array of strings in some form.
Positional arguments
(TODO)
Add a syntax to say that an argument is not specified by name, but is instead positional. A type wrapper seems like a nice way to do this.
const MyArgs = struct {
age: Positional(u32),
height: Positional(f32),
foo: bool,
};
// ./a file1.txt file2.txtAliases
(TODO)
Add a syntax to say that an argument is an alias for another one, so you can provide duplicate functionality without thinking about it in your usage code. This would also let you define "short" versions of commands.
This could be done as a zero-bit type wrapper or a static constant so that there is a canonical value to use in the usage code.
const MyArgs = struct {
file: []const u8,
const fileName = Alias("file");
const f = Alias("file");
};
// All these are equivalent:
// ./a --file foo.txt
// ./a --fileName foo.txt
// ./a -f foo.txtAdd more options
Add some user parameters.
- Should
--be allowed at the start of value names? - Should
-argument prefixes be treated like--(Windows often uses-, Linux/macOS--) - What function should be used for outputing information? (default:
std.debug.warn) - What function/extra context should be used when printing
usage?
Repeatable arguments
(TODO)
Add a syntax to say that an argument can be repeated multiple or unlimited times
const MyArgs = struct {
file: []const u8,
repeatedFile: RepeatedOnce("file"),
};
// All these are equivalent:
// ./a --file 1.txt
// ./a --file 1.txt --file 2.txt
// ./a --file 1.txt --file 2.txt --file 3.txt 'file' argument can only be provided twiceconst MyArgs = struct {
// Can be repeated any number of times,
// ends up being accessible as a slice.
id: Repeatable(u32),
};