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 field_names: [flags.len][:0]const u8 = undefined;42 var field_types: [flags.len]type = undefined;43 var field_attrs: [flags.len]std.builtin.Type.StructField.Attributes = undefined;44 for (flags, 0..) |flag, i| {45 field_names[i] = flag.name;46 switch (flag.kind) {47 .boolean => {48 field_types[i] = bool;49 field_types[i] = .{50 .default_value = &false,51 .@"comptime" = false,52 .@"align" = @alignOf(bool),53 };54 },55 .arg => {56 field_types[i] = ?[:0]const u8;57 field_attrs[i] = .{58 .default_value_ptr = &@as(?[:0]const u8, null),59 .@"comptime" = false,60 .@"align" = @alignOf(?[:0]const u8),61 };62 },63 }64 }65 break :flags_type @Struct(66 .auto,67 null,68 &field_names,69 &field_types,70 &field_attrs,71 );72 };73 };7475 pub fn parse(args: []const Arg) !Result {76 var result_flags: Result.Flags = .{};7778 var i: usize = 0;79 outer: while (i < args.len) : (i += 1) {80 const arg = switch (Arg) {81 [*:0]const u8 => mem.sliceTo(args[i], 0),82 [:0]const u8 => args[i],83 else => unreachable,84 };85 if (arg[0] != '-') {86 continue;87 }8889 var flag_found = false;90 inline for (flags) |flag| {91 if (mem.eql(u8, "-" ++ flag.name, arg)) {92 flag_found = true;93 switch (flag.kind) {94 .boolean => @field(result_flags, flag.name) = true,95 .arg => {96 i += 1;97 if (i == args.len) {98 std.log.err("option '-" ++ flag.name ++99 "' requires an argument but none was provided!", .{});100 return error.MissingFlagArgument;101 }102 @field(result_flags, flag.name) = switch (Arg) {103 [*:0]const u8 => mem.sliceTo(args[i], 0),104 [:0]const u8 => args[i],105 else => unreachable,106 };107 },108 }109 continue :outer;110 }111 }112 if (!flag_found) {113 std.log.err("option '{s}' is unknown", .{arg});114 return error.UnknownFlag;115 }116 break;117 }118119 return Result{120 .args = args[i..],121 .flags = result_flags,122 };123 }124 };125}