Minor improvements
This commit is contained in:
parent
725a5c2bdd
commit
48cddd405c
3 changed files with 147 additions and 46 deletions
|
|
@ -6,6 +6,7 @@ pub const Arg = union(enum(u2)) {
|
||||||
Flag: struct {
|
Flag: struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
value: ?[]const u8,
|
value: ?[]const u8,
|
||||||
|
short: bool = false,
|
||||||
consumed: bool = false,
|
consumed: bool = false,
|
||||||
},
|
},
|
||||||
Positional: struct {
|
Positional: struct {
|
||||||
|
|
@ -34,6 +35,7 @@ pub const Arg = union(enum(u2)) {
|
||||||
flag = try Arg.parse_flag(arg[2..]);
|
flag = try Arg.parse_flag(arg[2..]);
|
||||||
} else if (std.mem.startsWith(u8, arg, "-")) {
|
} else if (std.mem.startsWith(u8, arg, "-")) {
|
||||||
flag = try Arg.parse_flag(arg[1..]);
|
flag = try Arg.parse_flag(arg[1..]);
|
||||||
|
flag.?.Flag.short = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flag) |f| {
|
if (flag) |f| {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ pub const Extra = union(enum(u2)) {
|
||||||
/// A marker for any field `T` that you want to be parseable by the argument parser.
|
/// A marker for any field `T` that you want to be parseable by the argument parser.
|
||||||
pub fn Marker(comptime T: type) type {
|
pub fn Marker(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
value: T,
|
value: T = undefined,
|
||||||
extra: Extra,
|
extra: Extra,
|
||||||
/// Optional function that parses the value of the field.
|
/// Optional function that parses the value of the field.
|
||||||
/// If this is null, no parsing will take place.
|
/// If this is null, no parsing will take place.
|
||||||
|
|
@ -52,12 +52,6 @@ pub fn Marker(comptime T: type) type {
|
||||||
|
|
||||||
pub const help = @import("./help.zig");
|
pub const help = @import("./help.zig");
|
||||||
|
|
||||||
comptime {
|
|
||||||
if (builtin.mode == .Debug) {
|
|
||||||
std.mem.doNotOptimizeAway(help);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <https://git.cutie.zone/lyssieth/zither/issues/1>
|
/// <https://git.cutie.zone/lyssieth/zither/issues/1>
|
||||||
///
|
///
|
||||||
/// Parsing order of arguments is based on the order they are declared in `T`.
|
/// Parsing order of arguments is based on the order they are declared in `T`.
|
||||||
|
|
@ -65,22 +59,22 @@ pub fn parseArgs(comptime T: type, allocator: Allocator) !T {
|
||||||
const args = try std.process.argsAlloc(allocator);
|
const args = try std.process.argsAlloc(allocator);
|
||||||
defer std.process.argsFree(allocator, args);
|
defer std.process.argsFree(allocator, args);
|
||||||
|
|
||||||
return parseArgsFromSlice(T, allocator, args);
|
return parseArgsFromSlice(T, allocator, args[1..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// All items of `args` must be valid, otherwise you will get a General Protection Fault.
|
/// All items of `args` must be valid, otherwise you will get a General Protection Fault.
|
||||||
|
/// Do not pass the process name as an argument.
|
||||||
///
|
///
|
||||||
/// Parsing order of arguments is based on the order they are declared in `T`.
|
/// Parsing order of arguments is based on the order they are declared in `T`.
|
||||||
pub fn parseArgsFromSlice(comptime T: type, allocator: Allocator, args: [][]const u8) !T {
|
pub fn parseArgsFromSlice(comptime T: type, allocator: Allocator, args: [][]const u8) !T {
|
||||||
var flags = std.ArrayList(Arg).init(allocator);
|
var flags = std.ArrayList(Arg).init(allocator);
|
||||||
defer flags.deinit();
|
defer flags.deinit();
|
||||||
|
|
||||||
if (args.len < 2) {
|
if (args.len == 0) {
|
||||||
return error.NoArguments;
|
return error.NoArguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (args, 0..) |arg, idx| {
|
for (args) |arg| {
|
||||||
if (idx == 0) continue; // skip first arg: process name
|
|
||||||
if (arg.len == 0) continue; // skip empty args
|
if (arg.len == 0) continue; // skip empty args
|
||||||
if (@intFromPtr(arg.ptr) == 0xaaaaaaaaaaaaaaaa) return error.UninitializedArgument;
|
if (@intFromPtr(arg.ptr) == 0xaaaaaaaaaaaaaaaa) return error.UninitializedArgument;
|
||||||
|
|
||||||
|
|
@ -306,10 +300,24 @@ fn initFromParsed(comptime T: type, allocator: Allocator, flags: []Arg) !T {
|
||||||
|
|
||||||
switch (flag.*) {
|
switch (flag.*) {
|
||||||
.Flag => |f| {
|
.Flag => |f| {
|
||||||
log.warn("TODO: figure out what to do with remainder flag {s} (value: {?s})", .{
|
flag.setConsumed();
|
||||||
f.name,
|
const flagText = blk: {
|
||||||
f.value,
|
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}));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
.Positional => |p| {
|
.Positional => |p| {
|
||||||
flag.setConsumed();
|
flag.setConsumed();
|
||||||
|
|
@ -377,7 +385,6 @@ test "parse args" {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
flag: Marker([]const u8) = .{
|
flag: Marker([]const u8) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "flag",
|
.name = "flag",
|
||||||
|
|
@ -386,7 +393,6 @@ test "parse args" {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
toggle: Marker(bool) = .{
|
toggle: Marker(bool) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "toggle",
|
.name = "toggle",
|
||||||
|
|
@ -395,7 +401,6 @@ test "parse args" {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
positional: Marker([]const u8) = .{
|
positional: Marker([]const u8) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Positional = .{},
|
.Positional = .{},
|
||||||
},
|
},
|
||||||
|
|
@ -423,16 +428,15 @@ test "parse args" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "missing flag" {
|
test "missing flag" {
|
||||||
const args = try t.allocator.alloc([]const u8, 2);
|
const args = try t.allocator.alloc([]const u8, 1);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[1] = "1234";
|
args[0] = "1234";
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
flag: Marker(bool) = .{
|
flag: Marker(bool) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "flag",
|
.name = "flag",
|
||||||
|
|
@ -452,16 +456,15 @@ test "missing flag" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "missing toggle" {
|
test "missing toggle" {
|
||||||
const args = try t.allocator.alloc([]const u8, 2);
|
const args = try t.allocator.alloc([]const u8, 1);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[1] = "1234";
|
args[0] = "1234";
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
flag: Marker(bool) = .{
|
flag: Marker(bool) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "flag",
|
.name = "flag",
|
||||||
|
|
@ -481,14 +484,13 @@ test "missing toggle" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "missing positional because no args" {
|
test "missing positional because no args" {
|
||||||
const args = try t.allocator.alloc([]const u8, 1);
|
const args = try t.allocator.alloc([]const u8, 0);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
positional: Marker([]const u8) = .{
|
positional: Marker([]const u8) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Positional = .{},
|
.Positional = .{},
|
||||||
},
|
},
|
||||||
|
|
@ -504,16 +506,15 @@ test "missing positional because no args" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "missing positional because empty arg" {
|
test "missing positional because empty arg" {
|
||||||
const args = try t.allocator.alloc([]const u8, 2);
|
const args = try t.allocator.alloc([]const u8, 1);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[1] = "";
|
args[0] = "";
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
positional: Marker([]const u8) = .{
|
positional: Marker([]const u8) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Positional = .{},
|
.Positional = .{},
|
||||||
},
|
},
|
||||||
|
|
@ -529,12 +530,12 @@ test "missing positional because empty arg" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse fn (positional)" {
|
test "parse fn (positional)" {
|
||||||
const args = try t.allocator.alloc([]const u8, 4);
|
const args = try t.allocator.alloc([]const u8, 3);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[1] = "1234";
|
args[0] = "1234";
|
||||||
args[2] = "true";
|
args[1] = "true";
|
||||||
args[3] = "Value";
|
args[2] = "Value";
|
||||||
|
|
||||||
const DemoEnum = enum(u8) {
|
const DemoEnum = enum(u8) {
|
||||||
NotValue,
|
NotValue,
|
||||||
|
|
@ -545,7 +546,6 @@ test "parse fn (positional)" {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
flag: Marker(u16) = .{
|
flag: Marker(u16) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Positional = .{},
|
.Positional = .{},
|
||||||
},
|
},
|
||||||
|
|
@ -553,7 +553,6 @@ test "parse fn (positional)" {
|
||||||
},
|
},
|
||||||
|
|
||||||
boolean: Marker(bool) = .{
|
boolean: Marker(bool) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Positional = .{},
|
.Positional = .{},
|
||||||
},
|
},
|
||||||
|
|
@ -561,7 +560,6 @@ test "parse fn (positional)" {
|
||||||
},
|
},
|
||||||
|
|
||||||
enumeration: Marker(DemoEnum) = .{
|
enumeration: Marker(DemoEnum) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Positional = .{},
|
.Positional = .{},
|
||||||
},
|
},
|
||||||
|
|
@ -582,12 +580,12 @@ test "parse fn (positional)" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse fn (flag)" {
|
test "parse fn (flag)" {
|
||||||
const args = try t.allocator.alloc([]const u8, 4);
|
const args = try t.allocator.alloc([]const u8, 3);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
args[1] = "--number=1234";
|
args[0] = "--number=1234";
|
||||||
args[2] = "--boolean=yes";
|
args[1] = "--boolean=yes";
|
||||||
args[3] = "--enumeration=Value";
|
args[2] = "--enumeration=Value";
|
||||||
|
|
||||||
const DemoEnum = enum(u8) {
|
const DemoEnum = enum(u8) {
|
||||||
NotValue,
|
NotValue,
|
||||||
|
|
@ -598,7 +596,6 @@ test "parse fn (flag)" {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|
||||||
number: Marker(u16) = .{
|
number: Marker(u16) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "number",
|
.name = "number",
|
||||||
|
|
@ -609,7 +606,6 @@ test "parse fn (flag)" {
|
||||||
},
|
},
|
||||||
|
|
||||||
boolean: Marker(bool) = .{
|
boolean: Marker(bool) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "boolean",
|
.name = "boolean",
|
||||||
|
|
@ -620,7 +616,6 @@ test "parse fn (flag)" {
|
||||||
},
|
},
|
||||||
|
|
||||||
enumeration: Marker(DemoEnum) = .{
|
enumeration: Marker(DemoEnum) = .{
|
||||||
.value = undefined,
|
|
||||||
.extra = Extra{
|
.extra = Extra{
|
||||||
.Flag = .{
|
.Flag = .{
|
||||||
.name = "enumeration",
|
.name = "enumeration",
|
||||||
|
|
@ -644,7 +639,7 @@ test "parse fn (flag)" {
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse failure because no args" {
|
test "parse failure because no args" {
|
||||||
const args = try t.allocator.alloc([]const u8, 1);
|
const args = try t.allocator.alloc([]const u8, 0);
|
||||||
defer t.allocator.free(args);
|
defer t.allocator.free(args);
|
||||||
|
|
||||||
const Demo = struct {
|
const Demo = struct {
|
||||||
|
|
@ -655,9 +650,112 @@ test "parse failure because no args" {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
args[0] = "zither";
|
|
||||||
|
|
||||||
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
const result = parseArgsFromSlice(Demo, t.allocator, args);
|
||||||
|
|
||||||
try t.expectError(error.NoArguments, result);
|
try t.expectError(error.NoArguments, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,5 +9,6 @@ comptime {
|
||||||
|
|
||||||
if (builtin.is_test) {
|
if (builtin.is_test) {
|
||||||
std.mem.doNotOptimizeAway(args);
|
std.mem.doNotOptimizeAway(args);
|
||||||
|
std.mem.doNotOptimizeAway(args.help);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue