GithubHelp home page GithubHelp logo

prajwalch / yazap Goto Github PK

View Code? Open in Web Editor NEW
117.0 3.0 13.0 17.96 MB

๐Ÿ”ง The ultimate Zig library for seamless command line argument parsing.

Home Page: https://prajwalch.github.io/yazap

License: MIT License

Zig 100.00%
argument-parser argument-parsing command-line-arguments-parser flags parser subcommands zig ziglang cli

yazap's Introduction

test pages-build-deployment

Yazap

Note: This branch targets the master branch of zig. See supported versions table.

The ultimate zig library for seamless command-line parsing. Effortlessly handles options, subcommands, and custom arguments with ease.

Inspired by clap-rs and andrewrk/ziglang: src-self-hosted/arg.zig

Supported versions table

yazap Zig
main master
0.5.1 0.12.0, 0.12.1 and 0.13.0
<= 0.5.0 Not supported to any

Key Features:

  • Options (short and long):

    • Providing values with =, space, or no space (-f=value, -f value, -fvalue).
    • Supports delimiter-separated values with = or without space (-f=v1,v2,v3, -fv1:v2:v3).
    • Chaining multiple short boolean options (-abc).
    • Providing values and delimiter-separated values for multiple chained options using = (-abc=val, -abc=v1,v2,v3).
    • Specifying an option multiple times (-a 1 -a 2 -a 3).
  • Positional arguments:

    • Supports positional arguments alongside options for more flexible command-line inputs. For example:
      • command <positional_arg>
      • command <arg1> <arg2> <arg3>
  • Nested subcommands:

    • Organize commands with nested subcommands for a structured command-line interface. For example:
      • command subcommand
      • command subcommand subsubcommand
  • Automatic help handling and generation

  • Custom Argument definition:

    • Define custom Argument types for specific application requirements.

Limitations:

  • Does not support delimiter-separated values using space (-f v1,v2,v3).
  • Does not support providing value and delimiter-separated values for multiple chained options using space (-abc value, -abc v1,v2,v3).

Installing

  1. Run the following command:
zig fetch --save git+https://github.com/prajwalch/yazap
  1. Add the following to build.zig:
const yazap = b.dependency("yazap", .{});
exe.root_module.addImport("yazap", yazap.module("yazap"));

Documentation

For detailed and comprehensive documentation, please visit this link.

Building and Running Examples

The examples can be found here. To build all of them, run the following command on your terminal:

$ zig build examples

After the compilation finishes, you can run each example by executing the corresponding binary:

$ ./zig-out/bin/example_name

To view the usage and available options for each example, you can use -h or --help flag:

$ ./zig-out/bin/example_name --help

Usage

Initializing Yazap

To begin using yazap, the first step is to create an instance of App by calling App.init(allocator, "Your app name", "optional description"). This function internally creates a root command for your application.

var app = App.init(allocator, "myls", "My custom ls");
defer app.deinit();

Obtaining the Root Command

The App itself does not provide any methods for adding arguments to your command. Its main purpose is to initialize the library, invoke the parser, and free associated structures. To add arguments and subcommands, you'll need to use the root command.

To obtain the root command, simply call App.rootCommand(), which returns a pointer to it. This gives you access to the core command of your application.

var myls = app.rootCommand();

Adding Arguments

Once you have obtained the root command, you can proceed to arguments using the provided methods in the Command. For a complete list of available methods, refer to the Command API documentation.

try myls.addArg(Arg.positional("FILE", null, null));
try myls.addArg(Arg.booleanOption("all", 'a', "Don't ignore the hidden directories"));
try myls.addArg(Arg.booleanOption("recursive", 'R', "List subdirectories recursively"));
try myls.addArg(Arg.booleanOption("one-line", '1', null));
try myls.addArg(Arg.booleanOption("size", 's', null));
try myls.addArg(Arg.booleanOption("version", null, null));

try myls.addArg(Arg.singleValueOption("ignore", 'I', null));
try myls.addArg(Arg.singleValueOption("hide", null, null));

try myls.addArg(Arg.singleValueOptionWithValidValues("color", 'C', null, &[_][]const u8{
    "always",
    "auto",
    "never",
}));

Alternatively, you can add multiple arguments in a single function call using Command.addArgs():

try myls.addArgs(&[_]Arg {
    Arg.positional("FILE", null, null),
    Arg.booleanOption("all", 'a', "Don't ignore the hidden directories"),
    Arg.booleanOption("recursive", 'R', "List subdirectories recursively"),
    Arg.booleanOption("one-line", '1', null),
    Arg.booleanOption("size", 's', null),
    Arg.booleanOption("version", null, null),

    Arg.singleValueOption("ignore", 'I', null),
    Arg.singleValueOption("hide", null, null),

    Arg.singleValueOptionWithValidValues("color", 'C', null, &[_][]const u8{
        "always",
        "auto",
        "never",
    }),
});

Adding Subcommands

To create a subcommand, you can use App.createCommand("name", "optional description"). Once you have created a subcommand, you can add its own arguments and subcommands just like the root command then add it to the root command using Command.addSubcommand().

var update_cmd = app.createCommand("update", "Update the app or check for new updates");
try update_cmd.addArg(Arg.booleanOption("check-only", null, "Only check for new update"));
try update_cmd.addArg(Arg.singleValueOptionWithValidValues("branch", 'b', "Branch to update", &[_][]const u8{ 
    "stable",
    "nightly",
    "beta"
}));

try myls.addSubcommand(update_cmd);

Parsing Arguments

Once you have finished adding arguments and subcommands, call App.parseProcess() to start the parsing process. This function internally utilizes std.process.argsAlloc to obtain the raw arguments. Alternatively, you can use App.parseFrom() and pass your own raw arguments, which can be useful during testing. Both functions return a constant pointer to ArgMatches.

const matches = try app.parseProcess();

if (matches.containsArg("version")) {
    log.info("v0.1.0", .{});
    return;
}

if (matches.getSingleValue("FILE")) |f| {
    log.info("List contents of {f}");
    return;
}

if (matches.subcommandMatches("update")) |update_cmd_matches| {
    if (update_cmd_matches.containsArg("check-only")) {
        std.log.info("Check and report new update", .{});
        return;
    }

    if (update_cmd_matches.getSingleValue("branch")) |branch| {
        std.log.info("Branch to update: {s}", .{branch});
        return;
    }
    return;
}

if (matches.containsArg("all")) {
    log.info("show all", .{});
    return;
}

if (matches.containsArg("recursive")) {
    log.info("show recursive", .{});
    return;
}

if (matches.getSingleValue("ignore")) |pattern| {
    log.info("ignore pattern = {s}", .{pattern});
    return;
}

if (matches.containsArg("color")) {
    const when = matches.getSingleValue("color").?;

    log.info("color={s}", .{when});
    return;
}

Handling Help

The handling of -h or --help option and the automatic display of usage information are taken care by the library. However, if you need to manually display the help information, there are two functions available: App.displayHelp() and App.displaySubcommandHelp().

  • App.displayHelp() prints the help information for the root command, providing a simple way to display the overall usage and description of the application.

  • On the other hand, App.displaySubcommandHelp() queries the sepecifed subcommand on the command line and displays its specific usage information.

if (!matches.containsArgs()) {
    try app.displayHelp();
    return;
}

if (matches.subcommandMatches("update")) |update_cmd_matches| {
    if (!update_cmd_matches.containsArgs()) {
        try app.displaySubcommandHelp();
        return;
    }
}

Putting it All Together

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

const allocator = std.heap.page_allocator;
const log = std.log;
const App = yazap.App;
const Arg = yazap.Arg;

pub fn main() anyerror!void {
    var app = App.init(allocator, "myls", "My custom ls");
    defer app.deinit();

    var myls = app.rootCommand();

    var update_cmd = app.createCommand("update", "Update the app or check for new updates");
    try update_cmd.addArg(Arg.booleanOption("check-only", null, "Only check for new update"));
    try update_cmd.addArg(Arg.singleValueOptionWithValidValues("branch", 'b', "Branch to update", &[_][]const u8{
        "stable",
        "nightly",
        "beta"
    }));

    try myls.addSubcommand(update_cmd);

    try myls.addArg(Arg.positional("FILE", null, null));
    try myls.addArg(Arg.booleanOption("all", 'a', "Don't ignore the hidden directories"));
    try myls.addArg(Arg.booleanOption("recursive", 'R', "List subdirectories recursively"));
    try myls.addArg(Arg.booleanOption("one-line", '1', null));
    try myls.addArg(Arg.booleanOption("size", 's', null));
    try myls.addArg(Arg.booleanOption("version", null, null));
    try myls.addArg(Arg.singleValueOption("ignore", 'I', null));
    try myls.addArg(Arg.singleValueOption("hide", null, null));

    try myls.addArg(Arg.singleValueOptionWithValidValues("color", 'C', null, &[_][]const u8{
        "always",
        "auto",
        "never",
    }));

    const matches = try app.parseProcess();
    
    if (!matches.containsArgs()) {
        try app.displayHelp();
        return;
    }

    if (matches.containsArg("version")) {
        log.info("v0.1.0", .{});
        return;
    }

    if (matches.getSingleValue("FILE")) |f| {
        log.info("List contents of {f}");
        return;
    }

    if (matches.subcommandMatches("update")) |update_cmd_matches| {
        if (!update_cmd_matches.containsArgs()) {
            try app.displaySubcommandHelp();
            return;
        }

        if (update_cmd_matches.containsArg("check-only")) {
            std.log.info("Check and report new update", .{});
            return;
        }
        if (update_cmd_matches.getSingleValue("branch")) |branch| {
            std.log.info("Branch to update: {s}", .{branch});
            return;
        }
        return;
    }

    if (matches.containsArg("all")) {
        log.info("show all", .{});
        return;
    }

    if (matches.containsArg("recursive")) {
        log.info("show recursive", .{});
        return;
    }

    if (matches.getSingleValue("ignore")) |pattern| {
        log.info("ignore pattern = {s}", .{pattern});
        return;
    }

    if (matches.containsArg("color")) {
        const when = matches.getSingleValue("color").?;

        log.info("color={s}", .{when});
        return;
    }
}

Alternate Parsers

yazap's People

Contributors

cdmistman avatar higuoxing avatar joshua-software-dev avatar karlseguin avatar knnut avatar monomycelium avatar prajwalch avatar signaryk 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

yazap's Issues

Support passing positional arguments after options

It seems that the parser is confused if it encounters a boolean flag before the positional argument:

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

var gpa = std.heap.GeneralPurposeAllocator(.{
    .safety = true,
}){};

pub fn main() !void {
    const allocator = gpa.allocator();

    var app = yazap.App.init(allocator, "example", null);
    defer app.deinit();

    var root_command = app.rootCommand();

    try root_command.addArg(yazap.flag.boolean("example", null, null));
    try root_command.takesSingleValue("file");

    var args = try app.parseProcess();

    if (args.valueOf("file")) |file| {
        std.debug.print("got file: '{s}'\n", .{ file });
    }
}
$ ./example foo --example
got file: 'foo'
~/tmp/yazap-repro/zig-out/bin (me)
$ ./example --example foo
Unknown Command 'foo'
error: UnknownCommand
/home/me/tmp/yazap-repro/libs/yazap/src/Parser.zig:444:9: 0x25b893 in parseSubCommand (example)
        return Error.UnknownCommand;
        ^
/home/me/tmp/yazap-repro/libs/yazap/src/Parser.zig:109:17: 0x244209 in parse (example)
                try self.parseSubCommand(token.value),
                ^
/home/me/tmp/yazap-repro/libs/yazap/src/App.zig:60:9: 0x21d765 in parseFrom (example)
        return e;
        ^
/home/me/tmp/yazap-repro/libs/yazap/src/App.zig:50:5: 0x2181a0 in parseProcess (example)
    return self.parseFrom(self.process_args.?[1..]);
    ^
/home/me/tmp/yazap-repro/src/main.zig:19:16: 0x217b2e in main (example)
    var args = try app.parseProcess();

`CommandArgumentNotProvided` error when `argRequired` is set to false

Hi, I want to show a help message when no arguments are passed or the "-h" flag is passed.
I set the argRequired option to false, but it returned an error when no arguments passed.

env

  • yazap v0.2.0
  • Zig v0.9.1

Current behavior

...
var parser = Command.new(allocator, "sample");
defer parser.deinit();

var subcmd = Command.new(allocator, "init");
try subcmd.addArg(flag.boolean("help", 'h'));
try subcmd.takesSingleValue("FOOBAR");
subcmd.argRequired(false);
try parser.addSubcommand(subcmd);

var args = try parser.parseProcess();
defer args.deinit();
...
error(zigarg): The command 'sample' requires a value but none is provided

error: CommandArgumentNotProvided
/src/lib/zig-arg/src/parser/Parser.zig:415:13: 0x25401b in .zig-arg.parser.Parser.parseSubCommand (sample)
            return self.err_ctx.err;
            ^
