diff options
| author | 2020-11-21 17:42:28 +0100 | |
|---|---|---|
| committer | 2020-11-21 17:42:28 +0100 | |
| commit | db7525e307233f64f18d229db61a682bfff02d0e (patch) | |
| tree | d85c171e4384c4b6cdb8cfa25c5b06532619ccfa /clap | |
| parent | Fix spelling error (diff) | |
| download | zig-clap-db7525e307233f64f18d229db61a682bfff02d0e.tar.gz zig-clap-db7525e307233f64f18d229db61a682bfff02d0e.tar.xz zig-clap-db7525e307233f64f18d229db61a682bfff02d0e.zip | |
Support all arguments after -- being positionals
Diffstat (limited to 'clap')
| -rw-r--r-- | clap/streaming.zig | 184 |
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; | |||
| 10 | const os = std.os; | 10 | const os = std.os; |
| 11 | const testing = std.testing; | 11 | const testing = std.testing; |
| 12 | 12 | ||
| 13 | /// The result returned from ::StreamingClap.next | 13 | /// The result returned from StreamingClap.next |
| 14 | pub fn Arg(comptime Id: type) type { | 14 | pub 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. |
| 26 | pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { | 26 | pub 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 | } |