Backlog/engine/core/src/args.zig

133 lines
5.5 KiB
Zig

// CLI argument parsing library
const std = @import("std");
pub fn ParseArgs(comptime T: type) !T {
// I think i am actually totally cool with leaking this one and leaving it around... just dont spam call this function
var iter = try std.process.argsWithAllocator(std.heap.c_allocator);
var args: T = .{};
const shortBuf: [32]u8 = undefined;
_ = shortBuf;
var longBuf: [256]u8 = undefined;
while (iter.next()) |a| {
inline for (@typeInfo(T).@"struct".fields) |field| {
const longRef = try std.fmt.bufPrint(&longBuf, "--{s}", .{field.name});
if (std.mem.startsWith(u8, a, longRef)) {
// check if it has a value argument
if (a.len > longRef.len) {
if (a[longRef.len] == '=' and a.len > longRef.len + 1) {
const right = a[longRef.len + 1 ..];
switch (@typeInfo(field.type)) {
.bool => {
if (std.mem.eql(u8, right, "true") or
std.mem.eql(u8, right, "t") or
std.mem.eql(u8, right, "True") or
std.mem.eql(u8, right, "TRUE") or
std.mem.eql(u8, right, "T") or
std.mem.eql(u8, right, "1"))
{
@field(args, field.name) = true;
} else if (std.mem.eql(u8, right, "false") or
std.mem.eql(u8, right, "f") or
std.mem.eql(u8, right, "False") or
std.mem.eql(u8, right, "FALSE") or
std.mem.eql(u8, right, "F") or
std.mem.eql(u8, right, "0"))
{
@field(args, field.name) = false;
} else {
std.debug.print("Error parsing: non-boolean argument", .{});
@field(args, field.name) = false;
}
},
.int => {
@field(args, field.name) = try std.fmt.parseInt(field.type, right, 10);
},
.float => {
@field(args, field.name) = try std.fmt.parseFloat(field.type, right);
},
.pointer => |pointer| {
switch (pointer.size) {
.slice => {
@field(args, field.name) = right;
},
else => {
@compileError("only u8 slices are supported");
},
}
},
else => {
@compileError("unable to generate argparse, unsupported struct type in field: " ++ field.name);
},
}
} else {
std.debug.print("Error parsing arguments: Expected value after argument assignment '{s}'\n", .{a});
return error.MalformedArgument;
}
} else {
switch (@typeInfo(field.type)) {
.bool => {
@field(args, field.name) = true;
},
.int, .float => {
std.debug.print("Error parsing arguments: Expected value '{s}'\n", .{a});
return error.MalformedArgument;
},
.pointer => |pointer| {
switch (pointer.size) {
.slice => {
std.debug.print("Error parsing arguments: Expected value '{s}'\n", .{a});
return error.MalformedArgument;
},
else => {
@compileError("only u8 slices are supported");
},
}
},
else => {
@compileError("unable to generate argparse, unsupported struct type in field: " ++ field.name);
},
}
}
break;
}
}
}
return args;
}
pub fn main() !void {
const A = struct {
foo: f32 = 0,
bar: i32 = 0,
baz: bool = false,
test_file: []const u8 = "",
};
var iter = std.process.args();
const args = try ParseArgs(A, &iter);
std.debug.print("A: {any}", .{args});
}
test "test args" {
const A = struct {
foo: f32 = 0,
bar: i32 = 0,
baz: bool = false,
test_file: []const u8 = "",
};
const args = try ParseArgs(A, &.{ "--foo=32", "--bar=22", "--baz", "--test_file=lmao2nova" });
std.debug.assert(args.foo == 32);
std.debug.assert(args.bar == 22);
std.debug.assert(args.baz == true);
std.debug.assert(std.mem.eql(u8, args.test_file, "lmao2nova"));
}