From 08bab91e1e8ced3f06c128a57bd991b9a1b901fc Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Sun, 23 Aug 2020 13:48:12 +1000 Subject: parse and validate multiple option --- clap.zig | 106 ++++++++++++++++++++++++++++++++++++----------------- clap/comptime.zig | 23 ++++++++---- clap/streaming.zig | 34 ++++++++++++----- 3 files changed, 111 insertions(+), 52 deletions(-) diff --git a/clap.zig b/clap.zig index aedab32..547e352 100644 --- a/clap.zig +++ b/clap.zig @@ -25,6 +25,12 @@ pub const Names = struct { long: ?[]const u8 = null, }; +pub const Values = enum { + None, + One, + Many, +}; + /// Represents a parameter for the command line. /// Parameters come in three kinds: /// * Short ("-a"): Should be used for the most commonly used parameters in your program. @@ -50,7 +56,7 @@ pub fn Param(comptime Id: type) type { return struct { id: Id = Id{}, names: Names = Names{}, - takes_value: bool = false, + takes_value: Values = .None, }; } @@ -86,8 +92,13 @@ pub fn parseParam(line: []const u8) !Param(Help) { const start = mem.indexOfScalar(u8, help_msg, '<').? + 1; const len = mem.indexOfScalar(u8, help_msg[start..], '>') orelse break :blk; res.id.value = help_msg[start..][0..len]; - res.takes_value = true; - help_msg = help_msg[start + len + 1 ..]; + if (mem.startsWith(u8, help_msg[start + len + 1 ..], "...")) { + res.takes_value = .Many; + help_msg = help_msg[start + len + 1 + 3 ..]; + } else { + res.takes_value = .One; + help_msg = help_msg[start + len + 1 ..]; + } } } @@ -110,8 +121,13 @@ pub fn parseParam(line: []const u8) !Param(Help) { const start = mem.indexOfScalar(u8, help_msg, '<').? + 1; const len = mem.indexOfScalar(u8, help_msg[start..], '>') orelse break :blk; res.id.value = help_msg[start..][0..len]; - res.takes_value = true; - help_msg = help_msg[start + len + 1 ..]; + if (mem.startsWith(u8, help_msg[start + len + 1 ..], "...")) { + res.takes_value = .Many; + help_msg = help_msg[start + len + 1 + 3 ..]; + } else { + res.takes_value = .One; + help_msg = help_msg[start + len + 1 ..]; + } } } @@ -134,7 +150,20 @@ test "parseParam" { .short = 's', .long = find(text, "long"), }, - .takes_value = true, + .takes_value = .One, + }, try parseParam(text)); + + text = "-s, --long ... Help text"; + testing.expectEqual(Param(Help){ + .id = Help{ + .msg = find(text, "Help text"), + .value = find(text, "value"), + }, + .names = Names{ + .short = 's', + .long = find(text, "long"), + }, + .takes_value = .Many, }, try parseParam(text)); text = "--long Help text"; @@ -147,7 +176,7 @@ test "parseParam" { .short = null, .long = find(text, "long"), }, - .takes_value = true, + .takes_value = .One, }, try parseParam(text)); text = "-s Help text"; @@ -160,7 +189,7 @@ test "parseParam" { .short = 's', .long = null, }, - .takes_value = true, + .takes_value = .One, }, try parseParam(text)); text = "-s, --long Help text"; @@ -173,7 +202,7 @@ test "parseParam" { .short = 's', .long = find(text, "long"), }, - .takes_value = false, + .takes_value = .None, }, try parseParam(text)); text = "-s Help text"; @@ -186,7 +215,7 @@ test "parseParam" { .short = 's', .long = null, }, - .takes_value = false, + .takes_value = .None, }, try parseParam(text)); text = "--long Help text"; @@ -199,7 +228,7 @@ test "parseParam" { .short = null, .long = find(text, "long"), }, - .takes_value = false, + .takes_value = .None, }, try parseParam(text)); text = "--long Help text"; @@ -212,7 +241,7 @@ test "parseParam" { .short = null, .long = find(text, "long"), }, - .takes_value = true, + .takes_value = .One, }, try parseParam(text)); testing.expectError(error.NoParamFound, parseParam("Help")); @@ -334,8 +363,11 @@ fn printParam( try stream.print("--{}", .{l}); } - if (param.takes_value) - try stream.print(" <{}>", .{valueText(context, param)}); + switch (param.takes_value) { + .None => {}, + .One => try stream.print(" <{}>", .{valueText(context, param)}), + .Many => try stream.print(" <{}>...", .{valueText(context, param)}), + } } /// A wrapper around helpFull for simple helpText and valueText functions that @@ -400,28 +432,30 @@ test "clap.help" { try help( slice_stream.outStream(), comptime &[_]Param(Help){ - parseParam("-a Short flag. ") catch unreachable, - parseParam("-b Short option.") catch unreachable, - parseParam("--aa Long flag. ") catch unreachable, - parseParam("--bb Long option. ") catch unreachable, - parseParam("-c, --cc Both flag. ") catch unreachable, - parseParam("-d, --dd Both option. ") catch unreachable, + parseParam("-a Short flag. ") catch unreachable, + parseParam("-b Short option.") catch unreachable, + parseParam("--aa Long flag. ") catch unreachable, + parseParam("--bb Long option. ") catch unreachable, + parseParam("-c, --cc Both flag. ") catch unreachable, + parseParam("-d, --dd Both option. ") catch unreachable, + parseParam("-d, --dd ... Both repeated option. ") catch unreachable, Param(Help){ .id = Help{ .msg = "Positional. This should not appear in the help message.", }, - .takes_value = true, + .takes_value = .One, }, }, ); const expected = "" ++ - "\t-a \tShort flag.\n" ++ - "\t-b \tShort option.\n" ++ - "\t --aa \tLong flag.\n" ++ - "\t --bb \tLong option.\n" ++ - "\t-c, --cc \tBoth flag.\n" ++ - "\t-d, --dd \tBoth option.\n"; + "\t-a \tShort flag.\n" ++ + "\t-b \tShort option.\n" ++ + "\t --aa \tLong flag.\n" ++ + "\t --bb \tLong option.\n" ++ + "\t-c, --cc \tBoth flag.\n" ++ + "\t-d, --dd \tBoth option.\n" ++ + "\t-d, --dd ...\tBoth repeated option.\n"; const actual = slice_stream.getWritten(); if (!mem.eql(u8, actual, expected)) { @@ -458,7 +492,7 @@ pub fn usageFull( const cs = cos.outStream(); for (params) |param| { const name = param.names.short orelse continue; - if (param.takes_value) + if (param.takes_value != .None) continue; if (cos.bytes_written == 0) @@ -470,7 +504,7 @@ pub fn usageFull( var positional: ?Param(Id) = null; for (params) |param| { - if (!param.takes_value and param.names.short != null) + if (param.takes_value == .None and param.names.short != null) continue; const prefix = if (param.names.short) |_| "-" else "--"; @@ -485,8 +519,11 @@ pub fn usageFull( try cs.writeByte(' '); try cs.print("[{}{}", .{ prefix, name }); - if (param.takes_value) - try cs.print(" <{}>", .{try valueText(context, param)}); + switch (param.takes_value) { + .None => {}, + .One => try cs.print(" <{}>", .{try valueText(context, param)}), + .Many => try cs.print(" <{}>...", .{try valueText(context, param)}), + } try cs.writeByte(']'); } @@ -575,10 +612,10 @@ test "usage" { .id = Help{ .value = "file", }, - .takes_value = true, + .takes_value = .One, }, }); - try testUsage("[-ab] [-c ] [-d ] [--e] [--f] [--g ] [--h ] ", comptime &[_]Param(Help){ + try testUsage("[-ab] [-c ] [-d ] [--e] [--f] [--g ] [--h ] [-i ...] ", comptime &[_]Param(Help){ parseParam("-a") catch unreachable, parseParam("-b") catch unreachable, parseParam("-c ") catch unreachable, @@ -587,11 +624,12 @@ test "usage" { parseParam("--f") catch unreachable, parseParam("--g ") catch unreachable, parseParam("--h ") catch unreachable, + parseParam("-i ...") catch unreachable, Param(Help){ .id = Help{ .value = "file", }, - .takes_value = true, + .takes_value = .One, }, }); } diff --git a/clap/comptime.zig b/clap/comptime.zig index 90d34e9..cecfcb2 100644 --- a/clap/comptime.zig +++ b/clap/comptime.zig @@ -13,7 +13,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) for (params) |param| { var index: usize = 0; if (param.names.long != null or param.names.short != null) { - const ptr = if (param.takes_value) &options else &flags; + const ptr = if (param.takes_value != .None) &options else &flags; index = ptr.*; ptr.* += 1; } @@ -52,7 +52,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) const param = arg.param; if (param.names.long == null and param.names.short == null) { try pos.append(arg.value.?); - } else if (param.takes_value) { + } else if (param.takes_value != .None) { // If we don't have any optional parameters, then this code should // never be reached. debug.assert(res.options.len != 0); @@ -80,7 +80,7 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) pub fn flag(parser: @This(), comptime name: []const u8) bool { const param = comptime findParam(name); - if (param.takes_value) + if (param.takes_value != .None) @compileError(name ++ " is an option and not a flag."); return parser.flags[param.id]; @@ -88,14 +88,21 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) pub fn allOptions(parser: @This(), comptime name: []const u8) [][]const u8 { const param = comptime findParam(name); - if (!param.takes_value) + if (param.takes_value == .None) @compileError(name ++ " is a flag and not an option."); + if (param.takes_value == .One) + @compileError(name ++ " takes one option, not multiple."); return parser.options[param.id].items; } pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 { - const items = parser.allOptions(name); + const param = comptime findParam(name); + if (param.takes_value == .None) + @compileError(name ++ " is a flag and not an option."); + if (param.takes_value == .Many) + @compileError(name ++ " takes many options, not one."); + const items = parser.options[param.id].items; return if (items.len > 0) items[0] else null; } @@ -127,9 +134,9 @@ test "clap.comptime.ComptimeClap" { clap.parseParam("-a, --aa ") catch unreachable, clap.parseParam("-b, --bb ") catch unreachable, clap.parseParam("-c, --cc ") catch unreachable, - clap.parseParam("-d, --dd ") catch unreachable, + clap.parseParam("-d, --dd ...") catch unreachable, clap.Param(clap.Help){ - .takes_value = true, + .takes_value = .One, }, }); @@ -151,6 +158,6 @@ test "clap.comptime.ComptimeClap" { testing.expectEqualStrings("0", args.option("--cc").?); testing.expectEqual(@as(usize, 1), args.positionals().len); testing.expectEqualStrings("something", args.positionals()[0]); - testing.expectEqualStrings("a", args.option("-d").?); + testing.expectEqualSlices([]const u8, &[_][]const u8{ "a", "b" }, args.allOptions("-d")); testing.expectEqualSlices([]const u8, &[_][]const u8{ "a", "b" }, args.allOptions("--dd")); } diff --git a/clap/streaming.zig b/clap/streaming.zig index 95ee581..b843bff 100644 --- a/clap/streaming.zig +++ b/clap/streaming.zig @@ -75,7 +75,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { if (!mem.eql(u8, name, match)) continue; - if (!param.takes_value) { + if (param.takes_value == .None) { if (maybe_value != null) return error.DoesntTakeValue; @@ -128,7 +128,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { // Before we return, we have to set the new state of the clap defer { - if (arg.len <= next_index or param.takes_value) { + if (arg.len <= next_index or param.takes_value != .None) { parser.state = State.Normal; } else { parser.state = State{ @@ -140,7 +140,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { } } - if (!param.takes_value) + if (param.takes_value == .None) return Arg(Id){ .param = param }; if (arg.len <= next_index) { @@ -194,20 +194,26 @@ test "clap.streaming.StreamingClap: short params" { clap.Param(u8){ .id = 2, .names = clap.Names{ .short = 'c' }, - .takes_value = true, + .takes_value = .One, + }, + clap.Param(u8){ + .id = 3, + .names = clap.Names{ .short = 'd' }, + .takes_value = .Many, }, }; const a = ¶ms[0]; const b = ¶ms[1]; const c = ¶ms[2]; + const d = ¶ms[3]; testNoErr( ¶ms, &[_][]const u8{ "-a", "-b", "-ab", "-ba", "-c", "0", "-c=0", "-ac", - "0", "-ac=0", + "0", "-ac=0", "-d=0", }, &[_]Arg(u8){ Arg(u8){ .param = a }, @@ -222,6 +228,7 @@ test "clap.streaming.StreamingClap: short params" { Arg(u8){ .param = c, .value = "0" }, Arg(u8){ .param = a }, Arg(u8){ .param = c, .value = "0" }, + Arg(u8){ .param = d, .value = "0" }, }, ); } @@ -239,26 +246,33 @@ test "clap.streaming.StreamingClap: long params" { clap.Param(u8){ .id = 2, .names = clap.Names{ .long = "cc" }, - .takes_value = true, + .takes_value = .One, + }, + clap.Param(u8){ + .id = 3, + .names = clap.Names{ .long = "dd" }, + .takes_value = .Many, }, }; const aa = ¶ms[0]; const bb = ¶ms[1]; const cc = ¶ms[2]; + const dd = ¶ms[3]; testNoErr( ¶ms, &[_][]const u8{ "--aa", "--bb", "--cc", "0", - "--cc=0", + "--cc=0", "--dd=0", }, &[_]Arg(u8){ Arg(u8){ .param = aa }, Arg(u8){ .param = bb }, Arg(u8){ .param = cc, .value = "0" }, Arg(u8){ .param = cc, .value = "0" }, + Arg(u8){ .param = dd, .value = "0" }, }, ); } @@ -266,7 +280,7 @@ test "clap.streaming.StreamingClap: long params" { test "clap.streaming.StreamingClap: positional params" { const params = [_]clap.Param(u8){clap.Param(u8){ .id = 0, - .takes_value = true, + .takes_value = .One, }}; testNoErr( @@ -301,11 +315,11 @@ test "clap.streaming.StreamingClap: all params" { .short = 'c', .long = "cc", }, - .takes_value = true, + .takes_value = .One, }, clap.Param(u8){ .id = 3, - .takes_value = true, + .takes_value = .One, }, }; -- cgit v1.2.3