From 5e1480a7a7537451f7196498ac2988bda8273a9b Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Thu, 6 Sep 2018 17:11:58 +0200 Subject: Removed the extended api. Refactored tests --- build.zig | 42 ------ clap.zig | 376 +++++++++++++++++++++++++++++++++++++++++++++++++++++ index.zig | 2 - src/core.zig | 376 ----------------------------------------------------- src/extended.zig | 233 --------------------------------- test.zig | 198 ++++++++++++++++++++++++++++ tests/core.zig | 120 ----------------- tests/extended.zig | 306 ------------------------------------------- 8 files changed, 574 insertions(+), 1079 deletions(-) delete mode 100644 build.zig create mode 100644 clap.zig delete mode 100644 index.zig delete mode 100644 src/core.zig delete mode 100644 src/extended.zig create mode 100644 test.zig delete mode 100644 tests/core.zig delete mode 100644 tests/extended.zig diff --git a/build.zig b/build.zig deleted file mode 100644 index 6ec8837..0000000 --- a/build.zig +++ /dev/null @@ -1,42 +0,0 @@ -const Builder = @import("std").build.Builder; - -pub fn build(b: *Builder) void { - const mode = b.standardReleaseOptions(); - - { - const example_step = b.step("examples", "Build all examples"); - const examples = [][]const u8{}; - - b.default_step.dependOn(example_step); - inline for (examples) |example| { - comptime const path = "examples/" ++ example ++ ".zig"; - const exe = b.addExecutable(example, path); - exe.setBuildMode(mode); - exe.addPackagePath("clap", "index.zig"); - - const step = b.step("build-" ++ example, "Build '" ++ path ++ "'"); - step.dependOn(&exe.step); - example_step.dependOn(step); - } - } - - { - const test_step = b.step("tests", "Run all tests"); - const tests = [][]const u8{ - "core", - "extended", - }; - - b.default_step.dependOn(test_step); - inline for (tests) |test_name| { - comptime const path = "tests/" ++ test_name ++ ".zig"; - const t = b.addTest(path); - t.setBuildMode(mode); - //t.addPackagePath("clap", "index.zig"); - - const step = b.step("test-" ++ test_name, "Run test '" ++ test_name ++ "'"); - step.dependOn(&t.step); - test_step.dependOn(step); - } - } -} diff --git a/clap.zig b/clap.zig new file mode 100644 index 0000000..bdd1bf4 --- /dev/null +++ b/clap.zig @@ -0,0 +1,376 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +const os = std.os; +const heap = std.heap; +const mem = std.mem; +const debug = std.debug; + +/// The names a ::Param can have. +pub const Names = struct { + /// No prefix + bare: ?[]const u8, + + /// '-' prefix + short: ?u8, + + /// '--' prefix + long: ?[]const u8, + + /// Initializes no names + pub fn none() Names { + return Names{ + .bare = null, + .short = null, + .long = null, + }; + } + + /// Initializes a bare name + pub fn bare(b: []const u8) Names { + return Names{ + .bare = b, + .short = null, + .long = null, + }; + } + + /// Initializes a short name + pub fn short(s: u8) Names { + return Names{ + .bare = null, + .short = s, + .long = null, + }; + } + + /// Initializes a long name + pub fn long(l: []const u8) Names { + return Names{ + .bare = null, + .short = null, + .long = l, + }; + } + + /// Initializes a name with a prefix. + /// ::short is set to ::name[0], and ::long is set to ::name. + /// This function asserts that ::name.len != 0 + pub fn prefix(name: []const u8) Names { + debug.assert(name.len != 0); + + return Names{ + .bare = null, + .short = name[0], + .long = name, + }; + } +}; + +/// 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. +/// * They can take a value three different ways. +/// * "-a value" +/// * "-a=value" +/// * "-avalue" +/// * They chain if they don't take values: "-abc". +/// * The last given parameter can take a value in the same way that a single parameter can: +/// * "-abc value" +/// * "-abc=value" +/// * "-abcvalue" +/// * Long ("--long-param"): Should be used for less common parameters, or when no single character +/// can describe the paramter. +/// * They can take a value two different ways. +/// * "--long-param value" +/// * "--long-param=value" +/// * Bare ("bare"): Should be used as for sub-commands and other keywords. +/// * They can take a value two different ways. +/// * "command value" +/// * "command=value" +/// * Value ("value"): Should be used as the primary parameter of the program, like a filename or +/// an expression to parse. +/// * Value parameters must take a value. +pub fn Param(comptime Id: type) type { + return struct { + const Self = this; + + id: Id, + takes_value: bool, + names: Names, + + pub fn init(id: Id, takes_value: bool, names: Names) Self { + // Assert, that if the param have no name, then it has to take + // a value. + debug.assert(names.bare != null or + names.long != null or + names.short != null or + takes_value); + + return Self{ + .id = id, + .takes_value = takes_value, + .names = names, + }; + } + }; +} + +/// The result returned from ::Clap.next +pub fn Arg(comptime Id: type) type { + return struct { + const Self = this; + + param: *const Param(Id), + value: ?[]const u8, + + pub fn init(param: *const Param(Id), value: ?[]const u8) Self { + return Self{ + .param = param, + .value = value, + }; + } + }; +} + +/// A interface for iterating over command line arguments +pub fn ArgIterator(comptime E: type) type { + return struct { + const Self = this; + const Error = E; + + nextFn: fn (iter: *Self) Error!?[]const u8, + + pub fn next(iter: *Self) Error!?[]const u8 { + return iter.nextFn(iter); + } + }; +} + +/// An ::ArgIterator, which iterates over a slice of arguments. +/// This implementation does not allocate. +pub const ArgSliceIterator = struct { + const Error = error{}; + + args: []const []const u8, + index: usize, + iter: ArgIterator(Error), + + pub fn init(args: []const []const u8) ArgSliceIterator { + return ArgSliceIterator{ + .args = args, + .index = 0, + .iter = ArgIterator(Error){ .nextFn = nextFn }, + }; + } + + fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 { + const self = @fieldParentPtr(ArgSliceIterator, "iter", iter); + if (self.args.len <= self.index) + return null; + + defer self.index += 1; + return self.args[self.index]; + } +}; + +/// An ::ArgIterator, which wraps the ArgIterator in ::std. +/// On windows, this iterator allocates. +pub const OsArgIterator = struct { + const Error = os.ArgIterator.NextError; + + arena: heap.ArenaAllocator, + args: os.ArgIterator, + iter: ArgIterator(Error), + + pub fn init(allocator: *mem.Allocator) OsArgIterator { + return OsArgIterator{ + .arena = heap.ArenaAllocator.init(allocator), + .args = os.args(), + .iter = ArgIterator(Error){ .nextFn = nextFn }, + }; + } + + pub fn deinit(iter: *OsArgIterator) void { + iter.arena.deinit(); + } + + fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 { + const self = @fieldParentPtr(OsArgIterator, "iter", iter); + if (builtin.os == builtin.Os.windows) { + return try self.args.next(self.allocator) orelse return null; + } else { + return self.args.nextPosix(); + } + } +}; + +/// A command line argument parser which, given an ::ArgIterator, will parse arguments according +/// 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. +pub fn Clap(comptime Id: type, comptime ArgError: type) type { + return struct { + const Self = this; + + const State = union(enum) { + Normal, + Chaining: Chaining, + + const Chaining = struct { + arg: []const u8, + index: usize, + }; + }; + + params: []const Param(Id), + iter: *ArgIterator(ArgError), + state: State, + + pub fn init(params: []const Param(Id), iter: *ArgIterator(ArgError)) Self { + var res = Self{ + .params = params, + .iter = iter, + .state = State.Normal, + }; + + return res; + } + + /// Get the next ::Arg that matches a ::Param. + pub fn next(clap: *Self) !?Arg(Id) { + const ArgInfo = struct { + const Kind = enum { + Long, + Short, + Bare, + }; + + arg: []const u8, + kind: Kind, + }; + + switch (clap.state) { + State.Normal => { + const full_arg = (try clap.iter.next()) orelse return null; + const arg_info = blk: { + var arg = full_arg; + var kind = ArgInfo.Kind.Bare; + + if (mem.startsWith(u8, arg, "--")) { + arg = arg[2..]; + kind = ArgInfo.Kind.Long; + } else if (mem.startsWith(u8, arg, "-")) { + arg = arg[1..]; + kind = ArgInfo.Kind.Short; + } + + // We allow long arguments to go without a name. + // This allows the user to use "--" for something important + if (kind != ArgInfo.Kind.Long and arg.len == 0) + return error.InvalidArgument; + + break :blk ArgInfo{ .arg = arg, .kind = kind }; + }; + + const arg = arg_info.arg; + const kind = arg_info.kind; + const eql_index = mem.indexOfScalar(u8, arg, '='); + + switch (kind) { + ArgInfo.Kind.Bare, ArgInfo.Kind.Long => { + for (clap.params) |*param| { + const match = switch (kind) { + ArgInfo.Kind.Bare => param.names.bare orelse continue, + ArgInfo.Kind.Long => param.names.long orelse continue, + else => unreachable, + }; + const name = if (eql_index) |i| arg[0..i] else arg; + const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; + + if (!mem.eql(u8, name, match)) + continue; + if (!param.takes_value) { + if (maybe_value != null) + return error.DoesntTakeValue; + + return Arg(Id).init(param, null); + } + + const value = blk: { + if (maybe_value) |v| + break :blk v; + + break :blk (try clap.iter.next()) orelse return error.MissingValue; + }; + + return Arg(Id).init(param, value); + } + }, + ArgInfo.Kind.Short => { + return try clap.chainging(State.Chaining{ + .arg = full_arg, + .index = (full_arg.len - arg.len), + }); + }, + } + + // We do a final pass to look for value parameters matches + if (kind == ArgInfo.Kind.Bare) { + for (clap.params) |*param| { + if (param.names.bare) |_| continue; + if (param.names.short) |_| continue; + if (param.names.long) |_| continue; + + return Arg(Id).init(param, arg); + } + } + + return error.InvalidArgument; + }, + @TagType(State).Chaining => |state| return try clap.chainging(state), + } + } + + fn chainging(clap: *Self, state: State.Chaining) !?Arg(Id) { + const arg = state.arg; + const index = state.index; + const next_index = index + 1; + + for (clap.params) |*param| { + const short = param.names.short orelse continue; + if (short != arg[index]) + continue; + + // Before we return, we have to set the new state of the clap + defer { + if (arg.len <= next_index or param.takes_value) { + clap.state = State.Normal; + } else { + clap.state = State{ + .Chaining = State.Chaining{ + .arg = arg, + .index = next_index, + }, + }; + } + } + + if (!param.takes_value) + return Arg(Id).init(param, null); + + if (arg.len <= next_index) { + const value = (try clap.iter.next()) orelse return error.MissingValue; + return Arg(Id).init(param, value); + } + + if (arg[next_index] == '=') { + return Arg(Id).init(param, arg[next_index + 1 ..]); + } + + return Arg(Id).init(param, arg[next_index..]); + } + + return error.InvalidArgument; + } + }; +} diff --git a/index.zig b/index.zig deleted file mode 100644 index 805d72f..0000000 --- a/index.zig +++ /dev/null @@ -1,2 +0,0 @@ -pub const core = @import("src/core.zig"); -pub const extended = @import("src/extended.zig"); diff --git a/src/core.zig b/src/core.zig deleted file mode 100644 index bdd1bf4..0000000 --- a/src/core.zig +++ /dev/null @@ -1,376 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); - -const os = std.os; -const heap = std.heap; -const mem = std.mem; -const debug = std.debug; - -/// The names a ::Param can have. -pub const Names = struct { - /// No prefix - bare: ?[]const u8, - - /// '-' prefix - short: ?u8, - - /// '--' prefix - long: ?[]const u8, - - /// Initializes no names - pub fn none() Names { - return Names{ - .bare = null, - .short = null, - .long = null, - }; - } - - /// Initializes a bare name - pub fn bare(b: []const u8) Names { - return Names{ - .bare = b, - .short = null, - .long = null, - }; - } - - /// Initializes a short name - pub fn short(s: u8) Names { - return Names{ - .bare = null, - .short = s, - .long = null, - }; - } - - /// Initializes a long name - pub fn long(l: []const u8) Names { - return Names{ - .bare = null, - .short = null, - .long = l, - }; - } - - /// Initializes a name with a prefix. - /// ::short is set to ::name[0], and ::long is set to ::name. - /// This function asserts that ::name.len != 0 - pub fn prefix(name: []const u8) Names { - debug.assert(name.len != 0); - - return Names{ - .bare = null, - .short = name[0], - .long = name, - }; - } -}; - -/// 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. -/// * They can take a value three different ways. -/// * "-a value" -/// * "-a=value" -/// * "-avalue" -/// * They chain if they don't take values: "-abc". -/// * The last given parameter can take a value in the same way that a single parameter can: -/// * "-abc value" -/// * "-abc=value" -/// * "-abcvalue" -/// * Long ("--long-param"): Should be used for less common parameters, or when no single character -/// can describe the paramter. -/// * They can take a value two different ways. -/// * "--long-param value" -/// * "--long-param=value" -/// * Bare ("bare"): Should be used as for sub-commands and other keywords. -/// * They can take a value two different ways. -/// * "command value" -/// * "command=value" -/// * Value ("value"): Should be used as the primary parameter of the program, like a filename or -/// an expression to parse. -/// * Value parameters must take a value. -pub fn Param(comptime Id: type) type { - return struct { - const Self = this; - - id: Id, - takes_value: bool, - names: Names, - - pub fn init(id: Id, takes_value: bool, names: Names) Self { - // Assert, that if the param have no name, then it has to take - // a value. - debug.assert(names.bare != null or - names.long != null or - names.short != null or - takes_value); - - return Self{ - .id = id, - .takes_value = takes_value, - .names = names, - }; - } - }; -} - -/// The result returned from ::Clap.next -pub fn Arg(comptime Id: type) type { - return struct { - const Self = this; - - param: *const Param(Id), - value: ?[]const u8, - - pub fn init(param: *const Param(Id), value: ?[]const u8) Self { - return Self{ - .param = param, - .value = value, - }; - } - }; -} - -/// A interface for iterating over command line arguments -pub fn ArgIterator(comptime E: type) type { - return struct { - const Self = this; - const Error = E; - - nextFn: fn (iter: *Self) Error!?[]const u8, - - pub fn next(iter: *Self) Error!?[]const u8 { - return iter.nextFn(iter); - } - }; -} - -/// An ::ArgIterator, which iterates over a slice of arguments. -/// This implementation does not allocate. -pub const ArgSliceIterator = struct { - const Error = error{}; - - args: []const []const u8, - index: usize, - iter: ArgIterator(Error), - - pub fn init(args: []const []const u8) ArgSliceIterator { - return ArgSliceIterator{ - .args = args, - .index = 0, - .iter = ArgIterator(Error){ .nextFn = nextFn }, - }; - } - - fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 { - const self = @fieldParentPtr(ArgSliceIterator, "iter", iter); - if (self.args.len <= self.index) - return null; - - defer self.index += 1; - return self.args[self.index]; - } -}; - -/// An ::ArgIterator, which wraps the ArgIterator in ::std. -/// On windows, this iterator allocates. -pub const OsArgIterator = struct { - const Error = os.ArgIterator.NextError; - - arena: heap.ArenaAllocator, - args: os.ArgIterator, - iter: ArgIterator(Error), - - pub fn init(allocator: *mem.Allocator) OsArgIterator { - return OsArgIterator{ - .arena = heap.ArenaAllocator.init(allocator), - .args = os.args(), - .iter = ArgIterator(Error){ .nextFn = nextFn }, - }; - } - - pub fn deinit(iter: *OsArgIterator) void { - iter.arena.deinit(); - } - - fn nextFn(iter: *ArgIterator(Error)) Error!?[]const u8 { - const self = @fieldParentPtr(OsArgIterator, "iter", iter); - if (builtin.os == builtin.Os.windows) { - return try self.args.next(self.allocator) orelse return null; - } else { - return self.args.nextPosix(); - } - } -}; - -/// A command line argument parser which, given an ::ArgIterator, will parse arguments according -/// 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. -pub fn Clap(comptime Id: type, comptime ArgError: type) type { - return struct { - const Self = this; - - const State = union(enum) { - Normal, - Chaining: Chaining, - - const Chaining = struct { - arg: []const u8, - index: usize, - }; - }; - - params: []const Param(Id), - iter: *ArgIterator(ArgError), - state: State, - - pub fn init(params: []const Param(Id), iter: *ArgIterator(ArgError)) Self { - var res = Self{ - .params = params, - .iter = iter, - .state = State.Normal, - }; - - return res; - } - - /// Get the next ::Arg that matches a ::Param. - pub fn next(clap: *Self) !?Arg(Id) { - const ArgInfo = struct { - const Kind = enum { - Long, - Short, - Bare, - }; - - arg: []const u8, - kind: Kind, - }; - - switch (clap.state) { - State.Normal => { - const full_arg = (try clap.iter.next()) orelse return null; - const arg_info = blk: { - var arg = full_arg; - var kind = ArgInfo.Kind.Bare; - - if (mem.startsWith(u8, arg, "--")) { - arg = arg[2..]; - kind = ArgInfo.Kind.Long; - } else if (mem.startsWith(u8, arg, "-")) { - arg = arg[1..]; - kind = ArgInfo.Kind.Short; - } - - // We allow long arguments to go without a name. - // This allows the user to use "--" for something important - if (kind != ArgInfo.Kind.Long and arg.len == 0) - return error.InvalidArgument; - - break :blk ArgInfo{ .arg = arg, .kind = kind }; - }; - - const arg = arg_info.arg; - const kind = arg_info.kind; - const eql_index = mem.indexOfScalar(u8, arg, '='); - - switch (kind) { - ArgInfo.Kind.Bare, ArgInfo.Kind.Long => { - for (clap.params) |*param| { - const match = switch (kind) { - ArgInfo.Kind.Bare => param.names.bare orelse continue, - ArgInfo.Kind.Long => param.names.long orelse continue, - else => unreachable, - }; - const name = if (eql_index) |i| arg[0..i] else arg; - const maybe_value = if (eql_index) |i| arg[i + 1 ..] else null; - - if (!mem.eql(u8, name, match)) - continue; - if (!param.takes_value) { - if (maybe_value != null) - return error.DoesntTakeValue; - - return Arg(Id).init(param, null); - } - - const value = blk: { - if (maybe_value) |v| - break :blk v; - - break :blk (try clap.iter.next()) orelse return error.MissingValue; - }; - - return Arg(Id).init(param, value); - } - }, - ArgInfo.Kind.Short => { - return try clap.chainging(State.Chaining{ - .arg = full_arg, - .index = (full_arg.len - arg.len), - }); - }, - } - - // We do a final pass to look for value parameters matches - if (kind == ArgInfo.Kind.Bare) { - for (clap.params) |*param| { - if (param.names.bare) |_| continue; - if (param.names.short) |_| continue; - if (param.names.long) |_| continue; - - return Arg(Id).init(param, arg); - } - } - - return error.InvalidArgument; - }, - @TagType(State).Chaining => |state| return try clap.chainging(state), - } - } - - fn chainging(clap: *Self, state: State.Chaining) !?Arg(Id) { - const arg = state.arg; - const index = state.index; - const next_index = index + 1; - - for (clap.params) |*param| { - const short = param.names.short orelse continue; - if (short != arg[index]) - continue; - - // Before we return, we have to set the new state of the clap - defer { - if (arg.len <= next_index or param.takes_value) { - clap.state = State.Normal; - } else { - clap.state = State{ - .Chaining = State.Chaining{ - .arg = arg, - .index = next_index, - }, - }; - } - } - - if (!param.takes_value) - return Arg(Id).init(param, null); - - if (arg.len <= next_index) { - const value = (try clap.iter.next()) orelse return error.MissingValue; - return Arg(Id).init(param, value); - } - - if (arg[next_index] == '=') { - return Arg(Id).init(param, arg[next_index + 1 ..]); - } - - return Arg(Id).init(param, arg[next_index..]); - } - - return error.InvalidArgument; - } - }; -} diff --git a/src/extended.zig b/src/extended.zig deleted file mode 100644 index f7fc87d..0000000 --- a/src/extended.zig +++ /dev/null @@ -1,233 +0,0 @@ -pub const core = @import("core.zig"); - -const builtin = @import("builtin"); -const std = @import("std"); - -const mem = std.mem; -const fmt = std.fmt; -const debug = std.debug; -const io = std.io; - -const assert = debug.assert; - -pub const Param = struct { - field: []const u8, - names: core.Names, - kind: Kind, - - required: bool, - position: ?usize, - - pub fn flag(field: []const u8, names: core.Names) Param { - return init( - field, - names, - Kind.Flag, - ); - } - - pub fn option( - field: []const u8, - names: core.Names, - comptime parser: Parser, - ) Param { - return init( - field, - names, - Kind{ .Option = parser }, - ); - } - - pub fn subcommand( - field: []const u8, - names: core.Names, - comptime command: Command, - ) Param { - return init( - field, - names, - Kind{ .Subcommand = command }, - ); - } - - pub fn init(field: []const u8, names: core.Names, kind: Kind) Param { - return Param{ - .field = field, - .names = names, - .kind = kind, - .required = false, - .position = null, - }; - } - - pub const Kind = union(enum) { - Flag, - Option: Parser, - Subcommand: Command, - }; -}; - -const Opaque = @OpaqueType(); -pub const Command = struct { - params: []const Param, - - Result: type, - default: *const Opaque, - - pub fn init(comptime Result: type, default: *const Result, params: []const Param) Command { - return Command{ - .params = params, - .Result = Result, - .default = @ptrCast(*const Opaque, default), - }; - } -}; - -pub const Parser = struct { - const UnsafeFunction = *const void; - - FieldType: type, - Errors: type, - func: UnsafeFunction, - - pub fn init(comptime FieldType: type, comptime Errors: type, func: ParseFunc(FieldType, Errors)) Parser { - return Parser{ - .FieldType = FieldType, - .Errors = Errors, - .func = @ptrCast(UnsafeFunction, func), - }; - } - - fn parse(comptime parser: Parser, field_ptr: *parser.FieldType, arg: []const u8) parser.Errors!void { - return @ptrCast(ParseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg); - } - - fn ParseFunc(comptime FieldType: type, comptime Errors: type) type { - return fn (*FieldType, []const u8) Errors!void; - } - - pub fn int(comptime Int: type, comptime radix: u8) Parser { - const func = struct { - fn i(field_ptr: *Int, arg: []const u8) !void { - field_ptr.* = try fmt.parseInt(Int, arg, radix); - } - }.i; - return Parser.init(Int, @typeOf(func).ReturnType.ErrorSet, func); - } - - const string = Parser.init([]const u8, error{}, struct { - fn s(field_ptr: *[]const u8, arg: []const u8) (error{}!void) { - field_ptr.* = arg; - } - }.s); -}; - -pub fn Clap(comptime Result: type) type { - return struct { - const Self = this; - - default: Result, - params: []const Param, - - // TODO: pass-by-value - pub fn parse( - comptime clap: *const Self, - comptime Error: type, - iter: *core.ArgIterator(Error), - ) !Result { - // We initialize the core.Clap without any params, and fill them out in parseHelper. - var c = core.Clap(usize, Error).init([]core.Param(usize){}, iter); - - const top_level_command = comptime Command.init(Result, &clap.default, clap.params); - return try parseHelper(&top_level_command, Error, &c); - } - - // TODO: pass-by-value - fn parseHelper( - comptime command: *const Command, - comptime Error: type, - clap: *core.Clap(usize, Error), - ) !command.Result { - var result = @ptrCast(*const command.Result, command.default).*; - - var handled = comptime blk: { - var res: [command.params.len]bool = undefined; - for (command.params) |p, i| { - res[i] = !p.required; - } - - break :blk res; - }; - - // We replace the current clap with the commands parameters, so that we preserve the that - // claps state. This is important, as core.Clap could be in a Chaining state, and - // constructing a new core.Clap would skip the last chaining arguments. - clap.params = comptime blk: { - var res: [command.params.len]core.Param(usize) = undefined; - - for (command.params) |p, i| { - const id = i; - res[id] = core.Param(usize){ - .id = id, - .takes_value = p.kind == Param.Kind.Option, - .names = p.names, - }; - } - - break :blk res; - }; - - var pos: usize = 0; - - arg_loop: while (try clap.next()) |arg| : (pos += 1) { - inline for (command.params) |param, i| { - if (arg.param.id == i and (param.position orelse pos) == pos) { - handled[i] = true; - - switch (param.kind) { - Param.Kind.Flag => { - getFieldPtr(&result, param.field).* = true; - }, - Param.Kind.Option => |parser| { - try parser.parse(getFieldPtr(&result, param.field), arg.value.?); - }, - Param.Kind.Subcommand => |sub_command| { - getFieldPtr(&result, param.field).* = try sub_command.parseHelper(Error, clap); - - // After parsing a subcommand, there should be no arguments left. - break :arg_loop; - }, - } - continue :arg_loop; - } - } - - return error.InvalidArgument; - } - - for (handled) |h| { - if (!h) - return error.ParamNotHandled; - } - - return result; - } - - fn GetFieldPtrReturn(comptime Struct: type, comptime field: []const u8) type { - var inst: Struct = undefined; - const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse { - return @typeOf(&@field(inst, field)); - }; - - return GetFieldPtrReturn(@typeOf(@field(inst, field[0..dot_index])), field[dot_index + 1 ..]); - } - - fn getFieldPtr(curr: var, comptime field: []const u8) GetFieldPtrReturn(@typeOf(curr).Child, field) { - const dot_index = comptime mem.indexOfScalar(u8, field, '.') orelse { - return &@field(curr, field); - }; - - return getFieldPtr(&@field(curr, field[0..dot_index]), field[dot_index + 1 ..]); - } - }; -} diff --git a/test.zig b/test.zig new file mode 100644 index 0000000..1708353 --- /dev/null +++ b/test.zig @@ -0,0 +1,198 @@ +const std = @import("std"); +const clap = @import("clap.zig"); + +const debug = std.debug; +const mem = std.mem; + +const assert = debug.assert; + +const ArgSliceIterator = clap.ArgSliceIterator; +const Names = clap.Names; +const Param = clap.Param(u8); +const Clap = clap.Clap(u8, ArgSliceIterator.Error); +const Arg = clap.Arg(u8); + +fn testNoErr(params: []const Param, args: []const []const u8, results: []const Arg) void { + var arg_iter = ArgSliceIterator.init(args); + var c = Clap.init(params, &arg_iter.iter); + + for (results) |res| { + const arg = (c.next() catch unreachable) orelse unreachable; + debug.assert(res.param == arg.param); + const expected_value = res.value orelse { + debug.assert(arg.value == null); + continue; + }; + const actual_value = arg.value orelse unreachable; + debug.assert(mem.eql(u8, expected_value, actual_value)); + } + + if (c.next() catch unreachable) |_| { + unreachable; + } +} + +test "clap: short" { + const params = []Param{ + Param.init(0, false, Names.short('a')), + Param.init(1, false, Names.short('b')), + Param.init(2, true, Names.short('c')), + }; + + const a = ¶ms[0]; + const b = ¶ms[1]; + const c = ¶ms[2]; + + testNoErr( + params, + [][]const u8{ + "-a", "-b", "-ab", "-ba", + "-c", "0", "-c=0", + "-ac", "0", "-ac=0", + }, + []const Arg{ + Arg.init(a, null), + Arg.init(b, null), + Arg.init(a, null), + Arg.init(b, null), + Arg.init(b, null), + Arg.init(a, null), + Arg.init(c, "0"), + Arg.init(c, "0"), + Arg.init(a, null), + Arg.init(c, "0"), + Arg.init(a, null), + Arg.init(c, "0"), + }, + ); +} + +test "clap: long" { + const params = []Param{ + Param.init(0, false, Names.long("aa")), + Param.init(1, false, Names.long("bb")), + Param.init(2, true, Names.long("cc")), + }; + + const aa = ¶ms[0]; + const bb = ¶ms[1]; + const cc = ¶ms[2]; + + testNoErr( + params, + [][]const u8{ + "--aa", "--bb", + "--cc", "0", "--cc=0", + }, + []const Arg{ + Arg.init(aa, null), + Arg.init(bb, null), + Arg.init(cc, "0"), + Arg.init(cc, "0"), + }, + ); +} + +test "clap: bare" { + const params = []Param{ + Param.init(0, false, Names.bare("aa")), + Param.init(1, false, Names.bare("bb")), + Param.init(2, true, Names.bare("cc")), + }; + + const aa = ¶ms[0]; + const bb = ¶ms[1]; + const cc = ¶ms[2]; + + testNoErr( + params, + [][]const u8{ + "aa", "bb", + "cc", "0", "cc=0", + }, + []const Arg{ + Arg.init(aa, null), + Arg.init(bb, null), + Arg.init(cc, "0"), + Arg.init(cc, "0"), + }, + ); +} + +test "clap: none" { + const params = []Param{ + Param.init(0, true, Names.none()), + }; + + testNoErr( + params, + [][]const u8{"aa", "bb"}, + []const Arg{ + Arg.init(¶ms[0], "aa"), + Arg.init(¶ms[0], "bb"), + }, + ); +} + +test "clap: all" { + const params = []Param{ + Param.init(0, false, Names{ + .bare = "aa", + .short = 'a', + .long = "aa", + }), + Param.init(1, false, Names{ + .bare = "bb", + .short = 'b', + .long = "bb", + }), + Param.init(2, true, Names{ + .bare = "cc", + .short = 'c', + .long = "cc", + }), + Param.init(3, true, Names.none()), + }; + + const aa = ¶ms[0]; + const bb = ¶ms[1]; + const cc = ¶ms[2]; + const bare = ¶ms[3]; + + testNoErr( + params, + [][]const u8{ + "-a", "-b", "-ab", "-ba", + "-c", "0", "-c=0", + "-ac", "0", "-ac=0", + "--aa", "--bb", + "--cc", "0", "--cc=0", + "aa", "bb", + "cc", "0", "cc=0", + "something", + }, + []const Arg{ + Arg.init(aa, null), + Arg.init(bb, null), + Arg.init(aa, null), + Arg.init(bb, null), + Arg.init(bb, null), + Arg.init(aa, null), + Arg.init(cc, "0"), + Arg.init(cc, "0"), + Arg.init(aa, null), + Arg.init(cc, "0"), + Arg.init(aa, null), + Arg.init(cc, "0"), + Arg.init(aa, null), + Arg.init(bb, null), + Arg.init(cc, "0"), + Arg.init(cc, "0"), + Arg.init(aa, null), + Arg.init(bb, null), + Arg.init(cc, "0"), + Arg.init(cc, "0"), + Arg.init(bare, "something"), + }, + ); +} diff --git a/tests/core.zig b/tests/core.zig deleted file mode 100644 index 765b161..0000000 --- a/tests/core.zig +++ /dev/null @@ -1,120 +0,0 @@ -const std = @import("std"); -const clap = @import("../index.zig"); - -const debug = std.debug; -const mem = std.mem; -const core = clap.core; - -const assert = debug.assert; - -const ArgSliceIterator = core.ArgSliceIterator; -const Names = core.Names; -const Param = core.Param; -const Clap = core.Clap; - -fn testNoErr(params: []const Param(u8), args: []const []const u8, ids: []const u8, values: []const ?[]const u8) void { - var arg_iter = ArgSliceIterator.init(args); - var iter = Clap(u8, ArgSliceIterator.Error).init(params, &arg_iter.iter); - - var i: usize = 0; - while (iter.next() catch unreachable) |arg| : (i += 1) { - debug.assert(ids[i] == arg.param.id); - const expected_value = values[i] orelse { - debug.assert(arg.value == null); - continue; - }; - const actual_value = arg.value orelse unreachable; - - debug.assert(mem.eql(u8, expected_value, actual_value)); - } -} - -test "clap.core: short" { - const params = []Param(u8){ - Param(u8).init(0, false, Names.short('a')), - Param(u8).init(1, false, Names.short('b')), - Param(u8).init(2, true, Names.short('c')), - }; - - testNoErr(params, [][]const u8{"-a"}, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8{ "-a", "-b" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); - testNoErr(params, [][]const u8{"-ab"}, []u8{ 0, 1 }, []?[]const u8{ null, null }); - testNoErr(params, [][]const u8{"-c=100"}, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{"-c100"}, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{ "-c", "100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{ "-abc", "100" }, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); - testNoErr(params, [][]const u8{"-abc=100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); - testNoErr(params, [][]const u8{"-abc100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); -} - -test "clap.core: long" { - const params = []Param(u8){ - Param(u8).init(0, false, Names.long("aa")), - Param(u8).init(1, false, Names.long("bb")), - Param(u8).init(2, true, Names.long("cc")), - }; - - testNoErr(params, [][]const u8{"--aa"}, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8{ "--aa", "--bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); - testNoErr(params, [][]const u8{"--cc=100"}, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{ "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); -} - -test "clap.core: bare" { - const params = []Param(u8){ - Param(u8).init(0, false, Names.bare("aa")), - Param(u8).init(1, false, Names.bare("bb")), - Param(u8).init(2, true, Names.bare("cc")), - }; - - testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8{ "aa", "bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); - testNoErr(params, [][]const u8{"cc=100"}, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{ "cc", "100" }, []u8{2}, []?[]const u8{"100"}); -} - -test "clap.core: none" { - const params = []Param(u8){Param(u8).init(0, true, Names.none())}; - - testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{"aa"}); -} - -test "clap.core: all" { - const params = []Param(u8){ - Param(u8).init(0, false, Names{ - .bare = "aa", - .short = 'a', - .long = "aa", - }), - Param(u8).init(1, false, Names{ - .bare = "bb", - .short = 'b', - .long = "bb", - }), - Param(u8).init(2, true, Names{ - .bare = "cc", - .short = 'c', - .long = "cc", - }), - Param(u8).init(3, true, Names.none()), - }; - - testNoErr(params, [][]const u8{"-a"}, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8{ "-a", "-b" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); - testNoErr(params, [][]const u8{"-ab"}, []u8{ 0, 1 }, []?[]const u8{ null, null }); - testNoErr(params, [][]const u8{"-c=100"}, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{"-c100"}, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{ "-c", "100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{ "-abc", "100" }, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); - testNoErr(params, [][]const u8{"-abc=100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); - testNoErr(params, [][]const u8{"-abc100"}, []u8{ 0, 1, 2 }, []?[]const u8{ null, null, "100" }); - testNoErr(params, [][]const u8{"--aa"}, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8{ "--aa", "--bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); - testNoErr(params, [][]const u8{"--cc=100"}, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{ "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{"aa"}, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8{ "aa", "bb" }, []u8{ 0, 1 }, []?[]const u8{ null, null }); - testNoErr(params, [][]const u8{"cc=100"}, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{ "cc", "100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8{"dd"}, []u8{3}, []?[]const u8{"dd"}); -} diff --git a/tests/extended.zig b/tests/extended.zig deleted file mode 100644 index 9670814..0000000 --- a/tests/extended.zig +++ /dev/null @@ -1,306 +0,0 @@ -const std = @import("std"); -const clap = @import("../index.zig"); - -const debug = std.debug; -const mem = std.mem; -const core = clap.core; -const extended = clap.extended; - -const assert = debug.assert; - -const ArgSliceIterator = core.ArgSliceIterator; -const Names = core.Names; -const Clap = extended.Clap; -const Param = extended.Param; -const Parser = extended.Parser; - -pub fn Test(comptime Expect: type) type { - return struct { - const Self = this; - - args: []const []const u8, - kind: Kind, - - const Kind = union(enum) { - Success: Expect, - Fail: error, - }; - - pub fn success(args: []const []const u8, expected: Expect) Self { - return Self{ - .args = args, - .kind = Kind{ .Success = expected }, - }; - } - - pub fn fail(args: []const []const u8, err: error) Self { - return Self{ - .args = args, - .kind = Kind{ .Fail = err }, - }; - } - - pub fn run(t: Self, comptime parser: var) void { - var iter = ArgSliceIterator.init(t.args); - const actual = parser.parse(ArgSliceIterator.Error, &iter.iter); - - switch (t.kind) { - Kind.Success => |expected| { - const actual_value = actual catch unreachable; - inline for (@typeInfo(Expect).Struct.fields) |field| { - assert(@field(expected, field.name) == @field(actual_value, field.name)); - } - }, - Kind.Fail => |expected| { - if (actual) |_| { - unreachable; - } else |actual_err| { - assert(actual_err == expected); - } - }, - } - } - }; -} - -test "clap.extended: short" { - const S = struct { - a: bool, - b: u8, - }; - - const parser = comptime Clap(S){ - .default = S{ - .a = false, - .b = 0, - }, - .params = []Param{ - p: { - var res = Param.flag("a", Names.short('a')); - res.required = true; - res.position = 0; - break :p res; - }, - Param.option("b", Names.short('b'), Parser.int(u8, 10)), - }, - }; - - const T = Test(S); - const tests = []T{ - T.success( - [][]const u8{"-a"}, - S{ - .a = true, - .b = 0, - }, - ), - T.success( - [][]const u8{ "-a", "-b", "100" }, - S{ - .a = true, - .b = 100, - }, - ), - T.success( - [][]const u8{ "-a", "-b=100" }, - S{ - .a = true, - .b = 100, - }, - ), - T.success( - [][]const u8{ "-a", "-b100" }, - S{ - .a = true, - .b = 100, - }, - ), - T.success( - [][]const u8{ "-ab", "100" }, - S{ - .a = true, - .b = 100, - }, - ), - T.success( - [][]const u8{"-ab=100"}, - S{ - .a = true, - .b = 100, - }, - ), - T.success( - [][]const u8{"-ab100"}, - S{ - .a = true, - .b = 100, - }, - ), - T.fail( - [][]const u8{"-q"}, - error.InvalidArgument, - ), - T.fail( - [][]const u8{"--a"}, - error.InvalidArgument, - ), - T.fail( - [][]const u8{"-b=100"}, - error.ParamNotHandled, - ), - T.fail( - [][]const u8{ "-b=100", "-a" }, - error.InvalidArgument, - ), - }; - - for (tests) |t| { - t.run(parser); - } -} - -test "clap.extended: long" { - const S = struct { - a: bool, - b: u8, - }; - - const parser = comptime Clap(S){ - .default = S{ - .a = false, - .b = 0, - }, - .params = []Param{ - p: { - var res = Param.flag("a", Names.long("a")); - res.required = true; - res.position = 0; - break :p res; - }, - Param.option("b", Names.long("b"), Parser.int(u8, 10)), - }, - }; - - const T = Test(S); - const tests = []T{ - T.success( - [][]const u8{"--a"}, - S{ - .a = true, - .b = 0, - }, - ), - T.success( - [][]const u8{ "--a", "--b", "100" }, - S{ - .a = true, - .b = 100, - }, - ), - T.success( - [][]const u8{ "--a", "--b=100" }, - S{ - .a = true, - .b = 100, - }, - ), - T.fail( - [][]const u8{"--a=100"}, - error.DoesntTakeValue, - ), - T.fail( - [][]const u8{"--q"}, - error.InvalidArgument, - ), - T.fail( - [][]const u8{"-a"}, - error.InvalidArgument, - ), - T.fail( - [][]const u8{"--b=100"}, - error.ParamNotHandled, - ), - T.fail( - [][]const u8{ "--b=100", "--a" }, - error.InvalidArgument, - ), - }; - - for (tests) |t| { - t.run(parser); - } -} - -test "clap.extended: bare" { - const S = struct { - a: bool, - b: u8, - }; - - const parser = comptime Clap(S){ - .default = S{ - .a = false, - .b = 0, - }, - .params = []Param{ - p: { - var res = Param.flag("a", Names.bare("a")); - res.required = true; - res.position = 0; - break :p res; - }, - Param.option("b", Names.bare("b"), Parser.int(u8, 10)), - }, - }; - - const T = Test(S); - const tests = []T{ - T.success( - [][]const u8{"a"}, - S{ - .a = true, - .b = 0, - }, - ), - T.success( - [][]const u8{ "a", "b", "100" }, - S{ - .a = true, - .b = 100, - }, - ), - T.success( - [][]const u8{ "a", "b=100" }, - S{ - .a = true, - .b = 100, - }, - ), - T.fail( - [][]const u8{"a=100"}, - error.DoesntTakeValue, - ), - T.fail( - [][]const u8{"--a"}, - error.InvalidArgument, - ), - T.fail( - [][]const u8{"-a"}, - error.InvalidArgument, - ), - T.fail( - [][]const u8{"b=100"}, - error.ParamNotHandled, - ), - T.fail( - [][]const u8{ "b=100", "--a" }, - error.InvalidArgument, - ), - }; - - for (tests) |t| { - t.run(parser); - } -} - -// TODO: Test sub commands and sub field access -- cgit v1.2.3