1const std = @import("std");
2const builtin = @import("builtin");
3const string = [:0]const u8;
4const List = std.array_list.Managed(string);
5const extras = @import("extras");
6const linux = @import("sys-linux");
7
8const 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
16var allocator: std.mem.Allocator = undefined;
17var singles: std.array_hash_map.String(string) = .empty;
18var multis: std.array_hash_map.String(List) = .empty;
19
20pub fn init(alloc: std.mem.Allocator) void {
21 allocator = alloc;
22}
23
24pub fn deinit() void {
25 singles.deinit(allocator);
26 for (multis.values()) |array_list| {
27 array_list.deinit();
28 }
29 multis.deinit(allocator);
30}
31
32pub fn addSingle(name: string) !void {
33 try singles.putNoClobber(allocator, name, "");
34}
35
36pub fn addMulti(name: string) !void {
37 try multis.putNoClobber(allocator, name, List.init(allocator));
38}
39
40pub 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
52pub 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
85pub 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
116fn 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
126pub 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
131pub 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
136pub 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}