Skip to content

spikenardco/flags.zig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

89 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

flags.zig

A type-safe command-line argument parser for Zig. Taking inspiration from Rust clap, and TigerBeetle's flags implementation, it lets you define flags using a struct or union(enum) and parses command-line arguments into it.

  • Zero runtime overhead — parsing happens at comptime where possible
  • Type safety — catch errors at compile time, not runtime
  • Idiomatic Zig — works with the grain of the language
  • Zero external dependencies

Features

  • Multiple flag types (bool, string, int, float, enum)
  • Struct-based argument definition
  • Default values via struct fields
  • Error handling for invalid/unknown flags
  • Positional arguments support
  • Subcommands via union(enum)
  • Slice support (multiple values per flag)
  • Two parsing patterns: repeated, comma-separated

Installation

1. Fetch the library

zig fetch --save git+https://github.com/doxalabs/flags.zig

2. Add to your build.zig

const flags = b.dependency("flags", .{});
exe.root_module.addImport("flags", flags.module("flags"));

Quick Start

const std = @import("std");
const flags = @import("flags");

pub fn main(init: std.process.Init) !void {
    const allocator = init.arena.allocator();
    const args = try init.minimal.args.toSlice(allocator);

    // Define flags as a struct with slice support
    const Args = struct {
        name: []const u8 = "world",
        age: u32 = 25,
        active: bool = false,
        
        // Multiple values supported
        files: []const []const u8 = &[_][]const u8{},
        ports: []u16 = &[_]u16{8080},
    };

    const parsed = try flags.parse(allocator, args, Args);
    defer flags.deinit(allocator, parsed);

    std.debug.print("Hello {s}! Age: {d}, Active: {}\n", .{
        parsed.name, parsed.age, parsed.active
    });
}
./program --name=alice --age=30 --active

# Slices accept repeated flags or comma-separated values
./program --files=a.txt --files=b.txt --files=c.txt
./program --files=a.txt,b.txt,c.txt

Advanced Features

Help Documentation

Running with -h or --help prints your help text.

If no help declaration is found, it prints "No help available" with a hint to declare one.

Help text is defined by declaring pub const help on your struct or union type:

const Args = struct {
    verbose: bool = false,
    port: u16 = 8080,
    
    pub const help = 
        \\Options:
        \\  --verbose    Enable verbose output (default: false)
        \\  --port       Port to listen on (default: 8080)
    ;
};

Subcommands

Git-style subcommands using union(enum):

const CLI = union(enum) {
    start: struct {
        host: []const u8 = "localhost",
        port: u16 = 8080,
    },
    stop: struct {
        force: bool = false,
    },
    
    pub const help = 
        \\ Server management CLI
        \\ commands:
        \\  start       Start the server
        \\      --host     Hostname to bind to (default: localhost)
        \\      --port     Port to listen on (default: 8080)
        \\  stop        Stop the server
        \\      --force    Force stop (default: false)
    ;
};

const cli = try flags.parse(allocator, args, CLI);
switch (cli) {
    .start => |s| startServer(s.host, s.port),
    .stop => |s| stopServer(s.force),
}

Global Flags with Subcommands

Combine top-level flags with subcommands using a struct that contains a union(enum):

const CLI = struct {
    verbose: bool = false,
    config: ?[]const u8 = null,
    command: union(enum) {
        serve: struct {
            host: []const u8 = "0.0.0.0",
            port: u16 = 8080,
        },
        migrate: struct {
            dry_run: bool = false,
        },
    },
};

const cli = try flags.parse(allocator, args, CLI);
if (cli.verbose) std.debug.print("verbose mode\n", .{});
switch (cli.command) {
    .serve => |s| startServer(s.host, s.port),
    .migrate => |m| runMigration(m.dry_run),
}
prog --verbose serve --port=3000
prog --config=app.toml migrate --dry_run

Positional Arguments

Use the @"--" marker to separate flags from positional arguments:

const Args = struct {
    verbose: bool = false,
    @"--": void,
    input: []const u8,
    output: []const u8 = "output.txt",
};

// Usage: program --verbose input.txt output.txt

Note: All flag arguments (--name=value) must appear before any positional arguments. Once the first positional value is parsed, subsequent --flag arguments are treated as positional values. Use the explicit -- separator to disambiguate:

program --verbose -- input.txt --flag-is-positional

Best Practices

DO

  1. Use struct defaults for common values
  2. Define help via pub const help declarations
  3. Use unions for mutually exclusive subcommands
  4. Leverage enums for constrained choices
  5. Use optional types for truly optional flags

DON'T

  1. Don't skip error handling
  2. Don't make all flags optional (defeats type safety)
  3. Don't use runtime string manipulation for help

Parser vs Application Boundary

The parser handles syntax; the application handles semantics. Keep these in application code:

  • Date/time interpretation (--due=tomorrow)
  • File I/O, encryption, network calls
  • Interactive prompts and terminal I/O
  • Output formatting and display
  • Command aliases (ttask)
  • Configuration file loading

The parser extracts typed values. What you do with them is your business.

Not planned

  • No short flags — only long flags (--flag=value), except -h for help. For brevity, use --v instead of -v
  • No custom types — only built-in types and enums
  • No nested slices — slices of slices not supported ([][]T)
  • Equals syntax only — use --name=value not --name value
  • Strict boolean values — only true and false are accepted (no 1, 0, yes, no, etc.)
  • No subcommands + positional args — use either subcommands or positional arguments, not both in the same struct

Credits

This library draws significant inspiration from two exceptional projects:

  • TigerBeetle's flags — struct-based flag definitions and zero-cost abstractions
  • Rust clap — declarative API design and derive-style patterns

About

A type-safe command-line argument parser for Zig

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages