{"id":22448546,"url":"https://github.com/knutwalker/args-lex","last_synced_at":"2025-07-30T21:09:26.855Z","repository":{"id":265873243,"uuid":"896772304","full_name":"knutwalker/args-lex","owner":"knutwalker","description":"A commandline arguments lexer for Zig","archived":false,"fork":false,"pushed_at":"2025-03-22T18:40:29.000Z","size":143,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-22T19:22:49.502Z","etag":null,"topics":["argparse","clap","cli","zig"],"latest_commit_sha":null,"homepage":"https://knutwalker.github.io/args-lex/","language":"Zig","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/knutwalker.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-12-01T09:10:39.000Z","updated_at":"2025-03-22T18:40:32.000Z","dependencies_parsed_at":null,"dependency_job_id":"5a069b21-ee74-4516-a885-92c3c8a0c9c6","html_url":"https://github.com/knutwalker/args-lex","commit_stats":null,"previous_names":["knutwalker/args-lex"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knutwalker%2Fargs-lex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knutwalker%2Fargs-lex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knutwalker%2Fargs-lex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/knutwalker%2Fargs-lex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/knutwalker","download_url":"https://codeload.github.com/knutwalker/args-lex/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245841234,"owners_count":20681181,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["argparse","clap","cli","zig"],"created_at":"2024-12-06T04:29:00.811Z","updated_at":"2025-03-27T11:41:53.741Z","avatar_url":"https://github.com/knutwalker.png","language":"Zig","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- Autogenerated: Edit `README.md.tpl` and run `zig build readme` --\u003e\n\n# args_lex [![CI Status][ci-badge]][ci-url] ![License: MIT][license-badge]\n\n[ci-badge]: https://github.com/knutwalker/args-lex/actions/workflows/ci.yml/badge.svg\n[ci-url]: https://github.com/knutwalker/args-lex\n[license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=shield\n\nA lexer to parse command line arguments\n\n\u003e [!NOTE]\n\u003e This is not a full CLI parser.\n\u003e A lot of functionality is intentionally missing.\n\n## Features / Parsing capabilities*\n\n- Long arguments: `--flag`\n    - values separated by space or `=` (`--flag=value`, `--flag value`)\n    - multiple occurrences: `--flag one --flag two`\n- Short arguments: `-a`\n    - multiple flags grouped together: `-abc`\n    - values separated by space or `=` (`-a=value`, `-a value`, `-avalue`)\n    - repeated flags: `-vvvv`\n- Positional arguments: `arg`\n    - escaping remaining arguments after `--`\n\n_*_: Some of those features are not directly implemented by this library.\nHowever, parsers written using the lexer can choose to support those features\nand are not locked out of anything.\n\n## Non-Features\n\n- help generation\n- arguments to type mapping\n- builder API\n\n## Installation\n\nUpdate to latest version:\n\n```sh\nzig fetch --save git+https://github.com/knutwalker/args-lex.git\n```\n\nAdd to `build.zig`:\n\n```zig\nexe.root_module.addImport(\"args_lex\", b.dependency(\"args_lex\", .{}).module(\"args_lex\"));\n```\n\n\u003e [!IMPORTANT]\n\u003e `args_lex` tracks Zig `0.14.0`\n\n## Examples\n\n### Demo\n\nA simple demo, showing the basics of the API:\n\n```zig\nconst std = @import(\"std\");\n\nconst args_lex = @import(\"args_lex\");\n\npub fn main() !void {\n    var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);\n    defer arena.deinit();\n\n    var args = try args_lex.Args.init(arena.allocator());\n    defer args.deinit();\n\n    while (args.next()) |arg| {\n        switch (arg) {\n            .escape =\u003e while (args.nextAsValue()) |value| {\n                std.debug.print(\"POSITIONAL: {s}\\n\", .{value});\n            },\n            .shorts =\u003e |shorts_arg| {\n                var shorts = shorts_arg;\n                while (shorts.next()) |short| switch (short) {\n                    .flag =\u003e |flag| std.debug.print(\"-{u}\\n\", .{flag}),\n                    .suffix =\u003e |s| std.debug.print(\"non utf8 flag: {any}\\n\", .{s}),\n                };\n            },\n            .long =\u003e |long| {\n                std.debug.print(\"--{s}\", .{long.flag});\n                if (long.value) |v| std.debug.print(\"={s}\", .{v});\n                std.debug.print(\"\\n\", .{});\n            },\n            .value =\u003e |v| std.debug.print(\"POSITIONAL: {s}\\n\", .{v}),\n        }\n    }\n}\n\n```\n\n### Usage\n\nA more detailed example showing more usage patterns:\n\n```zig\nconst std = @import(\"std\");\n\nconst args_lex = @import(\"args_lex\");\n\npub fn main() !void {\n    var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{};\n    defer _ = gpa.deinit();\n    const alloc = gpa.allocator();\n\n    // The args should be provided by build.zig as:\n    // `\"-a -42 -bcdef --flag --long=value --also yes -- remaining --args\"`\n    // The lexer does not map automatically to types, we can do this on our own.\n    const MyOptions = struct {\n        a: i32 = -1,\n        b: bool = false,\n        c: ?[:0]const u8 = null,\n        flag: bool = false,\n        long: [:0]const u8 = \"default\",\n        also: enum { auto, yes, no } = .auto,\n        remainder: ?[]const [:0]const u8 = null,\n    };\n\n    // Args can be read lazily (and on POSIX systems without allocations)\n    // by using `Args`, which uses `std.process.args` internally.\n    var process_args = try args_lex.Args.init(alloc);\n\n    // This should be a `defer` instead of the `errdefer`.\n    // In this example, we explicitly ``deinit` the args lexer to test for\n    // dangling pointers and use-after-free.\n    errdefer process_args.deinit();\n\n    // Process args can be read from an existing allocation with `SliceArgs`.\n    // This can be used for args from `argsAlloc`\n    const args_alloc = try std.process.argsAlloc(alloc);\n    defer std.process.argsFree(alloc, args_alloc);\n    var slice_args = args_lex.SliceArgs.init(args_alloc);\n    errdefer slice_args.deinit();\n\n    // One benefit of SliceArgs is that they can be reset with `reset`.\n    // One might, for example, do a first pass to check for `--help`,\n    // then `reset` and do a second pass for the actual parsing.\n\n    // We can also use `std.os.argv` if it is supported by the OS.\n    var args_impls = if (comptime args_lex.OsArgs.isSupported()) impls: {\n        const os_args = args_lex.OsArgs.init();\n        break :impls .{ process_args, slice_args, os_args };\n    } else .{ process_args, slice_args };\n\n    inline for (\u0026args_impls) |*args| {\n\n        // Skip the binary name\n        _ = args.nextAsValue().?;\n\n        // Initialize the result type with all defaults.\n        // If some args are required, they need to be wrapped in an option\n        // and validated after the loop.\n        var options: MyOptions = .{};\n\n        // The main lexing loop. `next` will return an `*Arg`.\n        // Don't store to pointers without making a copy,\n        // every call to `next` will invalidate those pointers.\n        // Calling `next` (or any of its variants) will never fail or panic.\n        // A `null` indicated the end of the arguments list.\n        while (args.next()) |arg| {\n            switch (arg) {\n                // `arg` is the `--` escape. Usually that means that the remaining\n                // args are no longer supposed to be parsed.\n                // That decision is up to the user, and we are doing just that.\n                .escape =\u003e {\n                    var remainder = std.ArrayList([:0]const u8).init(alloc);\n                    defer remainder.deinit();\n\n                    // `nextAsValue` will return the next args without any parsing\n                    while (args.nextAsValue()) |arg_value| {\n                        // The slices returned by any \"value\" like access are\n                        // invalidated when `deinit` is called.\n                        // It is recommended to `dupe` any slice that should\n                        // remain valid after the parsing is done.\n                        const value = try alloc.dupeZ(u8, arg_value);\n                        try remainder.append(value);\n                    }\n\n                    options.remainder = try remainder.toOwnedSlice();\n                },\n                // `arg` is one or more short flags, or a negative number.\n                // That is, it starts with `-`, but not with `--`.\n                .shorts =\u003e |shorts_arg| {\n                    // Command line parsers often do not support negative numbers,\n                    // because it is ambiguous. We can choose to look for negative\n                    // numbers and treat is as a `value` instead. In this example,\n                    // we imply that any negative number must be for the `a` flag.\n                    if (shorts_arg.looks_like_number()) {\n                        // `value` will return everything *after* the leading `-`,\n                        // so we need to negate the number on our own.\n                        options.a = -(try std.fmt.parseInt(i32, shorts_arg.peekValue(), 10));\n                        continue;\n                    }\n                    // Iterate over any short flag. There is no special handling\n                    // of `=` done in `next`.\n                    var shorts = shorts_arg;\n                    while (shorts.next()) |short| switch (short) {\n                        // The typical case, where the short flag is a valid\n                        // Unicode codepoint (`flag` here is a `u21`).\n                        .flag =\u003e |flag| switch (flag) {\n                            // allow, but ignore a flag\n                            'a' =\u003e {},\n                            // flag is given, here we set the option to `true`\n                            'b' =\u003e options.b = true,\n                            // short flags can also take a value, optionally separated\n                            // with a `=`. When we see the `c` flag, we take the\n                            // remainder as its value.\n                            'c' =\u003e options.c = try alloc.dupeZ(u8, shorts.value()),\n                            else =\u003e return error.UnknownFlag,\n                        },\n                        // In case we encounter an invalid Unicode sequence as\n                        // short flags, the parser bails and returns the remainder\n                        // as an unparsed suffix. It is up to the user to decide\n                        // what to do in this case.\n                        .suffix =\u003e return error.UnknownFlag,\n                    };\n                },\n                // `arg` is a long flag.\n                // That is it starts with `--` and is not just `--` (`.escape`).\n                .long =\u003e |long| {\n                    // we can check for the actual arg using `flag` and `value`.\n                    if (std.mem.eql(u8, long.flag, \"flag\")) {\n                        // Some flags are not allowed to take values,\n                        // we can check for that.\n                        if (long.value) |_| {\n                            return error.UnexpectedValue;\n                        }\n                        options.flag = true;\n                    } else if (std.mem.eql(u8, long.flag, \"long\")) {\n                        // the provided `value` is only set if it was given using the\n                        // `--flag=value` syntax, but often `--flag value` should\n                        // also be allowed. We can use `nextAsValue` to take the next\n                        // argument as value, no matter how it looks.\n                        // Here, the flag also requires a value.\n                        const value = long.value orelse (args.nextAsValue() orelse return error.MissingValue);\n                        options.long = try alloc.dupeZ(u8, value);\n                    } else if (std.mem.eql(u8, long.flag, \"also\")) {\n                        // Here, the flag has an optional value.\n                        const value = long.value orelse args.nextAsValue();\n                        // If there is a value, we can validate it.\n                        // Here, we only allow values that are enum tags.\n                        // A missing value will default to the `.auto` option.\n                        options.also = if (value) |v|\n                            (std.meta.stringToEnum(@TypeOf(options.also), v) orelse return error.UnknownValueForEnumFlag)\n                        else\n                            .auto;\n                    } else {\n                        return error.UnknownFlag;\n                    }\n                },\n                // Any plain value that is not a `-` short flag, `--` long flag\n                // or the special value `--` (`.escape`).\n                // It is up to the user to decide what to do.\n                // Those might be a subcommand, a freestanding/positional args,\n                // or - depending on how the parsing loop is written - values for\n                // a previous short or long option.\n                // Here, we don't allow those args.\n                .value =\u003e return error.UnexpectedValueBeforeEscape,\n            }\n        }\n\n        // print the result so that we can see something in the example\n        std.debug.print(\n            \\\\Parsed options using {s}:\n            \\\\  --a: {}\n            \\\\  --b: {}\n            \\\\  --c: {?s}\n            \\\\  --flag: {}\n            \\\\  --long: {s}\n            \\\\  --also: {s}\n            \\\\  --remainder: {?s}\n            \\\\\n        , .{\n            @typeName(@TypeOf(args.*)),\n            options.a,\n            options.b,\n            options.c,\n            options.flag,\n            options.long,\n            @tagName(options.also),\n            options.remainder,\n        });\n\n        // explicit call to test that we don't have values pointing to the args buffer.\n        // Usually, this would be done by `defer args.deinit()`.\n        args.deinit();\n\n        // validate that we parsed into the same result for any impl\n        const expected = MyOptions{\n            .a = -42,\n            .b = true,\n            .c = \"def\",\n            .flag = true,\n            .long = \"value\",\n            .also = .yes,\n            .remainder = \u0026[_][:0]const u8{ \"remaining\", \"--args\" },\n        };\n        defer {\n            if (options.c) |c| alloc.free(c);\n            alloc.free(options.long);\n            if (options.remainder) |remainder| {\n                for (remainder) |value| alloc.free(value);\n                alloc.free(remainder);\n            }\n        }\n\n        try std.testing.expectEqualDeep(expected, options);\n    }\n}\n\n```\n\n## License\n\nargs_lex is licensed under the [MIT License](http://opensource.org/licenses/MIT)\n\n---\n\n\u003c!-- vim: set ft=markdown: --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknutwalker%2Fargs-lex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fknutwalker%2Fargs-lex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fknutwalker%2Fargs-lex/lists"}