From 5f7b75d5523d9581eca5a72a362868ff517992e8 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Fri, 25 Feb 2022 19:40:00 +0100 Subject: Allow for clap to parse argument values into types This changes - `.flag`, `.option`, `.options` and `.positionals` are now just fields you access on the result of `parse` and `parseEx`. - `clap.ComptimeClap` has been removed. - `clap.StreamingClap` is now called `clap.streaming.Clap` - `parse` and `parseEx` now takes a `value_parsers` argument that provides the parsers to parse values. - Remove `helpEx`, `helpFull`, `usageEx` and `usageFull`. They now just expect `Id` to have methods for getting the description and value texts. --- clap/comptime.zig | 237 ----------------------------------------------------- clap/parsers.zig | 48 +++++++++++ clap/streaming.zig | 34 +++++--- 3 files changed, 69 insertions(+), 250 deletions(-) delete mode 100644 clap/comptime.zig create mode 100644 clap/parsers.zig (limited to 'clap') diff --git a/clap/comptime.zig b/clap/comptime.zig deleted file mode 100644 index b440004..0000000 --- a/clap/comptime.zig +++ /dev/null @@ -1,237 +0,0 @@ -const clap = @import("../clap.zig"); -const std = @import("std"); - -const debug = std.debug; -const heap = std.heap; -const io = std.io; -const mem = std.mem; -const testing = std.testing; - -/// Deprecated: Use `parseEx` instead -pub fn ComptimeClap( - comptime Id: type, - comptime params: []const clap.Param(Id), -) type { - comptime var flags: usize = 0; - comptime var single_options: usize = 0; - comptime var multi_options: usize = 0; - comptime var converted_params: []const clap.Param(usize) = &.{}; - for (params) |param| { - var index: usize = 0; - if (param.names.long != null or param.names.short != null) { - const ptr = switch (param.takes_value) { - .none => &flags, - .one => &single_options, - .many => &multi_options, - }; - index = ptr.*; - ptr.* += 1; - } - - converted_params = converted_params ++ [_]clap.Param(usize){.{ - .id = index, - .names = param.names, - .takes_value = param.takes_value, - }}; - } - - return struct { - multi_options: [multi_options][]const []const u8, - single_options: [single_options][]const u8, - single_options_is_set: std.PackedIntArray(u1, single_options), - flags: std.PackedIntArray(u1, flags), - pos: []const []const u8, - allocator: mem.Allocator, - - pub fn parse(iter: anytype, opt: clap.ParseOptions) !@This() { - const allocator = opt.allocator; - var multis = [_]std.ArrayList([]const u8){undefined} ** multi_options; - for (multis) |*multi| - multi.* = std.ArrayList([]const u8).init(allocator); - - var pos = std.ArrayList([]const u8).init(allocator); - - var res = @This(){ - .multi_options = .{undefined} ** multi_options, - .single_options = .{undefined} ** single_options, - .single_options_is_set = std.PackedIntArray(u1, single_options).init( - .{0} ** single_options, - ), - .flags = std.PackedIntArray(u1, flags).init(.{0} ** flags), - .pos = undefined, - .allocator = allocator, - }; - - var stream = clap.StreamingClap(usize, @typeInfo(@TypeOf(iter)).Pointer.child){ - .params = converted_params, - .iter = iter, - .diagnostic = opt.diagnostic, - }; - while (try stream.next()) |arg| { - const param = arg.param; - if (param.names.long == null and param.names.short == null) { - try pos.append(arg.value.?); - } else if (param.takes_value == .one) { - debug.assert(res.single_options.len != 0); - if (res.single_options.len != 0) { - res.single_options[param.id] = arg.value.?; - res.single_options_is_set.set(param.id, 1); - } - } else if (param.takes_value == .many) { - debug.assert(multis.len != 0); - if (multis.len != 0) - try multis[param.id].append(arg.value.?); - } else { - debug.assert(res.flags.len != 0); - if (res.flags.len != 0) - res.flags.set(param.id, 1); - } - } - - for (multis) |*multi, i| - res.multi_options[i] = multi.toOwnedSlice(); - res.pos = pos.toOwnedSlice(); - - return res; - } - - pub fn deinit(parser: @This()) void { - for (parser.multi_options) |o| - parser.allocator.free(o); - parser.allocator.free(parser.pos); - } - - pub fn flag(parser: @This(), comptime name: []const u8) bool { - const param = comptime findParam(name); - if (param.takes_value != .none) - @compileError(name ++ " is an option and not a flag."); - - return parser.flags.get(param.id) != 0; - } - - pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 { - 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."); - if (parser.single_options_is_set.get(param.id) == 0) - return null; - return parser.single_options[param.id]; - } - - pub fn options(parser: @This(), comptime name: []const u8) []const []const u8 { - const param = comptime findParam(name); - 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.multi_options[param.id]; - } - - pub fn positionals(parser: @This()) []const []const u8 { - return parser.pos; - } - - fn findParam(comptime name: []const u8) clap.Param(usize) { - comptime { - for (converted_params) |param| { - if (param.names.short) |s| { - if (mem.eql(u8, name, "-" ++ [_]u8{s})) - return param; - } - if (param.names.long) |l| { - if (mem.eql(u8, name, "--" ++ l)) - return param; - } - } - - @compileError(name ++ " is not a parameter."); - } - } - }; -} - -test "" { - const params = comptime &.{ - 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("

