Giter Site home page Giter Site logo

zig-clap's Introduction

zig-clap

A simple and easy to use command line argument parser library for Zig.

The master branch of zig-clap targets the master branch of Zig. For a version of zig-clap that targets a specific Zig release, have a look at the releases. Each release specifies the Zig version it compiles with in the release notes.

Features

  • Short arguments -a
    • Chaining -abc where a and b does not take values.
    • Multiple specifications are tallied (e.g. -v -v).
  • Long arguments --long
  • Supports both passing values using spacing and = (-a 100, -a=100)
    • Short args also support passing values with no spacing or = (-a100)
    • This all works with chaining (-ba 100, -ba=100, -ba100)
  • Supports options that can be specified multiple times (-e 1 -e 2 -e 3)
  • Print help message from parameter specification.
  • Parse help message to parameter specification.

API Reference

Automatically generated API Reference for the project can be found at https://Hejsil.github.io/zig-clap. Note that Zig autodoc is in beta; the website may be broken or incomplete.

Examples

clap.parse

The simplest way to use this library is to just call the clap.parse function.

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

const debug = std.debug;
const io = std.io;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    // First we specify what parameters our program can take.
    // We can use `parseParamsComptime` to parse a string into an array of `Param(Help)`
    const params = comptime clap.parseParamsComptime(
        \\-h, --help             Display this help and exit.
        \\-n, --number <usize>   An option parameter, which takes a value.
        \\-s, --string <str>...  An option parameter which can be specified multiple times.
        \\<str>...
        \\
    );

    // Initialize our diagnostics, which can be used for reporting useful errors.
    // This is optional. You can also pass `.{}` to `clap.parse` if you don't
    // care about the extra information `Diagnostics` provides.
    var diag = clap.Diagnostic{};
    var res = clap.parse(clap.Help, &params, clap.parsers.default, .{
        .diagnostic = &diag,
        .allocator = gpa.allocator(),
    }) catch |err| {
        // Report useful error and exit
        diag.report(io.getStdErr().writer(), err) catch {};
        return err;
    };
    defer res.deinit();

    if (res.args.help != 0)
        debug.print("--help\n", .{});
    if (res.args.number) |n|
        debug.print("--number = {}\n", .{n});
    for (res.args.string) |s|
        debug.print("--string = {s}\n", .{s});
    for (res.positionals) |pos|
        debug.print("{s}\n", .{pos});
}

The result will contain an args field and a positionals field. args will have one field for each none positional parameter of your program. The name of the field will be the longest name of the parameter.

The fields in args are typed. The type is based on the name of the value the parameter takes. Since --number takes a usize the field res.args.number has the type usize.

Note that this is only the case because clap.parsers.default has a field called usize which contains a parser that returns usize. You can pass in something other than clap.parsers.default if you want some other mapping.

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

const debug = std.debug;
const io = std.io;
const process = std.process;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    // First we specify what parameters our program can take.
    // We can use `parseParamsComptime` to parse a string into an array of `Param(Help)`
    const params = comptime clap.parseParamsComptime(
        \\-h, --help             Display this help and exit.
        \\-n, --number <INT>     An option parameter, which takes a value.
        \\-a, --answer <ANSWER>  An option parameter which takes an enum.
        \\-s, --string <STR>...  An option parameter which can be specified multiple times.
        \\<FILE>...
        \\
    );

    // Declare our own parsers which are used to map the argument strings to other
    // types.
    const YesNo = enum { yes, no };
    const parsers = comptime .{
        .STR = clap.parsers.string,
        .FILE = clap.parsers.string,
        .INT = clap.parsers.int(usize, 10),
        .ANSWER = clap.parsers.enumeration(YesNo),
    };

    var diag = clap.Diagnostic{};
    var res = clap.parse(clap.Help, &params, parsers, .{
        .diagnostic = &diag,
        .allocator = gpa.allocator(),
    }) catch |err| {
        diag.report(io.getStdErr().writer(), err) catch {};
        return err;
    };
    defer res.deinit();

    if (res.args.help != 0)
        debug.print("--help\n", .{});
    if (res.args.number) |n|
        debug.print("--number = {}\n", .{n});
    if (res.args.answer) |a|
        debug.print("--answer = {s}\n", .{@tagName(a)});
    for (res.args.string) |s|
        debug.print("--string = {s}\n", .{s});
    for (res.positionals) |pos|
        debug.print("{s}\n", .{pos});
}

streaming.Clap

The streaming.Clap is the base of all the other parsers. It's a streaming parser that uses an args.Iterator to provide it with arguments lazily.

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

const debug = std.debug;
const io = std.io;
const process = std.process;

