A production-grade, high-performance command-line argument parsing library for Zig, inspired by Python's argparse with a clean, intuitive, and developer-friendly API.
Note
args.zig is a relatively new project, but it is designed and tested with production use in mind. The API is intended to be stable, and the library focuses on performance, correctness, and real-world CLI needs.
⭐ If you love args.zig, make sure to give it a star!
- Fast & Zero Allocations - Minimal memory footprint with efficient parsing
- Intuitive API - Python argparse-inspired fluent interface
- Auto-Generated Help - Formatted help text for better understanding out of the box
- Shell Completions - Generate completions for Bash, Zsh, Fish, PowerShell, Nushell
- Environment Variables - Fallback to env vars for configuration
- Subcommands - Full support for Git-style subcommands
- Declarative Structs - Parse directly into Zig structs with
parseInto - Colored Output - ANSI color support for beautiful terminal output
- Update Checker - Automatic non-blocking update notifications (enabled by default)
- Comprehensive Validation - Type checking, choices, and custom validators for complex parsing
- Negated Long Flags - Familiar
--no-flagsupport for boolean toggles - Configurable Matching - Optional case-insensitive matching for long options and choices
- Inverse Flags API -
addFalseFlaghelper for explicit disable-style options - Positional Validation -
choices,expect, validators, and hidden positional support - CMD Selection Helpers - Built-in
--selectand--allhelper APIs - Question Flow Selection - Prompt users to choose select/all when flags are omitted
- Include/Exclude Filters - Reusable
--includeand--excludehelpers for CMD workflows - Strict Filter Resolution - Canonicalize choices, dedupe values, and detect include/exclude conflicts
- File & Extension Support - Reusable helpers for file paths, directories, and allowed extensions
- Typed Input Validators - Built-in validators for email, URL, IPv4, hostname/port endpoints, UUID, ISO dates, year/time, JSON payloads, and absolute paths
- CSV Select/All Resolution - Resolve
--select users,groupsand--allinto normalized target sets - Well Tested - Extensive test coverage across all modules
Install the latest stable release (v0.0.4):
zig fetch --save https://github.com/muhammad-fiaz/args.zig/archive/refs/tags/0.0.4.tar.gzInstall the previous stable release (v0.0.3):
zig fetch --save https://github.com/muhammad-fiaz/args.zig/archive/refs/tags/0.0.3.tar.gzInstall the latest development version:
zig fetch --save git+https://github.com/muhammad-fiaz/args.zigThen add it to your build.zig:
const args_dep = b.dependency("args", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("args", args_dep.module("args"));const std = @import("std");
const args = @import("args");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Create argument parser
var parser = try args.ArgumentParser.init(allocator, .{
.name = "myapp",
.version = "1.0.0",
.description = "A sample application built with args.zig",
});
defer parser.deinit();
// Add arguments
try parser.addFlag("verbose", .{
.short = 'v',
.help = "Enable verbose output",
});
try parser.addOption("output", .{
.short = 'o',
.help = "Output file path",
.default = "output.txt",
});
try parser.addPositional("input", .{
.help = "Input file to process",
});
// Parse command-line arguments
var result = try parser.parseProcess();
defer result.deinit();
// Use parsed values
const verbose = result.getBool("verbose") orelse false;
const output = result.getString("output") orelse "output.txt";
const input = result.getString("input") orelse "unknown";
if (verbose) {
std.debug.print("Processing {s} -> {s}\n", .{ input, output });
}
}Brief usage pattern:
var parser = try args.ArgumentParser.init(allocator, .{ .name = "app" });
defer parser.deinit();
try parser.addFlag("verbose", .{ .short = 'v' });
try parser.addOption("output", .{ .short = 'o' });
var result = try parser.parseProcess();
defer result.deinit();// Boolean flag
try parser.addFlag("verbose", .{ .short = 'v', .help = "Verbose mode" });
// String option
try parser.addOption("config", .{ .short = 'c', .help = "Config file" });
// Integer option
try parser.addOption("count", .{
.short = 'n',
.value_type = .int,
.default = "10",
});
// Choice option
try parser.addOption("format", .{
.short = 'f',
.choices = &[_][]const u8{ "json", "xml", "csv" },
});// -v, -vv, -vvv for increasing verbosity
try parser.addCounter("verbose", .{ .short = 'v' });
var result = try parser.parse(&[_][]const u8{ "-v", "-v", "-v" });
const verbosity = result.get("verbose").?.counter; // = 3try parser.addSubcommand(.{
.name = "clone",
.help = "Clone a repository",
.args = &[_]args.ArgSpec{
.{ .name = "url", .positional = true, .required = true },
.{ .name = "depth", .short = 'd', .long = "depth", .value_type = .int },
},
});
try parser.addSubcommand(.{
.name = "init",
.help = "Initialize a new repository",
});// Generate Bash completion script
const bash_script = try parser.generateCompletion(.bash);
std.debug.print("{s}", .{bash_script});
// Also supports: .zsh, .fish, .powershell, .nushelltry parser.addOption("token", .{
.help = "API token",
.env_var = "API_TOKEN", // Falls back to $API_TOKEN
});Use dedicated helpers for common API and configuration inputs:
try parser.addEmailOption("email", .{ .short = 'e', .required = true, .env_var = "APP_EMAIL" });
try parser.addUrlOption("endpoint", .{});
try parser.addIpv4Option("host", .{});
try parser.addIpOption("host-any", .{}); // IPv4 or IPv6
try parser.addIpv6Option("host-v6", .{});
try parser.addHostNameOption("hostname", .{});
try parser.addPortOption("port", .{});
try parser.addEndpointOption("service", .{}); // host:port
try parser.addKeyValueOption("label", .{}); // key=value
try parser.addUuidOption("request-id", .{});
try parser.addIsoDateOption("run-date", .{});
try parser.addIsoDateTimeOption("timestamp", .{});
try parser.addYearOption("year", .{});
try parser.addTimeOption("time", .{});
try parser.addAbsolutePathOption("workspace", .{});
try parser.addJsonOption("payload", .{});
try parser.addOption("retries", .{
.value_type = .int,
.validator = args.Validators.intRange(1, 10),
.default = "3",
});
try parser.addOption("peer", .{
.validator = args.Validators.anyIp,
});
var result = try parser.parseProcess();
defer result.deinit();
const email = result.getString("email") orelse "";
const service = result.getString("service") orelse "localhost:8080";
const retries = result.getInt("retries") orelse 3;
const label = result.getKeyValue("label");Long boolean flags support --no-<name> by default:
try parser.addFlag("cache", .{ .help = "Enable cache" });
var result = try parser.parse(&[_][]const u8{"--no-cache"});
defer result.deinit();
const cache_enabled = result.getBool("cache") orelse true; // falseUse addFalseFlag when your primary option semantics are "disable this behavior":
try parser.addFalseFlag("color", .{ .help = "Disable color output" });
var result = try parser.parse(&[_][]const u8{"--color"});
defer result.deinit();
const color_enabled = result.getBool("color") orelse true; // falseUse helpers to quickly model common command patterns:
try parser.addSelectOrAllCsv(.{
.select_short = 's',
.all_short = 'a',
});This creates an exclusive pair (--select <csv-list> vs --all).
Normalize selections into canonical values:
var resolved = try args.resolveSelectOrAllStrict(allocator, &result, .{
.choices = &[_][]const u8{ "users", "groups", "logs" },
.allow_prefix_match = true,
.dedupe = true,
});
defer resolved.deinit();Resolve selection from parsed args or ask the user when missing:
const decision = try args.resolveSelectOrAllWithPrompt(allocator, &parsed, .{
.question = "Select target",
.choices = &[_][]const u8{ "users", "groups", "logs" },
.default_choice = "users",
.allow_all = true,
});Use reusable helpers for filter-style commands:
try parser.addIncludeExclude(.{ .include_short = 'i', .exclude_short = 'x' });
var parsed = try parser.parseProcess();
defer parsed.deinit();
var filters = try args.resolveIncludeExclude(allocator, &parsed, "include", "exclude");
defer filters.deinit();For stricter behavior (choice normalization, deduplication, and conflict checks):
var strict_filters = try args.resolveIncludeExcludeStrict(allocator, &parsed, .{
.choices = &[_][]const u8{ "users", "groups", "logs" },
.all_keyword = "all",
});
defer strict_filters.deinit();Use dedicated helpers for path/file/directory workflows:
try parser.addFileOptionWithExtensions("input", &[_][]const u8{ "json", "yaml", "toml" }, .{
.short = 'i',
.must_exist = false,
});
try parser.addDirectoryOption("workspace", .{
.short = 'w',
.must_exist = false,
});
const output_name_validator = args.Validators.filePolicy(&[_][]const u8{"json"}, false, 3, 64);
try parser.addFileNameOption("output-name", .{
.short = 'o',
.validator = output_name_validator,
});You can still compose validators manually when needed:
const custom_validator = args.Validators.all(&[_]args.ValidatorFn{
args.Validators.fileName,
args.Validators.fileNameLength(3, 64),
});// Create a named group
try parser.addArgumentGroup("Server Options", .{
.description = "Configuration for the server",
});
// Arguments added after will belong to this group
try parser.addOption("host", .{ .help = "Bind address" });
try parser.addOption("port", .{ .value_type = .int, .help = "Port number" });
// Reset to default (ungrouped)
parser.setGroup(null);try parser.addArgumentGroup("Mode", .{
.exclusive = true,
.required = true, // User MUST choose exactly one
});
try parser.addFlag("interactive", .{ .short = 'i' });
try parser.addFlag("batch", .{ .short = 'b' });fn validateUser(val: []const u8) args.validation.ValidationResult {
if (val.len < 3) return .{ .err = "username too short" };
return .{ .ok = {} };
}
try parser.addOption("user", .{
.help = "Username",
.validator = validateUser,
});
// See examples/custom_parsing.zig for complex format validation
// e.g. --mode 1920x1080@60Hz
try parser.addOption("mode", .{
.help = "Display mode",
.validator = validateMode,
.metavar = "<W>x<H>[@<R>Hz]",
});You can define multiple names (aliases) for a single argument:
try parser.addArg(.{
.name = "verbose",
.long = "verbose",
.aliases = &[_][]const u8{ "v", "loud", "debug" },
.action = .store_true,
.help = "Enable verbose output",
});Trigger a function immediately when an argument is parsed:
fn onOutput(name: []const u8, value: ?[]const u8) void {
std.debug.print("Option {s} received value: {s}\n", .{name, value orelse "null"});
}
// ...
try parser.addArg(.{
.name = "output",
.long = "output",
.action = .callback,
.callback = onOutput,
});Define your CLI interface using a native Zig struct:
const Config = struct {
verbose: bool,
output: ?[]const u8,
count: i32,
};
// Parse directly into the struct
var parsed = try args.parseInto(allocator, Config, .{
.name = "myapp",
}, null);
defer parsed.deinit();
std.debug.print("Count: {d}\n", .{parsed.options.count});The update checker is enabled by default to keep you informed about new features and fixes. To disable it:
// Method 1: Global disable (Recommended)
args.disableUpdateCheck();
// Method 2: Per-parser configuration
var parser = try args.ArgumentParser.init(allocator, .{
.name = "myapp",
.config = .{ .check_for_updates = false },
});var parser = try args.ArgumentParser.init(allocator, .{
.name = "myapp",
.config = args.Config.minimal(), // No colors, no update check
});# Build library
zig build
# Run tests
zig build test
# Run all examples in one go
zig build run-all-examples
# Run examples
zig build run-basic
zig build run-advanced
zig build run-config_modes
zig build run-negated_flags
zig build run-positional_validation
zig build run-select_all
zig build run-question_flow
zig build run-include_exclude
zig build run-include_exclude_strict
zig build run-file_support
zig build run-data_input_validation
zig build run-network_endpoints
zig build run-error_handling
zig build run-subcommand_suggestions
zig build run-decryption_options
zig build run-update_check
# Run benchmarks
zig build bench
# Format code
zig build fmtUse the following commands to validate target coverage:
# Native target tests (runs tests)
zig build test
# Cross-target compile validation (builds all artifacts for each target)
zig build -Dtarget=x86_64-windows-gnu
zig build -Dtarget=x86_64-linux-gnu
zig build -Dtarget=aarch64-macos
# Targeted test invocations (must run on matching host/runner)
zig build test -Dtarget=x86_64-windows-gnu
zig build test -Dtarget=x86_64-linux-gnu
zig build test -Dtarget=aarch64-macosOn Windows hosts, Linux/macOS test binaries can be compiled but not executed directly. Run those test commands on Linux/macOS CI runners (or native machines) for full runtime verification.
Run benchmarks to see the performance:
zig build benchTypical results on modern hardware (10,000 iterations):
| Benchmark | Avg Time | Throughput |
|---|---|---|
| Simple Flags (3 flags) | ~33 μs | ~30,000 ops/sec |
| Multiple Options (3 options) | ~34 μs | ~29,200 ops/sec |
| Positional Arguments | ~24 μs | ~40,700 ops/sec |
| Counters (-vvv -dd) | ~24 μs | ~41,800 ops/sec |
| Subcommands (2 subcommands) | ~23 μs | ~43,500 ops/sec |
| Mixed Arguments (complex CLI) | ~40 μs | ~24,600 ops/sec |
| Argument Groups | ~23 μs | ~42,900 ops/sec |
| Callbacks | ~23 μs | ~42,400 ops/sec |
| Negated Flags | ~22 μs | ~45,000 ops/sec |
| Select/All Helpers | ~25 μs | ~39,500 ops/sec |
| Select/All CSV Strict Resolve | ~53 μs | ~18,800 ops/sec |
| Include/Exclude Strict Resolve | ~31 μs | ~31,800 ops/sec |
| Prompt Resolution (Parsed) | ~24 μs | ~41,600 ops/sec |
| Suggestion Lookup | ~2 μs | ~500,000 ops/sec |
| Subcommand Suggestion Lookup | ~2 μs | ~500,000 ops/sec |
| Help Text Generation | ~46 μs | ~21,500 ops/sec |
| Shell Completion Generation (Bash) | ~23 μs | ~43,300 ops/sec |
| Shell Completion Generation (Zsh) | ~24 μs | ~41,900 ops/sec |
| Declarative Structs | ~29 μs | ~34,600 ops/sec |
| Expect Validation | ~18 μs | ~56,400 ops/sec |
| File Extension Validation | ~21 μs | ~47,100 ops/sec |
| File Name Policy Validation | ~22 μs | ~46,200 ops/sec |
| Typed Input Validation | ~138 μs | ~7,300 ops/sec |
| Decryption Option (Base64) | ~30 μs | ~33,000 ops/sec |
Note
Results vary based on hardware and system load. Tested on Windows x86_64 with Zig 0.15.0. If you want the latest release benchmarks, you can find them on the repository releases.
Full documentation is available at muhammad-fiaz.github.io/args.zig.
Contributions are welcome! Please read our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See our Code of Conduct for community guidelines.
For security concerns, please see our Security Policy.
This project is licensed under the MIT License - see the LICENSE file for details.
If you find this project helpful, consider supporting it:
- Star this repository
- Report bugs and suggest features
- Sponsor on GitHub
- Buy me a coffee
