1// Zero allocation argument parsing for unix-like systems (taken from River).2// Includes a minor modifications for error handling on unknown flags.3//4// Released under the Zero Clause BSD (0BSD) license:5//6// Copyright 2023 Isaac Freund7//8// Permission to use, copy, modify, and/or distribute this software for any9// purpose with or without fee is hereby granted.10//11// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES12// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF13// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR14// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES15// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN16// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF17// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.1819const std = @import("std");20const mem = std.mem;2122pub const Flag = struct {23 name: [:0]const u8,24 kind: enum { boolean, arg },25};2627pub fn parser(comptime Arg: type, comptime flags: []const Flag) type {28 switch (Arg) {29 // TODO consider allowing []const u830 [:0]const u8, [*:0]const u8 => {}, // ok31 else => @compileError("invalid argument type: " ++ @typeName(Arg)),32 }33 return struct {34 pub const Result = struct {35 /// Remaining args after the recognized flags36 args: []const Arg,37 /// Data obtained from parsed flags38 flags: Flags,3940 pub const Flags = flags_type: {41 var fields: []const std.builtin.Type.StructField = &.{};42 for (flags) |flag| {43 const field: std.builtin.Type.StructField = switch (flag.kind) {44 .boolean => .{45 .name = flag.name,46 .type = bool,47 .default_value = &false,48 .is_comptime = false,49 .alignment = @alignOf(bool),50 },51 .arg => .{52 .name = flag.name,53 .type = ?[:0]const u8,54 .default_value_ptr = &@as(?[:0]const u8, null),55 .is_comptime = false,56 .alignment = @alignOf(?[:0]const u8),57 },58 };59 fields = fields ++ [_]std.builtin.Type.StructField{field};60 }61 break :flags_type @Type(.{ .@"struct" = .{62 .layout = .auto,63 .fields = fields,64 .decls = &.{},65 .is_tuple = false,66 } });67 };68 };6970 pub fn parse(args: []const Arg) !Result {71 var result_flags: Result.Flags = .{};7273 var i: usize = 0;74 outer: while (i < args.len) : (i += 1) {75 const arg = switch (Arg) {76 [*:0]const u8 => mem.sliceTo(args[i], 0),77 [:0]const u8 => args[i],78 else => unreachable,79 };80 if (arg[0] != '-') {81 continue;82 }8384 var flag_found = false;85 inline for (flags) |flag| {86 if (mem.eql(u8, "-" ++ flag.name, arg)) {87 flag_found = true;88 switch (flag.kind) {89 .boolean => @field(result_flags, flag.name) = true,90 .arg => {91 i += 1;92 if (i == args.len) {93 std.log.err("option '-" ++ flag.name ++94 "' requires an argument but none was provided!", .{});95 return error.MissingFlagArgument;96 }97 @field(result_flags, flag.name) = switch (Arg) {98 [*:0]const u8 => mem.sliceTo(args[i], 0),99 [:0]const u8 => args[i],100 else => unreachable,101 };102 },103 }104 continue :outer;105 }106 }107 if (!flag_found) {108 std.log.err("option '{s}' is unknown", .{arg});109 return error.UnknownFlag;110 }111 break;112 }113114 return Result{115 .args = args[i..],116 .flags = result_flags,117 };118 }119 };120}