summaryrefslogtreecommitdiff
path: root/core.zig
diff options
context:
space:
mode:
Diffstat (limited to 'core.zig')
-rw-r--r--core.zig493
1 files changed, 0 insertions, 493 deletions
diff --git a/core.zig b/core.zig
deleted file mode 100644
index 306ff63..0000000
--- a/core.zig
+++ /dev/null
@@ -1,493 +0,0 @@
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: &const Names) Self {
103 // Assert, that if the param have no name, then it has to take
104 // a value.
105 debug.assert(
106 names.bare != null or
107 names.long != null or
108 names.short != null or
109 takes_value
110 );
111
112 return Self{
113 .id = id,
114 .takes_value = takes_value,
115 .names = names.*,
116 };
117 }
118 };
119}
120
121/// The result returned from ::Clap.next
122pub fn Arg(comptime Id: type) type {
123 return struct {
124 const Self = this;
125
126 id: Id,
127 value: ?[]const u8,
128
129 pub fn init(id: Id, value: ?[]const u8) Self {
130 return Self {
131 .id = id,
132 .value = value,
133 };
134 }
135 };
136}
137
138/// A interface for iterating over command line arguments
139pub const ArgIterator = struct {
140 const Error = error{OutOfMemory};
141
142 nextFn: fn(iter: &ArgIterator, allocator: &mem.Allocator) Error!?[]const u8,
143
144 pub fn next(iter: &ArgIterator, allocator: &mem.Allocator) Error!?[]const u8 {
145 return iter.nextFn(iter, allocator);
146 }
147};
148
149/// An ::ArgIterator, which iterates over a slice of arguments.
150/// This implementation does not allocate.
151pub const ArgSliceIterator = struct {
152 args: []const []const u8,
153 index: usize,
154 iter: ArgIterator,
155
156 pub fn init(args: []const []const u8) ArgSliceIterator {
157 return ArgSliceIterator {
158 .args = args,
159 .index = 0,
160 .iter = ArgIterator {
161 .nextFn = nextFn,
162 },
163 };
164 }
165
166 fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error!?[]const u8 {
167 const self = @fieldParentPtr(ArgSliceIterator, "iter", iter);
168 if (self.args.len <= self.index)
169 return null;
170
171 defer self.index += 1;
172 return self.args[self.index];
173 }
174};
175
176/// An ::ArgIterator, which wraps the ArgIterator in ::std.
177/// On windows, this iterator allocates.
178pub const OsArgIterator = struct {
179 args: os.ArgIterator,
180 iter: ArgIterator,
181
182 pub fn init() OsArgIterator {
183 return OsArgIterator {
184 .args = os.args(),
185 .iter = ArgIterator {
186 .nextFn = nextFn,
187 },
188 };
189 }
190
191 fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error!?[]const u8 {
192 const self = @fieldParentPtr(OsArgIterator, "iter", iter);
193 if (builtin.os == builtin.Os.windows) {
194 return try self.args.next(allocator) ?? return null;
195 } else {
196 return self.args.nextPosix();
197 }
198 }
199};
200
201/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
202/// to the ::params. ::Clap parses in an iterating manner, so you have to use a loop together with
203/// ::Clap.next to parse all the arguments of your program.
204pub fn Clap(comptime Id: type) type {
205 return struct {
206 const Self = this;
207
208 const State = union(enum) {
209 Normal,
210 Chaining: Chaining,
211
212 const Chaining = struct {
213 arg: []const u8,
214 index: usize,
215 };
216 };
217
218 arena: heap.ArenaAllocator,
219 params: []const Param(Id),
220 inner: &ArgIterator,
221 state: State,
222
223 pub fn init(params: []const Param(Id), inner: &ArgIterator, allocator: &mem.Allocator) Self {
224 var res = Self {
225 .arena = heap.ArenaAllocator.init(allocator),
226 .params = params,
227 .inner = inner,
228 .state = State.Normal,
229 };
230
231 return res;
232 }
233
234 pub fn deinit(iter: &Self) void {
235 iter.arena.deinit();
236 }
237
238 /// Get the next ::Arg that matches a ::Param.
239 pub fn next(iter: &Self) !?Arg(Id) {
240 const ArgInfo = struct {
241 const Kind = enum { Long, Short, Bare };
242
243 arg: []const u8,
244 kind: Kind,
245 };
246
247 switch (iter.state) {
248 State.Normal => {
249 const full_arg = (try iter.innerNext()) ?? return null;
250 const arg_info = blk: {
251 var arg = full_arg;
252 var kind = ArgInfo.Kind.Bare;
253
254 if (mem.startsWith(u8, arg, "--")) {
255 arg = arg[2..];
256 kind = ArgInfo.Kind.Long;
257 } else if (mem.startsWith(u8, arg, "-")) {
258 arg = arg[1..];
259 kind = ArgInfo.Kind.Short;
260 }
261
262 if (arg.len == 0)
263 return error.ArgWithNoName;
264
265 break :blk ArgInfo { .arg = arg, .kind = kind };
266 };
267
268 const arg = arg_info.arg;
269 const kind = arg_info.kind;
270 const eql_index = mem.indexOfScalar(u8, arg, '=');
271
272 switch (kind) {
273 ArgInfo.Kind.Bare,
274 ArgInfo.Kind.Long => {
275 for (iter.params) |*param| {
276 const match = switch (kind) {
277 ArgInfo.Kind.Bare => param.names.bare ?? continue,
278 ArgInfo.Kind.Long => param.names.long ?? continue,
279 else => unreachable,
280 };
281 const name = if (eql_index) |i| arg[0..i] else arg;
282 const maybe_value = if (eql_index) |i| arg[i + 1..] else null;
283
284 if (!mem.eql(u8, name, match))
285 continue;
286 if (!param.takes_value) {
287 if (maybe_value != null)
288 return error.DoesntTakeValue;
289
290 return Arg(Id).init(param.id, null);
291 }
292
293 const value = blk: {
294 if (maybe_value) |v|
295 break :blk v;
296
297 break :blk (try iter.innerNext()) ?? return error.MissingValue;
298 };
299
300 return Arg(Id).init(param.id, value);
301 }
302 },
303 ArgInfo.Kind.Short => {
304 return try iter.chainging(State.Chaining {
305 .arg = full_arg,
306 .index = (full_arg.len - arg.len),
307 });
308 },
309 }
310
311 // We do a final pass to look for value parameters matches
312 if (kind == ArgInfo.Kind.Bare) {
313 for (iter.params) |*param| {
314 if (param.names.bare) |_| continue;
315 if (param.names.short) |_| continue;
316 if (param.names.long) |_| continue;
317
318 return Arg(Id).init(param.id, arg);
319 }
320 }
321
322 return error.InvalidArgument;
323 },
324 @TagType(State).Chaining => |state| return try iter.chainging(state),
325 }
326 }
327
328 fn chainging(iter: &Self, state: &const State.Chaining) !?Arg(Id) {
329 const arg = state.arg;
330 const index = state.index;
331 const next_index = index + 1;
332
333 for (iter.params) |param| {
334 const short = param.names.short ?? continue;
335 if (short != arg[index])
336 continue;
337
338 // Before we return, we have to set the new state of the iterator
339 defer {
340 if (arg.len <= next_index or param.takes_value) {
341 iter.state = State.Normal;
342 } else {
343 iter.state = State { .Chaining = State.Chaining {
344 .arg = arg,
345 .index = next_index,
346 }};
347 }
348 }
349
350 if (!param.takes_value)
351 return Arg(Id).init(param.id, null);
352
353 if (arg.len <= next_index) {
354 const value = (try iter.innerNext()) ?? return error.MissingValue;
355 return Arg(Id).init(param.id, value);
356 }
357
358 if (arg[next_index] == '=') {
359 return Arg(Id).init(param.id, arg[next_index + 1..]);
360 }
361
362 return Arg(Id).init(param.id, arg[next_index..]);
363 }
364
365 return error.InvalidArgument;
366 }
367
368 fn innerNext(iter: &Self) !?[]const u8 {
369 return try iter.inner.next(&iter.arena.allocator);
370 }
371 };
372}
373
374fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void {
375 var arg_iter = ArgSliceIterator.init(args);
376 var iter = Clap(u8).init(params, &arg_iter.iter, debug.global_allocator);
377
378 var i: usize = 0;
379 while (iter.next() catch unreachable) |arg| : (i += 1) {
380 debug.assert(ids[i] == arg.id);
381 const expected_value = values[i] ?? {
382 debug.assert(arg.value == null);
383 continue;
384 };
385 const actual_value = arg.value ?? unreachable;
386
387 debug.assert(mem.eql(u8, expected_value, actual_value));
388 }
389}
390
391test "clap.core: short" {
392 const params = []Param(u8) {
393 Param(u8).init(0, false, Names.short('a')),
394 Param(u8).init(1, false, Names.short('b')),
395 Param(u8).init(2, true, Names.short('c')),
396 };
397
398 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
399 testNoErr(params, [][]const u8 { "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null});
400 testNoErr(params, [][]const u8 { "-ab" }, []u8{0,1}, []?[]const u8{null,null});
401 testNoErr(params, [][]const u8 { "-c=100" }, []u8{2}, []?[]const u8{"100"});
402 testNoErr(params, [][]const u8 { "-c100" }, []u8{2}, []?[]const u8{"100"});
403 testNoErr(params, [][]const u8 { "-c", "100" }, []u8{2}, []?[]const u8{"100"});
404 testNoErr(params, [][]const u8 { "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
405 testNoErr(params, [][]const u8 { "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
406 testNoErr(params, [][]const u8 { "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
407}
408
409test "clap.core: long" {
410 const params = []Param(u8) {
411 Param(u8).init(0, false, Names.long("aa")),
412 Param(u8).init(1, false, Names.long("bb")),
413 Param(u8).init(2, true, Names.long("cc")),
414 };
415
416 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null});
417 testNoErr(params, [][]const u8 { "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null});
418 testNoErr(params, [][]const u8 { "--cc=100" }, []u8{2}, []?[]const u8{"100"});
419 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
420}
421
422test "clap.core: bare" {
423 const params = []Param(u8) {
424 Param(u8).init(0, false, Names.bare("aa")),
425 Param(u8).init(1, false, Names.bare("bb")),
426 Param(u8).init(2, true, Names.bare("cc")),
427 };
428
429 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{null});
430 testNoErr(params, [][]const u8 { "aa", "bb" }, []u8{0,1}, []?[]const u8{null,null});
431 testNoErr(params, [][]const u8 { "cc=100" }, []u8{2}, []?[]const u8{"100"});
432 testNoErr(params, [][]const u8 { "cc", "100" }, []u8{2}, []?[]const u8{"100"});
433}
434
435test "clap.core: none" {
436 const params = []Param(u8) {
437 Param(u8).init(0, true, Names.none()),
438 };
439
440 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{"aa"});
441}
442
443test "clap.core: all" {
444 const params = []Param(u8) {
445 Param(u8).init(
446 0,
447 false,
448 Names{
449 .bare = "aa",
450 .short = 'a',
451 .long = "aa",
452 }
453 ),
454 Param(u8).init(
455 1,
456 false,
457 Names{
458 .bare = "bb",
459 .short = 'b',
460 .long = "bb",
461 }
462 ),
463 Param(u8).init(
464 2,
465 true,
466 Names{
467 .bare = "cc",
468 .short = 'c',
469 .long = "cc",
470 }
471 ),
472 Param(u8).init(3, true, Names.none()),
473 };
474
475 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
476 testNoErr(params, [][]const u8 { "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null});
477 testNoErr(params, [][]const u8 { "-ab" }, []u8{0,1}, []?[]const u8{null,null});
478 testNoErr(params, [][]const u8 { "-c=100" }, []u8{2}, []?[]const u8{"100"});
479 testNoErr(params, [][]const u8 { "-c100" }, []u8{2}, []?[]const u8{"100"});
480 testNoErr(params, [][]const u8 { "-c", "100" }, []u8{2}, []?[]const u8{"100"});
481 testNoErr(params, [][]const u8 { "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
482 testNoErr(params, [][]const u8 { "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
483 testNoErr(params, [][]const u8 { "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"});
484 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null});
485 testNoErr(params, [][]const u8 { "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null});
486 testNoErr(params, [][]const u8 { "--cc=100" }, []u8{2}, []?[]const u8{"100"});
487 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
488 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{null});
489 testNoErr(params, [][]const u8 { "aa", "bb" }, []u8{0,1}, []?[]const u8{null,null});
490 testNoErr(params, [][]const u8 { "cc=100" }, []u8{2}, []?[]const u8{"100"});
491 testNoErr(params, [][]const u8 { "cc", "100" }, []u8{2}, []?[]const u8{"100"});
492 testNoErr(params, [][]const u8 { "dd" }, []u8{3}, []?[]const u8{"dd"});
493}