From a1f024342d33fc7fe54a657c3b07a05e33f631c3 Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sun, 20 May 2018 01:21:15 +0200 Subject: The old clap now uses core.zig to get the same func as before --- clap.zig | 500 ----------------------------------------------------------- core.zig | 67 ++++---- example.exe | Bin 0 -> 182272 bytes example.pdb | Bin 0 -> 241664 bytes example.zig | 73 ++++----- extended.zig | 274 ++++++++++++++++++++++++++++++++ 6 files changed, 337 insertions(+), 577 deletions(-) delete mode 100644 clap.zig create mode 100644 example.exe create mode 100644 example.pdb create mode 100644 extended.zig diff --git a/clap.zig b/clap.zig deleted file mode 100644 index 269e9f1..0000000 --- a/clap.zig +++ /dev/null @@ -1,500 +0,0 @@ -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 fn Clap(comptime Result: type) type { - return struct { - const Self = this; - - program_name: []const u8, - author: []const u8, - version: []const u8, - about: []const u8, - command: Command, - defaults: Result, - - pub fn init(defaults: &const Result) Self { - return Self { - .program_name = "", - .author = "", - .version = "", - .about = "", - .command = Command.init(""), - .defaults = *defaults, - }; - } - - pub fn with(parser: &const Self, comptime field: []const u8, value: var) Self { - var res = *parser; - @field(res, field) = value; - return res; - } - - pub fn parse(comptime clap: &const Self, arguments: []const []const u8) !Result { - return parseCommand(CommandList { .command = clap.command, .prev = null }, clap.defaults, arguments); - } - - const CommandList = struct { - command: &const Command, - prev: ?&const CommandList, - }; - - fn parseCommand(comptime list: &const CommandList, defaults: &const Result, arguments: []const []const u8) !Result { - const command = list.command; - - const Arg = struct { - const Kind = enum { Long, Short, Value }; - - arg: []const u8, - kind: Kind, - }; - - const Iterator = struct { - index: usize, - slice: []const []const u8, - - const Pair = struct { - value: []const u8, - index: usize, - }; - - pub fn next(it: &this) ?[]const u8 { - const res = it.nextWithIndex() ?? return null; - return res.value; - } - - pub fn nextWithIndex(it: &this) ?Pair { - if (it.index >= it.slice.len) - return null; - - defer it.index += 1; - return Pair { - .value = it.slice[it.index], - .index = it.index, - }; - } - }; - - // NOTE: For now, a bitfield is used to keep track of the required arguments. - // This limits the user to 128 required arguments, which should be more - // than enough. - var required = comptime blk: { - var required_index : u128 = 0; - var required_res : u128 = 0; - for (command.arguments) |option| { - if (option.required) { - required_res |= 0x1 << required_index; - required_index += 1; - } - } - - break :blk required_res; - }; - - var result = *defaults; - - var it = Iterator { .index = 0, .slice = arguments }; - while (it.nextWithIndex()) |item| { - const arg_info = blk: { - var arg = item.value; - var kind = Arg.Kind.Value; - - if (mem.startsWith(u8, arg, "--")) { - arg = arg[2..]; - kind = Arg.Kind.Long; - } else if (mem.startsWith(u8, arg, "-")) { - arg = arg[1..]; - kind = Arg.Kind.Short; - } - - break :blk Arg { .arg = arg, .kind = kind }; - }; - const arg = arg_info.arg; - const arg_index = item.index; - const kind = arg_info.kind; - const eql_index = mem.indexOfScalar(u8, arg, '='); - - success: { - // TODO: Revert a lot of if statements when inline loop compiler bugs have been fixed - switch (kind) { - // TODO: Handle subcommands - Arg.Kind.Value => { - var required_index = usize(0); - inline for (command.arguments) |option| { - defer if (option.required) required_index += 1; - - if (option.short != null) continue; - if (option.long != null) continue; - const has_right_index = if (option.index) |index| index == it.index else true; - - if (has_right_index) { - if (option.takes_value) |parser| { - try parser.parse(&@field(result, option.field), arg); - } else { - @field(result, option.field) = true; - } - - required = newRequired(option, required, required_index); - break :success; - } - } - }, - Arg.Kind.Short => { - if (arg.len == 0) return error.InvalidArg; - - const end = (eql_index ?? arg.len) - 1; - - short_arg_loop: - for (arg[0..end]) |short_arg, i| { - var required_index = usize(0); - - inline for (command.arguments) |option| { - defer if (option.required) required_index += 1; - - const short = option.short ?? continue; - const has_right_index = if (option.index) |index| index == arg_index else true; - - if (has_right_index) { - if (short_arg == short) { - if (option.takes_value) |parser| { - const value = arg[i + 1..]; - try parser.parse(&@field(result, option.field), value); - break :success; - } else { - @field(result, option.field) = true; - continue :short_arg_loop; - } - - required = newRequired(option, required, required_index); - } - } - } - } - - const last_arg = arg[end]; - var required_index = usize(0); - inline for (command.arguments) |option| { - defer if (option.required) required_index += 1; - - const short = option.short ?? continue; - const has_right_index = if (option.index) |index| index == arg_index else true; - - if (has_right_index and last_arg == short) { - if (option.takes_value) |parser| { - const value = if (eql_index) |index| arg[index + 1..] else it.next() ?? return error.ArgMissingValue; - try parser.parse(&@field(result, option.field), value); - } else { - if (eql_index) |_| return error.ArgTakesNoValue; - @field(result, option.field) = true; - } - - required = newRequired(option, required, required_index); - break :success; - } - } - }, - Arg.Kind.Long => { - var required_index = usize(0); - inline for (command.arguments) |option| { - defer if (option.required) required_index += 1; - - const long = option.long ?? continue; - const has_right_index = if (option.index) |index| index == arg_index else true; - - if (has_right_index and mem.eql(u8, arg, long)) { - if (option.takes_value) |parser| { - const value = if (eql_index) |index| arg[index + 1..] else it.next() ?? return error.ArgMissingValue; - try parser.parse(&@field(result, option.field), value); - } else { - @field(result, option.field) = true; - } - - required = newRequired(option, required, required_index); - break :success; - } - } - } - } - - return error.InvalidArg; - } - } - - if (required != 0) { - return error.RequiredArgNotHandled; - } - - return result; - } - - fn newRequired(argument: &const Argument, old_required: u128, index: usize) u128 { - if (argument.required) - return old_required & ~(u128(1) << u7(index)); - - return old_required; - } - }; -} - -pub const Command = struct { - field: ?[]const u8, - name: []const u8, - arguments: []const Argument, - sub_commands: []const Command, - - pub fn init(command_name: []const u8) Command { - return Command { - .field = null, - .name = command_name, - .arguments = []Argument{ }, - .sub_commands = []Command{ }, - }; - } - - pub fn with(command: &const Command, comptime field: []const u8, value: var) Command { - var res = *command; - @field(res, field) = value; - return res; - } -}; - -const Parser = struct { - const UnsafeFunction = &const void; - - FieldType: type, - Errors: type, - func: UnsafeFunction, - - 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: takePtr(parser.FieldType), arg: []const u8) parser.Errors!void { - return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg); - } - - // TODO: This is a workaround, since we don't have pointer reform yet. - fn takePtr(comptime T: type) type { return &T; } - - fn parseFunc(comptime FieldType: type, comptime Errors: type) type { - return fn(&FieldType, []const u8) Errors!void; - } -}; - -pub const Argument = struct { - field: []const u8, - help: []const u8, - takes_value: ?Parser, - required: bool, - short: ?u8, - long: ?[]const u8, - index: ?usize, - - pub fn field(field_name: []const u8) Argument { - return Argument { - .field = field_name, - .help = "", - .takes_value = null, - .required = false, - .short = null, - .long = null, - .index = null, - }; - } - - pub fn arg(s: []const u8) Argument { - return Argument.field(s) - .with("short", if (s.len == 1) s[0] else null) - .with("long", if (s.len != 1) s else null); - } - - pub fn with(argument: &const Argument, comptime field_name: []const u8, value: var) Argument { - var res = *argument; - @field(res, field_name) = value; - return res; - } -}; - -pub const parse = struct { - 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 - ); - - const boolean = Parser.init( - []const u8, - error{InvalidBoolArg}, - struct { - fn b(comptime T: type, field_ptr: &T, arg: []const u8) (error{InvalidBoolArg}!void) { - if (mem.eql(u8, arg, "true")) { - *field_ptr = true; - } else if (mem.eql(u8, arg, "false")) { - *field_ptr = false; - } else { - return error.InvalidBoolArg; - } - } - }.b - ); -}; - - -const Options = struct { - str: []const u8, - int: i64, - uint: u64, - a: bool, - b: bool, - cc: bool, - - pub fn with(op: &const Options, comptime field: []const u8, value: var) Options { - var res = *op; - @field(res, field) = value; - return res; - } -}; - -const default = Options { - .str = "", - .int = 0, - .uint = 0, - .a = false, - .b = false, - .cc = false, -}; - -fn testNoErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: &const Options) void { - const actual = clap.parse(args) catch |err| { debug.warn("{}\n", @errorName(err)); unreachable; }; - assert(mem.eql(u8, expected.str, actual.str)); - assert(expected.int == actual.int); - assert(expected.uint == actual.uint); - assert(expected.a == actual.a); - assert(expected.b == actual.b); - assert(expected.cc == actual.cc); -} - -fn testErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: error) void { - if (clap.parse(args)) |actual| { - unreachable; - } else |err| { - assert(err == expected); - } -} - -test "clap.parse: short" { - const clap = comptime Clap(Options).init(default).with("command", - Command.init("").with("arguments", - []Argument { - Argument.arg("a"), - Argument.arg("b"), - Argument.field("int") - .with("short", 'i') - .with("takes_value", parse.int(i64, 10)) - } - ) - ); - - testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true)); - testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true)); - testNoErr(clap, [][]const u8 { "-i=100" }, default.with("int", 100)); - testNoErr(clap, [][]const u8 { "-i100" }, default.with("int", 100)); - testNoErr(clap, [][]const u8 { "-i", "100" }, default.with("int", 100)); - testNoErr(clap, [][]const u8 { "-ab" }, default.with("a", true).with("b", true)); - testNoErr(clap, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100)); - testNoErr(clap, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100)); - testNoErr(clap, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100)); -} - -test "clap.parse: long" { - const clap = comptime Clap(Options).init(default).with("command", - Command.init("").with("arguments", - []Argument { - Argument.arg("cc"), - Argument.arg("int").with("takes_value", parse.int(i64, 10)), - Argument.arg("uint").with("takes_value", parse.int(u64, 10)), - Argument.arg("str").with("takes_value", parse.string), - } - ) - ); - - testNoErr(clap, [][]const u8 { "--cc" }, default.with("cc", true)); - testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100)); -} - -test "clap.parse: value bool" { - const clap = comptime Clap(Options).init(default).with("command", - Command.init("").with("arguments", - []Argument { - Argument.field("a"), - } - ) - ); - - testNoErr(clap, [][]const u8 { "Hello World!" }, default.with("a", true)); -} - -test "clap.parse: value str" { - const clap = comptime Clap(Options).init(default).with("command", - Command.init("").with("arguments", - []Argument { - Argument.field("str").with("takes_value", parse.string), - } - ) - ); - - testNoErr(clap, [][]const u8 { "Hello World!" }, default.with("str", "Hello World!")); -} - -test "clap.parse: value int" { - const clap = comptime Clap(Options).init(default).with("command", - Command.init("").with("arguments", - []Argument { - Argument.field("int").with("takes_value", parse.int(i64, 10)), - } - ) - ); - - testNoErr(clap, [][]const u8 { "100" }, default.with("int", 100)); -} - -test "clap.parse: index" { - const clap = comptime Clap(Options).init(default).with("command", - Command.init("").with("arguments", - []Argument { - Argument.arg("a").with("index", 0), - Argument.arg("b").with("index", 1), - } - ) - ); - - testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true)); - testErr(clap, [][]const u8 { "-b", "-a" }, error.InvalidArg); -} diff --git a/core.zig b/core.zig index d9dc804..8b953a9 100644 --- a/core.zig +++ b/core.zig @@ -131,10 +131,10 @@ pub const OsArgIterator = struct { }; } - fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error![]const u8 { + fn nextFn(iter: &ArgIterator, allocator: &mem.Allocator) ArgIterator.Error!?[]const u8 { const self = @fieldParentPtr(OsArgIterator, "iter", iter); - if (builtin.os == builtin.Os.Windows) { - return try self.args.next(allocator); + if (builtin.os == builtin.Os.windows) { + return try self.args.next(allocator) ?? return null; } else { return self.args.nextPoxix(); } @@ -161,22 +161,19 @@ pub fn Iterator(comptime Id: type) type { params: []const Param(Id), inner: &ArgIterator, state: State, - command: []const u8, - pub fn init(params: []const Param(Id), inner: &ArgIterator, allocator: &mem.Allocator) !Self { + pub fn init(params: []const Param(Id), inner: &ArgIterator, allocator: &mem.Allocator) Self { var res = Self { .arena = heap.ArenaAllocator.init(allocator), .params = params, .inner = inner, .state = State.Normal, - .command = undefined, }; - res.command = (try res.innerNext()) ?? unreachable; return res; } - pub fn deinit(iter: &const Self) void { + pub fn deinit(iter: &Self) void { iter.arena.deinit(); } @@ -315,7 +312,7 @@ pub fn Iterator(comptime Id: type) type { 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 = Iterator(u8).init(params, &arg_iter.iter, debug.global_allocator) catch unreachable; + var iter = Iterator(u8).init(params, &arg_iter.iter, debug.global_allocator); var i: usize = 0; while (iter.next() catch unreachable) |arg| : (i += 1) { @@ -337,15 +334,15 @@ test "clap.parse: short" { Param(u8).init(2, "c", true), }; - testNoErr(params, [][]const u8 { "command", "-a" }, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8 { "command", "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null}); - testNoErr(params, [][]const u8 { "command", "-ab" }, []u8{0,1}, []?[]const u8{null,null}); - testNoErr(params, [][]const u8 { "command", "-c=100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8 { "command", "-c100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8 { "command", "-c", "100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8 { "command", "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); - testNoErr(params, [][]const u8 { "command", "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); - testNoErr(params, [][]const u8 { "command", "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); + 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.parse: long" { @@ -355,10 +352,10 @@ test "clap.parse: long" { Param(u8).init(2, "cc", true), }; - testNoErr(params, [][]const u8 { "command", "--aa" }, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8 { "command", "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null}); - testNoErr(params, [][]const u8 { "command", "--cc=100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8 { "command", "--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"}); } test "clap.parse: both" { @@ -368,17 +365,17 @@ test "clap.parse: both" { Param(u8).both(2, 'c', "cc", true), }; - testNoErr(params, [][]const u8 { "command", "-a" }, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8 { "command", "-a", "-b" }, []u8{0,1}, []?[]const u8{null,null}); - testNoErr(params, [][]const u8 { "command", "-ab" }, []u8{0,1}, []?[]const u8{null,null}); - testNoErr(params, [][]const u8 { "command", "-c=100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8 { "command", "-c100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8 { "command", "-c", "100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8 { "command", "-abc", "100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); - testNoErr(params, [][]const u8 { "command", "-abc=100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); - testNoErr(params, [][]const u8 { "command", "-abc100" }, []u8{0,1,2}, []?[]const u8{null,null,"100"}); - testNoErr(params, [][]const u8 { "command", "--aa" }, []u8{0}, []?[]const u8{null}); - testNoErr(params, [][]const u8 { "command", "--aa", "--bb" }, []u8{0,1}, []?[]const u8{null,null}); - testNoErr(params, [][]const u8 { "command", "--cc=100" }, []u8{2}, []?[]const u8{"100"}); - testNoErr(params, [][]const u8 { "command", "--cc", "100" }, []u8{2}, []?[]const u8{"100"}); + 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"}); } diff --git a/example.exe b/example.exe new file mode 100644 index 0000000..760b0ec Binary files /dev/null and b/example.exe differ diff --git a/example.pdb b/example.pdb new file mode 100644 index 0000000..6ec0856 Binary files /dev/null and b/example.pdb differ diff --git a/example.zig b/example.zig index 4d63b02..14b5487 100644 --- a/example.zig +++ b/example.zig @@ -1,12 +1,13 @@ const std = @import("std"); -const clap = @import("clap.zig"); +const core = @import("core.zig"); +const clap = @import("extended.zig"); const debug = std.debug; const os = std.os; -const Clap = clap.Clap; -const Command = clap.Command; -const Argument = clap.Argument; +const Clap = clap.Clap; +const Param = clap.Param; +const Parser = clap.Parser; const Options = struct { print_values: bool, @@ -34,46 +35,34 @@ const Options = struct { // d = V=5 pub fn main() !void { - const parser = comptime Clap(Options).init( - Options { - .print_values = false, - .a = 0, - .b = 0, - .c = 0, - .d = "", - } - ) - .with("program_name", "My Test Command") - .with("author", "Hejsil") - .with("version", "v1") - .with("about", "Prints some values to the screen... Maybe.") - .with("command", Command.init("command") - .with("arguments", - []Argument { - Argument.arg("a") - .with("help", "Set the a field of Option.") - .with("takes_value", clap.parse.int(i64, 10)), - Argument.arg("b") - .with("help", "Set the b field of Option.") - .with("takes_value", clap.parse.int(u64, 10)), - Argument.arg("c") - .with("help", "Set the c field of Option.") - .with("takes_value", clap.parse.int(u8, 10)), - Argument.arg("d") - .with("help", "Set the d field of Option.") - .with("takes_value", clap.parse.string), - Argument.field("print_values") - .with("help", "Print all not 0 values.") - .with("short", 'p') - .with("long", "print-values"), - } - ) - ); + const parser = comptime Clap(Options) { + .defaults = Options { + .print_values = false, + .a = 0, + .b = 0, + .c = 0, + .d = "", + }, + .params = []Param { + Param.init("a") + .with("takes_value", Parser.int(i64, 10)), + Param.init("b") + .with("takes_value", Parser.int(u64, 10)), + Param.init("c") + .with("takes_value", Parser.int(u8, 10)), + Param.init("d") + .with("takes_value", Parser.string), + Param.init("print_values") + .with("short", 'p') + .with("long", "print-values"), + } + }; - const args = try os.argsAlloc(debug.global_allocator); - defer os.argsFree(debug.global_allocator, args); + var arg_iter = core.OsArgIterator.init(); + const iter = &arg_iter.iter; + const command = iter.next(debug.global_allocator); - const options = try parser.parse(args[1..]); + const options = try parser.parse(debug.global_allocator, iter); if (options.print_values) { if (options.a != 0) debug.warn("a = {}\n", options.a); diff --git a/extended.zig b/extended.zig new file mode 100644 index 0000000..a5c8e89 --- /dev/null +++ b/extended.zig @@ -0,0 +1,274 @@ +const builtin = @import("builtin"); +const std = @import("std"); +const core = @import("core.zig"); + +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, + short: ?u8, + long: ?[]const u8, + takes_value: ?Parser, + required: bool, + position: ?usize, + + pub fn init(name: []const u8) Param { + return Param { + .field = name, + .short = if (name.len == 1) name[0] else null, + .long = if (name.len > 1) name else null, + .takes_value = null, + .required = false, + .position = null, + }; + } + + pub fn with(param: &const Param, comptime field_name: []const u8, value: var) Param { + var res = *param; + @field(res, field_name) = value; + return res; + } +}; + +pub fn Clap(comptime Result: type) type { + return struct { + const Self = this; + + defaults: Result, + params: []const Param, + + pub fn parse(comptime clap: &const Self, allocator: &mem.Allocator, arg_iter: &core.ArgIterator) !Result { + var result = clap.defaults; + const core_params = comptime blk: { + var res: [clap.params.len]core.Param(usize) = undefined; + + for (clap.params) |p, i| { + res[i] = core.Param(usize) { + .id = i, + .short = p.short, + .long = p.long, + .takes_value = p.takes_value != null, + }; + } + + break :blk res; + }; + + var handled = comptime blk: { + var res: [clap.params.len]bool = undefined; + for (clap.params) |p, i| { + res[i] = !p.required; + } + + break :blk res; + }; + + var pos: usize = 0; + var iter = core.Iterator(usize).init(core_params, arg_iter, allocator); + defer iter.deinit(); + while (try iter.next()) |arg| : (pos += 1) { + inline for(clap.params) |param, i| { + if (arg.id == i) { + if (param.position) |expected| { + if (expected != pos) + return error.InvalidPosition; + } + + if (param.takes_value) |parser| { + try parser.parse(&@field(result, param.field), ??arg.value); + } else { + @field(result, param.field) = true; + } + handled[i] = true; + } + } + } + + return result; + } + }; +} + +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: takePtr(parser.FieldType), arg: []const u8) parser.Errors!void { + return @ptrCast(parseFunc(parser.FieldType, parser.Errors), parser.func)(field_ptr, arg); + } + + // TODO: This is a workaround, since we don't have pointer reform yet. + fn takePtr(comptime T: type) type { return &T; } + + 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 + ); +}; + + +const Options = struct { + str: []const u8, + int: i64, + uint: u64, + a: bool, + b: bool, + cc: bool, + + pub fn with(op: &const Options, comptime field: []const u8, value: var) Options { + var res = *op; + @field(res, field) = value; + return res; + } +}; + +const default = Options { + .str = "", + .int = 0, + .uint = 0, + .a = false, + .b = false, + .cc = false, +}; + +fn testNoErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: &const Options) void { + var arg_iter = core.ArgSliceIterator.init(args); + const actual = clap.parse(debug.global_allocator, &arg_iter.iter) catch unreachable; + assert(mem.eql(u8, expected.str, actual.str)); + assert(expected.int == actual.int); + assert(expected.uint == actual.uint); + assert(expected.a == actual.a); + assert(expected.b == actual.b); + assert(expected.cc == actual.cc); +} + +fn testErr(comptime clap: &const Clap(Options), args: []const []const u8, expected: error) void { + var arg_iter = core.ArgSliceIterator.init(args); + if (clap.parse(debug.global_allocator, &arg_iter.iter)) |actual| { + unreachable; + } else |err| { + assert(err == expected); + } +} + +test "clap.parse: short" { + const clap = comptime Clap(Options) { + .defaults = default, + .params = []Param { + Param.init("a"), + Param.init("b"), + Param.init("int") + .with("short", 'i') + .with("takes_value", Parser.int(i64, 10)) + } + }; + + testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true)); + testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true)); + testNoErr(clap, [][]const u8 { "-i=100" }, default.with("int", 100)); + testNoErr(clap, [][]const u8 { "-i100" }, default.with("int", 100)); + testNoErr(clap, [][]const u8 { "-i", "100" }, default.with("int", 100)); + testNoErr(clap, [][]const u8 { "-ab" }, default.with("a", true).with("b", true)); + testNoErr(clap, [][]const u8 { "-abi", "100" }, default.with("a", true).with("b", true).with("int", 100)); + testNoErr(clap, [][]const u8 { "-abi=100" }, default.with("a", true).with("b", true).with("int", 100)); + testNoErr(clap, [][]const u8 { "-abi100" }, default.with("a", true).with("b", true).with("int", 100)); +} + +test "clap.parse: long" { + const clap = comptime Clap(Options) { + .defaults = default, + .params = []Param { + Param.init("cc"), + Param.init("int").with("takes_value", Parser.int(i64, 10)), + Param.init("uint").with("takes_value", Parser.int(u64, 10)), + Param.init("str").with("takes_value", Parser.string), + } + }; + + testNoErr(clap, [][]const u8 { "--cc" }, default.with("cc", true)); + testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100)); +} + +test "clap.parse: value bool" { + const clap = comptime Clap(Options) { + .defaults = default, + .params = []Param { + Param.init("a"), + } + }; + + testNoErr(clap, [][]const u8 { "-a" }, default.with("a", true)); +} + +test "clap.parse: value str" { + const clap = comptime Clap(Options) { + .defaults = default, + .params = []Param { + Param.init("str").with("takes_value", Parser.string), + } + }; + + testNoErr(clap, [][]const u8 { "--str", "Hello World!" }, default.with("str", "Hello World!")); +} + +test "clap.parse: value int" { + const clap = comptime Clap(Options) { + .defaults = default, + .params = []Param { + Param.init("int").with("takes_value", Parser.int(i64, 10)), + } + }; + + testNoErr(clap, [][]const u8 { "--int", "100" }, default.with("int", 100)); +} + +test "clap.parse: position" { + const clap = comptime Clap(Options) { + .defaults = default, + .params = []Param { + Param.init("a").with("position", 0), + Param.init("b").with("position", 1), + } + }; + + testNoErr(clap, [][]const u8 { "-a", "-b" }, default.with("a", true).with("b", true)); + testErr(clap, [][]const u8 { "-b", "-a" }, error.InvalidPosition); +} -- cgit v1.2.3