Skip to content

ChainSafe/zig-cli

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

zig-cli

Comptime CLI argument parser for Zig 0.16+.

Type-safe options generated at compile time — no runtime allocations for the result struct.

Features

  • Comptime result types — parsed struct fields match declared types exactly
  • Long and short options--port 8080 or -p 8080
  • Equals form--port=8080
  • Flags--verbose sets bool to true
  • Enum parsing--mode fast with compile-time variant validation
  • Optional types?[]const u8 defaults to null
  • Environment variable fallbackenv: "MY_PORT" checks env before defaulting
  • Subcommandsmyapp beacon, myapp validator with per-command option sets
  • RC config resolver hook — plug in YAML/JSON/TOML config via a simple callback
  • Option groups — organize options into named sections for help readability
  • "Did you mean?" — Levenshtein-based typo correction for unknown options/commands
  • Auto-generated help--help / -h prints formatted usage at comptime
  • Version flag--version / -V

Flags are presence-only. If you need explicit true / false parsing, use cli.option(bool, ...) instead of cli.flag(...).

Positional arguments are currently rejected unless used as the selected subcommand.

Usage

Simple (no subcommands)

const std = @import("std");
const cli = @import("zig-cli");

const spec = .{
    .name = "my-app",
    .description = "My application",
    .version = "0.1.0",
    .options = .{
        .port = cli.option(u16, .{
            .long = "port",
            .short = 'p',
            .description = "Listen port",
            .env = "MY_APP_PORT",
        }, 8080),
        .data_dir = cli.option([]const u8, .{
            .long = "data-dir",
            .short = 'd',
            .description = "Data directory",
        }, "./data"),
        .network = cli.option(enum { mainnet, sepolia, holesky }, .{
            .long = "network",
            .description = "Target network",
        }, .mainnet),
        .verbose = cli.flag(.{
            .long = "verbose",
            .short = 'v',
            .description = "Enable verbose output",
        }),
    },
};

pub fn main(init: std.process.Init.Minimal) !void {
    var args_iter: std.process.Args.Iterator = .init(init.args);

    const args = cli.parse(spec, &args_iter, std.heap.page_allocator) catch |err| switch (err) {
        error.HelpRequested, error.VersionRequested => std.process.exit(0),
        else => std.process.exit(1),
    };

    std.debug.print("port={d} dir={s} network={s} verbose={}\n", .{
        args.port, args.data_dir, @tagName(args.network), args.verbose,
    });
}

Subcommands

const std = @import("std");
const cli = @import("zig-cli");

const app_spec = cli.app(.{
    .name = "lodestar-z",
    .description = "Ethereum consensus client",
    .version = "0.1.0",
    .commands = .{
        .beacon = cli.command(.{
            .description = "Run beacon node",
            .options = .{
                .api_port = cli.option(u16, .{
                    .long = "api-port",
                    .description = "REST API port",
                    .group = "api",
                }, 5052),
                .metrics = cli.flag(.{
                    .long = "metrics",
                    .description = "Enable metrics",
                    .group = "metrics",
                }),
            },
        }),
        .validator = cli.command(.{
            .description = "Run validator client",
            .options = .{
                .beacon_url = cli.option([]const u8, .{
                    .long = "beacon-url",
                    .description = "Beacon node URL",
                }, "http://localhost:5052"),
            },
        }),
    },
    .global_options = .{
        .network = cli.option(enum { mainnet, sepolia, holesky }, .{
            .long = "network",
            .short = 'n',
            .description = "Target network",
        }, .mainnet),
    },
});

pub fn main(init: std.process.Init.Minimal) !void {
    const allocator = std.heap.page_allocator;
    var args_iter: std.process.Args.Iterator = .init(init.args);

    // parseApp returns a tagged union:
    const result = cli.parseApp(app_spec, &args_iter, allocator) catch |err| switch (err) {
        error.HelpRequested, error.VersionRequested => std.process.exit(0),
        else => std.process.exit(1),
    };

    switch (result) {
        .beacon => |opts| {
            // opts.api_port, opts.metrics, opts.network (global)
        },
        .validator => |opts| {
            // opts.beacon_url, opts.network (global)
        },
    }
}

Global app options may appear before or after the subcommand, for example: lodestar-z --network holesky beacon lodestar-z beacon --network holesky

RC Config Resolver

// Resolution order: CLI args > env vars > resolver > defaults
const args = cli.parseWithResolver(spec, &args_iter, allocator, struct {
    fn resolve(name: []const u8) ?[]const u8 {
        // Look up in YAML/JSON/TOML config — return value or null
        return my_config.get(name);
    }
}.resolve) catch |err| { ... };

// Also works with subcommands:
const result = cli.parseAppWithResolver(app_spec, &args_iter, allocator, resolver);

Option Groups

.options = .{
    .api_port = cli.option(u16, .{
        .long = "api-port",
        .description = "REST API port",
        .group = "api",          // ← grouped under "Api Options:"
    }, 5052),
    .metrics = cli.flag(.{
        .long = "metrics",
        .description = "Enable metrics",
        .group = "metrics",      // ← grouped under "Metrics Options:"
    }),
    .data_dir = cli.option([]const u8, .{
        .long = "data-dir",
        .description = "Data directory",
        // no group → appears under "OPTIONS:"
    }, "./data"),
},

Sample Help Output

$ lodestar-z --help
lodestar-z v0.1.0 — Ethereum consensus client

USAGE:
  lodestar-z <COMMAND> [OPTIONS]

COMMANDS:
  beacon         Run beacon node
  validator      Run validator client

GLOBAL OPTIONS:
  -n, --network <VALUE>   Target network [mainnet|sepolia|holesky] (default: mainnet)

Use 'lodestar-z <COMMAND> --help' for more information on a command.

$ lodestar-z beacon --help
lodestar-z beacon — Run beacon node

USAGE:
  lodestar-z beacon [OPTIONS]

Api Options:
      --api-port <NUM>      REST API port (default: 5052)

Metrics Options:
      --metrics             Enable metrics

Global Options:
  -n, --network <VALUE>     Target network [mainnet|sepolia|holesky] (default: mainnet)

  -h, --help                Show this help
  -V, --version             Show version

Requirements

  • Zig 0.16.0+

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages