// Zero allocation argument parsing for unix-like systems. // Released under the Zero Clause BSD (0BSD) license: // // Copyright 2022 Isaac Freund // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. const std = @import("std"); const cstr = std.cstr; pub const Flag = struct { name: [*:0]const u8, kind: enum { boolean, arg }, }; pub fn ParseResult(comptime flags: []const Flag) type { return struct { const Self = @This(); const FlagData = struct { name: [*:0]const u8, value: union { boolean: bool, arg: ?[*:0]const u8, }, }; /// Remaining args after the recognized flags args: []const [*:0]const u8, /// Data obtained from parsed flags flag_data: [flags.len]FlagData = blk: { // Init all flags to false/null var flag_data: [flags.len]FlagData = undefined; inline for (flags, &flag_data) |flag, *flag_data_i| { flag_data_i.* = switch (flag.kind) { .boolean => .{ .name = flag.name, .value = .{ .boolean = false }, }, .arg => .{ .name = flag.name, .value = .{ .arg = null }, }, }; } break :blk flag_data; }, pub fn boolFlag(self: Self, flag_name: [*:0]const u8) bool { for (self.flag_data) |flag_data| { if (cstr.cmp(flag_data.name, flag_name) == 0) return flag_data.value.boolean; } unreachable; // Invalid flag_name } pub fn argFlag(self: Self, flag_name: [*:0]const u8) ?[:0]const u8 { for (self.flag_data) |flag_data| { if (cstr.cmp(flag_data.name, flag_name) == 0) { return std.mem.span(flag_data.value.arg); } } unreachable; // Invalid flag_name } }; } pub fn parse(args: []const [*:0]const u8, comptime flags: []const Flag) !ParseResult(flags) { var ret: ParseResult(flags) = .{ .args = undefined }; var arg_idx: usize = 0; while (arg_idx < args.len) : (arg_idx += 1) { var parsed_flag = false; inline for (flags, &ret.flag_data) |flag, *flag_data_i| { if (cstr.cmp(flag.name, args[arg_idx]) == 0) { switch (flag.kind) { .boolean => flag_data_i.*.value.boolean = true, .arg => { arg_idx += 1; if (arg_idx == args.len) { std.log.err("option '" ++ flag.name ++ "' requires an argument but none was provided!", .{}); return error.MissingFlagArgument; } flag_data_i.*.value.arg = args[arg_idx]; }, } parsed_flag = true; } } if (!parsed_flag) break; } ret.args = args[arg_idx..]; return ret; }