From c06c93608cb3befe77c78ba25c70b14db6f7b319 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Fri, 25 Feb 2022 19:44:52 +0100 Subject: Revert "Change clap into generating a struct" This reverts commit cfaac64c404fb1c2e892880410aa3b7dd881ea58. --- clap.zig | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 217 insertions(+), 48 deletions(-) (limited to 'clap.zig') diff --git a/clap.zig b/clap.zig index f10b61b..39bbef2 100644 --- a/clap.zig +++ b/clap.zig @@ -13,8 +13,8 @@ test "clap" { testing.refAllDecls(@This()); } -pub const streaming = @import("clap/streaming.zig"); -pub const untyped = @import("clap/untyped.zig"); +pub const ComptimeClap = @import("clap/comptime.zig").ComptimeClap; +pub const StreamingClap = @import("clap/streaming.zig").StreamingClap; /// The names a ::Param can have. pub const Names = struct { @@ -23,23 +23,6 @@ pub const Names = struct { /// '--' prefix long: ?[]const u8 = null, - - pub fn longest(names: *const Names) Longest { - if (names.long) |long| - return .{ .kind = .long, .name = long }; - if (names.short) |*short| { - // TODO: Zig cannot figure out @as(*const [1]u8, short) in the ano literal - const casted: *const [1]u8 = short; - return .{ .kind = .short, .name = casted }; - } - - return .{ .kind = .positinal, .name = "" }; - } - - pub const Longest = struct { - kind: enum { long, short, positinal }, - name: []const u8, - }; }; /// Whether a param takes no value (a flag), one value, or can be specified multiple times. @@ -78,6 +61,155 @@ pub fn Param(comptime Id: type) type { }; } +/// Takes a string and parses it to a Param(Help). +/// This is the reverse of 'help' but for at single parameter only. +pub fn parseParam(line: []const u8) !Param(Help) { + // This function become a lot less ergonomic to use once you hit the eval branch quota. To + // avoid this we pick a sane default. Sadly, the only sane default is the biggest possible + // value. If we pick something a lot smaller and a user hits the quota after that, they have + // no way of overriding it, since we set it here. + // We can recosider this again if: + // * We get parseParams: https://github.com/Hejsil/zig-clap/issues/39 + // * We get a larger default branch quota in the zig compiler (stage 2). + // * Someone points out how this is a really bad idea. + @setEvalBranchQuota(std.math.maxInt(u32)); + + var found_comma = false; + var it = mem.tokenize(u8, line, " \t"); + var param_str = it.next() orelse return error.NoParamFound; + + const short_name = if (!mem.startsWith(u8, param_str, "--") and + mem.startsWith(u8, param_str, "-")) + blk: { + found_comma = param_str[param_str.len - 1] == ','; + if (found_comma) + param_str = param_str[0 .. param_str.len - 1]; + + if (param_str.len != 2) + return error.InvalidShortParam; + + const short_name = param_str[1]; + if (!found_comma) { + var res = parseParamRest(it.rest()); + res.names.short = short_name; + return res; + } + + param_str = it.next() orelse return error.NoParamFound; + break :blk short_name; + } else null; + + const long_name = if (mem.startsWith(u8, param_str, "--")) blk: { + if (param_str[param_str.len - 1] == ',') + return error.TrailingComma; + + break :blk param_str[2..]; + } else if (found_comma) { + return error.TrailingComma; + } else if (short_name == null) { + return parseParamRest(mem.trimLeft(u8, line, " \t")); + } else null; + + var res = parseParamRest(it.rest()); + res.names.long = long_name; + res.names.short = short_name; + return res; +} + +fn parseParamRest(line: []const u8) Param(Help) { + if (mem.startsWith(u8, line, "<")) blk: { + const len = mem.indexOfScalar(u8, line, '>') orelse break :blk; + const takes_many = mem.startsWith(u8, line[len + 1 ..], "..."); + const help_start = len + 1 + @as(usize, 3) * @boolToInt(takes_many); + return .{ + .takes_value = if (takes_many) .many else .one, + .id = .{ + .msg = mem.trim(u8, line[help_start..], " \t"), + .value = line[1..len], + }, + }; + } + + return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } }; +} + +fn expectParam(expect: Param(Help), actual: Param(Help)) !void { + try testing.expectEqualStrings(expect.id.msg, actual.id.msg); + try testing.expectEqualStrings(expect.id.value, actual.id.value); + try testing.expectEqual(expect.names.short, actual.names.short); + try testing.expectEqual(expect.takes_value, actual.takes_value); + if (expect.names.long) |long| { + try testing.expectEqualStrings(long, actual.names.long.?); + } else { + try testing.expectEqual(@as(?[]const u8, null), actual.names.long); + } +} + +test "parseParam" { + try expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "value" }, + .names = .{ .short = 's', .long = "long" }, + .takes_value = .one, + }, try parseParam("-s, --long Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "value" }, + .names = .{ .short = 's', .long = "long" }, + .takes_value = .many, + }, try parseParam("-s, --long ... Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "value" }, + .names = .{ .long = "long" }, + .takes_value = .one, + }, try parseParam("--long Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "value" }, + .names = .{ .short = 's' }, + .takes_value = .one, + }, try parseParam("-s Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text" }, + .names = .{ .short = 's', .long = "long" }, + }, try parseParam("-s, --long Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text" }, + .names = .{ .short = 's' }, + }, try parseParam("-s Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text" }, + .names = .{ .long = "long" }, + }, try parseParam("--long Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "A | B" }, + .names = .{ .long = "long" }, + .takes_value = .one, + }, try parseParam("--long Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "A" }, + .names = .{}, + .takes_value = .one, + }, try parseParam(" Help text")); + + try expectParam(Param(Help){ + .id = .{ .msg = "Help text", .value = "A" }, + .names = .{}, + .takes_value = .many, + }, try parseParam("... Help text")); + + try testing.expectError(error.TrailingComma, parseParam("--long, Help")); + try testing.expectError(error.TrailingComma, parseParam("-s, Help")); + try testing.expectError(error.InvalidShortParam, parseParam("-ss Help")); + try testing.expectError(error.InvalidShortParam, parseParam("-ss Help")); + try testing.expectError(error.InvalidShortParam, parseParam("- Help")); +} + /// Optional diagnostics used for reporting useful errors pub const Diagnostic = struct { arg: []const u8 = "", @@ -174,7 +306,7 @@ test "Diagnostic.report" { pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { return struct { arena: std.heap.ArenaAllocator, - clap: untyped.Clap(Id, params), + clap: ComptimeClap(Id, params), exe_arg: ?[]const u8, pub fn deinit(a: *@This()) void { @@ -210,6 +342,43 @@ pub const ParseOptions = struct { diagnostic: ?*Diagnostic = null, }; +/// Same as `parseEx` but uses the `args.OsIterator` by default. +pub fn parse( + comptime Id: type, + comptime params: []const Param(Id), + opt: ParseOptions, +) !Args(Id, params) { + var arena = heap.ArenaAllocator.init(opt.allocator); + errdefer arena.deinit(); + + var iter = try process.ArgIterator.initWithAllocator(arena.allocator()); + const exe_arg = iter.next(); + + const clap = try parseEx(Id, params, &iter, .{ + // Let's reuse the arena from the `OSIterator` since we already have it. + .allocator = arena.allocator(), + .diagnostic = opt.diagnostic, + }); + + return Args(Id, params){ + .exe_arg = exe_arg, + .arena = arena, + .clap = clap, + }; +} + +/// Parses the command line arguments passed into the program based on an +/// array of `Param`s. +pub fn parseEx( + comptime Id: type, + comptime params: []const Param(Id), + iter: anytype, + opt: ParseOptions, +) !ComptimeClap(Id, params) { + const Clap = ComptimeClap(Id, params); + return try Clap.parse(iter, opt); +} + /// Will print a help message in the following format: /// -s, --long helpText /// -s, helpText @@ -357,15 +526,15 @@ test "clap.help" { try help( slice_stream.writer(), comptime &.{ - untyped.parseParam("-a Short flag.") catch unreachable, - untyped.parseParam("-b Short option.") catch unreachable, - untyped.parseParam("--aa Long flag.") catch unreachable, - untyped.parseParam("--bb Long option.") catch unreachable, - untyped.parseParam("-c, --cc Both flag.") catch unreachable, - untyped.parseParam("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable, - untyped.parseParam("-d, --dd Both option.") catch unreachable, - untyped.parseParam("-d, --dd ... Both repeated option.") catch unreachable, - untyped.parseParam( + 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("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable, + parseParam("-d, --dd Both option.") catch unreachable, + parseParam("-d, --dd ... Both repeated option.") catch unreachable, + parseParam( "

