| 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); |
| 3 | const string = [:0]const u8; |
| 4 | const List = std.array_list.Managed(string); |
| 5 | const extras = @import("extras"); |
| 6 | const linux = @import("sys-linux"); |
| 7 | |
| 8 | const sys = switch (builtin.target.os.tag) { |
| 9 | .linux => linux, |
| 10 | .freebsd => @import("sys-freebsd"), |
| 11 | .netbsd => @import("sys-netbsd"), |
| 12 | .openbsd => @import("sys-openbsd"), |
| 13 | else => unreachable, // TODO: |
| 14 | }; |
| 15 | |
| 16 | var allocator: std.mem.Allocator = undefined; |
| 17 | var singles: std.array_hash_map.String(string) = .empty; |
| 18 | var multis: std.array_hash_map.String(List) = .empty; |
| 19 | |
| 20 | pub fn init(alloc: std.mem.Allocator) void { |
| 21 | allocator = alloc; |
| 22 | } |
| 23 | |
| 24 | pub fn deinit() void { |
| 25 | singles.deinit(allocator); |
| 26 | for (multis.values()) |array_list| { |
| 27 | array_list.deinit(); |
| 28 | } |
| 29 | multis.deinit(allocator); |
| 30 | } |
| 31 | |
| 32 | pub fn addSingle(name: string) !void { |
| 33 | try singles.putNoClobber(allocator, name, ""); |
| 34 | } |
| 35 | |
| 36 | pub fn addMulti(name: string) !void { |
| 37 | try multis.putNoClobber(allocator, name, List.init(allocator)); |
| 38 | } |
| 39 | |
| 40 | pub const FlagDashKind = enum { |
| 41 | single, |
| 42 | double, |
| 43 | |
| 44 | pub fn hypen(self: FlagDashKind) string { |
| 45 | return switch (self) { |
| 46 | .single => "-", |
| 47 | .double => "--", |
| 48 | }; |
| 49 | } |
| 50 | }; |
| 51 | |
| 52 | pub fn parse(k: FlagDashKind, args: std.process.Args) !std.process.Args.Iterator { |
| 53 | const dash = k.hypen(); |
| 54 | var argiter = args.iterate(); |
| 55 | defer argiter.deinit(); |
| 56 | var argi: usize = 0; |
| 57 | blk: while (argiter.next()) |item| : (argi += 1) { |
| 58 | const data = item; |
| 59 | if (argi == 0) continue; |
| 60 | const name: string = @ptrCast(extras.trimPrefix(data, dash)); |
| 61 | if (data.len == name.len) return error.BadFlag; |
| 62 | |
| 63 | for (singles.keys()) |jtem| { |
| 64 | if (std.mem.eql(u8, name, jtem)) { |
| 65 | const value = argiter.next().?; |
| 66 | argi += 1; |
| 67 | try singles.put(allocator, name, value); |
| 68 | continue :blk; |
| 69 | } |
| 70 | } |
| 71 | for (multis.keys()) |jtem| { |
| 72 | if (std.mem.eql(u8, name, jtem)) { |
| 73 | const value = argiter.next().?; |
| 74 | argi += 1; |
| 75 | try multis.getEntry(name).?.value_ptr.append(value); |
| 76 | continue :blk; |
| 77 | } |
| 78 | } |
| 79 | std.log.err("Unrecognized argument: {s}{s}", .{ dash, name }); |
| 80 | sys.exit(1); |
| 81 | } |
| 82 | return argiter; |
| 83 | } |
| 84 | |
| 85 | pub fn parseEnv() !void { |
| 86 | for (singles.keys(), singles.values()) |k, *v| { |
| 87 | var buf: [64]u8 = @splat(0); |
| 88 | for (k, 0..) |c, i| buf[i] = switch (c) { |
| 89 | 'A'...'Z', '_', '0'...'9' => |a| a, |
| 90 | 'a'...'z' => |a| a - 'a' + 'A', |
| 91 | '-' => '_', |
| 92 | else => unreachable, |
| 93 | }; |
| 94 | if (sys.getenv(buf[0..k.len :0])) |value| { |
| 95 | v.* = value; |
| 96 | } |
| 97 | } |
| 98 | for (multis.keys(), multis.values()) |k, *v| { |
| 99 | var n: usize = 1; |
| 100 | while (true) : (n += 1) { |
| 101 | var buf: [64]u8 = @splat(0); |
| 102 | for (k, 0..) |c, i| buf[i] = switch (c) { |
| 103 | 'A'...'Z', '_', '0'...'9' => |a| a, |
| 104 | 'a'...'z' => |a| a - 'a' + 'A', |
| 105 | '-' => '_', |
| 106 | else => unreachable, |
| 107 | }; |
| 108 | const buf2 = try std.fmt.bufPrint(buf[k.len..], "_{d}", .{n}); |
| 109 | const w = buf[0 .. k.len + buf2.len :0]; |
| 110 | const value = sys.getenv(w) orelse break; |
| 111 | try v.append(value); |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | fn fixNameForEnv(alloc: std.mem.Allocator, input: string) ![:0]const u8 { |
| 117 | var ret = try extras.asciiUpper(alloc, input); |
| 118 | for (0..ret.len) |i| { |
| 119 | if (ret[i] == '-') { |
| 120 | ret[i] = '_'; |
| 121 | } |
| 122 | } |
| 123 | return ret; |
| 124 | } |
| 125 | |
| 126 | pub fn getSingle(name: string) ?string { |
| 127 | const x = singles.get(name) orelse return null; |
| 128 | return if (x.len > 0) x else null; |
| 129 | } |
| 130 | |
| 131 | pub fn getMulti(name: string) ?[]const string { |
| 132 | const x = (multis.get(name) orelse return null).items; |
| 133 | return if (x.len > 0) x else null; |
| 134 | } |
| 135 | |
| 136 | pub fn getBool(name: string, default: bool) bool { |
| 137 | const x = getSingle(name) orelse return default; |
| 138 | if (std.mem.eql(u8, x, "true")) return true; |
| 139 | const y = extras.parseDigits(u1, x, 2) catch return default; |
| 140 | return y > 0; |
| 141 | } |