Awesome
Yazap
[!NOTE] This branch targets the master branch of zig. See supported versions table.
The ultimate zig library for seamless command-line parsing. Effortlessly handles options, subcommands, and custom arguments with ease.
Inspired by clap-rs and andrewrk/ziglang: src-self-hosted/arg.zig
Supported versions table
yazap | Zig |
---|---|
main | master |
0.5.1 | 0.12.0 , 0.12.1 and 0.13.0 |
<= 0.5.0 | Not supported to any |
Key Features:
-
- Providing values with
=
, space, or no space (-f=value
,-f value
,-fvalue
). - Supports delimiter-separated values with
=
or without space (-f=v1,v2,v3
,-fv1:v2:v3
). - Chaining multiple short boolean options (
-abc
). - Providing values and delimiter-separated values for multiple chained options using
=
(-abc=val
,-abc=v1,v2,v3
). - Specifying an option multiple times (
-a 1 -a 2 -a 3
).
- Providing values with
-
- Supports positional arguments alongside options for more flexible command-line inputs. For example:
command <positional_arg>
command <arg1> <arg2> <arg3>
- Supports positional arguments alongside options for more flexible command-line inputs. For example:
-
- Organize commands with nested subcommands for a structured command-line interface. For example:
command subcommand
command subcommand subsubcommand
- Organize commands with nested subcommands for a structured command-line interface. For example:
-
Custom Argument definition:
- Define custom Argument types for specific application requirements.
Limitations:
- Does not support delimiter-separated values using space (
-f v1,v2,v3
). - Does not support providing value and delimiter-separated values for multiple
chained options using space (
-abc value, -abc v1,v2,v3
).
Installing
- Run the following command:
zig fetch --save git+https://github.com/prajwalch/yazap
- Add the following to
build.zig
:
const yazap = b.dependency("yazap", .{});
exe.root_module.addImport("yazap", yazap.module("yazap"));
Documentation
For detailed and comprehensive documentation, please visit this link.
[!WARNING] The documentation site is currently broken, in the meantime check out the source code.
Building and Running Examples
The examples can be found here. To build all of them, run the following command on your terminal:
$ zig build examples
After the compilation finishes, you can run each example by executing the corresponding binary:
$ ./zig-out/bin/example_name
To view the usage and available options for each example, you can use -h
or
--help
flag:
$ ./zig-out/bin/example_name --help
Usage
Initializing Yazap
To begin using yazap
, the first step is to create an instance of
App by calling
App.init(allocator, "Your app name", "optional description")
. This function
internally creates a root command for your application.
var app = App.init(allocator, "myls", "My custom ls");
defer app.deinit();
Obtaining the Root Command
The App itself does not provide any methods for adding arguments to your command. Its main purpose is to initialize the library, to invoke the parser with necessary arguments, and to deinitilize the library.
To add arguments and subcommands, acquire the root command by calling App.rootCommand()
.
This gives you access to the core command of your application by returning a pointer to it.
var myls = app.rootCommand();
Adding Arguments
Once you have obtained the root command, you can proceed to add arguments and
subcommands using the methods available in the Command
. For a complete list
of available methods, refer to the Command API
documentation.
try myls.addArg(Arg.positional("FILE", null, null));
try myls.addArg(Arg.booleanOption("all", 'a', "Don't ignore the hidden directories"));
try myls.addArg(Arg.booleanOption("recursive", 'R', "List subdirectories recursively"));
try myls.addArg(Arg.booleanOption("one-line", '1', null));
try myls.addArg(Arg.booleanOption("size", 's', null));
try myls.addArg(Arg.booleanOption("version", null, null));
try myls.addArg(Arg.singleValueOption("ignore", 'I', null));
try myls.addArg(Arg.singleValueOption("hide", null, null));
try myls.addArg(Arg.singleValueOptionWithValidValues(
"color",
'C',
"Colorize the output",
&[_][]const u8{ "always", "auto", "never" }
));
Alternatively, you can add multiple arguments in a single function call using
Command.addArgs()
:
try myls.addArgs(&[_]Arg {
Arg.positional("FILE", null, null),
Arg.booleanOption("all", 'a', "Don't ignore the hidden directories"),
Arg.booleanOption("recursive", 'R', "List subdirectories recursively"),
Arg.booleanOption("one-line", '1', null),
Arg.booleanOption("size", 's', null),
Arg.booleanOption("version", null, null),
Arg.singleValueOption("ignore", 'I', null),
Arg.singleValueOption("hide", null, null),
Arg.singleValueOptionWithValidValues(
"color",
'C',
"Colorize the output",
&[_][]const u8{ "always", "auto", "never" }
),
});
Note that for any option that accepts value, you can set its value placeholder to display in the help message. If you don't set the placeholder, the option name will be displayed by default.
var ignore_opt = Arg.singleValueOption("ignore", 'I', null);
ignore_opt.setValuePlaceholder("PATTERN");
var hide_opt = Arg.singleValueOption("hide", null, null);
hide_opt.setValuesPlaceholder("PATTERN");
var color_opt = Arg.singleValueOptionWithValidValues(
"color",
'C',
"Colorize the output",
&[_][]const u8{ "always", "auto", "never" }
);
color_opt.setValuePlaceholder("WHEN");
try myls.addArgs(&[_]Arg{ ignore_opt, hide_opt, color_opt });
Adding Subcommands
To create a subcommand, use App.createCommand("name", "optional description")
then
you can add its own arguments and subcommands just like the root command. After you
finish adding arguments, add it as a root subcommand by calling Command.addSubcommand()
.
var update_cmd = app.createCommand("update", "Update the app or check for new updates");
try update_cmd.addArg(Arg.booleanOption("check-only", null, "Only check for new update"));
try update_cmd.addArg(Arg.singleValueOptionWithValidValues(
"branch",
'b',
"Branch to update",
&[_][]const u8{ "stable", "nightly", "beta" }
));
try myls.addSubcommand(update_cmd);
Parsing Arguments
Once you have finished adding all the arguments and subcommands, call App.parseProcess()
to start parsing the arguments given to the current process. This function internally utilizes
std.process.argsAlloc
to obtain the raw arguments. Alternatively, you can use App.parseFrom()
and pass your own raw
arguments, which can be useful during testing. Both functions returns
ArgMatches
.
const matches = try app.parseProcess();
if (matches.containsArg("version")) {
log.info("v0.1.0", .{});
return;
}
if (matches.getSingleValue("FILE")) |f| {
log.info("List contents of {f}");
return;
}
if (matches.subcommandMatches("update")) |update_cmd_matches| {
if (update_cmd_matches.containsArg("check-only")) {
std.log.info("Check and report new update", .{});
return;
}
if (update_cmd_matches.getSingleValue("branch")) |branch| {
std.log.info("Branch to update: {s}", .{branch});
return;
}
return;
}
if (matches.containsArg("all")) {
log.info("show all", .{});
return;
}
if (matches.containsArg("recursive")) {
log.info("show recursive", .{});
return;
}
if (matches.getSingleValue("ignore")) |pattern| {
log.info("ignore pattern = {s}", .{pattern});
return;
}
if (matches.containsArg("color")) {
const when = matches.getSingleValue("color").?;
log.info("color={s}", .{when});
return;
}
Handling Help
-h
and --h
flag is globally available to all the commands and subcommands and
handled automatically when they are passed to command line. However, if you need to
manually display the help message there are currently two ways to do it.
1. By invoking App.displayHelp()
and App.displaySubcommandHelp()
.
App.displayHelp()
displays the help message for the root command and
other hand App.displaySubcommandHelp()
displays the help message for the
active subcommand.
For e.x.: if gh auth login
were passed then App.displayHelp()
would display the
help for gh
and App.displaySubcommandHelp()
display the help for login
.
Example:
if (!matches.containsArgs()) {
try app.displayHelp();
return;
}
if (matches.subcommandMatches("update")) |update_cmd_matches| {
if (!update_cmd_matches.containsArgs()) {
try app.displaySubcommandHelp();
return;
}
}
2. By setting .help_on_empty_args
property to the command.
The .help_on_empty_args
property which when set to a command, it instructs
the handler to display the help message for that particular command when arguments
are not provided. It behaves exactly like the code shown at the example above.
Example:
var app = App.init(allocator, "myls", "My custom ls");
defer app.deinit();
var myls = app.rootCommand();
myls.setProperty(.help_on_empty_args);
var update_cmd = app.createCommand("update", "Update the app or check for new updates");
update_cmd.setProperty(.help_on_empty_args);
try myls.addSubcommand(update_cmd);
const matches = try myls.parseProcess();
// --snip--
Putting it All Together
const std = @import("std");
const yazap = @import("yazap");
const allocator = std.heap.page_allocator;
const log = std.log;
const App = yazap.App;
const Arg = yazap.Arg;
pub fn main() anyerror!void {
var app = App.init(allocator, "myls", "My custom ls");
defer app.deinit();
var myls = app.rootCommand();
myls.setProperty(.help_on_empty_args);
try myls.addArgs(&[_]Arg {
Arg.positional("FILE", null, null),
Arg.booleanOption("all", 'a', "Don't ignore the hidden directories"),
Arg.booleanOption("recursive", 'R', "List subdirectories recursively"),
Arg.booleanOption("one-line", '1', null),
Arg.booleanOption("size", 's', null),
Arg.booleanOption("version", null, null),
});
var ignore_opt = Arg.singleValueOption("ignore", 'I', null);
ignore_opt.setValuePlaceholder("PATTERN");
var hide_opt = Arg.singleValueOption("hide", null, null);
hide_opt.setValuesPlaceholder("PATTERN");
var color_opt = Arg.singleValueOptionWithValidValues(
"color",
'C',
"Colorize the output",
&[_][]const u8{ "always", "auto", "never" }
);
color_opt.setValuePlaceholder("WHEN");
try myls.addArgs(&[_]Arg{ ignore_opt, hide_opt, color_opt });
// Update subcommand.
var update_cmd = app.createCommand("update", "Update the app or check for new updates");
update_cmd.setProperty(.help_on_empty_args);
try update_cmd.addArg(Arg.booleanOption("check-only", null, "Only check for new update"));
try update_cmd.addArg(Arg.singleValueOptionWithValidValues(
"branch",
'b',
"Branch to update",
&[_][]const u8{ "stable", "nightly", "beta" }
));
try myls.addSubcommand(update_cmd);
// Get the parse result.
const matches = try app.parseProcess();
if (matches.containsArg("version")) {
log.info("v0.1.0", .{});
return;
}
if (matches.getSingleValue("FILE")) |f| {
log.info("List contents of {f}");
return;
}
if (matches.subcommandMatches("update")) |update_cmd_matches| {
if (update_cmd_matches.containsArg("check-only")) {
std.log.info("Check and report new update", .{});
return;
}
if (update_cmd_matches.getSingleValue("branch")) |branch| {
std.log.info("Branch to update: {s}", .{branch});
return;
}
return;
}
if (matches.containsArg("all")) {
log.info("show all", .{});
return;
}
if (matches.containsArg("recursive")) {
log.info("show recursive", .{});
return;
}
if (matches.getSingleValue("ignore")) |pattern| {
log.info("ignore pattern = {s}", .{pattern});
return;
}
if (matches.containsArg("color")) {
const when = matches.getSingleValue("color").?;
log.info("color={s}", .{when});
return;
}
}
Alternate Parsers
- Hejsil/zig-clap - Simple command line argument parsing library
- winksaville/zig-parse-args - Parse command line arguments
- MasterQ32/zig-args - Simple-to-use argument parser with struct-based config