Implements #30677 (based roughly on the design outlined in this comment)
Implements #24510 (the second half of the original "juicy main" proposal)
This PR introduces APIs for parsing command-line arguments, situated underneath the new namespace std.cli.
std.cli.ArgsParser, automatic parsing into a second "juicy main" parameter
std.cli.ArgsParser is a minimal, opinionated, high-level API for parsing a process's arguments into a struct instance. While it exists as a regular API that can be called from code, in practice users will be more likely to use it indirectly by declaring a "juicy main" function with a second parameter.
Below is a simple example program that will automatically parse a command-line invocation like program_name https://server.example.com --poll-interval 30 --debug:
// User-defined structconstArgs=struct{server:[:0]constu8,@"--poll-interval":i32=60,@"--debug":bool=false,};pubfnmain(init:std.process.Init,args:Args)!void{// ...}Fields whose names begin with @"--" are considered to be options and receive their values from the command line via --name=value or --name value. All other fields are considered to be positional arguments and receive their values from the command line in the same order they are declared.
Supported field types are integers (e.g. i32), floats (e.g. f64), enums (e.g. enum { red, green, blue }) and strings ([:0]const u8, with or without a sentinel), as well as optionals (?T) and slices ([]T) of the aforementioned types. Option fields additionally support void and bool (and their optional equivalents ?void and ?bool).
The API automatically handles printing of usage error messages in response to incorrect usage. It also automatically handles the -h/--help option (and --version option, if applicable) by generating help text, which can be customized by declaring pub const @"--help": std.cli.Help(T):
constArgs=struct{op:enum{sum,min,max},first_value:i32,rest_values:[]consti32,@"--output":?[:0]constu8,@"--pretty":bool=true,@"--verbose":?void,pubconst@"--help":std.cli.Help(Args)=.{.command_name="calculatinator",.description="Calculates numerical properties of sequences of numbers.",.args=.{.op=.{.display="(sum|min|max)",.description="Numerical property of interest"},.first_value=.{.display="<value>...",.description="Integer values"},.rest_values=.{.hidden=true},.@"--output"=.{.display="<file>",.description="Output to a file instead of stdout"},.@"--pretty"=.{.description="Pretty-print output"},.@"--verbose"=.{.description="Output verbose diagnostics to stderr"},},};pubconst@"--version":std.SemanticVersion=.{.major=1,.minor=2,.patch=0};};pubfnmain(init:std.process.Init,args:Args)!void{// ...}Screenshot from 2026年05月13日 02-28-32
(Plaintext --help output)
--help output)Usage: calculatinator [<option>...] (sum|min|max) <value>...
Calculates numerical properties of sequences of numbers.
Arguments:
(sum|min|max) Numerical property of interest
<value>... Integer values
Options:
--output=<file> Output to a file instead of stdout
--[no-]pretty Pretty-print output
--verbose Output verbose diagnostics to stderr
-h, --help Print this help and exit
--version Print version and exit
std.cli.ArgsParser is intentionally designed to be simple and straight to the point, offloading the responsibility of parsing options and positional arguments on the command line into primitives, so that users can quickly get into action and focus on writing the code that makes their applications unique.
Extensibility or customizability are explicit non-goals. It is, for example, not possible customize the parser's behavior or parse custom types, and only long options names like --foo are supported, not short aliases like -f. The goal is to provide a simple and intuitive interface for receiving program arguments via the command line, which is something that many programs will eventually find themselves needing to do, even if they are not fully-fledged CLIs. Users developing more sophisticated CLIs will probably need to opt out of using this API and instead parse the process's arguments via other means. std.cli.ArgsTokenizer, described in more detail below, may assist with such endeavors.
std.cli.ArgsTokenizer (lower level getopt_long-style iterator API)
std.cli.ArgsTokenizer is a lower level API that tokenizes/splits an []const []const u8 argument vector into positional arguments and options based on a user-specified set of recognized options. It is roughly equivalent in functionality and behavior to getopt_long, the granddaddy of all parsers which established the de-facto command-line option parsing behavior.
Developers of advanced CLIs whose needs cannot be fulfilled by std.cli.ArgsParser are encouraged to give std.cli.ArgsTokenizer a try and see if it can serve as a foundational building block for their custom parsing approaches. It handles all standard forms of options and option-arguments (-fbar, -f bar, --foo=bar, --foo bar), stacked short options (-xyzfbar), the -- end-of-options delimiter and other de-facto conventions. It exposes two different variants of its functions, one for statically known sets of options and a second for runtime-known sets, and an important detail about its design is that the set of options can be swapped out mid-iteration for tasks like parsing subcommands.
testArgsTokenizer{constOption=union(enum){@"--amend":void,@"-m":[]constu8,@"--message":[]constu8,};varamend:bool=false;varmessage:?[]constu8=null;varpositionals:std.ArrayList([]constu8)=.empty;deferpositionals.deinit(std.testing.allocator);constargs:[]const[]constu8=&.{"git","commit","--amend","-mFix bugs"};vartokenizer:std.cli.ArgsTokenizer=.init(args);constprogram_name=tokenizer.nextPositional();while(tokenizer.next(Option))|token|switch(token){.option=>|option|switch(option){.@"--amend"=>{amend=true;},.@"-m",.@"--message"=>|arg|{message=arg;},},.end_of_options=>{},.positional=>|arg|{trypositionals.append(std.testing.allocator,arg);},.invalid_option=>|invalid|{returninvalid.err;},};trystd.testing.expectEqualStrings("git",program_nameorelse"");trystd.testing.expectEqual(1,positionals.items.len);trystd.testing.expectEqualStrings("commit",positionals.items[0]);trystd.testing.expectEqual(true,amend);trystd.testing.expectEqualStrings("Fix bugs",messageorelse"");}std.cli.ArgsTokenizer is used internally by std.cli.ArgsParser. I also believe that the tokenizer API is powerful/flexible enough that it could replace/enhance significant portions of the manual argument parsing that is performed inside the Zig compiler itself.
Pre-merge checklist
- Port all of the compiler's internal tools to these APIs (those underneath
tools/; not the compiler itself) - Properly document
std.cli.ArgsParserwith doc comments that clearly describe the parser's behavior and any relevant restrictions - Add more tests for
std.cli.ArgsParser, both in the form oftestdeclarations and standalone tests - Implement subcommand parsing (via
union(enum))
Breaking changes
- Relatively minor, but
std.fmt.parseInt()andstd.fmt.parseUnsigned()now reject inputs that contain consecutive underscores, e.g.1_2__3or0xffff__ff80. This is for consistency withstd.fmt.parseFloat(), and with Zig itself.
Follow-up tasks
Should this PR be merged, I also intend to open issues proposing the following changes to std:
- Move
std.Io.Terminaltostd.cli.Terminal - Add more styles (e.g. italic) to
std.cli.Terminal.Color - Enhance
std.cli.Terminalwith functionality for detecting the terminal's width and rendering text with word wrapping (intended for rendering help text)