Positional. This should not appear in the help message.", ) catch unreachable, }, @@ -498,37 +667,37 @@ fn testUsage(expected: []const u8, params: []const Param(Help)) !void { test "usage" { @setEvalBranchQuota(100000); try testUsage("[-ab]", &.{ - try untyped.parseParam("-a"), - try untyped.parseParam("-b"), + try parseParam("-a"), + try parseParam("-b"), }); try testUsage("[-a ] [-b ]", &.{ - try untyped.parseParam("-a "), - try untyped.parseParam("-b "), + try parseParam("-a "), + try parseParam("-b "), }); try testUsage("[--a] [--b]", &.{ - try untyped.parseParam("--a"), - try untyped.parseParam("--b"), + try parseParam("--a"), + try parseParam("--b"), }); try testUsage("[--a ] [--b ]", &.{ - try untyped.parseParam("--a "), - try untyped.parseParam("--b "), + try parseParam("--a "), + try parseParam("--b "), }); try testUsage("", &.{ - try untyped.parseParam(""), + try parseParam(""), }); try testUsage( "[-ab] [-c ] [-d ] [--e] [--f] [--g ] [--h ] [-i ...] ", &.{ - try untyped.parseParam("-a"), - try untyped.parseParam("-b"), - try untyped.parseParam("-c "), - try untyped.parseParam("-d "), - try untyped.parseParam("--e"), - try untyped.parseParam("--f"), - try untyped.parseParam("--g "), - try untyped.parseParam("--h "), - try untyped.parseParam("-i ..."), - try untyped.parseParam(""), + try parseParam("-a"), + try parseParam("-b"), + try parseParam("-c "), + try parseParam("-d "), + try parseParam("--e"), + try parseParam("--f"), + try parseParam("--g "), + try parseParam("--h "), + try parseParam("-i ..."), + try parseParam(""), }, ); } -- cgit v1.2.3