This has been a great project for getting familiar with Zig’s awesome comptime/meta-programming capabilities, and it’s gotten to a state where I think it could be useful for others .
There are already a few libraries that serve the same purpose [1] , so I’ll highlight some key differences:
- Zero allocations.
- Declaratively define your command as a plain Zig type.
- Single-function API.
It’s mainly inspired by tigerbeetle’s library which blew my mind when I saw it as it’s exactly the kind of design I wanted. I copied some code for that initially and it now has a wider range of features:
- Automatic help generation (at comptime) - customized with user-declared descriptions for each flag/command.
- Switch (shorthand) declarations.
- Multi-level subcommands.
- Developer-friendly compile errors if you’re Command type is invalid.
Here’s an example, you can find more here:
const std = @import("std");
const flags = @import("flags");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var args = try std.process.argsWithAllocator(gpa.allocator());
defer args.deinit();
const result = flags.parse(&args, Overview, .{});
try std.json.stringify(
result,
.{ .whitespace = .indent_2 },
std.io.getStdOut().writer(),
);
}
// The name of your type should match your executable name, e.g "my-program" -> "MyProgram".
// This can be overridden in the call to `flags.parse`.
const Overview = struct {
// Optional description of the program.
pub const description =
\\This is a dummy command for testing purposes.
\\There are a bunch of options for demonstration purposes.
;
// Optional description of some or all of the flags (must match field names in the struct).
pub const descriptions = .{
.force = "Use the force",
.optional = "You don't need this one",
.override = "You can change this if you want",
.required = "You have to set this!",
.age = "How old?",
.power = "How strong?",
.size = "How big?",
};
force: bool, // Set to `true` only if '--force' is passed.
optional: ?[]const u8, // Set to null if not passed.
override: []const u8 = "defaulty", // "defaulty" if not passed.
required: []const u8, // fatal error if not passed.
// Integer and float types are parsed automatically with specific error messages for bad input.
age: ?u8,
power: f32 = 9000,
// Restrict choice with enums:
size: enum {
small,
medium,
large,
// Displayed in the '--help' message.
pub const descriptions = .{
.small = "The least big",
.medium = "Not quite small, not quite big",
.large = "The biggest",
};
} = .medium,
// The 'positional' field is a special field that defines arguments that are not associated
// with any --flag. Hence the name "positional" arguments.
positional: struct {
first: []const u8,
second: u32,
// Optional positional arguments must come at the end.
third: ?u8,
pub const descriptions = .{
.first = "The first argument (required)",
.second = "The second argument (required)",
.third = "The third argument (optional)",
};
},
// Subcommands can be defined through the `command` field, which should be a union with struct
// fields which are defined the same way this struct is. Subcommands may be nested.
command: union(enum) {
frobnicate: struct {
pub const descriptions = .{
.level = "Frobnication level",
};
level: u8,
},
defrabulise: struct {
supercharge: bool,
},
pub const descriptions = .{
.frobnicate = "Frobnicate everywhere",
.defrabulise = "Defrabulise everyone",
};
},
// Optional declaration to define shorthands. These can be chained e.g '-fs large'.
pub const switches = .{
.force = 'f',
.age = 'a',
.power = 'p',
.size = 's',
};
};
Here’s the generated help message:
$ zig build run-example -- --help
Usage: overview [-f | --force] [--optional <optional>] [--override <override>]
--required <required> [-a | --age <age>] [-p | --power <power>]
[-s | --size <size>] <FIRST> <SECOND> [<THIRD>] <command>
This is a dummy command for testing purposes.
There are a bunch of options for demonstration purposes.
Options:
-f, --force Use the force
--optional You don't need this one
--override You can change this if you want
--required You have to set this!
-a, --age How old?
-p, --power How strong?
-s, --size How big?
small The least big
medium Not quite small, not quite big
large The biggest
-h, --help Show this help and exit
Arguments:
<FIRST> The first argument (required)
<SECOND> The second argument (required)
<THIRD> The third argument (optional)
Commands:
frobnicate Frobnicate everywhere
defrabulise Defrabulise everyone
mainly ‘zig-clap’, ‘yazap’, and ‘cova’. ↩︎