Command-line flags for Zig, the getopt_long way: -xvf, --out=file, and a while loop.
Every command-line tool ends up with the same loop over argv. Writing it isn't hard. It's tedious, and the edges are where it goes wrong: -xvf is three flags, -ofile and -o file mean the same thing, --out=file splits on the first =, and after -- a token that starts with a dash is a path. cli handles those edges and hands you one token at a time. Which flags exist, what their values mean, and what the help text says stay in your code, where you can read them.
while (args.next()) |tok| switch (tok) {
.positional => |path| try paths.append(arena, path),
.option => |opt| {
if (opt.is("output", 'o')) {
output = args.takeValue(opt) orelse return 2;
} else if (opt.isSwitch("verbose", 'v')) {
verbose = true;
} else return 2;
},
};cli.Args reads argv one token at a time and never builds an argv slice. On POSIX it allocates nothing. It writes nothing either: help text and error messages belong to the command.
- The cursor:
Args.next()yields.positionalor.option, andtakeValuereads the value that goes with an option. - Grammar:
--name,--name=value,-abc,-ovalue, and-o value.-is positional. - Short clusters:
-lahand-vvvv, and a flag that takes the rest of its cluster as a value. - The separator: after
--, every token is positional, even-weird-name. - Subcommands: an enum and a switch.
cli.Argsdoes not see them. - Values: strings at the boundary, and
cli.parsefor a primitive or a type's ownparse. - Help and errors, input and output, tests, and patterns: the command writes the help text, the caller owns the writers,
fromSlicetests a parse function, and fixed arity, repeated flags, and counters are short loops.
Not implemented:
- Generated help, man pages, and shell completions: the command writes its own help text.
- A typed flag table or derive: a new flag is one more
ifin the loop. - Long-option abbreviations,
--no-negation, and a special-n=valueform.
zig fetch --save https://baskingspot.dev/cli/v0.3.1.tar.gzAdd the module to your build:
const cli = b.dependency("cli", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("cli", cli.module("cli"));Args.init is the allocation-free POSIX constructor. Args.initAllocator is the portable form for Windows and WASI. Both capture the first argv token as args.program_name.
cli builds with Zig 0.16 and 0.17.
- The guide says how the cursor works and how to build a command around it, with examples that compile.
- The API reference documents every symbol.
examples/has a kitchen-sink program and five focused ones:git,ls,tar,verbose, andcodec.
just cinix develop provides the toolchain. just lists the recipes, and just ci runs what CI runs.
MIT. See LICENSE. The logo files in docs/static/ are baskingspot marks and
are not covered by it. See docs/static/LICENSE.
Built with LLM assistance. How, in detail.