") catch unreachable, - }; - - var iter = clap.args.SliceIterator{ - .args = &.{ - "-a", "-c", "0", "something", "-d", "a", "--dd", "b", - }, - }; - var args = try clap.parseEx(clap.Help, params, &iter, .{ .allocator = testing.allocator }); - defer args.deinit(); - - try testing.expect(args.flag("-a")); - try testing.expect(args.flag("--aa")); - try testing.expect(!args.flag("-b")); - try testing.expect(!args.flag("--bb")); - try testing.expectEqualStrings("0", args.option("-c").?); - try testing.expectEqualStrings("0", args.option("--cc").?); - try testing.expectEqual(@as(usize, 1), args.positionals().len); - try testing.expectEqualStrings("something", args.positionals()[0]); - try testing.expectEqualSlices([]const u8, &.{ "a", "b" }, args.options("-d")); - try testing.expectEqualSlices([]const u8, &.{ "a", "b" }, args.options("--dd")); -} - -test "empty" { - var iter = clap.args.SliceIterator{ .args = &.{} }; - var args = try clap.parseEx(u8, &.{}, &iter, .{ .allocator = testing.allocator }); - defer args.deinit(); -} - -fn testErr( - comptime params: []const clap.Param(u8), - args_strings: []const []const u8, - expected: []const u8, -) !void { - var diag = clap.Diagnostic{}; - var iter = clap.args.SliceIterator{ .args = args_strings }; - _ = clap.parseEx(u8, params, &iter, .{ - .allocator = testing.allocator, - .diagnostic = &diag, - }) catch |err| { - var buf: [1024]u8 = undefined; - var fbs = io.fixedBufferStream(&buf); - diag.report(fbs.writer(), err) catch return error.TestFailed; - try testing.expectEqualStrings(expected, fbs.getWritten()); - return; - }; - - try testing.expect(false); -} - -test "errors" { - const params = [_]clap.Param(u8){ - .{ - .id = 0, - .names = .{ .short = 'a', .long = "aa" }, - }, - .{ - .id = 1, - .names = .{ .short = 'c', .long = "cc" }, - .takes_value = .one, - }, - }; - - try testErr(¶ms, &.{"q"}, "Invalid argument 'q'\n"); - try testErr(¶ms, &.{"-q"}, "Invalid argument '-q'\n"); - try testErr(¶ms, &.{"--q"}, "Invalid argument '--q'\n"); - try testErr(¶ms, &.{"--q=1"}, "Invalid argument '--q'\n"); - try testErr(¶ms, &.{"-a=1"}, "The argument '-a' does not take a value\n"); - try testErr(¶ms, &.{"--aa=1"}, "The argument '--aa' does not take a value\n"); - try testErr(¶ms, &.{"-c"}, "The argument '-c' requires a value but none was supplied\n"); - try testErr( - ¶ms, - &.{"--cc"}, - "The argument '--cc' requires a value but none was supplied\n", - ); -} diff --git a/clap/parsers.zig b/clap/parsers.zig new file mode 100644 index 0000000..49b95a9 --- /dev/null +++ b/clap/parsers.zig @@ -0,0 +1,48 @@ +const std = @import("std"); + +const fmt = std.fmt; + +pub const default = .{ + .string = string, + .str = string, + .u8 = int(u8, 0), + .u16 = int(u16, 0), + .u32 = int(u32, 0), + .u64 = int(u64, 0), + .usize = int(usize, 0), + .i8 = int(i8, 0), + .i16 = int(i16, 0), + .i32 = int(i32, 0), + .i64 = int(i64, 0), + .isize = int(isize, 0), + .f32 = float(f32), + .f64 = float(f64), +}; + +pub fn string(in: []const u8) error{}![]const u8 { + return in; +} + +pub fn int(comptime T: type, comptime radix: u8) fn ([]const u8) fmt.ParseIntError!T { + return struct { + fn parse(in: []const u8) fmt.ParseIntError!T { + return fmt.parseInt(T, in, radix); + } + }.parse; +} + +pub fn float(comptime T: type) fn ([]const u8) fmt.ParseFloatError!T { + return struct { + fn parse(in: []const u8) fmt.ParseFloatError!T { + return fmt.parseFloat(T, in); + } + }.parse; +} + +fn ReturnType(comptime P: type) type { + return @typeInfo(P).Fn.return_type.?; +} + +pub fn Result(comptime P: type) type { + return @typeInfo(ReturnType(P)).ErrorUnion.payload; +} diff --git a/clap/streaming.zig b/clap/streaming.zig index 8eca51a..2ab9c8d 100644 --- a/clap/streaming.zig +++ b/clap/streaming.zig @@ -10,7 +10,7 @@ const mem = std.mem; const os = std.os; const testing = std.testing; -/// The result returned from StreamingClap.next +/// The result returned from Clap.next pub fn Arg(comptime Id: type) type { return struct { const Self = @This(); @@ -20,10 +20,18 @@ pub fn Arg(comptime Id: type) type { }; } +pub const Error = error{ + MissingValue, + InvalidArgument, + DoesntTakeValue, +}; + /// A command line argument parser which, given an ArgIterator, will parse arguments according -/// to the params. StreamingClap parses in an iterating manner, so you have to use a loop -/// together with StreamingClap.next to parse all the arguments of your program. -pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { +/// to the params. Clap parses in an iterating manner, so you have to use a loop together with +/// Clap.next to parse all the arguments of your program. +/// +/// This parser is the building block for all the more complicated parsers. +pub fn Clap(comptime Id: type, comptime ArgIterator: type) type { return struct { const State = union(enum) { normal, @@ -71,7 +79,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { continue; if (param.takes_value == .none) { if (maybe_value != null) - return parser.err(arg, .{ .long = name }, error.DoesntTakeValue); + return parser.err(arg, .{ .long = name }, Error.DoesntTakeValue); return Arg(Id){ .param = param }; } @@ -81,13 +89,13 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { break :blk v; break :blk parser.iter.next() orelse - return parser.err(arg, .{ .long = name }, error.MissingValue); + return parser.err(arg, .{ .long = name }, Error.MissingValue); }; return Arg(Id){ .param = param, .value = value }; } - return parser.err(arg, .{ .long = name }, error.InvalidArgument); + return parser.err(arg, .{ .long = name }, Error.InvalidArgument); }, .short => return try parser.chaining(.{ .arg = arg, @@ -105,7 +113,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { return Arg(Id){ .param = param, .value = arg }; } else { - return parser.err(arg, .{}, error.InvalidArgument); + return parser.err(arg, .{}, Error.InvalidArgument); }, } } @@ -137,13 +145,13 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { const next_is_eql = if (next_index < arg.len) arg[next_index] == '=' else false; if (param.takes_value == .none) { if (next_is_eql) - return parser.err(arg, .{ .short = short }, error.DoesntTakeValue); + return parser.err(arg, .{ .short = short }, Error.DoesntTakeValue); return Arg(Id){ .param = param }; } if (arg.len <= next_index) { const value = parser.iter.next() orelse - return parser.err(arg, .{ .short = short }, error.MissingValue); + return parser.err(arg, .{ .short = short }, Error.MissingValue); return Arg(Id){ .param = param, .value = value }; } @@ -154,7 +162,7 @@ pub fn StreamingClap(comptime Id: type, comptime ArgIterator: type) type { return Arg(Id){ .param = param, .value = arg[next_index..] }; } - return parser.err(arg, .{ .short = arg[index] }, error.InvalidArgument); + return parser.err(arg, .{ .short = arg[index] }, Error.InvalidArgument); } fn positionalParam(parser: *@This()) ?*const clap.Param(Id) { @@ -209,7 +217,7 @@ fn testNoErr( results: []const Arg(u8), ) !void { var iter = args.SliceIterator{ .args = args_strings }; - var c = StreamingClap(u8, args.SliceIterator){ + var c = Clap(u8, args.SliceIterator){ .params = params, .iter = &iter, }; @@ -236,7 +244,7 @@ fn testErr( ) !void { var diag: clap.Diagnostic = undefined; var iter = args.SliceIterator{ .args = args_strings }; - var c = StreamingClap(u8, args.SliceIterator){ + var c = Clap(u8, args.SliceIterator){ .params = params, .iter = &iter, .diagnostic = &diag, -- cgit v1.2.3