From b126b9588519e397682016496d538544517c44a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Motiejus=20Jak=C5=A1tys?= Date: Wed, 25 May 2022 09:33:56 +0300 Subject: [PATCH] add ifreund's flags.zig From https://gist.github.com/ifreund/41e91a3e4aeb0a3da57258c14e2a8cea Thanks, Isaac. --- lib/flags.zig | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 lib/flags.zig diff --git a/lib/flags.zig b/lib/flags.zig new file mode 100644 index 0000000..9aa57a0 --- /dev/null +++ b/lib/flags.zig @@ -0,0 +1,105 @@ +// 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: [][*: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, 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: [][*: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) |flag, flag_idx| { + if (cstr.cmp(flag.name, args[arg_idx]) == 0) { + switch (flag.kind) { + .boolean => ret.flag_data[flag_idx].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; + } + ret.flag_data[flag_idx].value.arg = args[arg_idx]; + }, + } + parsed_flag = true; + } + } + if (!parsed_flag) break; + } + + ret.args = args[arg_idx..]; + + return ret; +}