Minor improvements

This commit is contained in:
Lyssieth 2024-11-28 22:56:55 +02:00
parent 725a5c2bdd
commit 48cddd405c
Signed by untrusted user who does not match committer: lyssieth
GPG key ID: 200268854934CFAB
3 changed files with 147 additions and 46 deletions

View file

@ -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| {

View file

@ -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);
}

View file

@ -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);
} }
} }