diff options
| author | 2018-04-27 00:16:32 +0200 | |
|---|---|---|
| committer | 2018-04-27 00:16:32 +0200 | |
| commit | 5f9278c93e181e8a47d4d9a3dd5a4f5ac2958ac0 (patch) | |
| tree | 74174dd6551ec5f255aa1f72d7554f12e8cc9f03 | |
| parent | Fixed test (diff) | |
| download | zig-clap-5f9278c93e181e8a47d4d9a3dd5a4f5ac2958ac0.tar.gz zig-clap-5f9278c93e181e8a47d4d9a3dd5a4f5ac2958ac0.tar.xz zig-clap-5f9278c93e181e8a47d4d9a3dd5a4f5ac2958ac0.zip | |
Added the ability to have argument have indexs
| -rw-r--r-- | clap.zig | 129 |
1 files changed, 81 insertions, 48 deletions
| @@ -53,19 +53,31 @@ pub fn Clap(comptime Result: type) type { | |||
| 53 | 53 | ||
| 54 | arg: []const u8, | 54 | arg: []const u8, |
| 55 | kind: Kind, | 55 | kind: Kind, |
| 56 | after_eql: ?[]const u8, | ||
| 57 | }; | 56 | }; |
| 58 | 57 | ||
| 59 | const Iterator = struct { | 58 | const Iterator = struct { |
| 60 | index: usize, | 59 | index: usize, |
| 61 | slice: []const []const u8, | 60 | slice: []const []const u8, |
| 62 | 61 | ||
| 62 | const Pair = struct { | ||
| 63 | value: []const u8, | ||
| 64 | index: usize, | ||
| 65 | }; | ||
| 66 | |||
| 63 | pub fn next(it: &this) ?[]const u8 { | 67 | pub fn next(it: &this) ?[]const u8 { |
| 68 | const res = it.nextWithIndex() ?? return null; | ||
| 69 | return res.value; | ||
| 70 | } | ||
| 71 | |||
| 72 | pub fn nextWithIndex(it: &this) ?Pair { | ||
| 64 | if (it.index >= it.slice.len) | 73 | if (it.index >= it.slice.len) |
| 65 | return null; | 74 | return null; |
| 66 | 75 | ||
| 67 | defer it.index += 1; | 76 | defer it.index += 1; |
| 68 | return it.slice[it.index]; | 77 | return Pair { |
| 78 | .value = it.slice[it.index], | ||
| 79 | .index = it.index, | ||
| 80 | }; | ||
| 69 | } | 81 | } |
| 70 | }; | 82 | }; |
| 71 | 83 | ||
| @@ -88,9 +100,9 @@ pub fn Clap(comptime Result: type) type { | |||
| 88 | var result = *defaults; | 100 | var result = *defaults; |
| 89 | 101 | ||
| 90 | var it = Iterator { .index = 0, .slice = arguments }; | 102 | var it = Iterator { .index = 0, .slice = arguments }; |
| 91 | while (it.next()) |item| { | 103 | while (it.nextWithIndex()) |item| { |
| 92 | const arg_info = blk: { | 104 | const arg_info = blk: { |
| 93 | var arg = item; | 105 | var arg = item.value; |
| 94 | var kind = Arg.Kind.Value; | 106 | var kind = Arg.Kind.Value; |
| 95 | 107 | ||
| 96 | if (mem.startsWith(u8, arg, "--")) { | 108 | if (mem.startsWith(u8, arg, "--")) { |
| @@ -101,71 +113,79 @@ pub fn Clap(comptime Result: type) type { | |||
| 101 | kind = Arg.Kind.Short; | 113 | kind = Arg.Kind.Short; |
| 102 | } | 114 | } |
| 103 | 115 | ||
| 104 | if (kind == Arg.Kind.Value) | 116 | break :blk Arg { .arg = arg, .kind = kind }; |
| 105 | break :blk Arg { .arg = arg, .kind = kind, .after_eql = null }; | ||
| 106 | |||
| 107 | |||
| 108 | if (mem.indexOfScalar(u8, arg, '=')) |index| { | ||
| 109 | break :blk Arg { .arg = arg[0..index], .kind = kind, .after_eql = arg[index + 1..] }; | ||
| 110 | } else { | ||
| 111 | break :blk Arg { .arg = arg, .kind = kind, .after_eql = null }; | ||
| 112 | } | ||
| 113 | }; | 117 | }; |
| 114 | const arg = arg_info.arg; | 118 | const arg = arg_info.arg; |
| 119 | const arg_index = item.index; | ||
| 115 | const kind = arg_info.kind; | 120 | const kind = arg_info.kind; |
| 116 | const after_eql = arg_info.after_eql; | 121 | const eql_index = mem.indexOfScalar(u8, arg, '='); |
| 117 | 122 | ||
| 118 | success: { | 123 | success: { |
| 124 | // TODO: Revert a lot of if statements when inline loop compiler bugs have been fixed | ||
| 119 | switch (kind) { | 125 | switch (kind) { |
| 120 | // TODO: Handle subcommands | 126 | // TODO: Handle subcommands |
| 121 | Arg.Kind.Value => { | 127 | Arg.Kind.Value => { |
| 122 | var required_index = usize(0); | 128 | var required_index = usize(0); |
| 123 | inline for (command.arguments) |option| { | 129 | inline for (command.arguments) |option| { |
| 124 | defer if (option.required) required_index += 1; | 130 | defer if (option.required) required_index += 1; |
| 131 | |||
| 125 | if (option.short != null) continue; | 132 | if (option.short != null) continue; |
| 126 | if (option.long != null) continue; | 133 | if (option.long != null) continue; |
| 134 | const has_right_index = if (option.index) |index| index == it.index else true; | ||
| 127 | 135 | ||
| 128 | if (option.takes_value) |parser| { | 136 | if (has_right_index) { |
| 129 | try parser.parse(&@field(result, option.field), arg); | 137 | if (option.takes_value) |parser| { |
| 130 | } else { | 138 | try parser.parse(&@field(result, option.field), arg); |
| 131 | @field(result, option.field) = true; | 139 | } else { |
| 132 | } | 140 | @field(result, option.field) = true; |
| 141 | } | ||
| 133 | 142 | ||
| 134 | required = newRequired(option, required, required_index); | 143 | required = newRequired(option, required, required_index); |
| 135 | break :success; | 144 | break :success; |
| 145 | } | ||
| 136 | } | 146 | } |
| 137 | }, | 147 | }, |
| 138 | Arg.Kind.Short => { | 148 | Arg.Kind.Short => { |
| 139 | const arg_len = arg.len; | 149 | if (arg.len == 0) return error.InvalidArg; |
| 140 | if (arg.len == 0) return error.FoundShortOptionWithNoName; | 150 | |
| 141 | short_arg_loop: for (arg[0..arg.len - 1]) |short_arg| { | 151 | const end = (eql_index ?? arg.len) - 1; |
| 152 | |||
| 153 | short_arg_loop: | ||
| 154 | for (arg[0..end]) |short_arg, i| { | ||
| 142 | var required_index = usize(0); | 155 | var required_index = usize(0); |
| 156 | |||
| 143 | inline for (command.arguments) |option| { | 157 | inline for (command.arguments) |option| { |
| 144 | defer if (option.required) required_index += 1; | 158 | defer if (option.required) required_index += 1; |
| 159 | |||
| 145 | const short = option.short ?? continue; | 160 | const short = option.short ?? continue; |
| 146 | if (short_arg == short) { | 161 | const has_right_index = if (option.index) |index| index == arg_index else true; |
| 147 | if (option.takes_value) |_| return error.OptionMissingValue; | ||
| 148 | 162 | ||
| 149 | @field(result, option.field) = true; | 163 | if (has_right_index) { |
| 150 | required = newRequired(option, required, required_index); | 164 | if (short_arg == short) { |
| 151 | continue :short_arg_loop; | 165 | if (option.takes_value) |_| return error.OptionMissingValue; |
| 166 | |||
| 167 | @field(result, option.field) = true; | ||
| 168 | required = newRequired(option, required, required_index); | ||
| 169 | continue :short_arg_loop; | ||
| 170 | } | ||
| 152 | } | 171 | } |
| 153 | } | 172 | } |
| 154 | |||
| 155 | return error.InvalidArgument; | ||
| 156 | } | 173 | } |
| 157 | 174 | ||
| 158 | const last_arg = arg[arg.len - 1]; | 175 | const last_arg = arg[end]; |
| 159 | var required_index = usize(0); | 176 | var required_index = usize(0); |
| 160 | inline for (command.arguments) |option| { | 177 | inline for (command.arguments) |option| { |
| 161 | defer if (option.required) required_index += 1; | 178 | defer if (option.required) required_index += 1; |
| 179 | |||
| 162 | const short = option.short ?? continue; | 180 | const short = option.short ?? continue; |
| 181 | const has_right_index = if (option.index) |index| index == arg_index else true; | ||
| 163 | 182 | ||
| 164 | if (last_arg == short) { | 183 | if (has_right_index and last_arg == short) { |
| 165 | if (option.takes_value) |parser| { | 184 | if (option.takes_value) |parser| { |
| 166 | const value = after_eql ?? it.next() ?? return error.OptionMissingValue; | 185 | const value = if (eql_index) |index| arg[index + 1..] else it.next() ?? return error.ArgMissingValue; |
| 167 | try parser.parse(&@field(result, option.field), value); | 186 | try parser.parse(&@field(result, option.field), value); |
| 168 | } else { | 187 | } else { |
| 188 | if (eql_index) |_| return error.ArgTakesNoValue; | ||
| 169 | @field(result, option.field) = true; | 189 | @field(result, option.field) = true; |
| 170 | } | 190 | } |
| 171 | 191 | ||
| @@ -178,11 +198,13 @@ pub fn Clap(comptime Result: type) type { | |||
| 178 | var required_index = usize(0); | 198 | var required_index = usize(0); |
| 179 | inline for (command.arguments) |option| { | 199 | inline for (command.arguments) |option| { |
| 180 | defer if (option.required) required_index += 1; | 200 | defer if (option.required) required_index += 1; |
| 201 | |||
| 181 | const long = option.long ?? continue; | 202 | const long = option.long ?? continue; |
| 203 | const has_right_index = if (option.index) |index| index == arg_index else true; | ||
| 182 | 204 | ||
| 183 | if (mem.eql(u8, arg, long)) { | 205 | if (has_right_index and mem.eql(u8, arg, long)) { |
| 184 | if (option.takes_value) |parser| { | 206 | if (option.takes_value) |parser| { |
| 185 | const value = after_eql ?? it.next() ?? return error.OptionMissingValue; | 207 | const value = if (eql_index) |index| arg[index + 1..] else it.next() ?? return error.ArgMissingValue; |
| 186 | try parser.parse(&@field(result, option.field), value); | 208 | try parser.parse(&@field(result, option.field), value); |
| 187 | } else { | 209 | } else { |
| 188 | @field(result, option.field) = true; | 210 | @field(result, option.field) = true; |
| @@ -195,12 +217,12 @@ pub fn Clap(comptime Result: type) type { | |||
| 195 | } | 217 | } |
| 196 | } | 218 | } |
| 197 | 219 | ||
| 198 | return error.InvalidArgument; | 220 | return error.InvalidArg; |
| 199 | } | 221 | } |
| 200 | } | 222 | } |
| 201 | 223 | ||
| 202 | if (required != 0) { | 224 | if (required != 0) { |
| 203 | return error.RequiredArgumentWasntHandled; | 225 | return error.RequiredArgNotHandled; |
| 204 | } | 226 | } |
| 205 | 227 | ||
| 206 | return result; | 228 | return result; |
| @@ -271,6 +293,7 @@ pub const Argument = struct { | |||
| 271 | required: bool, | 293 | required: bool, |
| 272 | short: ?u8, | 294 | short: ?u8, |
| 273 | long: ?[]const u8, | 295 | long: ?[]const u8, |
| 296 | index: ?usize, | ||
| 274 | 297 | ||
| 275 | pub fn field(field_name: []const u8) Argument { | 298 | pub fn field(field_name: []const u8) Argument { |
| 276 | return Argument { | 299 | return Argument { |
| @@ -280,18 +303,14 @@ pub const Argument = struct { | |||
| 280 | .required = false, | 303 | .required = false, |
| 281 | .short = null, | 304 | .short = null, |
| 282 | .long = null, | 305 | .long = null, |
| 306 | .index = null, | ||
| 283 | }; | 307 | }; |
| 284 | } | 308 | } |
| 285 | 309 | ||
| 286 | pub fn arg(s: []const u8) Argument { | 310 | pub fn arg(s: []const u8) Argument { |
| 287 | return Argument { | 311 | return Argument.field(s) |
| 288 | .field = s, | 312 | .with("short", if (s.len == 1) s[0] else null) |
| 289 | .help = "", | 313 | .with("long", if (s.len != 1) s else null); |
| 290 | .takes_value = null, | ||
| 291 | .required = false, | ||
| 292 | .short = if (s.len == 1) s[0] else null, | ||
| 293 | .long = if (s.len != 1) s else null, | ||
| 294 | }; | ||
| 295 | } | 314 | } |
| 296 | 315 | ||
| 297 | pub fn with(argument: &const Argument, comptime field_name: []const u8, value: var) Argument { | 316 | pub fn with(argument: &const Argument, comptime field_name: []const u8, value: var) Argument { |
| @@ -377,8 +396,8 @@ fn testNoErr(comptime clap: &const Clap(Options), args: []const []const u8, expe | |||
| 377 | assert(expected.cc == actual.cc); | 396 | assert(expected.cc == actual.cc); |
| 378 | } | 397 | } |
| 379 | 398 | ||
| 380 | fn testErr(args: []const []const u8, expected: error) void { | 399 | fn testErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: error) void { |
| 381 | if (clap.parse(case.args)) |actual| { | 400 | if (clap.parse(args)) |actual| { |
| 382 | unreachable; | 401 | unreachable; |
| 383 | } else |err| { | 402 | } else |err| { |
| 384 | assert(err == expected); | 403 | assert(err == expected); |
| @@ -458,3 +477,17 @@ test "clap.parse: value int" { | |||
| 458 | 477 | ||
| 459 | testNoErr(clap, [][]const u8 { "100" }, default.with("int", 100)); | 478 | testNoErr(clap, [][]const u8 { "100" }, default.with("int", 100)); |
| 460 | } | 479 | } |
| 480 | |||
| 481 | test "clap.parse: index" { | ||
| 482 | const clap = comptime Clap(Options).init(default).with("command", | ||
| 483 | Command.init("").with("arguments", | ||
| 484 | []Argument { | ||
| 485 | Argument.arg("a").with("index", 0), | ||
| 486 | Argument.arg("b").with("index", 1), | ||
| 487 | } | ||
| 488 | ) | ||
| 489 | ); | ||
| 490 | |||
| 491 | testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true)); | ||
| 492 | testErr(clap, [][]const u8 { "-b", "-a" }, error.InvalidArg); | ||
| 493 | } | ||