diff options
Diffstat (limited to '')
| -rw-r--r-- | clap.zig | 68 |
1 files changed, 61 insertions, 7 deletions
| @@ -8,9 +8,8 @@ const io = std.io; | |||
| 8 | 8 | ||
| 9 | const assert = debug.assert; | 9 | const assert = debug.assert; |
| 10 | 10 | ||
| 11 | // TODO: Missing a few convinient features | 11 | // TODO: |
| 12 | // * Short arguments that doesn't take values should probably be able to be | 12 | // * Inform the caller which argument caused the error. |
| 13 | // chain like many linux programs: "rm -rf" | ||
| 14 | pub fn Option(comptime Result: type, comptime ParseError: type) type { | 13 | pub fn Option(comptime Result: type, comptime ParseError: type) type { |
| 15 | return struct { | 14 | return struct { |
| 16 | const Self = this; | 15 | const Self = this; |
| @@ -150,10 +149,10 @@ pub fn Parser(comptime Result: type, comptime ParseError: type, comptime default | |||
| 150 | const after_eql = arg_info.after_eql; | 149 | const after_eql = arg_info.after_eql; |
| 151 | 150 | ||
| 152 | success: { | 151 | success: { |
| 153 | var required_index = usize(0); | ||
| 154 | 152 | ||
| 155 | switch (kind) { | 153 | switch (kind) { |
| 156 | Arg.Kind.None => { | 154 | Arg.Kind.None => { |
| 155 | var required_index = usize(0); | ||
| 157 | inline for (options) |option| { | 156 | inline for (options) |option| { |
| 158 | defer if (option.kind == OptionT.Kind.Required) required_index += 1; | 157 | defer if (option.kind == OptionT.Kind.Required) required_index += 1; |
| 159 | if (option.short != null) continue; | 158 | if (option.short != null) continue; |
| @@ -165,10 +164,31 @@ pub fn Parser(comptime Result: type, comptime ParseError: type, comptime default | |||
| 165 | } | 164 | } |
| 166 | }, | 165 | }, |
| 167 | Arg.Kind.Short => { | 166 | Arg.Kind.Short => { |
| 167 | if (arg.len == 0) return error.FoundShortOptionWithNoName; | ||
| 168 | short_arg_loop: for (arg[0..arg.len - 1]) |short_arg| { | ||
| 169 | var required_index = usize(0); | ||
| 170 | inline for (options) |option| { | ||
| 171 | defer if (option.kind == OptionT.Kind.Required) required_index += 1; | ||
| 172 | const short = option.short ?? continue; | ||
| 173 | if (short_arg == short) { | ||
| 174 | if (option.takes_value) return error.OptionMissingValue; | ||
| 175 | |||
| 176 | try option.parse(&result, []u8{}); | ||
| 177 | required = newRequired(option, required, required_index); | ||
| 178 | continue :short_arg_loop; | ||
| 179 | } | ||
| 180 | } | ||
| 181 | |||
| 182 | return error.InvalidArgument; | ||
| 183 | } | ||
| 184 | |||
| 185 | const last_arg = arg[arg.len - 1]; | ||
| 186 | var required_index = usize(0); | ||
| 168 | inline for (options) |option| { | 187 | inline for (options) |option| { |
| 169 | defer if (option.kind == OptionT.Kind.Required) required_index += 1; | 188 | defer if (option.kind == OptionT.Kind.Required) required_index += 1; |
| 170 | const short = option.short ?? continue; | 189 | const short = option.short ?? continue; |
| 171 | if (arg.len == 1 and arg[0] == short) { | 190 | |
| 191 | if (last_arg == short) { | ||
| 172 | if (option.takes_value) { | 192 | if (option.takes_value) { |
| 173 | const value = after_eql ?? it.next() ?? return error.OptionMissingValue; | 193 | const value = after_eql ?? it.next() ?? return error.OptionMissingValue; |
| 174 | try option.parse(&result, value); | 194 | try option.parse(&result, value); |
| @@ -182,6 +202,7 @@ pub fn Parser(comptime Result: type, comptime ParseError: type, comptime default | |||
| 182 | } | 202 | } |
| 183 | }, | 203 | }, |
| 184 | Arg.Kind.Long => { | 204 | Arg.Kind.Long => { |
| 205 | var required_index = usize(0); | ||
| 185 | inline for (options) |option| { | 206 | inline for (options) |option| { |
| 186 | defer if (option.kind == OptionT.Kind.Required) required_index += 1; | 207 | defer if (option.kind == OptionT.Kind.Required) required_index += 1; |
| 187 | const long = option.long ?? continue; | 208 | const long = option.long ?? continue; |
| @@ -292,7 +313,15 @@ test "clap.parse.Example" { | |||
| 292 | fn bFromStr(color: &Self, str: []const u8) !void { | 313 | fn bFromStr(color: &Self, str: []const u8) !void { |
| 293 | color.b = try fmt.parseInt(u8, str, 10); | 314 | color.b = try fmt.parseInt(u8, str, 10); |
| 294 | } | 315 | } |
| 316 | |||
| 317 | // TODO: There is a segfault when we try to use the error set: @typeOf(fmt.parseInt).ReturnType.ErrorSet | ||
| 318 | fn setMax(color: &Self, str: []const u8) !void { | ||
| 319 | color.r = try fmt.parseInt(u8, "255", 10); | ||
| 320 | color.g = try fmt.parseInt(u8, "255", 10); | ||
| 321 | color.b = try fmt.parseInt(u8, "255", 10); | ||
| 322 | } | ||
| 295 | }; | 323 | }; |
| 324 | const Error = @typeOf(Color.setMax).ReturnType.ErrorSet; | ||
| 296 | 325 | ||
| 297 | const Case = struct { args: []const []const u8, res: Color, err: ?error }; | 326 | const Case = struct { args: []const []const u8, res: Color, err: ?error }; |
| 298 | const cases = []Case { | 327 | const cases = []Case { |
| @@ -322,6 +351,16 @@ test "clap.parse.Example" { | |||
| 322 | .err = null, | 351 | .err = null, |
| 323 | }, | 352 | }, |
| 324 | Case { | 353 | Case { |
| 354 | .args = [][]const u8 { "-mr", "100" }, | ||
| 355 | .res = Color { .r = 100, .g = 255, .b = 255 }, | ||
| 356 | .err = null, | ||
| 357 | }, | ||
| 358 | Case { | ||
| 359 | .args = [][]const u8 { "-mr=100" }, | ||
| 360 | .res = Color { .r = 100, .g = 255, .b = 255 }, | ||
| 361 | .err = null, | ||
| 362 | }, | ||
| 363 | Case { | ||
| 325 | .args = [][]const u8 { "-g", "200", "-b", "255" }, | 364 | .args = [][]const u8 { "-g", "200", "-b", "255" }, |
| 326 | .res = Color { .r = 0, .g = 0, .b = 0 }, | 365 | .res = Color { .r = 0, .g = 0, .b = 0 }, |
| 327 | .err = error.RequiredArgumentWasntHandled, | 366 | .err = error.RequiredArgumentWasntHandled, |
| @@ -336,10 +375,20 @@ test "clap.parse.Example" { | |||
| 336 | .res = Color { .r = 0, .g = 0, .b = 0 }, | 375 | .res = Color { .r = 0, .g = 0, .b = 0 }, |
| 337 | .err = error.OptionMissingValue, | 376 | .err = error.OptionMissingValue, |
| 338 | }, | 377 | }, |
| 378 | Case { | ||
| 379 | .args = [][]const u8 { "-" }, | ||
| 380 | .res = Color { .r = 0, .g = 0, .b = 0 }, | ||
| 381 | .err = error.FoundShortOptionWithNoName, | ||
| 382 | }, | ||
| 383 | Case { | ||
| 384 | .args = [][]const u8 { "-rg", "100" }, | ||
| 385 | .res = Color { .r = 0, .g = 0, .b = 0 }, | ||
| 386 | .err = error.OptionMissingValue, | ||
| 387 | }, | ||
| 339 | }; | 388 | }; |
| 340 | 389 | ||
| 341 | const COption = Option(Color, @typeOf(Color.rFromStr).ReturnType.ErrorSet); | 390 | const COption = Option(Color, Error); |
| 342 | const Clap = Parser(Color, @typeOf(Color.rFromStr).ReturnType.ErrorSet, | 391 | const Clap = Parser(Color, Error, |
| 343 | Color { .r = 0, .g = 0, .b = 0 }, | 392 | Color { .r = 0, .g = 0, .b = 0 }, |
| 344 | comptime []COption { | 393 | comptime []COption { |
| 345 | COption.init(Color.rFromStr) | 394 | COption.init(Color.rFromStr) |
| @@ -358,11 +407,16 @@ test "clap.parse.Example" { | |||
| 358 | .setShort('b') | 407 | .setShort('b') |
| 359 | .setLong("blue") | 408 | .setLong("blue") |
| 360 | .takesValue(true), | 409 | .takesValue(true), |
| 410 | COption.init(Color.setMax) | ||
| 411 | .setHelp("Set all values to max") | ||
| 412 | .setShort('m') | ||
| 413 | .setLong("max"), | ||
| 361 | } | 414 | } |
| 362 | ); | 415 | ); |
| 363 | 416 | ||
| 364 | for (cases) |case, i| { | 417 | for (cases) |case, i| { |
| 365 | if (Clap.parse(case.args)) |res| { | 418 | if (Clap.parse(case.args)) |res| { |
| 419 | assert(case.err == null); | ||
| 366 | assert(res.r == case.res.r); | 420 | assert(res.r == case.res.r); |
| 367 | assert(res.g == case.res.g); | 421 | assert(res.g == case.res.g); |
| 368 | assert(res.b == case.res.b); | 422 | assert(res.b == case.res.b); |