-
Notifications
You must be signed in to change notification settings - Fork 13
/
Command.zig
308 lines (277 loc) · 9.31 KB
/
Command.zig
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
const Command = @This();
const std = @import("std");
const Arg = @import("Arg.zig");
const mem = std.mem;
const Allocator = mem.Allocator;
const ArrayList = std.ArrayList;
const EnumSet = std.EnumSet;
/// Represents the different parsing behaviors that can be applied to a
/// command.
pub const Property = enum {
/// Configures to display help when arguments are not provided.
help_on_empty_args,
/// Specifies that a positional argument must be provided for the command.
positional_arg_required,
/// Specifies that a subcommand must be provided for the command.
subcommand_required,
};
allocator: Allocator,
name: []const u8,
description: ?[]const u8,
positional_args: ArrayList(Arg),
options: ArrayList(Arg),
subcommands: ArrayList(Command),
properties: EnumSet(Property) = .{},
/// Creates a new instance of `Command`.
///
/// **NOTE:** It is generally recommended to use `App.createCommand` to create a
/// new instance of a `Command`.
///
/// ## Examples
///
/// ```zig
/// var app = App.init(allocator, "myapp", "My app description");
/// defer app.deinit();
///
/// var subcmd1 = app.createCommand("subcmd1", "First Subcommand");
/// var subcmd2 = app.createCommand("subcmd2", "Second Subcommand");
/// ```
pub fn init(allocator: Allocator, name: []const u8, description: ?[]const u8) Command {
return Command{
.allocator = allocator,
.name = name,
.description = description,
.positional_args = ArrayList(Arg).init(allocator),
.options = ArrayList(Arg).init(allocator),
.subcommands = ArrayList(Command).init(allocator),
};
}
/// Deallocates all allocated memory.
pub fn deinit(self: *Command) void {
self.positional_args.deinit();
self.options.deinit();
for (self.subcommands.items) |*subcommand| {
subcommand.deinit();
}
self.subcommands.deinit();
}
/// Appends the new argument to the list of arguments.
///
/// **NOTE:** It returns an `error.DuplicatePositionalArgIndex` when attempting
/// to append two positional arguments with the same index. See the examples below.
///
/// ## Examples
///
/// ```zig
/// var app = App.init(allocator, "myapp", "My app description");
/// defer app.deinit();
///
/// var root = app.rootCommand();
/// try root.addArg(Arg.booleanOption("version", 'v', "Show version number"));
///
/// var test = app.createCommand("test", "Run test");
/// try test.addArg(Arg.positional("FILE", null, null));
/// ```
///
/// Appending two positional arguments with the same index.
///
/// ```zig
/// var app = App.init(allocator, "myapp", "My app description");
/// defer app.deinit();
///
/// var root = app.rootCommand();
/// try root.addArg(Arg.positional("FIRST", null, 1));
/// // Returns `error.DuplicatePositionalArgIndex`.
/// try root.addArg(Arg.positional("SECOND", null, 1));
/// ```
pub fn addArg(self: *Command, arg: Arg) !void {
var new_arg = arg;
const is_positional = (arg.short_name == null) and (arg.long_name == null);
// If its not a positional argument, append it and return.
if (!is_positional) {
return self.options.append(new_arg);
}
// Its a positonal argument.
//
// If the position is set check for position duplication.
if (new_arg.index != null) {
for (self.positional_args.items) |positional_arg| {
std.debug.assert(positional_arg.index != null);
if (positional_arg.index.? == new_arg.index.?) {
return error.DuplicatePositionalArgIndex;
}
}
// No duplication; append it.
return self.positional_args.append(new_arg);
}
// If the position is not set and if its the first positional argument
// then return immediately by giving it first position.
if (self.positional_args.items.len == 0) {
new_arg.setIndex(1);
return self.positional_args.append(new_arg);
}
// If the position is not set and if its not first positional argument
// then find the next position for it.
var current_position: usize = 1;
for (self.positional_args.items) |positional_arg| {
std.debug.assert(positional_arg.index != null);
if (positional_arg.index.? > current_position) {
current_position = positional_arg.index.?;
}
}
new_arg.setIndex(current_position + 1);
try self.positional_args.append(new_arg);
}
/// Appends multiple arguments to the list of arguments.
///
/// ## Examples
///
/// ```zig
/// var app = App.init(allocator, "myapp", "My app description");
/// defer app.deinit();
///
/// var root = app.rootCommand();
/// try root.addArgs(&[_]Arg {
/// Arg.singleValueOption("firstname", 'f', "First name"),
/// Arg.singleValueOption("lastname", 'l', "Last name"),
/// });
///
/// var address = app.createCommand("address", "Address");
/// try address.addArgs(&[_]Arg {
/// Arg.singleValueOption("street", 's', "Street name"),
/// Arg.singleValueOption("postal", 'p', "Postal code"),
/// });
/// ```
pub fn addArgs(self: *Command, args: []const Arg) !void {
for (args) |arg| try self.addArg(arg);
}
/// Appends the new subcommand to the list of subcommands.
///
/// ## Examples
///
/// ```zig
/// var app = App.init(allocator, "myapp", "My app description");
/// defer app.deinit();
///
/// var root = app.rootCommand();
///
/// var test = app.createCommand("test", "Run test");
/// try test.addArg(Arg.positional("FILE", null, null));
///
/// try root.addSubcommand(test);
/// ```
pub fn addSubcommand(self: *Command, new_subcommand: Command) !void {
return self.subcommands.append(new_subcommand);
}
/// Appends multiple subcommands to the list of subcommands.
///
/// ## Examples
///
/// ```zig
/// var app = App.init(allocator, "myapp", "My app description");
/// defer app.deinit();
///
/// var root = app.rootCommand();
///
/// try root.addSubcommands(&[_]Command{
/// app.createCommand("init-exe", "Initilize the project"),
/// app.createCommand("build", "Build the project"),
/// });
/// ```
pub fn addSubcommands(self: *Command, subcommands: []const Command) !void {
for (subcommands) |subcmd| try self.addSubcommand(subcmd);
}
/// Sets a property to the command, specifying how it should be parsed and
/// processed.
///
/// ## Examples
///
/// Setting a property to indicate that the positional argument is required:
///
/// ```zig
/// var app = App.init(allocator, "myapp", "My app description");
/// defer app.deinit();
///
/// var root = app.rootCommand();
///
/// try root.addArg(Arg.positional("SOURCE", "Source file to move", null));
/// try root.addArg(Arg.positional("DEST", "Destination path", null));
/// root.setProperty(.positional_arg_required);
/// ```
pub fn setProperty(self: *Command, property: Property) void {
return self.properties.insert(property);
}
/// Unsets a property from the command, reversing its effect on parsing and
/// processing.
pub fn unsetProperty(self: *Command, property: Property) void {
return self.properties.remove(property);
}
/// Checks if the command has a specific property set.
///
/// **NOTE:** This function is primarily used by the parser.
pub fn hasProperty(self: *const Command, property: Property) bool {
return self.properties.contains(property);
}
/// Returns the count of positional arguments in the positional argument list.
///
/// **NOTE:** This function is primarily used by the parser.
pub fn countPositionalArgs(self: *const Command) usize {
return (self.positional_args.items.len);
}
/// Returns the count of options in the option list.
///
/// **NOTE:** This function is primarily used by the parser.
pub fn countOptions(self: *const Command) usize {
return (self.options.items.len);
}
/// Returns the count of subcommands in the subcommand list.
///
/// **NOTE:** This function is primarily used by the parser.
pub fn countSubcommands(self: *const Command) usize {
return (self.subcommands.items.len);
}
/// Performs a linear search to find a positional argument with the given index.
///
/// **NOTE:** This function is primarily used by the parser.
pub fn findPositionalArgByIndex(self: *const Command, index: usize) ?*const Arg {
for (self.positional_args.items) |*pos_arg| {
std.debug.assert(pos_arg.index != null);
if (pos_arg.index.? == index) {
return pos_arg;
}
}
return null;
}
/// Performs a linear search to find a short option with the given short name.
///
/// **NOTE:** This function is primarily used by the parser.
pub fn findShortOption(self: *const Command, short_name: u8) ?*const Arg {
for (self.options.items) |*arg| {
if (arg.short_name) |s| {
if (s == short_name) return arg;
}
}
return null;
}
/// Performs a linear search to find a long option with the given long name.
///
/// **NOTE:** This function is primarily used by the parser.
pub fn findLongOption(self: *const Command, long_name: []const u8) ?*const Arg {
for (self.options.items) |*arg| {
if (arg.long_name) |l| {
if (mem.eql(u8, l, long_name)) return arg;
}
}
return null;
}
/// Performs a linear search to find a subcommand with the given subcommand name.
///
/// **NOTE:** This function is primarily used by the parser.
pub fn findSubcommand(self: *const Command, subcommand: []const u8) ?*const Command {
for (self.subcommands.items) |*subcmd| {
if (std.mem.eql(u8, subcmd.name, subcommand)) {
return subcmd;
}
}
return null;
}