1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
const std = @import("std");
const Allocator = std.mem.Allocator;
const ArrayList = std.ArrayList;
const HashMap = std.HashMap;
pub const FlagType = enum {
Boolean,
String,
Number,
};
pub const FlagDef = struct {
name: []const u8,
aliases: []const []const u8,
flag_type: FlagType,
description: []const u8,
default_value: ?[]const u8 = null,
};
pub const FlagValue = union(FlagType) {
Boolean: bool,
String: []const u8,
Number: i32,
};
pub const ParsedFlags = struct {
values: HashMap([]const u8, FlagValue, StringContext, std.hash_map.default_max_load_percentage),
allocator: Allocator,
const StringContext = struct {
pub fn hash(self: @This(), s: []const u8) u64 {
_ = self;
return std.hash_map.hashString(s);
}
pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
_ = self;
return std.mem.eql(u8, a, b);
}
};
pub fn init(allocator: Allocator) ParsedFlags {
return ParsedFlags{
.values = HashMap([]const u8, FlagValue, StringContext, std.hash_map.default_max_load_percentage).init(allocator),
.allocator = allocator,
};
}
pub fn deinit(self: *ParsedFlags) void {
self.values.deinit();
}
pub fn getBool(self: *const ParsedFlags, name: []const u8) bool {
if (self.values.get(name)) |value| {
return switch (value) {
.Boolean => |b| b,
else => false,
};
}
return false;
}
pub fn getString(self: *const ParsedFlags, name: []const u8) ?[]const u8 {
if (self.values.get(name)) |value| {
return switch (value) {
.String => |s| s,
else => null,
};
}
return null;
}
pub fn getNumber(self: *const ParsedFlags, name: []const u8) ?i32 {
if (self.values.get(name)) |value| {
return switch (value) {
.Number => |n| n,
else => null,
};
}
return null;
}
pub fn setValue(self: *ParsedFlags, name: []const u8, value: FlagValue) !void {
try self.values.put(name, value);
}
};
pub const CommandFlags = struct {
flags: []const FlagDef,
pub fn parse(self: *const CommandFlags, args: []const []const u8, allocator: Allocator) !ParsedFlags {
var parsed = ParsedFlags.init(allocator);
for (args) |arg| {
if (arg.len == 0 or arg[0] != '/') continue;
const flag_text = arg[1..];
if (flag_text.len == 0) continue;
var flag_name: []const u8 = undefined;
var flag_value: ?[]const u8 = null;
if (std.mem.indexOf(u8, flag_text, ":")) |colon_pos| {
flag_name = flag_text[0..colon_pos];
flag_value = flag_text[colon_pos + 1 ..];
} else {
flag_name = flag_text;
}
const flag_def = self.findFlag(flag_name) orelse {
return error.UnknownFlag;
};
const value = switch (flag_def.flag_type) {
.Boolean => FlagValue{ .Boolean = true },
.String => blk: {
const str_value = flag_value orelse flag_def.default_value orelse {
return error.MissingFlagValue;
};
break :blk FlagValue{ .String = try allocator.dupe(u8, str_value) };
},
.Number => blk: {
const str_value = flag_value orelse flag_def.default_value orelse {
return error.MissingFlagValue;
};
const num_value = std.fmt.parseInt(i32, str_value, 10) catch {
return error.InvalidNumber;
};
break :blk FlagValue{ .Number = num_value };
},
};
try parsed.setValue(flag_def.name, value);
}
return parsed;
}
fn findFlag(self: *const CommandFlags, name: []const u8) ?*const FlagDef {
for (self.flags) |*flag_def| {
if (std.mem.eql(u8, flag_def.name, name)) {
return flag_def;
}
for (flag_def.aliases) |alias| {
if (alias.len > 0 and alias[0] == '/') {
if (std.mem.eql(u8, alias[1..], name)) {
return flag_def;
}
} else if (std.mem.eql(u8, alias, name)) {
return flag_def;
}
}
}
return null;
}
pub fn getHelp(self: *const CommandFlags, command_name: []const u8, allocator: Allocator) ![]const u8 {
var help = ArrayList(u8).init(allocator);
defer help.deinit();
try help.appendSlice(command_name);
try help.appendSlice(" - Available flags:\n\n");
for (self.flags) |flag_def| {
try help.appendSlice(" /");
try help.appendSlice(flag_def.name);
if (flag_def.aliases.len > 0) {
try help.appendSlice(" (");
for (flag_def.aliases, 0..) |alias, i| {
if (i > 0) try help.appendSlice(", ");
try help.appendSlice(alias);
}
try help.appendSlice(")");
}
switch (flag_def.flag_type) {
.String => try help.appendSlice(":value"),
.Number => try help.appendSlice(":number"),
.Boolean => {},
}
try help.appendSlice(" - ");
try help.appendSlice(flag_def.description);
try help.appendSlice("\n");
}
return help.toOwnedSlice();
}
};
pub const FlagParseError = error{
UnknownFlag,
MissingFlagValue,
InvalidNumber,
OutOfMemory,
};
|