/src/lib/zig-arg/src/parser/Parser.zig:147:28: 0x24ee37 in .zig-arg.parser.Parser.parse (sample
            const subcmd = try self.parseSubCommand(token.value);
                           ^
/src/lib/zig-arg/src/Command.zig:168:9: 0x248e9b in .zig-arg.Command.parseFrom (sample)
        return e;
        ^
/src/lib/zig-arg/src/Command.zig:160:5: 0x243703 in .zig-arg.Command.parseProcess (sample)
    return self.parseFrom(self.process_args.?[1..]);
    ^
/src/src/main.zig:345:16: 0x23cb37 in main (sample)
    var args = try parser.parseProcess();

Expected behavior

no error when "argRequired" is false.

I guess the issue comes from around here. It should take care of this situation and suppress the error when "argRequired" is false.

https://github.com/PrajwalCH/yazap/blob/4153a51dea5f1c3771a85fbff1b9b330988a73db/src/parser/Parser.zig#L413-L415

Make a new release with new updates

This is already fixed on main, just make a release with it to be able to be used with zon

/.../build.zig:48:8: error: no field or member function named 'addModule' in 'Build.Step.Compile'
    exe.addModule(name, depend.module(name));
    ~~~^~~~~~~~~~
/usr/lib/zig/std/Build/Step/Compile.zig:1:1: note: struct declared here
const builtin = @import("builtin");
^~~~~
referenced by:
    build: /.../build.zig:28:5
    runBuild__anon_8996: /usr/lib/zig/std/Build.zig:2079:27
    remaining reference traces hidden; use '-freference-trace' to see all reference traces
/home/.../.cache/zig/p/12201e962f43c58af33e657d31402f403abb16a0ede31a3dc5a8f91dfb795ba0db5d/build.zig:3:21: error: root struct of file 'std' has no member named 'build'
pub fn build(b: *std.build.Builder) void {

Latest Zig, won't build

Taking out docs.emit_docs = .emit; from build.zig works, but not sure if that's the right/only solution.

integer constraint

Thanks for the library.

Any chance to add something like singleValueOptionInt(T) which parses the input into an int of type T (making sure both that it's a valid integer and that it's within min(T) and max(T).

Add arbitrary subcommands

Hello there,

first of all, great project!
However i was wondering whether or not it would be a good addition to actually allow for arbitrary subcommands, as an example:

I have created a simple cli-tool that converts the first argument given to the program, like for example so:
./my_binary 1337 which then would parse the 1337 and i would be able to use said value.

I am not sure if this would be in scope of this project, but i think it would greatly benefit this library.
Also; i might be able to implement and contribute it myself, however i am not sure if this is in scope of this project. (hence writing an issue)

make it work on zig `v0.10.1`

Yazap is already tested against v0.9.1 and v0.10.0 but running the zig build test command on v0.10.1 exists unexpectedly with code 5, no any stack traces or any kind of error.

$ zig build test
LLVM Emit Object... error: test...
error: The following command exited with error code 5:
...

But if i remove all the tests and run it again the above issue doesn't occurs. I'm not sure whether it's a issue on compiler or on project itself.

Both examples fail to compile

Error:

> zig build run
/home/dh/zig/zig-linux-x86_64-0.10.0-dev.4176+6d7b0690a/lib/std/fmt.zig:498:17: error: cannot format optional without a specifier (i.e. {?} or {any})
                @compileError("cannot format optional without a specifier (i.e. {?} or {any})");
                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    format__anon_9521: /home/dh/zig/zig-linux-x86_64-0.10.0-dev.4176+6d7b0690a/lib/std/fmt.zig:183:13
    print__anon_9237: /home/dh/zig/zig-linux-x86_64-0.10.0-dev.4176+6d7b0690a/lib/std/io/writer.zig:28:27
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

error: cat...
error: The following command exited with error code 1:
/home/dh/zig/zig-linux-x86_64-0.10.0-dev.4176+6d7b0690a/zig build-exe /home/dh/workspace/zigstuff/test/src/main.zig --cache-dir /home/dh/workspace/zigstuff/test/zig-cache --global-cache-dir /home/dh/.cache/zig --name cat --pkg-begin yazap /home/dh/workspace/zigstuff/test/libs/yazap/src/lib.zig --pkg-end --enable-cache
error: the following build command failed with exit code 1:
/home/dh/workspace/zigstuff/test/zig-cache/o/4cdeb5d65f9fe53c51b10b0e5c9af910/build /home/dh/zig/zig-linux-x86_64-0.10.0-dev.4176+6d7b0690a/zig /home/dh/workspace/zigstuff/test /home/dh/workspace/zigstuff/test/zig-cache /home/dh/.cache/zig run

Zig version:
0.10.0-dev.4176+6d7b0690a

Automatic help text generation

Hi, thank you for creating this awesome library.

I have one feature request. (it could have already been considered though)
It would be more helpful for users to have automatic help text generation based on configured flags.

Yazap already has a Command.newWithHelpTxt function to set the about field, but currently, it doesn't have any way to output its information except for directly reading about and constructing our own help message by ourselves.

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.