pub fn main() !void {
    const allocator = std.heap.page_allocator;

    // First we specify what parameters our program can take.
    const params = [_]clap.Param(u8){
        .{
            .id = 'h',
            .names = .{ .short = 'h', .long = "help" },
        },
        .{
            .id = 'n',
            .names = .{ .short = 'n', .long = "number" },
            .takes_value = .one,
        },
        .{ .id = 'f', .takes_value = .one },
    };

    var iter = try process.ArgIterator.initWithAllocator(allocator);
    defer iter.deinit();

    // Skip exe argument
    _ = iter.next();

    // Initialize our diagnostics, which can be used for reporting useful errors.
    // This is optional. You can also leave the `diagnostic` field unset if you
    // don't care about the extra information `Diagnostic` provides.
    var diag = clap.Diagnostic{};
    var parser = clap.streaming.Clap(u8, process.ArgIterator){
        .params = &params,
        .iter = &iter,
        .diagnostic = &diag,
    };

    // Because we use a streaming parser, we have to consume each argument parsed individually.
    while (parser.next() catch |err| {
        // Report useful error and exit
        diag.report(io.getStdErr().writer(), err) catch {};
        return err;
    }) |arg| {
        // arg.param will point to the parameter which matched the argument.
        switch (arg.param.id) {
            'h' => debug.print("Help!\n", .{}),
            'n' => debug.print("--number = {s}\n", .{arg.value.?}),

            // arg.value == null, if arg.param.takes_value == .none.
            // Otherwise, arg.value is the value passed with the argument, such as "-a=10"
            // or "-a 10".
            'f' => debug.print("{s}\n", .{arg.value.?}),
            else => unreachable,
        }
    }
}

Currently, this parser is the only parser that allows an array of Param that is generated at runtime.

help

The help prints a simple list of all parameters the program can take. It expects the Id to have a description method and an value method so that it can provide that in the output. HelpOptions is passed to help to control how the help message is printed.

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const params = comptime clap.parseParamsComptime(
        \\-h, --help     Display this help and exit.
        \\-v, --version  Output version information and exit.
        \\
    );

    var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{
        .allocator = gpa.allocator(),
    });
    defer res.deinit();

    // `clap.help` is a function that can print a simple help message. It can print any `Param`
    // where `Id` has a `describtion` and `value` method (`Param(Help)` is one such parameter).
    // The last argument contains options as to how `help` should print those parameters. Using
    // `.{}` means the default options.
    if (res.args.help != 0)
        return clap.help(std.io.getStdErr().writer(), clap.Help, &params, .{});
}
$ zig-out/bin/help --help
    -h, --help
            Display this help and exit.

    -v, --version
            Output version information and exit.

usage

The usage prints a small abbreviated version of the help message. It expects the Id to have a value method so it can provide that in the output.

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

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();

    const params = comptime clap.parseParamsComptime(
        \\-h, --help         Display this help and exit.
        \\-v, --version      Output version information and exit.
        \\    --value <str>  An option parameter, which takes a value.
        \\
    );

    var res = try clap.parse(clap.Help, &params, clap.parsers.default, .{
        .allocator = gpa.allocator(),
    });
    defer res.deinit();

    // `clap.usage` is a function that can print a simple help message. It can print any `Param`
    // where `Id` has a `value` method (`Param(Help)` is one such parameter).
    if (res.args.help != 0)
        return clap.usage(std.io.getStdErr().writer(), clap.Help, &params);
}
$ zig-out/bin/usage --help
[-hv] [--value <str>]

zig-clap's People

Contributors

abhinav avatar aherrmann avatar dbandstra avatar dependabot-preview[bot] avatar dependabot[bot] avatar der-teufel-programming avatar djpohly avatar giann avatar hejsil avatar joachimschmidt557 avatar kivikakk avatar kubkon avatar marcuswagberg avatar mattnite avatar mrivnak avatar nektro avatar paoda avatar sirius902 avatar squeek502 avatar taggon avatar the-king-of-toasters avatar travisstaloch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zig-clap's Issues

cannot format slice without a specifier (i.e. {s} or {any})

On branch zig-master, zig current seems to demand a format specifier for stream.print. In file clap.zig lines 275-278.

            error.DoesntTakeValue => try stream.print("The argument '{}{}' does not take a value\n", .{ a.prefix, a.name }),
            error.MissingValue => try stream.print("The argument '{}{}' requires a value but none was supplied\n", .{ a.prefix, a.name }),
            error.InvalidArgument => try stream.print("Invalid argument '{}{}'\n", .{ a.prefix, a.name }),
            else => try stream.print("Error while parsing arguments: {}\n", .{@errorName(err)}),

I'm new to zig, adding 's' in each parens seems to work?

`help` example doesn't show version

I'm trying to learn how zig-clap works, and I hoped the help example would be more illustrative:

$ ./zig-out/bin/help --help
	-h, --help   	Display this help and exit.
	-v, --version	Output version information and exit.
$ ./zig-out/bin/help -v
	-h, --help   	Display this help and exit.
	-v, --version	Output version information and exit.

Maybe this example should also show a simple version string output?

What I'm really looking for is an example where the args are parsed and then re-used as help output. i.e. I don't want to write out the arguments and their instructions only to re-write those arguments and their instructions in the "usage" block or "help" text.

