A dead-simple declarative CLI library for Zig.
Define your CLI as a single config struct. The library handles parsing, help generation, version output, subcommands, positionals, and typed flag values.
const std = @import("std");
const pluck = @import("pluck");
fn run(ctx: pluck.Context) !void {
const path = if (ctx.argStr("path").len > 0) ctx.argStr("path") else ".";
const depth = ctx.argInt("depth");
const noColor = ctx.argBool("no-color");
std.debug.print("path={s}, depth={d}, no-color={}\n", .{ path, depth, noColor });
}
pub fn main(init: std.process.Init) !void {
const alloc = init.arena.allocator();
const args = try init.minimal.args.toSlice(alloc);
const config = pluck.Config{
.name = "myapp",
.desc = "An example CLI",
.version = "0.1.0",
.userArgs = args,
.flags = &.{
.{ .long = "--depth", .short = "-d", .desc = "Max depth", .type = .Int },
.{ .long = "--no-color", .short = "", .desc = "Disable colors", .type = .Bool },
},
.positionals = &.{
.{ .name = "path", .desc = "Path to operate on" },
},
.run = &run,
};
try pluck.run(alloc, init.io, config);
}- Declarative config - no builder methods, no chained calls
- Typed flag values:
Bool,Int,Str - Positional arguments
- Subcommands with their own flags
- Auto-generated
--help/-h - Auto-generated
--version/-v - Clean error returns for missing values, invalid values, and unknown args
Inside your run function, pull values from the context:
ctx.argBool("flag-name")
ctx.argInt("flag-name")
ctx.argStr("flag-name")Positionals are accessed the same way as flags - by the name you gave them in the config.
.commands = &.{
.{
.name = "commit",
.desc = "Commit changes",
.flags = &.{
.{ .long = "--message", .short = "-m", .desc = "Message", .type = .Str },
},
.run = &commitFn,
},
},When the user runs myapp commit --message "hello", pluck dispatches to commitFn with the commit's flags parsed.
pluck.run returns a RunError for parsing issues:
error.MissingValue- a flag was given without its required valueerror.InvalidValue- a flag's value could not be parsed (e.g. non-numeric for anIntflag)error.UnknownArg- an unrecognized argument was passed
User callbacks can return any error - they propagate through.
pluck.run(alloc, io, config) catch |err| switch (err) {
error.MissingValue => std.debug.print("Missing value for flag\n", .{}),
error.UnknownArg => std.debug.print("Unknown argument\n", .{}),
error.InvalidValue => std.debug.print("Invalid value for flag\n", .{}),
else => return err,
};Add pluck to your build.zig.zon:
.dependencies = .{
.pluck = .{
.url = "https://codeberg.org/ScopeSV/pluck/archive/v0.1.0.tar.gz",
.hash = "...",
},
},Then in build.zig:
const pluck = b.dependency("pluck", .{});
exe.root_module.addImport("pluck", pluck.module("pluck"));MIT