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. --- README.md | 137 +++++++----- build.zig | 2 +- clap.zig | 546 ++++++++++++++++++++++++++++++--------------- clap/comptime.zig | 237 -------------------- clap/parsers.zig | 48 ++++ clap/streaming.zig | 34 +-- example/README.md.template | 51 ++--- example/help.zig | 8 +- example/simple-error.zig | 13 -- example/simple-ex.zig | 39 ++-- example/simple.zig | 24 +- example/streaming-clap.zig | 2 +- example/usage.zig | 14 +- 13 files changed, 570 insertions(+), 585 deletions(-) delete mode 100644 clap/comptime.zig create mode 100644 clap/parsers.zig delete mode 100644 example/simple-error.zig diff --git a/README.md b/README.md index 75b4b19..55a43d9 100644 --- a/README.md +++ b/README.md @@ -34,75 +34,98 @@ pub fn main() !void { // First we specify what parameters our program can take. // We can use `parseParam` to parse a string to a `Param(Help)` const params = comptime [_]clap.Param(clap.Help){ - clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, - clap.parseParam("-n, --number An option parameter, which takes a value.") catch unreachable, - clap.parseParam("-s, --string ... An option parameter which can be specified multiple times.") catch unreachable, - clap.parseParam("...") catch unreachable, + clap.parseParam("-h, --help Display this help and exit.") catch unreachable, + clap.parseParam("-n, --number An option parameter, which takes a value.") catch unreachable, + clap.parseParam("-s, --string ... An option parameter which can be specified multiple times.") catch unreachable, + clap.parseParam("...") catch unreachable, }; // Initalize our diagnostics, which can be used for reporting useful errors. // This is optional. You can also pass `.{}` to `clap.parse` if you don't // care about the extra information `Diagnostics` provides. var diag = clap.Diagnostic{}; - var args = clap.parse(clap.Help, ¶ms, .{ .diagnostic = &diag }) catch |err| { + var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ + .diagnostic = &diag, + }) catch |err| { // Report useful error and exit diag.report(io.getStdErr().writer(), err) catch {}; return err; }; - defer args.deinit(); + defer res.deinit(); - if (args.flag("--help")) + if (res.args.help) debug.print("--help\n", .{}); - if (args.option("--number")) |n| - debug.print("--number = {s}\n", .{n}); - for (args.options("--string")) |s| + if (res.args.number) |n| + debug.print("--number = {}\n", .{n}); + for (res.args.string) |s| debug.print("--string = {s}\n", .{s}); - for (args.positionals()) |pos| + for (res.positionals) |pos| debug.print("{s}\n", .{pos}); } ``` -The data structure returned has lookup speed on par with array access (`arr[i]`) and validates -that the strings you pass to `option`, `options` and `flag` are actually parameters that the -program can take: +The result will contain an `args` field and a `positionals` field. `args` will have one field +for each none positional parameter of your program. The name of the field will be the longest +name of the parameter. + +The fields in `args` are typed. The type is based on the name of the value the parameter takes. +Since `--number` takes a `usize` the field `res.args.number` has the type `usize`. + +Note that this is only the case because `clap.parsers.default` has a field called `usize` which +contains a parser that returns `usize`. You can pass in something other than `clap.parsers.default` +if you want some other mapping. ```zig const clap = @import("clap"); const std = @import("std"); +const debug = std.debug; +const io = std.io; +const process = std.process; + pub fn main() !void { + // First we specify what parameters our program can take. + // We can use `parseParam` to parse a string to a `Param(Help)` const params = comptime [_]clap.Param(clap.Help){ - clap.parseParam("-h, --help Display this help and exit.") catch unreachable, + clap.parseParam("-h, --help Display this help and exit.") catch unreachable, + clap.parseParam("-n, --number An option parameter, which takes a value.") catch unreachable, + clap.parseParam("-s, --string ... An option parameter which can be specified multiple times.") catch unreachable, + clap.parseParam("...") catch unreachable, }; - var args = try clap.parse(clap.Help, ¶ms, .{}); - defer args.deinit(); + // Declare our own parsers which are used to map the argument strings to other + // types. + const parsers = comptime .{ + .STR = clap.parsers.string, + .FILE = clap.parsers.string, + .INT = clap.parsers.int(usize, 10), + }; - _ = args.flag("--helps"); -} + var diag = clap.Diagnostic{}; + var res = clap.parse(clap.Help, ¶ms, parsers, .{ + .diagnostic = &diag, + }) catch |err| { + diag.report(io.getStdErr().writer(), err) catch {}; + return err; + }; + defer res.deinit(); -``` + if (res.args.help) + debug.print("--help\n", .{}); + if (res.args.number) |n| + debug.print("--number = {}\n", .{n}); + for (res.args.string) |s| + debug.print("--string = {s}\n", .{s}); + for (res.positionals) |pos| + debug.print("{s}\n", .{pos}); +} ``` -zig-clap/clap/comptime.zig:109:17: error: --helps is not a parameter. - @compileError(name ++ " is not a parameter."); - ^ -zig-clap/clap/comptime.zig:77:45: note: called from here - const param = comptime findParam(name); - ^ -zig-clap/clap.zig:238:31: note: called from here - return a.clap.flag(name); - ^ -zig-clap/example/simple-error.zig:16:18: note: called from here - _ = args.flag("--helps"); -``` - -There is also a `parseEx` variant that takes an argument iterator. -### `StreamingClap` +### `streaming.Clap` -The `StreamingClap` is the base of all the other parsers. It's a streaming parser that uses an +The `streaming.Clap` is the base of all the other parsers. It's a streaming parser that uses an `args.Iterator` to provide it with arguments lazily. ```zig @@ -140,7 +163,7 @@ pub fn main() !void { // This is optional. You can also leave the `diagnostic` field unset if you // don't care about the extra information `Diagnostic` provides. var diag = clap.Diagnostic{}; - var parser = clap.StreamingClap(u8, process.ArgIterator){ + var parser = clap.streaming.Clap(u8, process.ArgIterator){ .params = ¶ms, .iter = &iter, .diagnostic = &diag, @@ -173,8 +196,9 @@ is generated at runtime. ### `help` -The `help`, `helpEx` and `helpFull` are functions for printing a simple list of all parameters the -program can take. +The `help` prints a simple list of all parameters the program can take. It expects the +`Id` to have a `description` method and an `value` method so that it can provide that +in the output. ```zig const clap = @import("clap"); @@ -186,14 +210,14 @@ pub fn main() !void { clap.parseParam("-v, --version Output version information and exit.") catch unreachable, }; - var args = try clap.parse(clap.Help, ¶ms, .{}); - defer args.deinit(); + var res = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}); + defer res.deinit(); // clap.help is a function that can print a simple help message, given a // slice of Param(Help). There is also a helpEx, which can print a // help message for any Param, but it is more verbose to call. - if (args.flag("--help")) - return clap.help(std.io.getStdErr().writer(), ¶ms); + if (res.args.help) + return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms); } ``` @@ -204,19 +228,10 @@ $ zig-out/bin/help --help -v, --version Output version information and exit. ``` -The `help` functions are the simplest to call. It only takes an `OutStream` and a slice of -`Param(Help)`. - -The `helpEx` is the generic version of `help`. It can print a help message for any -`Param` give that the caller provides functions for getting the help and value strings. - -The `helpFull` is even more generic, allowing the functions that get the help and value strings -to return errors and take a context as a parameter. - ### `usage` -The `usage`, `usageEx` and `usageFull` are functions for printing a small abbreviated version -of the help message. +The `usage` prints a small abbreviated version of the help message. It expects the `Id` +to have a `value` method so it can provide that in the output. ```zig const clap = @import("clap"); @@ -224,19 +239,19 @@ const std = @import("std"); pub fn main() !void { const params = comptime [_]clap.Param(clap.Help){ - clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, - clap.parseParam("-v, --version Output version information and exit. ") catch unreachable, - clap.parseParam(" --value An option parameter, which takes a value.") catch unreachable, + clap.parseParam("-h, --help Display this help and exit.") catch unreachable, + clap.parseParam("-v, --version Output version information and exit.") catch unreachable, + clap.parseParam(" --value An option parameter, which takes a value.") catch unreachable, }; - var args = try clap.parse(clap.Help, ¶ms, .{}); - defer args.deinit(); + var res = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}); + defer res.deinit(); // clap.usage is a function that can print a simple usage message, given a // slice of Param(Help). There is also a usageEx, which can print a // usage message for any Param, but it is more verbose to call. - if (args.flag("--help")) - return clap.usage(std.io.getStdErr().writer(), ¶ms); + if (res.args.help) + return clap.usage(std.io.getStdErr().writer(), clap.Help, ¶ms); } ``` diff --git a/build.zig b/build.zig index 9b1c0ce..0d253e2 100644 --- a/build.zig +++ b/build.zig @@ -60,7 +60,7 @@ fn readMeStep(b: *Builder) *std.build.Step { const stream = file.writer(); try stream.print(@embedFile("example/README.md.template"), .{ @embedFile("example/simple.zig"), - @embedFile("example/simple-error.zig"), + @embedFile("example/simple-ex.zig"), @embedFile("example/streaming-clap.zig"), @embedFile("example/help.zig"), @embedFile("example/usage.zig"), diff --git a/clap.zig b/clap.zig index 39bbef2..1dc90ee 100644 --- a/clap.zig +++ b/clap.zig @@ -1,5 +1,6 @@ const std = @import("std"); +const builtin = std.builtin; const debug = std.debug; const heap = std.heap; const io = std.io; @@ -8,21 +9,37 @@ const process = std.process; const testing = std.testing; pub const args = @import("clap/args.zig"); +pub const parsers = @import("clap/parsers.zig"); +pub const streaming = @import("clap/streaming.zig"); test "clap" { testing.refAllDecls(@This()); } -pub const ComptimeClap = @import("clap/comptime.zig").ComptimeClap; -pub const StreamingClap = @import("clap/streaming.zig").StreamingClap; - -/// The names a ::Param can have. +/// The names a `Param` can have. pub const Names = struct { /// '-' prefix short: ?u8 = null, /// '--' 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. @@ -124,18 +141,18 @@ fn parseParamRest(line: []const u8) Param(Help) { return .{ .takes_value = if (takes_many) .many else .one, .id = .{ - .msg = mem.trim(u8, line[help_start..], " \t"), - .value = line[1..len], + .desc = mem.trim(u8, line[help_start..], " \t"), + .val = line[1..len], }, }; } - return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } }; + return .{ .id = .{ .desc = 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.expectEqualStrings(expect.id.desc, actual.id.desc); + try testing.expectEqualStrings(expect.id.val, actual.id.val); try testing.expectEqual(expect.names.short, actual.names.short); try testing.expectEqual(expect.takes_value, actual.takes_value); if (expect.names.long) |long| { @@ -147,58 +164,58 @@ fn expectParam(expect: Param(Help), actual: Param(Help)) !void { test "parseParam" { try expectParam(Param(Help){ - .id = .{ .msg = "Help text", .value = "value" }, + .id = .{ .desc = "Help text", .val = "val" }, .names = .{ .short = 's', .long = "long" }, .takes_value = .one, - }, try parseParam("-s, --long Help text")); + }, try parseParam("-s, --long Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text", .value = "value" }, + .id = .{ .desc = "Help text", .val = "val" }, .names = .{ .short = 's', .long = "long" }, .takes_value = .many, - }, try parseParam("-s, --long ... Help text")); + }, try parseParam("-s, --long ... Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text", .value = "value" }, + .id = .{ .desc = "Help text", .val = "val" }, .names = .{ .long = "long" }, .takes_value = .one, - }, try parseParam("--long Help text")); + }, try parseParam("--long Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text", .value = "value" }, + .id = .{ .desc = "Help text", .val = "val" }, .names = .{ .short = 's' }, .takes_value = .one, - }, try parseParam("-s Help text")); + }, try parseParam("-s Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text" }, + .id = .{ .desc = "Help text" }, .names = .{ .short = 's', .long = "long" }, }, try parseParam("-s, --long Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text" }, + .id = .{ .desc = "Help text" }, .names = .{ .short = 's' }, }, try parseParam("-s Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text" }, + .id = .{ .desc = "Help text" }, .names = .{ .long = "long" }, }, try parseParam("--long Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text", .value = "A | B" }, + .id = .{ .desc = "Help text", .val = "A | B" }, .names = .{ .long = "long" }, .takes_value = .one, }, try parseParam("--long Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text", .value = "A" }, + .id = .{ .desc = "Help text", .val = "A" }, .names = .{}, .takes_value = .one, }, try parseParam(" Help text")); try expectParam(Param(Help){ - .id = .{ .msg = "Help text", .value = "A" }, + .id = .{ .desc = "Help text", .val = "A" }, .names = .{}, .takes_value = .many, }, try parseParam("... Help text")); @@ -206,7 +223,7 @@ test "parseParam" { 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("-ss Help")); try testing.expectError(error.InvalidShortParam, parseParam("- Help")); } @@ -230,15 +247,15 @@ pub const Diagnostic = struct { Arg{ .prefix = "", .name = diag.arg }; switch (err) { - error.DoesntTakeValue => try stream.print( + streaming.Error.DoesntTakeValue => try stream.print( "The argument '{s}{s}' does not take a value\n", .{ a.prefix, a.name }, ), - error.MissingValue => try stream.print( + streaming.Error.MissingValue => try stream.print( "The argument '{s}{s}' requires a value but none was supplied\n", .{ a.prefix, a.name }, ), - error.InvalidArgument => try stream.print( + streaming.Error.InvalidArgument => try stream.print( "Invalid argument '{s}{s}'\n", .{ a.prefix, a.name }, ), @@ -303,34 +320,6 @@ test "Diagnostic.report" { ); } -pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { - return struct { - arena: std.heap.ArenaAllocator, - clap: ComptimeClap(Id, params), - exe_arg: ?[]const u8, - - pub fn deinit(a: *@This()) void { - a.arena.deinit(); - } - - pub fn flag(a: @This(), comptime name: []const u8) bool { - return a.clap.flag(name); - } - - pub fn option(a: @This(), comptime name: []const u8) ?[]const u8 { - return a.clap.option(name); - } - - pub fn options(a: @This(), comptime name: []const u8) []const []const u8 { - return a.clap.options(name); - } - - pub fn positionals(a: @This()) []const []const u8 { - return a.clap.positionals(); - } - }; -} - /// Options that can be set to customize the behavior of parsing. pub const ParseOptions = struct { /// The allocator used for all memory allocations. Defaults to the `heap.page_allocator`. @@ -346,59 +335,348 @@ pub const ParseOptions = struct { pub fn parse( comptime Id: type, comptime params: []const Param(Id), + comptime value_parsers: anytype, opt: ParseOptions, -) !Args(Id, params) { +) !Result(Id, params, value_parsers) { 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, .{ + const result = try parseEx(Id, params, value_parsers, &iter, .{ // Let's reuse the arena from the `OSIterator` since we already have it. .allocator = arena.allocator(), .diagnostic = opt.diagnostic, }); - return Args(Id, params){ + return Result(Id, params, value_parsers){ + .args = result.args, + .positionals = result.positionals, .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 Result( + comptime Id: type, + comptime params: []const Param(Id), + comptime value_parsers: anytype, +) type { + return struct { + args: Arguments(Id, params, value_parsers, .slice), + positionals: []const FindPositionalType(Id, params, value_parsers), + exe_arg: ?[]const u8, + arena: std.heap.ArenaAllocator, + + pub fn deinit(result: @This()) void { + result.arena.deinit(); + } + }; +} + +/// Parses the command line arguments passed into the program based on an array of parameters. +/// +/// The result will contain an `args` field which contains all the non positional arguments passed +/// in. There is a field in `args` for each parameter. The name of that field will be the result +/// of this expression: +/// ``` +/// param.names.longest().name` +/// ``` +/// +/// The fields can have types other that `[]const u8` and this is based on what `value_parsers` +/// you provide. The parser to use for each parameter is determined by the following expression: +/// ``` +/// @field(value_parsers, param.id.value()) +/// ``` +/// +/// Where `value` is a function that returns the name of the value this parameter takes. A parser +/// is simple a function with the signature: +/// ``` +/// fn ([]const u8) Error!T +/// ``` +/// +/// `T` can be any type and `Error` can be any error. You can pass `clap.parsers.default` if you +/// just wonna get something up and running. +/// +/// Caller ownes the result and should free it by calling `result.deinit()` pub fn parseEx( comptime Id: type, comptime params: []const Param(Id), + comptime value_parsers: anytype, iter: anytype, opt: ParseOptions, -) !ComptimeClap(Id, params) { - const Clap = ComptimeClap(Id, params); - return try Clap.parse(iter, opt); +) !ResultEx(Id, params, value_parsers) { + const allocator = opt.allocator; + var positionals = std.ArrayList( + FindPositionalType(Id, params, value_parsers), + ).init(allocator); + + var arguments = Arguments(Id, params, value_parsers, .list){}; + errdefer deinitArgs(Id, params, value_parsers, .list, allocator, &arguments); + + var stream = streaming.Clap(Id, @typeInfo(@TypeOf(iter)).Pointer.child){ + .params = params, + .iter = iter, + .diagnostic = opt.diagnostic, + }; + while (try stream.next()) |arg| { + inline for (params) |*param| { + if (param == arg.param) { + const parser = comptime switch (param.takes_value) { + .none => undefined, + .one, .many => @field(value_parsers, param.id.value()), + }; + + // TODO: Update opt.diagnostics when `parser` fails. This is blocked by compiler + // bugs that causes an infinit loop. + const longest = comptime param.names.longest(); + switch (longest.kind) { + .short, .long => switch (param.takes_value) { + .none => @field(arguments, longest.name) = true, + .one => @field(arguments, longest.name) = try parser(arg.value.?), + .many => { + const value = try parser(arg.value.?); + try @field(arguments, longest.name).append(allocator, value); + }, + }, + .positinal => try positionals.append(try parser(arg.value.?)), + } + } + } + } + + var result_args = Arguments(Id, params, value_parsers, .slice){}; + inline for (@typeInfo(@TypeOf(arguments)).Struct.fields) |field| { + if (@typeInfo(field.field_type) == .Struct and + @hasDecl(field.field_type, "toOwnedSlice")) + { + const slice = @field(arguments, field.name).toOwnedSlice(allocator); + @field(result_args, field.name) = slice; + } else { + @field(result_args, field.name) = @field(arguments, field.name); + } + } + + return ResultEx(Id, params, value_parsers){ + .args = result_args, + .positionals = positionals.toOwnedSlice(), + .allocator = allocator, + }; +} + +pub fn ResultEx( + comptime Id: type, + comptime params: []const Param(Id), + comptime value_parsers: anytype, +) type { + return struct { + args: Arguments(Id, params, value_parsers, .slice), + positionals: []const FindPositionalType(Id, params, value_parsers), + allocator: mem.Allocator, + + pub fn deinit(result: *@This()) void { + deinitArgs(Id, params, value_parsers, .slice, result.allocator, &result.args); + result.allocator.free(result.positionals); + } + }; +} + +fn FindPositionalType( + comptime Id: type, + comptime params: []const Param(Id), + comptime value_parsers: anytype, +) type { + for (params) |param| { + const longest = param.names.longest(); + if (longest.kind == .positinal) + return ParamType(Id, param, value_parsers); + } + + return []const u8; +} + +fn ParamType( + comptime Id: type, + comptime param: Param(Id), + comptime value_parsers: anytype, +) type { + const parser = switch (param.takes_value) { + .none => parsers.string, + .one, .many => @field(value_parsers, param.id.value()), + }; + return parsers.Result(@TypeOf(parser)); +} + +fn deinitArgs( + comptime Id: type, + comptime params: []const Param(Id), + comptime value_parsers: anytype, + comptime multi_arg_kind: MultiArgKind, + allocator: mem.Allocator, + arguments: *Arguments(Id, params, value_parsers, multi_arg_kind), +) void { + inline for (params) |param| { + const longest = comptime param.names.longest(); + if (longest.kind == .positinal) + continue; + if (param.takes_value != .many) + continue; + + switch (multi_arg_kind) { + .slice => allocator.free(@field(arguments, longest.name)), + .list => @field(arguments, longest.name).deinit(allocator), + } + } } +const MultiArgKind = enum { slice, list }; + +fn Arguments( + comptime Id: type, + comptime params: []const Param(Id), + comptime value_parsers: anytype, + comptime multi_arg_kind: MultiArgKind, +) type { + var fields: [params.len]builtin.TypeInfo.StructField = undefined; + + var i: usize = 0; + for (params) |param| { + const longest = param.names.longest(); + if (longest.kind == .positinal) + continue; + + const T = ParamType(Id, param, value_parsers); + const FieldType = switch (param.takes_value) { + .none => bool, + .one => ?T, + .many => switch (multi_arg_kind) { + .slice => []const T, + .list => std.ArrayListUnmanaged(T), + }, + }; + fields[i] = .{ + .name = longest.name, + .field_type = FieldType, + .default_value = switch (param.takes_value) { + .none => &false, + .one => &@as(?T, null), + .many => switch (multi_arg_kind) { + .slice => &@as([]const T, &[_]T{}), + .list => &std.ArrayListUnmanaged(T){}, + }, + }, + .is_comptime = false, + .alignment = @alignOf(FieldType), + }; + i += 1; + } + + return @Type(.{ .Struct = .{ + .layout = .Auto, + .fields = fields[0..i], + .decls = &.{}, + .is_tuple = false, + } }); +} + +test "" { + const params = comptime &.{ + parseParam("-a, --aa") catch unreachable, + parseParam("-b, --bb") catch unreachable, + parseParam("-c, --cc ") catch unreachable, + parseParam("-d, --dd ...") catch unreachable, + parseParam("") catch unreachable, + }; + + var iter = args.SliceIterator{ + .args = &.{ "-a", "-c", "0", "something", "-d", "1", "--dd", "2" }, + }; + var res = try parseEx(Help, params, parsers.default, &iter, .{ + .allocator = testing.allocator, + }); + defer res.deinit(); + + try testing.expect(res.args.aa); + try testing.expect(!res.args.bb); + try testing.expectEqualStrings("0", res.args.cc.?); + try testing.expectEqual(@as(usize, 1), res.positionals.len); + try testing.expectEqualStrings("something", res.positionals[0]); + try testing.expectEqualSlices(usize, &.{ 1, 2 }, res.args.dd); +} + +test "empty" { + var iter = args.SliceIterator{ .args = &.{} }; + var res = try parseEx(u8, &.{}, parsers.default, &iter, .{ .allocator = testing.allocator }); + defer res.deinit(); +} + +fn testErr( + comptime params: []const Param(Help), + args_strings: []const []const u8, + expected: []const u8, +) !void { + var diag = Diagnostic{}; + var iter = args.SliceIterator{ .args = args_strings }; + _ = parseEx(Help, params, parsers.default, &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 = comptime [_]Param(Help){ + parseParam("-a, --aa") catch unreachable, + parseParam("-c, --cc ") catch unreachable, + }; + + 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", + ); +} + +pub const Help = struct { + desc: []const u8 = "", + val: []const u8 = "", + + pub fn description(h: Help) []const u8 { + return h.desc; + } + + pub fn value(h: Help) []const u8 { + return h.val; + } +}; + /// Will print a help message in the following format: /// -s, --long helpText /// -s, helpText /// -s helpText /// --long helpText /// --long helpText -pub fn helpFull( - stream: anytype, - comptime Id: type, - params: []const Param(Id), - comptime Error: type, - context: anytype, - helpText: fn (@TypeOf(context), Param(Id)) Error![]const u8, - valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, -) !void { +pub fn help(stream: anytype, comptime Id: type, params: []const Param(Id)) !void { const max_spacing = blk: { var res: usize = 0; for (params) |param| { var cs = io.countingWriter(io.null_writer); - try printParam(cs.writer(), Id, param, Error, context, valueText); + try printParam(cs.writer(), Id, param); if (res < cs.bytes_written) res = @intCast(usize, cs.bytes_written); } @@ -412,13 +690,13 @@ pub fn helpFull( var cs = io.countingWriter(stream); try stream.writeAll("\t"); - try printParam(cs.writer(), Id, param, Error, context, valueText); + try printParam(cs.writer(), Id, param); try stream.writeByteNTimes(' ', max_spacing - @intCast(usize, cs.bytes_written)); - const help_text = try helpText(context, param); - var help_text_line_it = mem.split(u8, help_text, "\n"); + const description = param.id.description(); + var it = mem.split(u8, description, "\n"); var indent_line = false; - while (help_text_line_it.next()) |line| : (indent_line = true) { + while (it.next()) |line| : (indent_line = true) { if (indent_line) { try stream.writeAll("\t"); try stream.writeByteNTimes(' ', max_spacing); @@ -434,9 +712,6 @@ fn printParam( stream: anytype, comptime Id: type, param: Param(Id), - comptime Error: type, - context: anytype, - valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, ) !void { if (param.names.short) |s| { try stream.writeAll(&[_]u8{ '-', s }); @@ -458,66 +733,12 @@ fn printParam( return; try stream.writeAll(" <"); - try stream.writeAll(try valueText(context, param)); + try stream.writeAll(param.id.value()); try stream.writeAll(">"); if (param.takes_value == .many) try stream.writeAll("..."); } -/// A wrapper around helpFull for simple helpText and valueText functions that -/// cant return an error or take a context. -pub fn helpEx( - stream: anytype, - comptime Id: type, - params: []const Param(Id), - helpText: fn (Param(Id)) []const u8, - valueText: fn (Param(Id)) []const u8, -) !void { - const Context = struct { - helpText: fn (Param(Id)) []const u8, - valueText: fn (Param(Id)) []const u8, - - pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 { - return c.helpText(p); - } - - pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { - return c.valueText(p); - } - }; - - return helpFull( - stream, - Id, - params, - error{}, - Context{ - .helpText = helpText, - .valueText = valueText, - }, - Context.help, - Context.value, - ); -} - -pub const Help = struct { - msg: []const u8 = "", - value: []const u8 = "", -}; - -/// A wrapper around helpEx that takes a Param(Help). -pub fn help(stream: anytype, params: []const Param(Help)) !void { - try helpEx(stream, Help, params, getHelpSimple, getValueSimple); -} - -fn getHelpSimple(param: Param(Help)) []const u8 { - return param.id.msg; -} - -fn getValueSimple(param: Param(Help)) []const u8 { - return param.id.value; -} - test "clap.help" { var buf: [1024]u8 = undefined; var slice_stream = io.fixedBufferStream(&buf); @@ -525,6 +746,7 @@ test "clap.help" { @setEvalBranchQuota(10000); try help( slice_stream.writer(), + Help, comptime &.{ parseParam("-a Short flag.") catch unreachable, parseParam("-b Short option.") catch unreachable, @@ -556,18 +778,11 @@ test "clap.help" { } /// Will print a usage message in the following format: -/// [-abc] [--longa] [-d ] [--longb ] +/// [-abc] [--longa] [-d ] [--longb ] /// -/// First all none value taking parameters, which have a short name are -/// printed, then non positional parameters and finally the positinal. -pub fn usageFull( - stream: anytype, - comptime Id: type, - params: []const Param(Id), - comptime Error: type, - context: anytype, - valueText: fn (@TypeOf(context), Param(Id)) Error![]const u8, -) !void { +/// First all none value taking parameters, which have a short name are printed, then non +/// positional parameters and finally the positinal. +pub fn usage(stream: anytype, comptime Id: type, params: []const Param(Id)) !void { var cos = io.countingWriter(stream); const cs = cos.writer(); for (params) |param| { @@ -607,7 +822,7 @@ pub fn usageFull( try cs.writeAll(name); if (param.takes_value != .none) { try cs.writeAll(" <"); - try cs.writeAll(try valueText(context, param)); + try cs.writeAll(param.id.value()); try cs.writeAll(">"); if (param.takes_value == .many) try cs.writeAll("..."); @@ -621,46 +836,15 @@ pub fn usageFull( try cs.writeAll(" "); try cs.writeAll("<"); - try cs.writeAll(try valueText(context, p)); + try cs.writeAll(p.id.value()); try cs.writeAll(">"); } } -/// A wrapper around usageFull for a simple valueText functions that -/// cant return an error or take a context. -pub fn usageEx( - stream: anytype, - comptime Id: type, - params: []const Param(Id), - valueText: fn (Param(Id)) []const u8, -) !void { - const Context = struct { - valueText: fn (Param(Id)) []const u8, - - pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 { - return c.valueText(p); - } - }; - - return usageFull( - stream, - Id, - params, - error{}, - Context{ .valueText = valueText }, - Context.value, - ); -} - -/// A wrapper around usageEx that takes a Param(Help). -pub fn usage(stream: anytype, params: []const Param(Help)) !void { - try usageEx(stream, Help, params, getValueSimple); -} - fn testUsage(expected: []const u8, params: []const Param(Help)) !void { var buf: [1024]u8 = undefined; var fbs = io.fixedBufferStream(&buf); - try usage(fbs.writer(), params); + try usage(fbs.writer(), Help, params); try testing.expectEqualStrings(expected, fbs.getWritten()); } 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, diff --git a/example/README.md.template b/example/README.md.template index 7f5c545..9fbc1cc 100644 --- a/example/README.md.template +++ b/example/README.md.template @@ -27,33 +27,24 @@ The simplest way to use this library is to just call the `clap.parse` function. {s} ``` -The data structure returned has lookup speed on par with array access (`arr[i]`) and validates -that the strings you pass to `option`, `options` and `flag` are actually parameters that the -program can take: +The result will contain an `args` field and a `positionals` field. `args` will have one field +for each none positional parameter of your program. The name of the field will be the longest +name of the parameter. + +The fields in `args` are typed. The type is based on the name of the value the parameter takes. +Since `--number` takes a `usize` the field `res.args.number` has the type `usize`. + +Note that this is only the case because `clap.parsers.default` has a field called `usize` which +contains a parser that returns `usize`. You can pass in something other than `clap.parsers.default` +if you want some other mapping. ```zig {s} ``` -``` -zig-clap/clap/comptime.zig:109:17: error: --helps is not a parameter. - @compileError(name ++ " is not a parameter."); - ^ -zig-clap/clap/comptime.zig:77:45: note: called from here - const param = comptime findParam(name); - ^ -zig-clap/clap.zig:238:31: note: called from here - return a.clap.flag(name); - ^ -zig-clap/example/simple-error.zig:16:18: note: called from here - _ = args.flag("--helps"); -``` - -There is also a `parseEx` variant that takes an argument iterator. +### `streaming.Clap` -### `StreamingClap` - -The `StreamingClap` is the base of all the other parsers. It's a streaming parser that uses an +The `streaming.Clap` is the base of all the other parsers. It's a streaming parser that uses an `args.Iterator` to provide it with arguments lazily. ```zig @@ -65,8 +56,9 @@ is generated at runtime. ### `help` -The `help`, `helpEx` and `helpFull` are functions for printing a simple list of all parameters the -program can take. +The `help` prints a simple list of all parameters the program can take. It expects the +`Id` to have a `description` method and an `value` method so that it can provide that +in the output. ```zig {s} @@ -78,19 +70,10 @@ $ zig-out/bin/help --help -v, --version Output version information and exit. ``` -The `help` functions are the simplest to call. It only takes an `OutStream` and a slice of -`Param(Help)`. - -The `helpEx` is the generic version of `help`. It can print a help message for any -`Param` give that the caller provides functions for getting the help and value strings. - -The `helpFull` is even more generic, allowing the functions that get the help and value strings -to return errors and take a context as a parameter. - ### `usage` -The `usage`, `usageEx` and `usageFull` are functions for printing a small abbreviated version -of the help message. +The `usage` prints a small abbreviated version of the help message. It expects the `Id` +to have a `value` method so it can provide that in the output. ```zig {s} diff --git a/example/help.zig b/example/help.zig index de3b707..f3edb58 100644 --- a/example/help.zig +++ b/example/help.zig @@ -7,12 +7,12 @@ pub fn main() !void { clap.parseParam("-v, --version Output version information and exit.") catch unreachable, }; - var args = try clap.parse(clap.Help, ¶ms, .{}); - defer args.deinit(); + var res = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}); + defer res.deinit(); // clap.help is a function that can print a simple help message, given a // slice of Param(Help). There is also a helpEx, which can print a // help message for any Param, but it is more verbose to call. - if (args.flag("--help")) - return clap.help(std.io.getStdErr().writer(), ¶ms); + if (res.args.help) + return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms); } diff --git a/example/simple-error.zig b/example/simple-error.zig deleted file mode 100644 index c04a9c6..0000000 --- a/example/simple-error.zig +++ /dev/null @@ -1,13 +0,0 @@ -const clap = @import("clap"); -const std = @import("std"); - -pub fn main() !void { - const params = comptime [_]clap.Param(clap.Help){ - clap.parseParam("-h, --help Display this help and exit.") catch unreachable, - }; - - var args = try clap.parse(clap.Help, ¶ms, .{}); - defer args.deinit(); - - _ = args.flag("--helps"); -} diff --git a/example/simple-ex.zig b/example/simple-ex.zig index d2dc77e..6cb4c3f 100644 --- a/example/simple-ex.zig +++ b/example/simple-ex.zig @@ -6,43 +6,38 @@ const io = std.io; const process = std.process; pub fn main() !void { - const allocator = std.heap.page_allocator; - // First we specify what parameters our program can take. // We can use `parseParam` to parse a string to a `Param(Help)` const params = comptime [_]clap.Param(clap.Help){ - clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, - clap.parseParam("-n, --number An option parameter, which takes a value.") catch unreachable, + clap.parseParam("-h, --help Display this help and exit.") catch unreachable, + clap.parseParam("-n, --number An option parameter, which takes a value.") catch unreachable, clap.parseParam("-s, --string ... An option parameter which can be specified multiple times.") catch unreachable, - clap.parseParam("...") catch unreachable, + clap.parseParam("...") catch unreachable, }; - var iter = try process.ArgIterator.initWithAllocator(allocator); - defer iter.deinit(); - - // Skip exe argument - _ = iter.next(); + // Declare our own parsers which are used to map the argument strings to other + // types. + const parsers = comptime .{ + .STR = clap.parsers.string, + .FILE = clap.parsers.string, + .INT = clap.parsers.int(usize, 10), + }; - // Initalize our diagnostics, which can be used for reporting useful errors. - // This is optional. You can also pass `.{}` to `clap.parse` if you don't - // care about the extra information `Diagnostics` provides. var diag = clap.Diagnostic{}; - var args = clap.parseEx(clap.Help, ¶ms, &iter, .{ - .allocator = allocator, + var res = clap.parse(clap.Help, ¶ms, parsers, .{ .diagnostic = &diag, }) catch |err| { - // Report useful error and exit diag.report(io.getStdErr().writer(), err) catch {}; return err; }; - defer args.deinit(); + defer res.deinit(); - if (args.flag("--help")) + if (res.args.help) debug.print("--help\n", .{}); - if (args.option("--number")) |n| - debug.print("--number = {s}\n", .{n}); - for (args.options("--string")) |s| + if (res.args.number) |n| + debug.print("--number = {}\n", .{n}); + for (res.args.string) |s| debug.print("--string = {s}\n", .{s}); - for (args.positionals()) |pos| + for (res.positionals) |pos| debug.print("{s}\n", .{pos}); } diff --git a/example/simple.zig b/example/simple.zig index ff6d301..11e975e 100644 --- a/example/simple.zig +++ b/example/simple.zig @@ -8,29 +8,31 @@ pub fn main() !void { // First we specify what parameters our program can take. // We can use `parseParam` to parse a string to a `Param(Help)` const params = comptime [_]clap.Param(clap.Help){ - clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, - clap.parseParam("-n, --number An option parameter, which takes a value.") catch unreachable, - clap.parseParam("-s, --string ... An option parameter which can be specified multiple times.") catch unreachable, - clap.parseParam("...") catch unreachable, + clap.parseParam("-h, --help Display this help and exit.") catch unreachable, + clap.parseParam("-n, --number An option parameter, which takes a value.") catch unreachable, + clap.parseParam("-s, --string ... An option parameter which can be specified multiple times.") catch unreachable, + clap.parseParam("...") catch unreachable, }; // Initalize our diagnostics, which can be used for reporting useful errors. // This is optional. You can also pass `.{}` to `clap.parse` if you don't // care about the extra information `Diagnostics` provides. var diag = clap.Diagnostic{}; - var args = clap.parse(clap.Help, ¶ms, .{ .diagnostic = &diag }) catch |err| { + var res = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ + .diagnostic = &diag, + }) catch |err| { // Report useful error and exit diag.report(io.getStdErr().writer(), err) catch {}; return err; }; - defer args.deinit(); + defer res.deinit(); - if (args.flag("--help")) + if (res.args.help) debug.print("--help\n", .{}); - if (args.option("--number")) |n| - debug.print("--number = {s}\n", .{n}); - for (args.options("--string")) |s| + if (res.args.number) |n| + debug.print("--number = {}\n", .{n}); + for (res.args.string) |s| debug.print("--string = {s}\n", .{s}); - for (args.positionals()) |pos| + for (res.positionals) |pos| debug.print("{s}\n", .{pos}); } diff --git a/example/streaming-clap.zig b/example/streaming-clap.zig index a7ab7d8..cacda56 100644 --- a/example/streaming-clap.zig +++ b/example/streaming-clap.zig @@ -32,7 +32,7 @@ pub fn main() !void { // This is optional. You can also leave the `diagnostic` field unset if you // don't care about the extra information `Diagnostic` provides. var diag = clap.Diagnostic{}; - var parser = clap.StreamingClap(u8, process.ArgIterator){ + var parser = clap.streaming.Clap(u8, process.ArgIterator){ .params = ¶ms, .iter = &iter, .diagnostic = &diag, diff --git a/example/usage.zig b/example/usage.zig index 368a6b3..20d4736 100644 --- a/example/usage.zig +++ b/example/usage.zig @@ -3,17 +3,17 @@ const std = @import("std"); pub fn main() !void { const params = comptime [_]clap.Param(clap.Help){ - clap.parseParam("-h, --help Display this help and exit. ") catch unreachable, - clap.parseParam("-v, --version Output version information and exit. ") catch unreachable, - clap.parseParam(" --value An option parameter, which takes a value.") catch unreachable, + clap.parseParam("-h, --help Display this help and exit.") catch unreachable, + clap.parseParam("-v, --version Output version information and exit.") catch unreachable, + clap.parseParam(" --value An option parameter, which takes a value.") catch unreachable, }; - var args = try clap.parse(clap.Help, ¶ms, .{}); - defer args.deinit(); + var res = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{}); + defer res.deinit(); // clap.usage is a function that can print a simple usage message, given a // slice of Param(Help). There is also a usageEx, which can print a // usage message for any Param, but it is more verbose to call. - if (args.flag("--help")) - return clap.usage(std.io.getStdErr().writer(), ¶ms); + if (res.args.help) + return clap.usage(std.io.getStdErr().writer(), clap.Help, ¶ms); } -- cgit v1.2.3