Edit: I think I figured out what I was looking for:

    const params = comptime [_]clap.Param(clap.Help){
        clap.parseParam("-h, --help             Display this help and exit.") catch unreachable,
        clap.parseParam("-t, --trails <NUM>     Show trails per touch point.") catch unreachable,
    };

    var diag = clap.Diagnostic{};
    var args = clap.parse(clap.Help, &params, .{ .diagnostic = &diag }) catch |err| {
        diag.report(std.io.getStdErr().writer(), err) catch {};
        return err;
    };
    defer args.deinit();

    if (args.flag("--help")) {
        try clap.help(std.io.getStdErr().writer(), &params);  // <--- THIS is what I needed :)
        std.os.exit(1);
    }
    if (args.option("--trails")) |n| {
        std.debug.print("--trails = {s}\n", .{n});
        std.os.exit(1);
    }

Custom argument to value parsers

Currently, we are able to parse strings to a few different types depending on the type of the fields we are trying to set.

I don't think we should try to support more than the basic types (and []const u8), but the user should be able to provide custom parser:

const Options = struct {
    a: std.Arraylist([]const u8),
};

fn addToArraylist(comptime T: type, arraylist: &T, arg: []const u8) !void {
    try arraylist.append(arg);
}

const parser = comptime Clap(Options).Builder
    .init(
        Options {
            .a = std.Arraylist([]const u8).init(std.debug.global_allocator),
        }
    )
    .command(
        Command.Builder
            .init("command")
            .arguments(
                []Argument {
                    Argument.Builder
                        .init("a")
                        .help("Add to 'a'")
                        .short('a')
                        .takesValue(addToArraylist)
                        .build(),
                }
            )
            .build()
    )
    .build();

This would allow command -a something -a "something else" to save both values passed to a.

This does not go against the goal of being a non-allocating clap. zig-clap will not allocate by default, but we can't stop users from allocating in custom argument parsing function.

Passing sensitive input

Hi,
Not an issue but more of a question, i parse strings,ints and enums just fine, but lets say i want to pass a password,or some other sensitive info, that should not be logged in shell /shell history. Is this possible ?
For example passing -p parameter, and all letters after that are parsed but not "logged" to the shell,or are simply turned in to ****** asterisks.

Is something like this possible?

zig-clap doesn't build with zig 0.9.1

Taking a checkout of master (commit ac5f465) and running zig build results in the following error:

./clap.zig:913:19: error: default_value of field 'help' is of type '*const bool', expected 'bool' or '?bool'
    return @Type(.{ .Struct = .{
                  ^
./clap.zig:680:24: note: called from here
        args: Arguments(Id, params, value_parsers, .slice),
                       ^
./clap.zig:680:24: note: called from here
        args: Arguments(Id, params, value_parsers, .slice),

zig version is 0.9.1.
Running zig build test has the following errors:

./clap.zig:913:19: error: default_value of field 'str' is of type '*const ?[]const u8', expected '?[]const u8' or '??[]const u8'
    return @Type(.{ .Struct = .{
                  ^
./clap.zig:812:24: note: called from here
        args: Arguments(Id, params, value_parsers, .slice),
                       ^
./clap.zig:812:24: note: called from here
        args: Arguments(Id, params, value_parsers, .slice),
                       ^
./clap.zig:913:19: error: default_value of field 'aa' is of type '*const bool', expected 'bool' or '?bool'
    return @Type(.{ .Struct = .{
                  ^
./clap.zig:812:24: note: called from here
        args: Arguments(Id, params, value_parsers, .slice),
                       ^
./clap.zig:812:24: note: called from here
        args: Arguments(Id, params, value_parsers, .slice),
                       ^
./clap.zig:913:19: error: default_value of field 'aa' is of type '*const bool', expected 'bool' or '?bool'
    return @Type(.{ .Struct = .{
                  ^
./clap.zig:812:24: note: called from here
        args: Arguments(Id, params, value_parsers, .slice),
                       ^
./clap.zig:812:24: note: called from here
        args: Arguments(Id, params, value_parsers, .slice),

Accessing fields of substructs

Currently, this does not work:

const SubOptions = struct {
    b: u64,
};

const Options = struct {
    a: SubOptions,
};
const parser = comptime Clap(Options).Builder
    .init(
        Options {
            .a = SubOptions {
                .b = 0
            }
        }
    )
    .command(
        Command.Builder
            .init("command")
            .arguments(
                []Argument {
                    Argument.Builder
                        .init("a.b")
                        .help("Set the 'b' field of Option.")
                        .short('b')
                        .takesValue(true)
                        .build(),
                }
            )
            .build()
    )
    .build();
zig-clap/clap.zig:193:13: error: Field not found!
            @compileError("Field not found!");
            ^
zig-clap/clap.zig:196:89: note: called from here
        fn getFieldPtr(comptime T: type, res: &T, comptime field: []const u8) &FieldType(T, field) {
                                                                                        ^
zig-clap/clap.zig:196:79: note: called from here
        fn getFieldPtr(comptime T: type, res: &T, comptime field: []const u8) &FieldType(T, field) {
                                                                              ^
zig-clap/clap.zig:144:111: error: unable to evaluate constant expression
                                        *getFieldPtr(Result, &result, option.field) = try strToValue(FieldType(Result, option.field), value);
                                                                                                              ^
zig-clap/clap.zig:23:32: note: called from here
            return parseCommand(CommandList { .command = clap.command, .prev = null }, clap.defaults, arguments);
                               ^
zig-clap/sample.zig:49:37: note: called from here
    const options = try parser.parse(args[1..]);

Also, the error message should probably be better.

Error messages don't show the invalid argument.

Steps to reproduce:
1. Build the example programs (tested on 0.8.0 and master).
2. Run simple or simple-ex with an argument of -1.

Expected output:

Invalid argument '-1'
error: InvalidArgument
stacktrace...

Actual output:

Invalid argument ''
error: InvalidArgument
stacktrace...

Running the programs in GDB, it seems that the initialization at line 47 in comptime.zig clobbers opt.diagnostic (its value is <optimized out> when I step over it).

positionals causing runtime error

Given a sample program:

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

const debug = std.debug;

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    _ = gpa.deinit();
    var allocator = gpa.allocator();

    const params = comptime clap.parseParamsComptime(
        \\-h, --help         Display this help and exit.
        \\-v, --version      Output version information and exit.
        \\-i, --input <str>  Input file.
        \\-o, --output <str> Output file.
        \\<string>...
        \\
    );

    var diag = clap.Diagnostic{};
    var res = clap.parse(clap.Help, &params, clap.parsers.default, .{
        .diagnostic = &diag,
        .allocator = allocator,
    }) catch |err| {

        // Report useful error and exit
        diag.report(std.io.getStdErr().writer(), err) catch {};
        return err;
    };

    defer res.deinit();

    // Use the parsed arguments
    if (res.args.input) |input|
        debug.print("Input file: '{s}'\n", .{input});
    if (res.args.output) |output|
        debug.print("Output file: '{s}'\n", .{output});
    for (res.positionals) |pos|
        debug.print("{s}\n", .{pos});
}

Invoking it without positional arguments succeeds:

zig build run -- -i foo -o bar
Input file: 'foo'
Output file: 'bar'

but invoking it with positional arguments fails with a runtime error:

zig build run -- -i foo -o bar abc
run tup: error: the following command terminated unexpectedly:
/Users/brent/src/tup/zig-out/bin/tup -i foo -o bar abc
Build Summary: 3/5 steps succeeded; 1 failed (disable with --summary none)
run transitive failure
└─ run tup failure
error: the following build command failed with exit code 1:
/Users/brent/src/tup/zig-cache/o/1e2da75df902e3eabbc20965da49ceac/build /opt/homebrew/Cellar/zig/0.11.0/bin/zig /Users/brent/src/tup /Users/brent/src/tup/zig-cache /Users/brent/.cache/zig run -- -i foo -o bar abc

Debug statements narrowed it down to the following line in clap.zig:

.positional => try positionals.append(try parser(arg.value.?)),

Apparently it's a surprise that arg.value is null? I'm told this is not a catchable error.

Enum parser

The ability to specify an enum as a possible values parser for an option.

const MyEnum = enum {
    foo,
    bar,
};

const params = comptime clap.parseParamsComptime(
    \\-E, --my_enum <MyEnum>
);

const parsers = comptime .{
    .MyEnum = clap.parsers.enumParser(MyEnum),
};

var res = clap.parse(clap.Help, &params, parsers, .{}) catch unreachable;
defer res.deinit();

if (res.args.my_enum) |e| {
    const my_enum: MyEnum = e;
}

// usage:
// --my_enum foo --my_enum=bar
// -Efoo -E bar

Argument parsing in function

I want to refactor argument parsing into a separate function. It would parse the arguments, and validate them (min/max for numbers, permissions for file paths, etc.), then return the result of the parse function.

// use it in main()
pub fn main() {
  // ...
  const validatedArgs = parseAndValidateArgs(
    // ...
  );
}

pub fn parseAndValidateArgs() { // what is the return type?
  // ...
  const cliArgs = clap.parse(
    // ...
  );

  // validate here

  // then return
  return cliArgs;
}

How do I specify the return type of the parseAndValidateArgs function in the above snippet? Is there a clean and readable way of doing it?

How do i install it?

Hi i am new to zig and i would like to use this library for my project. But I don't see any install instructions on README so idk how can i use it.

License

Sorry to raise such a dry issue...

Would it be possible to apply MIT or similar to this code?

Unlicense/Public Domain can inhibit adoption of this library since different jurisdictions have different laws pertaining to Public Domain works and relinquishment of rights over those works.

Add clap.parseParams which parses a string into many parameters

This will give us a simpler api for the most common usage of parseParam

const params = clap.parseParams(comptime_allocator,
    \\    -h, --help      Print help
    \\    -v, --version   Print version
) catch unreachable;

We need a comptime allocator for this to be able to work at both runtime and comptime

A parameter without a long name crashes the `stage2` compiler

So, here is an example from the tutorial, except I removed the , --help part from the first flag.

const std = @import("std");

const clap = @import("clap");

pub fn main() !void {
    // Define the parameters
    const params = comptime clap.parseParamsComptime(
        \\-h                     Display this help and exit.
        \\-n, --number <usize>   An option parameter, which takes a value.
        \\-s, --string <str>...  An option parameter which can be specified multiple times.
        \\<str>...
        \\
    );
    // Parse the arguments
    var diag = clap.Diagnostic{};
    var res = clap.parse(clap.Help, &params, clap.parsers.default, .{
        .diagnostic = &diag,
    }) catch |err| {
        // Report useful error and exit
        diag.report(std.io.getStdErr().writer(), err) catch {};
        return err;
    };
    defer res.deinit();
}

Compiling this with Zig v0.11.0-dev.251+7c527c6df gives the following:

error: zig-clap-issue...
error: The following command terminated unexpectedly:
/var/home/paveloom/.zig/0.11.0-dev.251+7c527c6df/files/zig build-exe /var/home/paveloom/Playground/zig-clap-issue/src/main.zig --cache-dir /var/home/paveloom/Playground/zig-clap-issue/zig-cache --global-cache-dir /var/home/paveloom/.cache/zig --name zig-clap-issue --pkg-begin clap /var/home/paveloom/Playground/zig-clap-issue/.zigmod/deps/v/git/github.com/Hejsil/zig-clap/commit-dbc6b8e/clap.zig --pkg-end --enable-cache
error: the following build command failed with exit code 5:
/var/home/paveloom/Playground/zig-clap-issue/zig-cache/o/c2b998d2b5b9d41fadce3bee6d957fe9/build /var/home/paveloom/.zig/0.11.0-dev.251+7c527c6df/files/zig /var/home/paveloom/Playground/zig-clap-issue /var/home/paveloom/Playground/zig-clap-issue/zig-cache /var/home/paveloom/.cache/zig run

I would expect this syntax to work, considering you have tests for this use case (which pass for me):

zig-clap/clap.zig

Lines 1770 to 1816 in dbc6b8e

test "usage" {
@setEvalBranchQuota(100000);
try testUsage("[-ab]", &comptime parseParamsComptime(
\\-a
\\-b
\\
));
try testUsage("[-a <value>] [-b <v>]", &comptime parseParamsComptime(
\\-a <value>
\\-b <v>
\\
));
try testUsage("[--a] [--b]", &comptime parseParamsComptime(
\\--a
\\--b
\\
));
try testUsage("[--a <value>] [--b <v>]", &comptime parseParamsComptime(
\\--a <value>
\\--b <v>
\\
));
try testUsage("<file>", &comptime parseParamsComptime(
\\<file>
\\
));
try testUsage("<file>...", &comptime parseParamsComptime(
\\<file>...
\\
));
try testUsage(
"[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>",
&comptime parseParamsComptime(
\\-a
\\-b
\\-c <value>
\\-d <v>
\\--e
\\--f
\\--g <value>
\\--h <v>
\\-i <v>...
\\<file>
\\
),
);
}

`clap.help` with proper line wrapping

Currently, we just write the help message to a stream without thinking about wrapping or anything. This is simple, but it's not that great for long help messages, as the default terminal wrapping ruins the help messages format.

I think the solution here is to rework the api a little:

  • Have clap.help only take an array of Param(Help) and have it fetch stderr + tty width internally.
  • Have new flexible versions of clap.help which can be given a stream.

Incompatibility with 32bit builds

Hi. I was trying to build for target i386-windows and got this error:

.\lib\clap\clap.zig:293:38: error: expected type 'usize', found 'u64'
                res = counting_stream.bytes_written;

I think res is usize but bytes_written is always u64, and that's causing the conflict.

Short args that take values should be able to have the value immediately after the arg

This should work: command -Dvalue. Here, value is the value of the argument D, if D is a short arg and takes a value.

Questions:

  • Should this be allowed? command -value, where v is a short arg, and alue is its value?
    • I think this is a question of good argument design. If people wanna design their options in a poor way, we can't/shouldn't really stop them.
  • Should this work with chaining? -aDvalue, where a and D are short args. a doesn't take a value, and D does.
    • I don't think I should, but I need to look into how other claps does this.
    • Maybe it should work though, as -aD=value works.
  • Should this work: command -Dvalue=2? Here, D is a short arg taking a value, and value=2 is its value.
    • This would allow for user implemented custom runtime arguments, which is currently not supported by the lib itself.

Add support for arguments that are not tokenized yet

This is a slightly esoteric use case, but at a first glance it doesn't seem too hard to support.

I'd like to be able to feed to clap a argument string that hasn't been tokenized yet.
At the moment, I'm implementing my own custom ArgIterator, but I think having this implemented inside clap itself might be a good way of reusing (at least some of) the parsing logic that you already have.

To give a bit more context on the usecase: I want to allow users of zig-doctest to specify build options as a special comment in the first line of the script itself.

Remove default allocator assignment in clap

I am working on a project which uses clap in UEFI and having the default allocator in the structs is causing this error despite me overriding the allocator.

/home/ross/zig/lib/zig/std/os.zig:283:25: error: root struct of file 'os.uefi' has no member named 'getErrno'
pub const errno = system.getErrno;
                  ~~~~~~^~~~~~~~~
/home/ross/zig/lib/zig/std/os/uefi.zig:1:1: note: struct declared here
const std = @import("../std.zig");
^~~~~
referenced by:
    munmap: /home/ross/zig/lib/zig/std/os.zig:4539:13
    free: /home/ross/zig/lib/zig/std/heap/PageAllocator.zig:108:11
    vtable: /home/ross/zig/lib/zig/std/heap/PageAllocator.zig:13:13
    page_allocator: /home/ross/zig/lib/zig/std/heap.zig:241:33
    ParseOptions: /home/ross/.cache/zig/p/122066d86920926a4a674adaaa10f158dc4961c2a47e588e605765455d74ca72c2ad/clap.zig:649:36
    ParseOptions: /home/ross/.cache/zig/p/122066d86920926a4a674adaaa10f158dc4961c2a47e588e605765455d74ca72c2ad/clap.zig:643:26
    parseEx: /home/ross/.cache/zig/p/122066d86920926a4a674adaaa10f158dc4961c2a47e588e605765455d74ca72c2ad/clap.zig:728:10
    parse__anon_2995: /home/ross/.cache/zig/p/122066d86920926a4a674adaaa10f158dc4961c2a47e588e605765455d74ca72c2ad/clap.zig:666:24
    main: src/ziggybox.zig:20:25
    EfiMain: /home/ross/zig/lib/zig/std/start.zig:231:37

Best option is to make the allocator nullable and doing an orelse on it.

Supporting long options of more than one word

Some programs have long option flags that you can pass, such as --min-value. I cannot access the data member when passing the long name like that, but --minvalue works. Can this program support this? Thanks

Why the usage output is so simple?

The usage output is so simple even missing the description of the parameter. I think it's bad for user experience.

How about support usage output just like clap-rs?

$ demo --help
clap [..]
A simple to use, efficient, and full-featured Command Line Argument Parser

USAGE:
    demo[EXE] [OPTIONS] --name <NAME>

OPTIONS:
    -n, --name <NAME>      Name of the person to greet
    -c, --count <COUNT>    Number of times to greet [default: 1]
    -h, --help             Print help information
    -V, --version          Print version information

Doesn't compile on Windows (expected error union type)

I'm new to Zig and in the trying out phase so I don't feel comfortable making a PR, but couldn't get this to compile in my project. I have added the project as a submodule and running Windows 11 + zig 0.9.0, but when trying example code I'm getting:

.\libs\zig-clap\clap\args.zig:76:39: error: expected error union type, found '?std.process.NextError![:0]u8'
            return (try iter.args.next(iter.arena.allocator())) orelse return null;
                                      ^
.\libs\zig-clap\clap\args.zig:76:21: note: referenced here
            return (try iter.args.next(iter.arena.allocator())) orelse return null;

After some tinkering I noticed that I can get it to compile by removing the brackets around the try in the failing function:

diff --git a/clap/args.zig b/clap/args.zig
index 90c50fa..16299c8 100644
--- a/clap/args.zig
+++ b/clap/args.zig
@@ -73,7 +73,7 @@ pub const OsIterator = struct {

     pub fn next(iter: *OsIterator) Error!?[:0]const u8 {
         if (builtin.os.tag == .windows) {
-            return (try iter.args.next(iter.arena.allocator())) orelse return null;
+            return try iter.args.next(iter.arena.allocator()) orelse return null;
         } else {
             return iter.args.nextPosix();
         }

Proper error types

Currently, on error, we just return a zig error. This is not very useful, as it's quite hard to print good error messages when the failing argument index is not available.

clap.Param(clap.Help) can fail if we exceed EvalBranchQuota

/usr/local/lib/zig/std/mem.zig:819:5: error: evaluation exceeded 1000 backwards branches
    while (i < slice.len) : (i += 1) {
    ^
/usr/local/lib/zig/std/mem.zig:804:28: note: called from here
    return indexOfScalarPos(T, slice, 0, value);
                           ^
/usr/local/lib/zig/std/mem.zig:790:41: note: called from here
    while (begin < end and indexOfScalar(T, values_to_strip, slice[begin]) != null) : (begin += 1) {}
                                        ^
/home/nathan/src/zcore/lib/zig-clap/clap.zig:121:49: note: called from here
    return Param(Help){ .id = .{ .msg = mem.trim(u8, line, " \t") } };
                                                ^
/home/nathan/src/zcore/lib/zig-clap/clap.zig:101:29: note: called from here
    var res = parseParamRest(it.rest());
                            ^
./src/main.zig:14:18: note: called from here
  clap.parseParam("-q, --quiet            Never display a header.") catch unreachable,

I've managed to work around this by enclosing all of the comptime in a named block and setting the quota within.

const params = comptime par: {
  @setEvalBranchQuota(1200);
  break :par [_]clap.Param(clap.Help){
   ... <more code> ...
  };
};

This works but is less than ergonomic and requires finding a workaround by the use of the library. Not sure if there's a way to fix it within zig-clap, but at the least I would suggest showing an example of the issue and workaround in the documentation.

All parameter values get overwritten with 0xaa on Windows

  • zig version: zig-windows-x86_64-0.8.0
  • zig-clap verison: 0.4.0

Using the clap.parse example verbatim all parameter values get printed as ¬, since the []u8 get overwritten with 0xaa for some reason, when appending them to the appropriate ArrayList in the loop at clap/comptime.zig:69. The example code also infinite loops when trying to print the multiple value param.

zig.exe build run -- test abc -n 5 -s str sdkf
--number = ¬
--string =

(Also tested it on Ubuntu 20.04 LTS (using WSL2) and it works there)

EDIT:
Just a single option parameter seems to work:

zig.exe build run -- -n 512
--number = 512

EDIT2:
Changing clap.parse so that parseEx does not reuse OsIterator's allocator solves the issue somehow.

Multible values to one option

With #3, it would probably be useful to have command -a som som2 som3 be a shorthand for command -a som -a som2 -a som3.

Questions:

  • Should this always work like this, or should arguments be specified to be of a "many values" kind for it to work.
    • Maybe the user can specify an end arg, that stops the list command -a som som2 --a-end
  • Currently if a takes a value, then command -a -b have -b be the value of a. This should not work for "many values" arguments unless we require them to be ended with some other argument.

Parse int larger than u8

Currently it looks like you can only parse integer arguments represented as u8.

I get the following error when trying to use u16:

    const params = comptime clap.parseParamsComptime(
        \\-h, --help                       Display this help and exit.
        \\-l, --line-buffer <LINEBUFFER>   An option parameter, which takes a value.
        \\<FILE1>                          The base file
        \\<FILE2>                          The file to compare
        \\
    );
    const parsers = comptime .{
        .LINEBUFFER = clap.parsers.int(u16, 1024),
        .FILE1 = clap.parsers.string,
        .FILE2 = clap.parsers.string,
    };
src/main.zig:17:45: error: type 'u8' cannot represent integer value '1024'

The tests only cover u8 https://github.com/Hejsil/zig-clap/blob/master/clap/parsers.zig#L39

infinite loop in parseEx, "while (try stream.next())"

Experiencing the infinite loop issue in the parseEx "while (try stream.next())" code.

I see there is a "TODO" mentioning that a fix to this is blocked by compiler bugs which are causing the infinite loop.

Is there a suggested work around to avoid this? Or has the compiler possibly changed recently in a way to allow a work around?

Best naming of the Argument struct.

This is an issue because naming is important.

In Zig's self-hosted compiler, they are called Flag. I call them Argument, but they could be called Option too.

I thought a little about it, and if we treat programs like functions then we could apply the same logic. As far as I know, functions have parameters and are called with arguments. Aka the declaration is parameters and the values are arguments, so maybe we should call the struct Parameter?

Idk. I'll think about this for a while.

zig 0.6.0, error: expected type 'std.build.Id', found '*const [10:0]u8'

Using zig version 0.6.0+51682717d

./build.zig:62:31: error: expected type 'std.build.Id', found 'const [10:0]u8'
s.
= std.build.Step.init("ReadMeStep", b.allocator, struct {
^
/home/phdyex/host-specific/local/lib/zig/std/build.zig:2430:20: note: std.build.Id declared here
pub const Id = enum {
^
Thanks

A Zig'y way of constructing the clap

I don't think the java 'builder' way of doing things is really 'Zig'y, and I prefer the syntax way of the current arg.zig which honestly looks very nice;

Now, I'm not aware of what exactly he means by not 'Zig'y, but I have my own problems with the Builder pattern I'm using:

  • The indent level explodes quite fast with my current coding style.
  • zig fmt will ruine code written this way.
  • The builders them self are around 30% of the code in clap.zig
  • All the .build() at the end are ugly, and I don't like them.

We need a way to constructs arguments, which allows for the following:

  • Allows for optional, and required "arguments" to the construction of options.
    • I require that the user specifies the field, which an argument is mapped to, but the rest, I provide sensible defaults for, which can be overwritten if the user wants (This is the strength of the Builder Pattern)
  • Unlike arg.zig in the self hosted parser, arguments can have be long, short or both.
    • The reason we destinguish is to allow short arguments to chain: -abcd

@BraedonWooding I'd love to hear what you think. Any idea of a better way?

Use std.os.ArgIterator

If we want to be truly none allocating (at least of POSIX), then we should probably use the std.os.ArgIterator. Sadly, on Windows, this iterator allocates, and we can't really get around that (I think).

Questions:

  • What is the best way to skip the path of the executable (the first arg)?
    • Should we assume it's always there, and skip the first iteration?
    • What if the user of the lib wants to store the path somewhere.
    • We could, like with the current API, require the user to eat the exe path.

`chainging` typo

Just FYI there are a few refs to chainging in the source code... I think it's a typo?

Seems you have crashed the compiler. A way to work around this is to use the stage1 compiler. This is done by either passing `-fstage1` to `zig build-exe` or setting the `exe.stage1` field to `true` in your `build.zig` file.

    Seems you have crashed the compiler. A way to work around this is to use the stage1 compiler. This is done by either passing `-fstage1` to `zig build-exe` or setting the `exe.stage1` field to `true` in your `build.zig` file.

I'll have a look around to see if the has been reported to zig

Originally posted by @Hejsil in #84 (comment)

Can't install it using the built-in package manager

build.zig.zon

.{
  .name = "test_package",
  .version = "0.1.0",

  .dependencies = .{
    .clap = .{
      .url = "https://github.com/Hejsil/zig-clap/archive/refs/heads/master.tar.gz",
      .hash = "1220ec67bc0bab1c56c0787626b5d9229d504000b0b6dfd94df5934b1d398a7de8ba",
    },
  },
}

section in build.zig

 _ = b.dependency("clap", .{ .target = target, .optimize = optimize });

error

/home/user/.cache/zig/p/1220ec67bc0bab1c56c0787626b5d9229d504000b0b6dfd94df5934b1d398a7de8ba/build.zig:4:6: error: member function expected 2 argument(s), found 1
    b.addModule(.{ .name = "clap", .source_file = .{ .path = "clap.zig" } });
    ~^~~~~~~~~~

README examples need an update

The examples in the README seem to need an update, since I get errors saying container 'zig-clap.src.streaming.StreamingClap(u8,zig-clap.src.args.OsIterator)' has no member called 'init' and error: container 'zig-clap.clap.Param(u8)' has no member called 'flag' when replicating the examples. My source is below:

const std = @import("std");
const clap = @import("zig-clap/clap.zig");

const params = []clap.Param(u8) {
    clap.Param(u8).flag('h', clap.Names.both("help"))
};

pub fn main() !u8 {
    var direct_allocator = std.heap.DirectAllocator.init();
    defer direct_allocator.deinit();
    var arena_allocator = std.heap.ArenaAllocator.init(&direct_allocator.allocator);
    defer arena_allocator.deinit();

    const allocator = &arena_allocator.allocator;
    var iter = clap.args.OsIterator.init(allocator);
    defer iter.deinit();
    const executable = iter.next() catch unreachable;

    var parser = clap.StreamingClap(u8, clap.args.OsIterator).init(params, &iter);
    defer parser.deinit();

    while (try parser.next()) |arg| {
        switch (arg.param.id) {
            'h' => std.debug.warn("help\n"),
            else => unreachable
        }
    }
}

Sub commands

Allow the clap to parse subcommands.

const parser = comptime Clap(Options).Builder
    .init(
        Options {
            .print_values = false,
            .a = 0,
            .b = 0,
            .c = 0,
        }
    )
    .command(
        Command.Builder
            .init("com")
            .arguments(
                []Argument {
                    Argument.Builder
                        .init("a")
                        .help("Set the a field of Option.")
                        .short('a')
                        .takesValue(true)
                        .build(),
                }
            )
            .subCommands(
                []Command {
                    Command.Builder
                        .init("sub-com")
                        .arguments(
                            []Argument {
                                Argument.Builder
                                    .init("b")
                                    .help("Set the a field of Option.")
                                    .short('b')
                                    .takesValue(true)
                                    .build(),
                            }
                        )
                        .build()
                }
            )
            .build()
    )
    .build();

Subcommands have their own options, which cannot be accessed by their parent. com -a 1 sub-com -b 1 works, but com -a 1 -b 1 does not.

Questions:

  • Should sub commands be able to access the options of its parent?
    • com sub-com -a 1 -b 1
    • We could have an option that allows/disallows this.
  • It would probably be useful to be able to tell from the resulting struct that a subcommand was parse.
    • Should it just set some boolean field? Maybe subcommands will have sub results, which could be their own struct (or union, which would allow for all subcommands to return different tags of the same union).
  • Should subcommands have an option to be required?

Support for more complex parameters

I'd like to parse something like --mode <width>x<height>[@<refresh>Hz].
To my from my current understanding this is not possible.

I intended to write it as follows:

    const params = comptime [_]clap.Param(clap.Help) {
        .{
            .id = .{.msg = "defines the output mode", .value = "<width>x<height>[@<refresh>Hz]"},
            .names = .{ .short = 'm', .long = "mode" },
            .takes_value = .One,
        },
    };

    var args = try clap.parse(clap.Help, &params, std.heap.page_allocator, null);
    defer args.deinit();

    if (args.option("--mode")) |n| {
        // Parse width,height and refresh manually
    }

This would work but is to some extend a hack. The main problem with this is that all help* functions become useless because they put <> around the value string.

A simple fix would be to make the formatting in parseParam accept a custom formatstring. But I am not sure that this is the right solution.

When this usecase is considered valid then I'll spend some time thinking about a good solution.

Support for required parameter/argument

Hi there,
Great job with this library so far!

I'm missing required parameter functionality. Would it make sense for you to add it ?
I see that we could (in rough words) extend this function

pub fn Param(comptime Id: type) type {
    return struct {
        id: Id,
        names: Names = Names{},
        takes_value: Values = .none,
    };
}

with a field .required: bool = false and extend Errors with something like this:

pub const Error = error{
    MissingValue,
    InvalidArgument,
    DoesntTakeValue,
    MissingRequiredParam
};

I think I can prepare PR with such change.
This is only proposition of changes. What do you think ?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.