// 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")); }