2024-11-23 23:26:03 +00:00
|
|
|
const std = @import("std");
|
|
|
|
|
|
2024-11-25 17:06:17 +00:00
|
|
|
const builtin = @import("builtin");
|
|
|
|
|
|
|
|
|
|
const inDebugMode = builtin.mode == .Debug;
|
|
|
|
|
|
2024-11-23 23:26:03 +00:00
|
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
|
|
|
|
|
|
pub const parsers = @import("./parsers/parsers.zig");
|
|
|
|
|
|
|
|
|
|
const Arg = @import("./arg.zig").Arg;
|
2024-11-28 15:57:17 +00:00
|
|
|
const niceTypeName = @import("./utils.zig").niceTypeName;
|
2024-11-23 23:26:03 +00:00
|
|
|
|
2024-11-24 20:15:48 +00:00
|
|
|
const log = std.log.scoped(.args);
|
|
|
|
|
|
2024-11-23 23:26:03 +00:00
|
|
|
/// Metadata for a field that is parseable by the argument parser.
|
|
|
|
|
pub const Extra = union(enum(u2)) {
|
2024-11-28 15:57:17 +00:00
|
|
|
Positional: struct {
|
|
|
|
|
about: ?[]const u8 = null,
|
|
|
|
|
typeHint: ?[]const u8 = null,
|
|
|
|
|
},
|
2024-11-23 23:26:03 +00:00
|
|
|
Remainder,
|
|
|
|
|
Flag: struct {
|
|
|
|
|
name: []const u8,
|
|
|
|
|
short: ?[]const u8 = null,
|
2024-11-28 15:57:17 +00:00
|
|
|
about: ?[]const u8 = null,
|
|
|
|
|
typeHint: ?[]const u8 = null,
|
2024-11-23 23:26:03 +00:00
|
|
|
toggle: bool = false,
|
|
|
|
|
takesValue: bool = false,
|
2024-11-28 16:08:34 +00:00
|
|
|
/// Ensures that if the flag is present, the parser will not continue parsing.
|
|
|
|
|
///
|
|
|
|
|
/// Warning: this is a dangerous option, as all subsequent fields will be ignored even if they have values.
|
|
|
|
|
/// Only use this for flags like `--help` or `--version` or similar.
|
|
|
|
|
shortCircuit: bool = false,
|
2024-11-23 23:26:03 +00:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// A marker for any field `T` that you want to be parseable by the argument parser.
|
|
|
|
|
pub fn Marker(comptime T: type) type {
|
|
|
|
|
return struct {
|
2024-11-28 20:56:55 +00:00
|
|
|
value: T = undefined,
|
2024-11-23 23:26:03 +00:00
|
|
|
extra: Extra,
|
|
|
|
|
/// Optional function that parses the value of the field.
|
|
|
|
|
/// If this is null, no parsing will take place.
|
|
|
|
|
///
|
|
|
|
|
/// This function can be any function that takes a `[]const u8` and returns `anyerror!T`.
|
|
|
|
|
///
|
|
|
|
|
/// However, it is recommended to use `lys.args.parsers` for common types.
|
|
|
|
|
parse: ?parsers.ParseSignature(T) = null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-28 01:14:52 +00:00
|
|
|
pub const help = @import("./help.zig");
|
|
|
|
|
|
2024-11-23 23:26:03 +00:00
|
|
|
/// <https://git.cutie.zone/lyssieth/zither/issues/1>
|
2024-11-28 16:08:34 +00:00
|
|
|
///
|
|
|
|
|
/// Parsing order of arguments is based on the order they are declared in `T`.
|
2024-11-23 23:26:03 +00:00
|
|
|
pub fn parseArgs(comptime T: type, allocator: Allocator) !T {
|
|
|
|
|
const args = try std.process.argsAlloc(allocator);
|
|
|
|
|
defer std.process.argsFree(allocator, args);
|
|
|
|
|
|
2024-11-28 20:56:55 +00:00
|
|
|
return parseArgsFromSlice(T, allocator, args[1..]);
|
2024-11-23 23:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
/// All items of `args` must be valid, otherwise you will get a General Protection Fault.
|
2024-11-28 20:56:55 +00:00
|
|
|
/// Do not pass the process name as an argument.
|
2024-11-28 16:08:34 +00:00
|
|
|
///
|
|
|
|
|
/// Parsing order of arguments is based on the order they are declared in `T`.
|
2024-11-25 18:06:20 +00:00
|
|
|
pub fn parseArgsFromSlice(comptime T: type, allocator: Allocator, args: [][]const u8) !T {
|
2024-11-23 23:26:03 +00:00
|
|
|
var flags = std.ArrayList(Arg).init(allocator);
|
|
|
|
|
defer flags.deinit();
|
|
|
|
|
|
2024-11-28 20:56:55 +00:00
|
|
|
if (args.len == 0) {
|
2024-11-23 23:26:03 +00:00
|
|
|
return error.NoArguments;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-28 20:56:55 +00:00
|
|
|
for (args) |arg| {
|
2024-11-25 18:06:20 +00:00
|
|
|
if (arg.len == 0) continue; // skip empty args
|
|
|
|
|
if (@intFromPtr(arg.ptr) == 0xaaaaaaaaaaaaaaaa) return error.UninitializedArgument;
|
2024-11-23 23:26:03 +00:00
|
|
|
|
|
|
|
|
const argument = try Arg.parse_arg(arg);
|
|
|
|
|
|
|
|
|
|
try flags.append(argument);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var i: usize = 0;
|
|
|
|
|
for (0..flags.items.len) |idx| {
|
|
|
|
|
const flag = &flags.items[idx];
|
|
|
|
|
switch (flag.*) {
|
|
|
|
|
.Positional => |*p| {
|
|
|
|
|
p.idx = i;
|
|
|
|
|
i += 1;
|
|
|
|
|
},
|
|
|
|
|
.Flag => continue,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = initFromParsed(T, allocator, flags.items);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// helper that finds a flag by name (or short name), if it's not consumed
|
|
|
|
|
fn find(flags: []Arg, expected_long: []const u8, expected_short: ?[]const u8) ?*Arg {
|
|
|
|
|
for (flags) |*flag| {
|
|
|
|
|
if (flag.isConsumed()) continue; // skip consumed flags
|
|
|
|
|
|
|
|
|
|
switch (flag.*) {
|
|
|
|
|
.Flag => |f| {
|
|
|
|
|
if (std.mem.eql(u8, f.name, expected_long)) {
|
|
|
|
|
return flag;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (expected_short == null) continue;
|
|
|
|
|
|
|
|
|
|
if (std.mem.eql(u8, f.name, expected_short.?)) {
|
|
|
|
|
return flag;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
.Positional => continue,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// helper that takes the next non-consumed positional argument
|
|
|
|
|
fn nextPositional(flags: []Arg) ?*Arg {
|
|
|
|
|
for (flags) |*flag| {
|
|
|
|
|
if (flag.isConsumed()) continue; // skip consumed flags
|
|
|
|
|
|
|
|
|
|
switch (flag.*) {
|
|
|
|
|
.Flag => continue,
|
|
|
|
|
.Positional => |_| {
|
|
|
|
|
return flag;
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Does the actual work of initializing T from flags.
|
|
|
|
|
fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
|
|
|
|
comptime {
|
2024-11-24 20:15:48 +00:00
|
|
|
if (!@hasField(T, "allocator"))
|
|
|
|
|
@compileError("T must have an allocator");
|
|
|
|
|
if (!@hasDecl(T, "deinit"))
|
|
|
|
|
@compileError("T must have a deinit function");
|
2024-11-23 23:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var result = T{
|
|
|
|
|
.allocator = allocator,
|
|
|
|
|
};
|
|
|
|
|
errdefer result.deinit();
|
|
|
|
|
|
|
|
|
|
const this = @TypeOf(result);
|
|
|
|
|
const info = @typeInfo(this);
|
|
|
|
|
|
|
|
|
|
if ((info.Struct.fields.len -| 1) == 0) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline for (info.Struct.fields) |field| {
|
|
|
|
|
if (comptime std.mem.eql(u8, field.name, "allocator")) {
|
|
|
|
|
continue; // skip allocator
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fie = &@field(result, field.name);
|
2024-11-29 17:36:56 +00:00
|
|
|
const fieldType = @typeInfo(@TypeOf(fie.*.value));
|
2024-11-25 17:06:17 +00:00
|
|
|
const extra: Extra = fie.*.extra;
|
2024-11-23 23:26:03 +00:00
|
|
|
|
|
|
|
|
switch (extra) {
|
|
|
|
|
.Flag => |f| {
|
|
|
|
|
if (!(f.takesValue or f.toggle)) {
|
2024-11-25 18:06:20 +00:00
|
|
|
log.err("flag `{s}` must be a toggle or take a value, but it was neither", .{f.name});
|
|
|
|
|
unreachable;
|
2024-11-23 23:26:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const maybe_flag = find(flags, f.name, f.short);
|
|
|
|
|
|
|
|
|
|
if (maybe_flag) |flag| {
|
|
|
|
|
flag.setConsumed();
|
|
|
|
|
if (f.toggle) {
|
|
|
|
|
if (@TypeOf(fie.*.value) == bool) {
|
|
|
|
|
fie.*.value = true;
|
2024-11-28 16:08:34 +00:00
|
|
|
|
|
|
|
|
if (f.shortCircuit) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2024-11-23 23:26:03 +00:00
|
|
|
comptime continue;
|
|
|
|
|
} else {
|
2024-11-25 18:06:20 +00:00
|
|
|
log.err("invalid toggle `{s}`: expected T == bool, found: {s}", .{
|
2024-11-23 23:26:03 +00:00
|
|
|
f.name,
|
2024-11-25 18:06:20 +00:00
|
|
|
niceTypeName(@TypeOf(fie.*.value)),
|
2024-11-23 23:26:03 +00:00
|
|
|
});
|
2024-11-25 18:06:20 +00:00
|
|
|
return error.InvalidToggle;
|
2024-11-23 23:26:03 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (f.takesValue) {
|
|
|
|
|
if (flag.*.Flag.value) |value| {
|
|
|
|
|
if (@TypeOf(fie.*.value) == []const u8) {
|
|
|
|
|
fie.*.value = try result.allocator.dupe(u8, value);
|
2024-11-28 16:08:34 +00:00
|
|
|
|
|
|
|
|
if (f.shortCircuit) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2024-11-23 23:26:03 +00:00
|
|
|
comptime continue;
|
|
|
|
|
} else {
|
|
|
|
|
if (fie.*.parse) |parse_fn| {
|
2024-11-24 20:15:48 +00:00
|
|
|
fie.*.value = parse_fn(value) catch |err| {
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("could not parse flag `{s}`: `{s}`", .{
|
2024-11-24 20:15:48 +00:00
|
|
|
field.name,
|
|
|
|
|
@errorName(err),
|
|
|
|
|
});
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("- tried to parse: {s}", .{value});
|
2024-11-25 18:06:20 +00:00
|
|
|
log.err("- expected type: {s}", .{niceTypeName(@TypeOf(fie.*.value))});
|
2024-11-24 20:15:48 +00:00
|
|
|
return error.InvalidFlag;
|
|
|
|
|
};
|
2024-11-28 16:08:34 +00:00
|
|
|
|
|
|
|
|
if (f.shortCircuit) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2024-11-23 23:26:03 +00:00
|
|
|
comptime continue;
|
|
|
|
|
} else {
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("flag `{s}` expected a value, but no parser was provided for type `{s}`", .{
|
2024-11-24 20:15:48 +00:00
|
|
|
f.name,
|
2024-11-25 18:06:20 +00:00
|
|
|
niceTypeName(@TypeOf(fie.*.value)),
|
2024-11-24 20:15:48 +00:00
|
|
|
});
|
2024-11-23 23:26:03 +00:00
|
|
|
return error.NoValueForFlag;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unreachable;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("flag `{s}` expected a value, but none was provided", .{f.name});
|
2024-11-23 23:26:03 +00:00
|
|
|
return error.NoValueForFlag;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("invalid flag `{s}`: is not a toggle and doesn't take a value", .{f.name});
|
2024-11-23 23:26:03 +00:00
|
|
|
return error.InvalidFlag;
|
2024-11-25 18:06:20 +00:00
|
|
|
}
|
2024-11-25 17:06:17 +00:00
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
switch (fieldType) {
|
|
|
|
|
.Optional => |_| {
|
|
|
|
|
log.debug("flag `{s}` is optional, and we couldn't find a value for it, so leaving as default value", .{f.name});
|
|
|
|
|
comptime continue;
|
|
|
|
|
},
|
2024-11-25 17:06:17 +00:00
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
else => {
|
|
|
|
|
if (f.toggle) {
|
|
|
|
|
if (@TypeOf(fie.*.value) == bool) {
|
|
|
|
|
fie.*.value = false;
|
|
|
|
|
comptime continue;
|
2024-11-25 17:06:17 +00:00
|
|
|
} else {
|
2024-11-25 18:06:20 +00:00
|
|
|
log.err("invalid toggle `{s}`: expected T == bool, found: {s}", .{
|
2024-11-25 17:06:17 +00:00
|
|
|
f.name,
|
2024-11-25 18:06:20 +00:00
|
|
|
niceTypeName(@TypeOf(fie.*.value)),
|
2024-11-25 17:06:17 +00:00
|
|
|
});
|
2024-11-25 18:06:20 +00:00
|
|
|
return error.InvalidToggle;
|
2024-11-25 17:06:17 +00:00
|
|
|
}
|
2024-11-25 18:06:20 +00:00
|
|
|
}
|
2024-11-25 17:06:17 +00:00
|
|
|
|
2024-11-28 15:57:44 +00:00
|
|
|
if (builtin.is_test) {
|
|
|
|
|
// early return to not pollute the stderr
|
|
|
|
|
return error.NoValueForFlag;
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
if (inDebugMode) {
|
|
|
|
|
log.warn("flag `{s}` expected a value, but none was provided", .{f.name});
|
|
|
|
|
log.warn("expected type: {s}", .{niceTypeName(@TypeOf(fie.*.value))});
|
|
|
|
|
} else {
|
|
|
|
|
log.err("flag `{s}` is required (expected type `{s}`)", .{
|
|
|
|
|
f.name,
|
|
|
|
|
niceTypeName(@TypeOf(fie.*.value)),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log.warn("hint: try `--{s}=<value>`", .{f.name});
|
|
|
|
|
if (f.short) |short| {
|
|
|
|
|
log.warn("hint: try `-{s}=<value>`", .{short});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return error.NoValueForFlag;
|
|
|
|
|
},
|
2024-11-23 23:26:03 +00:00
|
|
|
}
|
2024-11-25 18:06:20 +00:00
|
|
|
|
|
|
|
|
log.err("invalid flag `{s}`: could not be located in args", .{f.name});
|
|
|
|
|
return error.CouldNotFindFlag;
|
2024-11-23 23:26:03 +00:00
|
|
|
},
|
|
|
|
|
.Remainder => {
|
|
|
|
|
var not_consumed = std.ArrayList([]const u8).init(result.allocator);
|
|
|
|
|
errdefer not_consumed.deinit();
|
|
|
|
|
|
|
|
|
|
for (flags) |*flag| {
|
|
|
|
|
if (flag.isConsumed()) continue;
|
|
|
|
|
|
|
|
|
|
switch (flag.*) {
|
|
|
|
|
.Flag => |f| {
|
2024-11-28 20:56:55 +00:00
|
|
|
flag.setConsumed();
|
|
|
|
|
const flagText = blk: {
|
|
|
|
|
if (f.value) |value| {
|
|
|
|
|
break :blk try std.fmt.allocPrint(result.allocator, "{s}={s}", .{
|
|
|
|
|
f.name,
|
|
|
|
|
value,
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
break :blk try result.allocator.dupe(u8, f.name);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
defer result.allocator.free(flagText);
|
|
|
|
|
|
|
|
|
|
if (f.short) {
|
|
|
|
|
try not_consumed.append(try std.fmt.allocPrint(result.allocator, "-{s}", .{flagText}));
|
|
|
|
|
} else {
|
|
|
|
|
try not_consumed.append(try std.fmt.allocPrint(result.allocator, "--{s}", .{flagText}));
|
|
|
|
|
}
|
2024-11-23 23:26:03 +00:00
|
|
|
},
|
|
|
|
|
.Positional => |p| {
|
|
|
|
|
flag.setConsumed();
|
|
|
|
|
try not_consumed.append(try result.allocator.dupe(u8, p.value));
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (@TypeOf(fie.*.value) == std.ArrayList([]const u8)) {
|
|
|
|
|
fie.*.value = not_consumed;
|
|
|
|
|
} else {
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("invalid remainder field `{s}`: expected T == `std.ArrayList([] const u8)`, got T == `{s}`", .{
|
2024-11-23 23:26:03 +00:00
|
|
|
field.name,
|
2024-11-25 18:06:20 +00:00
|
|
|
niceTypeName(@TypeOf(fie.*.value)),
|
2024-11-23 23:26:03 +00:00
|
|
|
});
|
|
|
|
|
return error.InvalidRemainder;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
.Positional => {
|
|
|
|
|
const next = nextPositional(flags);
|
|
|
|
|
|
|
|
|
|
if (next) |flag| {
|
|
|
|
|
flag.setConsumed();
|
|
|
|
|
if (@TypeOf(fie.*.value) == []const u8) {
|
|
|
|
|
fie.*.value = try result.allocator.dupe(u8, flag.*.Positional.value);
|
|
|
|
|
} else if (fie.*.parse) |parse_fn| {
|
|
|
|
|
fie.*.value = parse_fn(flag.*.Positional.value) catch |err| {
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("could not parse positional argument for `{s}`: `{s}`", .{
|
2024-11-23 23:26:03 +00:00
|
|
|
field.name,
|
|
|
|
|
@errorName(err),
|
|
|
|
|
});
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("- tried to parse: {s}", .{flag.*.Positional.value});
|
2024-11-25 18:06:20 +00:00
|
|
|
log.err("- expected type: {s}", .{niceTypeName(@TypeOf(fie.*.value))});
|
2024-11-23 23:26:03 +00:00
|
|
|
return error.InvalidPositional;
|
|
|
|
|
};
|
|
|
|
|
} else {
|
2024-11-25 17:06:17 +00:00
|
|
|
log.err("could not parse positional argument for `{s}`: no parser provided", .{field.name});
|
2024-11-23 23:26:03 +00:00
|
|
|
return error.InvalidPositional;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-11-29 17:36:56 +00:00
|
|
|
switch (fieldType) {
|
|
|
|
|
.Optional => {
|
|
|
|
|
if (!builtin.is_test) {
|
|
|
|
|
log.warn("could not find positional argument for `{s}`, using default value", .{field.name});
|
|
|
|
|
}
|
|
|
|
|
comptime continue;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => {},
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
if (builtin.is_test) {
|
2024-11-28 15:57:44 +00:00
|
|
|
// early return to not pollute the stderr
|
|
|
|
|
return error.NoArgumentFound;
|
2024-11-25 18:06:20 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-28 15:57:44 +00:00
|
|
|
log.err("could not find positional argument for `{s}`", .{field.name});
|
2024-11-25 18:06:20 +00:00
|
|
|
log.warn("hint: expected type: {s}", .{niceTypeName(@TypeOf(fie.*.value))});
|
|
|
|
|
log.warn("hint: try `<value>`", .{});
|
2024-11-23 23:26:03 +00:00
|
|
|
return error.NoArgumentFound;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const t = std.testing;
|
|
|
|
|
|
|
|
|
|
test "parse args" {
|
|
|
|
|
const args = try t.allocator.alloc([]const u8, 4);
|
|
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
flag: Marker([]const u8) = .{
|
|
|
|
|
.extra = Extra{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "flag",
|
|
|
|
|
.takesValue = true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
toggle: Marker(bool) = .{
|
|
|
|
|
.extra = Extra{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "toggle",
|
|
|
|
|
.toggle = true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
positional: Marker([]const u8) = .{
|
|
|
|
|
.extra = Extra{
|
2024-11-28 15:57:17 +00:00
|
|
|
.Positional = .{},
|
2024-11-23 23:26:03 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.allocator.free(self.flag.value);
|
|
|
|
|
self.allocator.free(self.positional.value);
|
|
|
|
|
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
args[0] = "";
|
|
|
|
|
args[1] = "--flag=value";
|
|
|
|
|
args[2] = "--toggle";
|
|
|
|
|
args[3] = "positional";
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
var result = try parseArgsFromSlice(Demo, t.allocator, args);
|
2024-11-23 23:26:03 +00:00
|
|
|
defer result.deinit();
|
|
|
|
|
|
|
|
|
|
try t.expectEqualStrings("value", result.flag.value);
|
|
|
|
|
try t.expect(result.toggle.value);
|
|
|
|
|
try t.expectEqualStrings("positional", result.positional.value);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-25 17:06:17 +00:00
|
|
|
test "missing flag" {
|
2024-11-28 20:56:55 +00:00
|
|
|
const args = try t.allocator.alloc([]const u8, 1);
|
2024-11-25 17:06:17 +00:00
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
2024-11-28 20:56:55 +00:00
|
|
|
args[0] = "1234";
|
2024-11-25 17:06:17 +00:00
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
flag: Marker(bool) = .{
|
|
|
|
|
.extra = Extra{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "flag",
|
2024-11-25 18:06:20 +00:00
|
|
|
.takesValue = true,
|
2024-11-25 17:06:17 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
.parse = parsers.boolean,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
2024-11-25 17:06:17 +00:00
|
|
|
try t.expectError(error.NoValueForFlag, result);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
test "missing toggle" {
|
2024-11-28 20:56:55 +00:00
|
|
|
const args = try t.allocator.alloc([]const u8, 1);
|
2024-11-25 18:06:20 +00:00
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
2024-11-28 20:56:55 +00:00
|
|
|
args[0] = "1234";
|
2024-11-25 18:06:20 +00:00
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
flag: Marker(bool) = .{
|
|
|
|
|
.extra = Extra{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "flag",
|
|
|
|
|
.toggle = true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = try parseArgsFromSlice(Demo, t.allocator, args);
|
|
|
|
|
|
|
|
|
|
try t.expect(result.flag.value == false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "missing positional because no args" {
|
2024-11-28 20:56:55 +00:00
|
|
|
const args = try t.allocator.alloc([]const u8, 0);
|
2024-11-25 18:06:20 +00:00
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
positional: Marker([]const u8) = .{
|
|
|
|
|
.extra = Extra{
|
2024-11-28 15:57:17 +00:00
|
|
|
.Positional = .{},
|
2024-11-25 18:06:20 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
|
|
|
|
try t.expectError(error.NoArguments, result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "missing positional because empty arg" {
|
2024-11-28 20:56:55 +00:00
|
|
|
const args = try t.allocator.alloc([]const u8, 1);
|
2024-11-25 18:06:20 +00:00
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
2024-11-28 20:56:55 +00:00
|
|
|
args[0] = "";
|
2024-11-25 18:06:20 +00:00
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
positional: Marker([]const u8) = .{
|
|
|
|
|
.extra = Extra{
|
2024-11-28 15:57:17 +00:00
|
|
|
.Positional = .{},
|
2024-11-25 18:06:20 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
|
|
|
|
try t.expectError(error.NoArgumentFound, result);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-29 17:36:56 +00:00
|
|
|
test "positional has default value so we get a free pass" {
|
|
|
|
|
const args = try t.allocator.alloc([]const u8, 1);
|
|
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
|
|
|
|
args[0] = "--toggle";
|
|
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
positional: Marker(?u8) = .{
|
|
|
|
|
.value = 255,
|
|
|
|
|
.extra = .{
|
|
|
|
|
.Positional = .{},
|
|
|
|
|
},
|
|
|
|
|
.parse = parsers.numNullable(u8),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const result = try parseArgsFromSlice(Demo, t.allocator, args);
|
|
|
|
|
|
|
|
|
|
try t.expectEqual(255, result.positional.value);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-23 23:26:03 +00:00
|
|
|
test "parse fn (positional)" {
|
2024-11-28 20:56:55 +00:00
|
|
|
const args = try t.allocator.alloc([]const u8, 3);
|
2024-11-23 23:26:03 +00:00
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
2024-11-28 20:56:55 +00:00
|
|
|
args[0] = "1234";
|
|
|
|
|
args[1] = "true";
|
|
|
|
|
args[2] = "Value";
|
2024-11-23 23:26:03 +00:00
|
|
|
|
|
|
|
|
const DemoEnum = enum(u8) {
|
|
|
|
|
NotValue,
|
|
|
|
|
Value,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
flag: Marker(u16) = .{
|
|
|
|
|
.extra = Extra{
|
2024-11-28 15:57:17 +00:00
|
|
|
.Positional = .{},
|
2024-11-23 23:26:03 +00:00
|
|
|
},
|
|
|
|
|
.parse = parsers.num(u16),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
boolean: Marker(bool) = .{
|
|
|
|
|
.extra = Extra{
|
2024-11-28 15:57:17 +00:00
|
|
|
.Positional = .{},
|
2024-11-23 23:26:03 +00:00
|
|
|
},
|
|
|
|
|
.parse = parsers.boolean,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
enumeration: Marker(DemoEnum) = .{
|
|
|
|
|
.extra = Extra{
|
2024-11-28 15:57:17 +00:00
|
|
|
.Positional = .{},
|
2024-11-23 23:26:03 +00:00
|
|
|
},
|
|
|
|
|
.parse = parsers.enumLiteral(DemoEnum),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
var result = try parseArgsFromSlice(Demo, t.allocator, args);
|
2024-11-23 23:26:03 +00:00
|
|
|
defer result.deinit();
|
|
|
|
|
|
|
|
|
|
try t.expectEqual(1234, result.flag.value);
|
|
|
|
|
try t.expect(result.boolean.value);
|
|
|
|
|
try t.expectEqual(DemoEnum.Value, result.enumeration.value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse fn (flag)" {
|
2024-11-28 20:56:55 +00:00
|
|
|
const args = try t.allocator.alloc([]const u8, 3);
|
2024-11-23 23:26:03 +00:00
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
2024-11-28 20:56:55 +00:00
|
|
|
args[0] = "--number=1234";
|
|
|
|
|
args[1] = "--boolean=yes";
|
|
|
|
|
args[2] = "--enumeration=Value";
|
2024-11-23 23:26:03 +00:00
|
|
|
|
|
|
|
|
const DemoEnum = enum(u8) {
|
|
|
|
|
NotValue,
|
|
|
|
|
Value,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
number: Marker(u16) = .{
|
|
|
|
|
.extra = Extra{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "number",
|
|
|
|
|
.takesValue = true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
.parse = parsers.num(u16),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
boolean: Marker(bool) = .{
|
|
|
|
|
.extra = Extra{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "boolean",
|
2024-11-25 18:06:20 +00:00
|
|
|
.toggle = true,
|
2024-11-23 23:26:03 +00:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
.parse = parsers.boolean,
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
enumeration: Marker(DemoEnum) = .{
|
|
|
|
|
.extra = Extra{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "enumeration",
|
|
|
|
|
.takesValue = true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
.parse = parsers.enumLiteral(DemoEnum),
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
var result = try parseArgsFromSlice(Demo, t.allocator, args);
|
2024-11-23 23:26:03 +00:00
|
|
|
defer result.deinit();
|
|
|
|
|
|
|
|
|
|
try t.expectEqual(1234, result.number.value);
|
|
|
|
|
try t.expect(result.boolean.value);
|
|
|
|
|
try t.expectEqual(DemoEnum.Value, result.enumeration.value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "parse failure because no args" {
|
2024-11-28 20:56:55 +00:00
|
|
|
const args = try t.allocator.alloc([]const u8, 0);
|
2024-11-23 23:26:03 +00:00
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-25 18:06:20 +00:00
|
|
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
2024-11-23 23:26:03 +00:00
|
|
|
|
|
|
|
|
try t.expectError(error.NoArguments, result);
|
|
|
|
|
}
|
2024-11-28 20:56:55 +00:00
|
|
|
|
|
|
|
|
test "remainder has value" {
|
|
|
|
|
const args = try t.allocator.alloc([]const u8, 3);
|
|
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
|
|
|
|
args[0] = "--flag=value";
|
|
|
|
|
args[1] = "--toggle";
|
|
|
|
|
args[2] = "positional";
|
|
|
|
|
|
|
|
|
|
const Demo = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
remainder: Marker(std.ArrayList([]const u8)) = .{
|
|
|
|
|
.extra = .{ .Remainder = {} },
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
for (self.remainder.value.items) |item| {
|
|
|
|
|
self.allocator.free(item);
|
|
|
|
|
}
|
|
|
|
|
self.remainder.value.deinit();
|
|
|
|
|
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var result = try parseArgsFromSlice(Demo, t.allocator, args);
|
|
|
|
|
defer result.deinit();
|
|
|
|
|
|
|
|
|
|
try t.expectEqual(3, result.remainder.value.items.len);
|
|
|
|
|
|
|
|
|
|
try t.expectEqualStrings("--flag=value", result.remainder.value.items[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test "sub command from remainder" {
|
|
|
|
|
const args = try t.allocator.alloc([]const u8, 3);
|
|
|
|
|
defer t.allocator.free(args);
|
|
|
|
|
|
|
|
|
|
args[0] = "--flag=value";
|
|
|
|
|
args[1] = "command";
|
|
|
|
|
args[2] = "--toggle";
|
|
|
|
|
|
|
|
|
|
const DemoOuter = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
positional: Marker([]const u8) = .{
|
|
|
|
|
.extra = .{ .Positional = .{} },
|
|
|
|
|
},
|
|
|
|
|
remainder: Marker(std.ArrayList([]const u8)) = .{
|
|
|
|
|
.extra = .{ .Remainder = {} },
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.allocator.free(self.positional.value);
|
|
|
|
|
|
|
|
|
|
for (self.remainder.value.items) |item| {
|
|
|
|
|
self.allocator.free(item);
|
|
|
|
|
}
|
|
|
|
|
self.remainder.value.deinit();
|
|
|
|
|
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var outerResult = try parseArgsFromSlice(DemoOuter, t.allocator, args);
|
|
|
|
|
defer outerResult.deinit();
|
|
|
|
|
|
|
|
|
|
try t.expectEqualStrings("command", outerResult.positional.value);
|
|
|
|
|
try t.expectEqual(2, outerResult.remainder.value.items.len);
|
|
|
|
|
|
|
|
|
|
try t.expectEqualStrings("--flag=value", outerResult.remainder.value.items[0]);
|
|
|
|
|
try t.expectEqualStrings("--toggle", outerResult.remainder.value.items[1]);
|
|
|
|
|
|
|
|
|
|
const DemoInner = struct {
|
|
|
|
|
allocator: Allocator,
|
|
|
|
|
|
|
|
|
|
flag: Marker([]const u8) = .{
|
|
|
|
|
.extra = .{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "flag",
|
|
|
|
|
.takesValue = true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
toggle: Marker(bool) = .{
|
|
|
|
|
.extra = Extra{
|
|
|
|
|
.Flag = .{
|
|
|
|
|
.name = "toggle",
|
|
|
|
|
.toggle = true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
fn deinit(self: *@This()) void {
|
|
|
|
|
self.allocator.free(self.flag.value);
|
|
|
|
|
self.* = undefined;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var innerResult = try parseArgsFromSlice(DemoInner, t.allocator, outerResult.remainder.value.items);
|
|
|
|
|
defer innerResult.deinit();
|
|
|
|
|
|
|
|
|
|
try t.expectEqualStrings("value", innerResult.flag.value);
|
|
|
|
|
try t.expect(innerResult.toggle.value);
|
|
|
|
|
}
|