zopt: low-ceremony command line parsing for Zig

Source: codeberg.org
29 points by hgrsd 18 hours ago on lobsters | 18 comments

zopt is a low-ceremony command line argument parser. It is lazy and uses "schema on read" semantics. Which is a fancy way of saying: it scans argv when you query it, rather than parses it upfront according to a schema.

zopt defers to the user's application code for help text, error/absence handling, and default values. zopt is a deliberately thin wrapper around std.process.Args rather than a batteries-included framework; as such, it is most useful for smaller-scale command-line tools with limited sets of options and without need for schema validation.

Although it is schemaless, zopt keeps track of the flags and options you query such that it can later provide you with positional arguments (defined as non-options/flags).

Philosophically, zopt can be seen as an attempt to create a pared down and aesthetically pleasing API, and it is willing to trade off the benefits of a schema-based command line parser for the ease of use.

Please see the changelog to follow along with changes and find out which versions are available.

Usage

First, add zopt as a dependency.

# latest
zig fetch --save git+https://codeberg.org/hgrsd/zopt/#HEAD

# specific version
zig fetch --save git+https://codeberg.org/hgrsd/zopt/#v0.0.11

API surface

const std = @import("std");
const Zopt = @import("zopt").Zopt;

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

    const Enum = enum { Foo, Bar };

    const z = try Zopt.from(arena, init.minimal.args);

    // flags match -F, --foo, or -F as part of multiple flags, e.g. "-FAX"
    const flag = z.flag('F', "foo");
    // short-only flag; matches -F, or -F as part of multiple flags, e.g. -FAO
    const short_flag = z.flag('F', null);

    // option matches -F <value>, -F=<value>, --foo <value> or --foo=<value>.
    const value = z.option('F', "foo");
    // short-only option matches -F <value>, -F=<value>
    const short_value = z.option('F', null);
    // long-only option matches --foo <value>, --foo=<value>
    const long_value = z.option(null, "foo");
    // compose with zig's orelse for default values
    const value_with_default = z.option('F', "foo") orelse "my_default";
    // fall back to help if value is absent
    const value_with_help_fallback = z.option('F', "foo") orelse help();
    // repeatable options; similar matching logic to z.option, collecting all matches
    const values = try z.optionRepeated('F', "foo");
    // optionAs allows parsing into specific (int, float, enum) types.
    const int_value = try z.optionAs(usize, 'F', "foo");
    const enum_value = try z.optionAs(Enum, 'E', "enum"); 

    // positionals returns a slice of positional arguments, where arguments are assumed to be positional that are
    // note that positionals does _not_ do any inspection of the shape of the values. This means that a misspelled
    // option, for instance, will be considered a positional argument.
    // As such, it is recommended to first validate that no unknown arguments are present in argv.
    const unknown = try z.unknownArguments();
    if (unknown.len > 0) {
        std.process.fatal("unknown argument found: {s}", .{ unknown });
    }
    // now it's safe to consider everything to be a positional arg
    const positional_args = try z.positionals();
    // positional returns a specific positional argument, if present, using the same heuristics as above
    const first_positional = try z.positional(0);
    const second_positional = try z.positional(1);
}

The examples folder contains examples of how to use zopt in various scenarios.

Each can be executed using the example name, and passed arguments to see it in action.

For a "real-world" example, see duplik's argument parsing.