summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--core.zig312
-rw-r--r--index.zig14
2 files changed, 179 insertions, 147 deletions
diff --git a/core.zig b/core.zig
index 51739ab..306ff63 100644
--- a/core.zig
+++ b/core.zig
@@ -6,6 +6,67 @@ const heap = std.heap;
6const mem = std.mem; 6const mem = std.mem;
7const debug = std.debug; 7const debug = std.debug;
8 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
9/// Represents a parameter for the command line. 70/// Represents a parameter for the command line.
10/// Parameters come in three kinds: 71/// Parameters come in three kinds:
11/// * Short ("-a"): Should be used for the most commonly used parameters in your program. 72/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
@@ -19,96 +80,50 @@ const debug = std.debug;
19/// * "-abc=value" 80/// * "-abc=value"
20/// * "-abcvalue" 81/// * "-abcvalue"
21/// * Long ("--long-param"): Should be used for less common parameters, or when no single character 82/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
22/// can describe the paramter. 83/// can describe the paramter.
23/// * They can take a value two different ways. 84/// * They can take a value two different ways.
24/// * "--long-param value" 85/// * "--long-param value"
25/// * "--long-param=value" 86/// * "--long-param=value"
26/// * Command ("command"): Should be used as for sub-commands and other keywords. 87/// * Bare ("bare"): Should be used as for sub-commands and other keywords.
27/// * They can take a value two different ways. 88/// * They can take a value two different ways.
28/// * "command value" 89/// * "command value"
29/// * "command=value" 90/// * "command=value"
30/// * Value ("value"): Should be used as the primary parameter of the program, like a filename or 91/// * Value ("value"): Should be used as the primary parameter of the program, like a filename or
31/// an expression to parse. 92/// an expression to parse.
93/// * Value parameters must take a value.
32pub fn Param(comptime Id: type) type { 94pub fn Param(comptime Id: type) type {
33 return struct { 95 return struct {
34 const Self = this; 96 const Self = this;
35 97
36 id: Id, 98 id: Id,
37 command: ?[]const u8,
38 short: ?u8,
39 long: ?[]const u8,
40 takes_value: bool, 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 );
41 111
42 pub fn short(id: Id, s: u8, takes_value: bool) Self {
43 return Self{
44 .id = id,
45 .command = null,
46 .short = s,
47 .long = null,
48 .takes_value = takes_value,
49 };
50 }
51
52 pub fn long(id: Id, l: []const u8, takes_value: bool) Self {
53 return Self{
54 .id = id,
55 .command = null,
56 .short = null,
57 .long = l,
58 .takes_value = takes_value,
59 };
60 }
61
62 pub fn command(id: Id, c: []const u8, takes_value: bool) Self {
63 return Self{ 112 return Self{
64 .id = id, 113 .id = id,
65 .command = c,
66 .short = null,
67 .long = null,
68 .takes_value = takes_value, 114 .takes_value = takes_value,
115 .names = names.*,
69 }; 116 };
70 } 117 }
71
72 pub fn value(id: Id) Self {
73 return Self{
74 .id = id,
75 .command = null,
76 .short = null,
77 .long = null,
78 .takes_value = true,
79 };
80 }
81
82 /// Initialize a ::Param.
83 /// If ::name.len == 0, then it's a value parameter: "value".
84 /// If ::name.len == 1, then it's a short parameter: "-s".
85 /// If ::name.len > 1, then it's a long parameter: "--long".
86 pub fn smart(id: Id, name: []const u8, takes_value: bool) Self {
87 return Self{
88 .id = id,
89 .command = null,
90 .short = if (name.len == 1) name[0] else null,
91 .long = if (name.len > 1) name else null,
92 .takes_value = takes_value,
93 };
94 }
95
96 pub fn with(param: &const Self, comptime field_name: []const u8, v: var) Self {
97 var res = param.*;
98 @field(res, field_name) = v;
99 return res;
100 }
101 }; 118 };
102} 119}
103 120
121/// The result returned from ::Clap.next
104pub fn Arg(comptime Id: type) type { 122pub fn Arg(comptime Id: type) type {
105 return struct { 123 return struct {
106 const Self = this; 124 const Self = this;
107 125
108 id: Id, 126 id: Id,
109
110 /// ::Iterator owns ::value. On windows, this means that when you call ::Iterator.deinit
111 /// ::value is freed.
112 value: ?[]const u8, 127 value: ?[]const u8,
113 128
114 pub fn init(id: Id, value: ?[]const u8) Self { 129 pub fn init(id: Id, value: ?[]const u8) Self {
@@ -120,6 +135,7 @@ pub fn Arg(comptime Id: type) type {
120 }; 135 };
121} 136}
122 137
138/// A interface for iterating over command line arguments
123pub const ArgIterator = struct { 139pub const ArgIterator = struct {
124 const Error = error{OutOfMemory}; 140 const Error = error{OutOfMemory};
125 141
@@ -130,6 +146,8 @@ pub const ArgIterator = struct {
130 } 146 }
131}; 147};
132 148
149/// An ::ArgIterator, which iterates over a slice of arguments.
150/// This implementation does not allocate.
133pub const ArgSliceIterator = struct { 151pub const ArgSliceIterator = struct {
134 args: []const []const u8, 152 args: []const []const u8,
135 index: usize, 153 index: usize,
@@ -155,6 +173,8 @@ pub const ArgSliceIterator = struct {
155 } 173 }
156}; 174};
157 175
176/// An ::ArgIterator, which wraps the ArgIterator in ::std.
177/// On windows, this iterator allocates.
158pub const OsArgIterator = struct { 178pub const OsArgIterator = struct {
159 args: os.ArgIterator, 179 args: os.ArgIterator,
160 iter: ArgIterator, 180 iter: ArgIterator,
@@ -178,8 +198,10 @@ pub const OsArgIterator = struct {
178 } 198 }
179}; 199};
180 200
181/// A ::CustomIterator with a default Windows buffer size. 201/// A command line argument parser which, given an ::ArgIterator, will parse arguments according
182pub fn Iterator(comptime Id: type) type { 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 {
183 return struct { 205 return struct {
184 const Self = this; 206 const Self = this;
185 207
@@ -190,7 +212,6 @@ pub fn Iterator(comptime Id: type) type {
190 const Chaining = struct { 212 const Chaining = struct {
191 arg: []const u8, 213 arg: []const u8,
192 index: usize, 214 index: usize,
193 param: &const Param(Id),
194 }; 215 };
195 }; 216 };
196 217
@@ -217,7 +238,7 @@ pub fn Iterator(comptime Id: type) type {
217 /// Get the next ::Arg that matches a ::Param. 238 /// Get the next ::Arg that matches a ::Param.
218 pub fn next(iter: &Self) !?Arg(Id) { 239 pub fn next(iter: &Self) !?Arg(Id) {
219 const ArgInfo = struct { 240 const ArgInfo = struct {
220 const Kind = enum { Long, Short, Command }; 241 const Kind = enum { Long, Short, Bare };
221 242
222 arg: []const u8, 243 arg: []const u8,
223 kind: Kind, 244 kind: Kind,
@@ -228,7 +249,7 @@ pub fn Iterator(comptime Id: type) type {
228 const full_arg = (try iter.innerNext()) ?? return null; 249 const full_arg = (try iter.innerNext()) ?? return null;
229 const arg_info = blk: { 250 const arg_info = blk: {
230 var arg = full_arg; 251 var arg = full_arg;
231 var kind = ArgInfo.Kind.Command; 252 var kind = ArgInfo.Kind.Bare;
232 253
233 if (mem.startsWith(u8, arg, "--")) { 254 if (mem.startsWith(u8, arg, "--")) {
234 arg = arg[2..]; 255 arg = arg[2..];
@@ -248,13 +269,13 @@ pub fn Iterator(comptime Id: type) type {
248 const kind = arg_info.kind; 269 const kind = arg_info.kind;
249 const eql_index = mem.indexOfScalar(u8, arg, '='); 270 const eql_index = mem.indexOfScalar(u8, arg, '=');
250 271
251 for (iter.params) |*param| { 272 switch (kind) {
252 switch (kind) { 273 ArgInfo.Kind.Bare,
253 ArgInfo.Kind.Command, 274 ArgInfo.Kind.Long => {
254 ArgInfo.Kind.Long => { 275 for (iter.params) |*param| {
255 const match = switch (kind) { 276 const match = switch (kind) {
256 ArgInfo.Kind.Command => param.command ?? continue, 277 ArgInfo.Kind.Bare => param.names.bare ?? continue,
257 ArgInfo.Kind.Long => param.long ?? continue, 278 ArgInfo.Kind.Long => param.names.long ?? continue,
258 else => unreachable, 279 else => unreachable,
259 }; 280 };
260 const name = if (eql_index) |i| arg[0..i] else arg; 281 const name = if (eql_index) |i| arg[0..i] else arg;
@@ -277,27 +298,22 @@ pub fn Iterator(comptime Id: type) type {
277 }; 298 };
278 299
279 return Arg(Id).init(param.id, value); 300 return Arg(Id).init(param.id, value);
280 }, 301 }
281 ArgInfo.Kind.Short => { 302 },
282 const short = param.short ?? continue; 303 ArgInfo.Kind.Short => {
283 if (short != arg[0]) 304 return try iter.chainging(State.Chaining {
284 continue; 305 .arg = full_arg,
285 306 .index = (full_arg.len - arg.len),
286 return try iter.chainging(State.Chaining { 307 });
287 .arg = full_arg, 308 },
288 .index = (full_arg.len - arg.len) + 1,
289 .param = param,
290 });
291 },
292 }
293 } 309 }
294 310
295 // We do a final pass to look for value parameters matches 311 // We do a final pass to look for value parameters matches
296 if (kind == ArgInfo.Kind.Command) { 312 if (kind == ArgInfo.Kind.Bare) {
297 for (iter.params) |*param| { 313 for (iter.params) |*param| {
298 if (param.short) |_| continue; 314 if (param.names.bare) |_| continue;
299 if (param.long) |_| continue; 315 if (param.names.short) |_| continue;
300 if (param.command) |_| continue; 316 if (param.names.long) |_| continue;
301 317
302 return Arg(Id).init(param.id, arg); 318 return Arg(Id).init(param.id, arg);
303 } 319 }
@@ -312,42 +328,40 @@ pub fn Iterator(comptime Id: type) type {
312 fn chainging(iter: &Self, state: &const State.Chaining) !?Arg(Id) { 328 fn chainging(iter: &Self, state: &const State.Chaining) !?Arg(Id) {
313 const arg = state.arg; 329 const arg = state.arg;
314 const index = state.index; 330 const index = state.index;
315 const curr_param = state.param; 331 const next_index = index + 1;
316
317 if (curr_param.takes_value) {
318 iter.state = State.Normal;
319 332
320 if (arg.len <= index) { 333 for (iter.params) |param| {
321 const value = (try iter.innerNext()) ?? return error.MissingValue; 334 const short = param.names.short ?? continue;
322 return Arg(Id).init(curr_param.id, value); 335 if (short != arg[index])
323 } 336 continue;
324 337
325 if (arg[index] == '=') { 338 // Before we return, we have to set the new state of the iterator
326 return Arg(Id).init(curr_param.id, arg[index + 1..]); 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 }
327 } 348 }
328 349
329 return Arg(Id).init(curr_param.id, arg[index..]); 350 if (!param.takes_value)
330 } 351 return Arg(Id).init(param.id, null);
331 352
332 if (arg.len <= index) { 353 if (arg.len <= next_index) {
333 iter.state = State.Normal; 354 const value = (try iter.innerNext()) ?? return error.MissingValue;
334 return Arg(Id).init(curr_param.id, null); 355 return Arg(Id).init(param.id, value);
335 } 356 }
336 357
337 for (iter.params) |*param| { 358 if (arg[next_index] == '=') {
338 const short = param.short ?? continue; 359 return Arg(Id).init(param.id, arg[next_index + 1..]);
339 if (short != arg[index]) 360 }
340 continue;
341 361
342 iter.state = State { .Chaining = State.Chaining { 362 return Arg(Id).init(param.id, arg[next_index..]);
343 .arg = arg,
344 .index = index + 1,
345 .param = param,
346 }};
347 return Arg(Id).init(curr_param.id, null);
348 } 363 }
349 364
350 // This actually returns an error for the next argument.
351 return error.InvalidArgument; 365 return error.InvalidArgument;
352 } 366 }
353 367
@@ -359,7 +373,7 @@ pub fn Iterator(comptime Id: type) type {
359 373
360fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void { 374fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void {
361 var arg_iter = ArgSliceIterator.init(args); 375 var arg_iter = ArgSliceIterator.init(args);
362 var iter = Iterator(u8).init(params, &arg_iter.iter, debug.global_allocator); 376 var iter = Clap(u8).init(params, &arg_iter.iter, debug.global_allocator);
363 377
364 var i: usize = 0; 378 var i: usize = 0;
365 while (iter.next() catch unreachable) |arg| : (i += 1) { 379 while (iter.next() catch unreachable) |arg| : (i += 1) {
@@ -376,9 +390,9 @@ fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u
376 390
377test "clap.core: short" { 391test "clap.core: short" {
378 const params = []Param(u8) { 392 const params = []Param(u8) {
379 Param(u8).smart(0, "a", false), 393 Param(u8).init(0, false, Names.short('a')),
380 Param(u8).smart(1, "b", false), 394 Param(u8).init(1, false, Names.short('b')),
381 Param(u8).smart(2, "c", true), 395 Param(u8).init(2, true, Names.short('c')),
382 }; 396 };
383 397
384 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null}); 398 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
@@ -394,9 +408,9 @@ test "clap.core: short" {
394 408
395test "clap.core: long" { 409test "clap.core: long" {
396 const params = []Param(u8) { 410 const params = []Param(u8) {
397 Param(u8).smart(0, "aa", false), 411 Param(u8).init(0, false, Names.long("aa")),
398 Param(u8).smart(1, "bb", false), 412 Param(u8).init(1, false, Names.long("bb")),
399 Param(u8).smart(2, "cc", true), 413 Param(u8).init(2, true, Names.long("cc")),
400 }; 414 };
401 415
402 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null}); 416 testNoErr(params, [][]const u8 { "--aa" }, []u8{0}, []?[]const u8{null});
@@ -405,11 +419,11 @@ test "clap.core: long" {
405 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); 419 testNoErr(params, [][]const u8 { "--cc", "100" }, []u8{2}, []?[]const u8{"100"});
406} 420}
407 421
408test "clap.core: command" { 422test "clap.core: bare" {
409 const params = []Param(u8) { 423 const params = []Param(u8) {
410 Param(u8).command(0, "aa", false), 424 Param(u8).init(0, false, Names.bare("aa")),
411 Param(u8).command(1, "bb", false), 425 Param(u8).init(1, false, Names.bare("bb")),
412 Param(u8).command(2, "cc", true), 426 Param(u8).init(2, true, Names.bare("cc")),
413 }; 427 };
414 428
415 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{null}); 429 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{null});
@@ -418,9 +432,9 @@ test "clap.core: command" {
418 testNoErr(params, [][]const u8 { "cc", "100" }, []u8{2}, []?[]const u8{"100"}); 432 testNoErr(params, [][]const u8 { "cc", "100" }, []u8{2}, []?[]const u8{"100"});
419} 433}
420 434
421test "clap.core: value" { 435test "clap.core: none" {
422 const params = []Param(u8) { 436 const params = []Param(u8) {
423 Param(u8).value(0), 437 Param(u8).init(0, true, Names.none()),
424 }; 438 };
425 439
426 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{"aa"}); 440 testNoErr(params, [][]const u8 { "aa" }, []u8{0}, []?[]const u8{"aa"});
@@ -428,16 +442,34 @@ test "clap.core: value" {
428 442
429test "clap.core: all" { 443test "clap.core: all" {
430 const params = []Param(u8) { 444 const params = []Param(u8) {
431 Param(u8).short(0, 'a', false) 445 Param(u8).init(
432 .with("long", "aa"[0..]) 446 0,
433 .with("command", "aa"[0..]), 447 false,
434 Param(u8).short(1, 'b', false) 448 Names{
435 .with("long", "bb"[0..]) 449 .bare = "aa",
436 .with("command", "bb"[0..]), 450 .short = 'a',
437 Param(u8).short(2, 'c', true) 451 .long = "aa",
438 .with("long", "cc"[0..]) 452 }
439 .with("command", "cc"[0..]), 453 ),
440 Param(u8).value(3), 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()),
441 }; 473 };
442 474
443 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null}); 475 testNoErr(params, [][]const u8 { "-a" }, []u8{0}, []?[]const u8{null});
diff --git a/index.zig b/index.zig
index c7aff0f..8226dfa 100644
--- a/index.zig
+++ b/index.zig
@@ -128,10 +128,12 @@ pub const Command = struct {
128 const id = i; 128 const id = i;
129 res[id] = core.Param(usize) { 129 res[id] = core.Param(usize) {
130 .id = id, 130 .id = id,
131 .command = null,
132 .short = p.short,
133 .long = p.long,
134 .takes_value = p.takes_value != null, 131 .takes_value = p.takes_value != null,
132 .names = core.Names{
133 .bare = null,
134 .short = p.short,
135 .long = p.long,
136 },
135 }; 137 };
136 } 138 }
137 139
@@ -139,10 +141,8 @@ pub const Command = struct {
139 const id = i + command.params.len; 141 const id = i + command.params.len;
140 res[id] = core.Param(usize) { 142 res[id] = core.Param(usize) {
141 .id = id, 143 .id = id,
142 .command = c.name,
143 .short = null,
144 .long = null,
145 .takes_value = false, 144 .takes_value = false,
145 .names = core.Names.bare(c.name),
146 }; 146 };
147 } 147 }
148 148
@@ -159,7 +159,7 @@ pub const Command = struct {
159 }; 159 };
160 160
161 var pos: usize = 0; 161 var pos: usize = 0;
162 var iter = core.Iterator(usize).init(core_params, arg_iter, allocator); 162 var iter = core.Clap(usize).init(core_params, arg_iter, allocator);
163 defer iter.deinit(); 163 defer iter.deinit();
164 164
165 arg_loop: 165 arg_loop: