summaryrefslogtreecommitdiff
path: root/clap.zig
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2018-09-06 17:11:58 +0200
committerGravatar Jimmi Holst Christensen2018-09-06 17:11:58 +0200
commit5e1480a7a7537451f7196498ac2988bda8273a9b (patch)
tree2c85b66feeeeec1114d9dbe2608ef64f3ef5ee76 /clap.zig
parentUpdated to use pass-by-value where possible (diff)
downloadzig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.tar.gz
zig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.tar.xz
zig-clap-5e1480a7a7537451f7196498ac2988bda8273a9b.zip
Removed the extended api. Refactored tests
Diffstat (limited to 'clap.zig')
-rw-r--r--clap.zig376
1 files changed, 376 insertions, 0 deletions
diff --git a/clap.zig b/clap.zig
new file mode 100644
index 0000000..bdd1bf4
--- /dev/null
+++ b/clap.zig
@@ -0,0 +1,376 @@
1const std = @import("std");
2const builtin = @import("builtin");
3
4const os = std.os;
5const heap = std.heap;
6const mem = std.mem;
7const debug = std.debug;
8
9/// The names a ::Param can have.
10pub const Names = struct {
11 /// No prefix
12 bare: ?[]const u8,
13
14 /// '-' prefix
15 short: ?u8,
16
17 /// '--' prefix
18 long: ?[]const u8,
19
20 /// Initializes no names
21 pub fn none() Names {
22 return Names{
23 .bare = null,
24 .short = null,
25 .long = null,
26 };
27 }
28
29 /// Initializes a bare name
30 pub fn bare(b: []const u8) Names {
31 return Names{
32 .bare = b,
33 .short = null,
34 .long = null,
35 };
36 }
37
38 /// Initializes a short name
39 pub fn short(s: u8) Names {
40 return Names{
41 .bare = null,
42 .short = s,
43 .long = null,
44 };
45 }
46
47 /// Initializes a long name
48 pub fn long(l: []const u8) Names {
49 return Names{
50 .bare = null,
51 .short = null,
52 .long = l,
53 };
54 }
55
56 /// Initializes a name with a prefix.
57 /// ::short is set to ::name[0], and ::long is set to ::name.
58 /// This function asserts that ::name.len != 0
59 pub fn prefix(name: []const u8) Names {
60 debug.assert(name.len != 0);
61
62 return Names{
63 .bare = null,
64 .short = name[0],
65 .long = name,
66 };
67 }
68};
69
70/// Represents a parameter for the command line.
71/// Parameters come in three kinds:
72/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
73/// * They can take a value three different ways.
74/// * "-a value"
75/// * "-a=value"
76/// * "-avalue"
77/// * They chain if they don't take values: "-abc".
78/// * The last given parameter can take a value in the same way that a single parameter can:
79/// * "-abc value"
80/// * "-abc=value"
81/// * "-abcvalue"
82/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
83/// can describe the paramter.
84/// * They can take a value two different ways.
85/// * "--long-param value"
86/// * "--long-param=value"
87/// * Bare ("bare"): Should be used as for sub-commands and other keywords.
88/// * They can take a value two different ways.
89/// * "command value"
90/// * "command=value"
91/// * Value ("value"): Should be used as the primary parameter of the program, like a filename or
92/// an expression to parse.
93/// * Value parameters must take a value.
94pub fn Param(comptime Id: type) type {
95 return struct {
96 const Self = this;
97
98 id: Id,
99 takes_value: bool,
100 names: Names,
101
102 pub fn init(id: Id, takes_value: bool, names: Names) Self {
103 // Assert, that if the param have no name, then it has to take
104 // a value.
105 debug.assert(names.bare != null or
106 names.long != null or
107 names.short != null or
108 takes_value);
109
110 return Self{
111 .id = id,
112 .takes_value = takes_value,
113 .names = names,
114 };
115 }
116 };
117}
118
119/// The result returned from ::Clap.next
120pub fn Arg(comptime Id: type) type {
121 return struct {
122 const Self = this;
123
124 param: *const Param(Id),
125 value: ?[]const u8,
126
127 pub fn init(param: *const Param(Id), value: ?[]const u8) Self {
128 return Self{
129 .param = param,
130 .value = value,
131 };
132 }
133 };
134}
135
136/// A interface for iterating over command line arguments
137pub fn ArgIterator(comptime E: type) type {
138 return struct {
139 const Self = this;
140 const Error = E;
141
142 nextFn: fn (iter: *Self) Error!?[]const u8,
143
144 pub fn next(iter: *Self) Error!?[]const u8 {
145 return iter.nextFn(iter);
146 }
147 };
148}
149
150/// An ::ArgIterator, which iterates over a slice of arguments.
151/// This implementation does not allocate.
152pub const ArgSliceIterator = struct {
153 const Error = error{};
154
155 args: []const []const u8,
156 index: usize,
157 iter: ArgIterator(Error),
158
159 pub fn init(args: []const []const u8) ArgSliceIterator {
160 return ArgSliceIterator{
161 .args = args,
162 .index = 0,
163 .iter = ArgIterator(Error){ .nextFn = nextFn },
164 };
165 }
166
167 fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 {
168 const self = @fieldParentPtr(ArgSliceIterator, "iter", iter);
169 if (self.args.len <= self.index)
170 return null;
171
172 defer self.index += 1;
173 return self.args[self.index];
174 }
175};
176
177/// An ::ArgIterator, which wraps the ArgIterator in ::std.
178/// On windows, this iterator allocates.
179pub const OsArgIterator = struct {
180 const Error = os.ArgIterator.NextError;
181
182 arena: heap.ArenaAllocator,
183 args: os.ArgIterator,
184 iter: ArgIterator(Error),
185
186 pub fn init(allocator: *mem.Allocator) OsArgIterator {
187 return OsArgIterator{
188 .arena = heap.ArenaAllocator.init(allocator),
189 .args = os.args(),
190 .iter = ArgIterator(Error){ .nextFn = nextFn },
191 };
192 }
193
194 pub fn deinit(iter: *OsArgIterator) void {
195 iter.arena.deinit();
196 }
197
198 fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 {
199 const self = @fieldParentPtr(OsArgIterator, "iter", iter);
200 if (builtin.os == builtin.Os.windows) {
201 return try self.args.next(self.allocator) orelse return null;
202 } else {
203 return self.args.nextPosix();
204 }
205 }
206};
207
208/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
209/// to the ::params. ::Clap parses in an iterating manner, so you have to use a loop together with
210/// ::Clap.next to parse all the arguments of your program.
211pub fn Clap(comptime Id: type, comptime ArgError: type) type {
212 return struct {
213 const Self = this;
214
215 const State = union(enum) {
216 Normal,
217 Chaining: Chaining,
218
219 const Chaining = struct {
220 arg: []const u8,
221 index: usize,
222 };
223 };
224
225 params: []const Param(Id),
226 iter: *ArgIterator(ArgError),
227 state: State,
228
229 pub fn init(params: []const Param(Id), iter: *ArgIterator(ArgError)) Self {
230 var res = Self{
231 .params = params,
232 .iter = iter,
233 .state = State.Normal,
234 };
235
236 return res;
237 }
238
239 /// Get the next ::Arg that matches a ::Param.
240 pub fn next(clap: *Self) !?Arg(Id) {
241 const ArgInfo = struct {
242 const Kind = enum {
243 Long,
244 Short,
245 Bare,
246 };
247
248 arg: []const u8,
249 kind: Kind,
250 };
251
252 switch (clap.state) {
253 State.Normal => {
254 const full_arg = (try clap.iter.next()) orelse return null;
255 const arg_info = blk: {
256 var arg = full_arg;
257 var kind = ArgInfo.Kind.Bare;
258
259 if (mem.startsWith(u8, arg, "--")) {
260 arg = arg[2..];
261 kind = ArgInfo.Kind.Long;
262 } else if (mem.startsWith(u8, arg, "-")) {
263 arg = arg[1..];
264 kind = ArgInfo.Kind.Short;
265 }
266
267 // We allow long arguments to go without a name.
268 // This allows the user to use "--" for something important
269 if (kind != ArgInfo.Kind.Long and arg.len == 0)
270 return error.InvalidArgument;
271
272 break :blk ArgInfo{ .arg = arg, .kind = kind };
273 };
274
275 const arg = arg_info.arg;
276 const kind = arg_info.kind;
277 const eql_index = mem.indexOfScalar(u8, arg, '=');
278
279 switch (kind) {
280 ArgInfo.Kind.Bare, ArgInfo.Kind.Long => {
281 for (clap.params) |*param| {
282 const match = switch (kind) {
283 ArgInfo.Kind.Bare => param.names.bare orelse continue,
284 ArgInfo.Kind.Long => param.names.long orelse continue,
285 else => unreachable,
286 };
287 const name = if (eql_index) |i| arg[0..i] else arg;
288 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
289
290 if (!mem.eql(u8, name, match))
291 continue;
292 if (!param.takes_value) {
293 if (maybe_value != null)
294 return error.DoesntTakeValue;
295
296 return Arg(Id).init(param, null);
297 }
298
299 const value = blk: {
300 if (maybe_value) |v|
301 break :blk v;
302
303 break :blk (try clap.iter.next()) orelse return error.MissingValue;
304 };
305
306 return Arg(Id).init(param, value);
307 }
308 },
309 ArgInfo.Kind.Short => {
310 return try clap.chainging(State.Chaining{
311 .arg = full_arg,
312 .index = (full_arg.len - arg.len),
313 });
314 },
315 }
316
317 // We do a final pass to look for value parameters matches
318 if (kind == ArgInfo.Kind.Bare) {
319 for (clap.params) |*param| {
320 if (param.names.bare) |_| continue;
321 if (param.names.short) |_| continue;
322 if (param.names.long) |_| continue;
323
324 return Arg(Id).init(param, arg);
325 }
326 }
327
328 return error.InvalidArgument;
329 },
330 @TagType(State).Chaining => |state| return try clap.chainging(state),
331 }
332 }
333
334 fn chainging(clap: *Self, state: State.Chaining) !?Arg(Id) {
335 const arg = state.arg;
336 const index = state.index;
337 const next_index = index + 1;
338
339 for (clap.params) |*param| {
340 const short = param.names.short orelse continue;
341 if (short != arg[index])
342 continue;
343
344 // Before we return, we have to set the new state of the clap
345 defer {
346 if (arg.len <= next_index or param.takes_value) {
347 clap.state = State.Normal;
348 } else {
349 clap.state = State{
350 .Chaining = State.Chaining{
351 .arg = arg,
352 .index = next_index,
353 },
354 };
355 }
356 }
357
358 if (!param.takes_value)
359 return Arg(Id).init(param, null);
360
361 if (arg.len <= next_index) {
362 const value = (try clap.iter.next()) orelse return error.MissingValue;
363 return Arg(Id).init(param, value);
364 }
365
366 if (arg[next_index] == '=') {
367 return Arg(Id).init(param, arg[next_index + 1 ..]);
368 }
369
370 return Arg(Id).init(param, arg[next_index..]);
371 }
372
373 return error.InvalidArgument;
374 }
375 };
376}