A simple flag parser for Zig programs.
- allowDups: Don't error when duplicate flags are set. Default is false.
- verbose: Print out error messages when errors occur. Default is false.
- writer: Required when using verbose option. Doesn't really do anything without it. Default is null.
- prefix: Print out a custom string for verbose messages. Default is null.
- allowDashInput: Allow input type flags to hold strings that begin with "-". Default is true.
- errOnNoArgs: Outputs an error if there are no arguments except argv[0]. Default is false.
- exitFirstErr: Exit on first error found. Default is true.
Config for Type.Flags.usage() method
- padding_left: Number of whitespaces before the tag. Default is 0
- printUntagged: Print untagged flags. Default is false.
- untaggedFirst: Print untagged flags first. Prints last when false. Default is true.
- Fetch with zig and add as module in build.zig
zig fetch --save https://github.com/koeir/flagparse/archive/refs/tags/v0.x.x.tar.gz // build.zig
const flagparse = b.dependency("flagparse", .{
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable(.{
.name = "example",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
})
});
exe.root_module.addImport("flagparse", flagparse.module("flagparse"));
b.installArtifact(exe);- Declare a list of flags with the built-in structs
const Switch = flagparse.Type.Switch; // default is false
const Argumentative = flagparse.Type.Argumentative; // default is null
const initflags: flagparse.Type.Flags = .{
.list = &[_] flagparse.Type.Flag
{
.{
.name = "recursive",
.tag = "Switches",
.long = "recursive",
.short = 'r',
.value = Switch, // a different default value can be set with .{ Switch/Argumentative = <value> }
.desc = "Recurse into directories",
},
.{
.name = "force",
.tag = "Switches",
.long = "force",
.short = 'f',
.vanity = "-[n|f], --[no-]force", // overwrites long and short flags in printing
.value = Switch,
.desc = "Skip confirmation prompts",
},
.{ // by default, untagged flags will not be printed
.name = "no-force",
.long = "no-force",
.short = 'n',
.value = Switch,
.desc = "Do not skip confirmation prompts",
},
// Arguments will accept the next argv
// e.g. -prf noob
// "noob" will be accepted as the file
.{
.name = "file",
.tag = "Input",
.long = "path",
.short = 'p',
.value = Argumentative,
.desc = "Path to file",
},
}
};- Initialize args, allocators, and error pointer for handling
const std = @import("std");
const flagparse = @import("flagparse");
pub fn main(init: std.process.Init) !void {
const io = init.io;
const min = init.minimal;
var gpa = std.heap.DebugAllocator(.{}){};
var arena: std.heap.ArenaAllocator = .init(gpa.allocator());
defer arena.deinit();
// points to last flag on error
var errptr: ?[]const u8 = null;
...
- Parse
const std = @import("std");
const flagparse = @import("flagparse");
pub fn main() !void {
...
// returns a tuple of Flags and resulting args
// resulting args is a maybe value
const result = flagparse.parse(arena.allocator(), min.args, initflags, &errptr, .{}) catch |err| {
const arg_error = errptr.?;
// handle err
return;
};
...
- Use
...
// retrieve tuple values
const flags = result.flags;
const flagless_args = result.argv;
const recursive: bool = flags.get_value("recursive").?.Switch;
const file: ?[:0]const u8 = flags.get_value("file").?.Argumentative;
if (recursive) // do stuff
if (flagless_args) |args| {
// do stuff
}
...Other ways to retrieve flags:
// returns null if not found
pub fn get(self: *const Self, name: []const u8) ?*const Flag {
return for (self.list) |flag| {
if (std.mem.eql(u8, flag.name, name)) break &flag;
} else null;
}
// errs if not found
pub fn try_get(self: *const Self, name: []const u8) FlagError!*const Flag {
return for (self.list) |flag| {
if (std.mem.eql(u8, flag.name, name)) break &flag;
} else FlagError.NoSuchFlag;
}
pub fn get_with_flag(self: *const Self, flag: []const u8) ?*const Flag {
return for (self.list) |*ret| {
if (ret.short) |short| {
if (flag[0] == short) break ret;
}
if (ret.long) |long| {
if (std.mem.eql(u8, flag, long)) break ret;
}
} else null;
}
pub fn get_value(self: *const Self, name: []const u8) ?FlagVal {
const flag = self.get(name) orelse return null;
return flag.value;
} // Type.Flag
pub const Format = struct {
fillerStyle: u8 = ' ',
greyOutFiller: bool = false,
greyOutDesc: bool = false,
columns: enum {
one, two
} = .two,
padding: struct {
left: usize = 1,
desc_left: usize = 1, // useless for columns.two; applied on top of .left
center: usize = 30, //useless for columns.one
} = .{},
};
// Type.Flags
pub const UsageConfig = struct {
padding_left: usize = 0,
printUntagged: bool = false,
untaggedFirst: bool = true,
tagStyle: enum {
brackets, colon, underline
} = .colon
};
The Flags struct has a method usage() that prints all flags with their respective tags. Tags are printed in the order that they first appear in the default flags.
Switches:
-r, --recursive Recurse into directories
-[n|f], --[no-]force Don\'t/skip confirmation prompts
Input:
-p <file>, --path <file> Path to fileIndividual flags: Type.Flag can be printed with their format() method via {f} print format.
// e.g.
// This affects the printing of `Type.Flags.usage()` too
flagparse.Type.Flag.fmt = {
.style = '.', // change what is printed between the flags and descriptions; default is whitespace (' ')
.columns= .two, // flags and descriptions as one/two columns; default is .two
.left = 1,
.center = 20,
} -r, --recursive.... Recurse into directoriesMore examples:
// Different style
flagparse.Type.Flag.fmt = .{
.columns = .one,
.padding = .{
.left = 5,
},
.style = ' ', // default
};
try stdout.writeAll("\nUsage:\n\n");
try initflags.usage(stdout, .{ .padding_left = 2, .tagStyle = .brackets });Usage:
[Switches]
-r, --recursive
Recurse into directories
--[no-]force
Don\'t/skip confirmation prompts
[Input]
-p <file>, --path <file>
Path to filepub const FlagErrs = error {
NoArgs, // argc < 2
NoSuchFlag, // unrecognized flag in arg list
FlagNotSwitch, // non-switch/non-bool Flag treated as a switch/bool
FlagNotArg, // non-argumentative flag treated as an argumentative
DuplicateFlag, // flag appears twice in arg list; can be ignored with config
ArgNoArg, // no argument given to argumentative flag
NoWriter, // no writer given when verbose is true
TypeMismatch, // a more general FlagNotSwitch/FlagNotArg
}