summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clap/streaming.zig184
1 files changed, 106 insertions, 78 deletions
diff --git a/clap/streaming.zig b/clap/streaming.zig
index 844cbf7..11145f0 100644
--- a/clap/streaming.zig
+++ b/clap/streaming.zig
@@ -10,7 +10,7 @@ const mem = std.mem;
10const os = std.os; 10const os = std.os;
11const testing = std.testing; 11const testing = std.testing;
12 12
13/// The result returned from ::StreamingClap.next 13/// The result returned from StreamingClap.next
14pub fn Arg(comptime Id: type) type { 14pub fn Arg(comptime Id: type) type {
15 return struct { 15 return struct {
16 const Self = @This(); 16 const Self = @This();
@@ -20,14 +20,15 @@ pub fn Arg(comptime Id: type) type {
20 }; 20 };
21} 21}
22 22
23/// A command line argument parser which, given an ::ArgIterator, will parse arguments according 23/// A command line argument parser which, given an ArgIterator, will parse arguments according
24/// to the ::params. ::StreamingClap parses in an iterating manner, so you have to use a loop together with 24/// to the params. StreamingClap parses in an iterating manner, so you have to use a loop together with
25/// ::StreamingClap.next to parse all the arguments of your program. 25/// StreamingClap.next to parse all the arguments of your program.
26pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { 26pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
27 return struct { 27 return struct {
28 const State = union(enum) { 28 const State = union(enum) {
29 normal, 29 normal,
30 chaining: Chaining, 30 chaining: Chaining,
31 rest_are_positional,
31 32
32 const Chaining = struct { 33 const Chaining = struct {
33 arg: []const u8, 34 arg: []const u8,
@@ -38,85 +39,73 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
38 params: []const clap.Param(Id), 39 params: []const clap.Param(Id),
39 iter: *ArgIterator, 40 iter: *ArgIterator,
40 state: State = .normal, 41 state: State = .normal,
42 positional: ?*const clap.Param(Id) = null,
41 43
42 /// Get the next ::Arg that matches a ::Param. 44 /// Get the next Arg that matches a Param.
43 pub fn next(parser: *@This(), diag: ?*clap.Diagnostic) !?Arg(Id) { 45 pub fn next(parser: *@This(), diag: ?*clap.Diagnostic) !?Arg(Id) {
44 const ArgInfo = struct { 46 switch (parser.state) {
45 arg: []const u8, 47 .normal => return try parser.normal(diag),
46 kind: enum { 48 .chaining => |state| return try parser.chainging(state, diag),
47 long, 49 .rest_are_positional => {
48 short, 50 const param = parser.positionalParam() orelse unreachable;
49 positional, 51 const value = (try parser.iter.next()) orelse return null;
52 return Arg(Id){ .param = param, .value = value };
50 }, 53 },
51 }; 54 }
55 }
52 56
53 switch (parser.state) { 57 fn normal(parser: *@This(), diag: ?*clap.Diagnostic) !?Arg(Id) {
54 .normal => { 58 const arg_info = (try parser.parseNextArg()) orelse return null;
55 const full_arg = (try parser.iter.next()) orelse return null; 59 const arg = arg_info.arg;
56 const arg_info = if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-")) 60 switch (arg_info.kind) {
57 ArgInfo{ .arg = full_arg, .kind = .positional } 61 .long => {
58 else if (mem.startsWith(u8, full_arg, "--")) 62 const eql_index = mem.indexOfScalar(u8, arg, '=');
59 ArgInfo{ .arg = full_arg[2..], .kind = .long } 63 const name = if (eql_index) |i| arg[0..i] else arg;
60 else if (mem.startsWith(u8, full_arg, "-")) 64 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null;
61 ArgInfo{ .arg = full_arg[1..], .kind = .short } 65
62 else 66 for (parser.params) |*param| {
63 ArgInfo{ .arg = full_arg, .kind = .positional }; 67 const match = param.names.long orelse continue;
64 68
65 const arg = arg_info.arg; 69 if (!mem.eql(u8, name, match))
66 const kind = arg_info.kind; 70 continue;
67 71 if (param.takes_value == .None) {
68 switch (kind) { 72 if (maybe_value != null)
69 .long => { 73 return err(diag, arg, .{ .long = name }, error.DoesntTakeValue);
70 const eql_index = mem.indexOfScalar(u8, arg, '='); 74
71 const name = if (eql_index) |i| arg[0..i] else arg; 75 return Arg(Id){ .param = param };
72 const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; 76 }
73 77
74 for (parser.params) |*param| { 78 const value = blk: {
75 const match = param.names.long orelse continue; 79 if (maybe_value) |v|
76 80 break :blk v;
77 if (!mem.eql(u8, name, match)) 81
78 continue; 82 break :blk (try parser.iter.next()) orelse
79 if (param.takes_value == .None) { 83 return err(diag, arg, .{ .long = name }, error.MissingValue);
80 if (maybe_value != null) 84 };
81 return err(diag, arg, .{ .long = name }, error.DoesntTakeValue); 85
82 86 return Arg(Id){ .param = param, .value = value };
83 return Arg(Id){ .param = param };
84 }
85
86 const value = blk: {
87 if (maybe_value) |v|
88 break :blk v;
89
90 break :blk (try parser.iter.next()) orelse
91 return err(diag, arg, .{ .long = name }, error.MissingValue);
92 };
93
94 return Arg(Id){ .param = param, .value = value };
95 }
96
97 return err(diag, arg, .{ .long = name }, error.InvalidArgument);
98 },
99 .short => return try parser.chainging(.{
100 .arg = full_arg,
101 .index = full_arg.len - arg.len,
102 }, diag),
103 .positional => {
104 for (parser.params) |*param| {
105 if (param.names.long) |_|
106 continue;
107 if (param.names.short) |_|
108 continue;
109
110 return Arg(Id){ .param = param, .value = arg };
111 }
112
113 return err(diag, arg, .{}, error.InvalidArgument);
114 },
115 } 87 }
116 88
117 unreachable; 89 return err(diag, arg, .{ .long = name }, error.InvalidArgument);
90 },
91 .short => return try parser.chainging(.{
92 .arg = arg,
93 .index = 0,
94 }, diag),
95 .positional => if (parser.positionalParam()) |param| {
96 // If we find a positional with the value `--` then we
97 // interpret the rest of the arguments as positional
98 // arguments.
99 if (mem.eql(u8, arg, "--")) {
100 parser.state = .rest_are_positional;
101 const value = (try parser.iter.next()) orelse return null;
102 return Arg(Id){ .param = param, .value = value };
103 }
104
105 return Arg(Id){ .param = param, .value = arg };
106 } else {
107 return err(diag, arg, .{}, error.InvalidArgument);
118 }, 108 },
119 .chaining => |state| return try parser.chainging(state, diag),
120 } 109 }
121 } 110 }
122 111
@@ -167,6 +156,44 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type {
167 return err(diag, arg, .{ .short = arg[index] }, error.InvalidArgument); 156 return err(diag, arg, .{ .short = arg[index] }, error.InvalidArgument);
168 } 157 }
169 158
159 fn positionalParam(parser: *@This()) ?*const clap.Param(Id) {
160 if (parser.positional) |p|
161 return p;
162
163 for (parser.params) |*param| {
164 if (param.names.long) |_|
165 continue;
166 if (param.names.short) |_|
167 continue;
168
169 parser.positional = param;
170 return param;
171 }
172
173 return null;
174 }
175
176 const ArgInfo = struct {
177 arg: []const u8,
178 kind: enum {
179 long,
180 short,
181 positional,
182 },
183 };
184
185 fn parseNextArg(parser: *@This()) !?ArgInfo {
186 const full_arg = (try parser.iter.next()) orelse return null;
187 if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-"))
188 return ArgInfo{ .arg = full_arg, .kind = .positional };
189 if (mem.startsWith(u8, full_arg, "--"))
190 return ArgInfo{ .arg = full_arg[2..], .kind = .long };
191 if (mem.startsWith(u8, full_arg, "-"))
192 return ArgInfo{ .arg = full_arg[1..], .kind = .short };
193
194 return ArgInfo{ .arg = full_arg, .kind = .positional };
195 }
196
170 fn err(diag: ?*clap.Diagnostic, arg: []const u8, names: clap.Names, _err: anytype) @TypeOf(_err) { 197 fn err(diag: ?*clap.Diagnostic, arg: []const u8, names: clap.Names, _err: anytype) @TypeOf(_err) {
171 if (diag) |d| 198 if (diag) |d|
172 d.* = .{ .arg = arg, .name = names }; 199 d.* = .{ .arg = arg, .name = names };
@@ -369,7 +396,7 @@ test "all params" {
369 "-c", "0", "-c=0", "-ac", 396 "-c", "0", "-c=0", "-ac",
370 "0", "-ac=0", "--aa", "--bb", 397 "0", "-ac=0", "--aa", "--bb",
371 "--cc", "0", "--cc=0", "something", 398 "--cc", "0", "--cc=0", "something",
372 "--", "-", 399 "-", "--", "--cc=0", "-a",
373 }, 400 },
374 &[_]Arg(u8){ 401 &[_]Arg(u8){
375 Arg(u8){ .param = aa }, 402 Arg(u8){ .param = aa },
@@ -389,8 +416,9 @@ test "all params" {
389 Arg(u8){ .param = cc, .value = "0" }, 416 Arg(u8){ .param = cc, .value = "0" },
390 Arg(u8){ .param = cc, .value = "0" }, 417 Arg(u8){ .param = cc, .value = "0" },
391 Arg(u8){ .param = positional, .value = "something" }, 418 Arg(u8){ .param = positional, .value = "something" },
392 Arg(u8){ .param = positional, .value = "--" },
393 Arg(u8){ .param = positional, .value = "-" }, 419 Arg(u8){ .param = positional, .value = "-" },
420 Arg(u8){ .param = positional, .value = "--cc=0" },
421 Arg(u8){ .param = positional, .value = "-a" },
394 }, 422 },
395 ); 423 );
396} 424}