From f3eb797336c1bbdae391657ef6ac54e4015b5fde Mon Sep 17 00:00:00 2001 From: Jimmi Holst Christensen Date: Sun, 6 Oct 2019 17:20:56 +0200 Subject: fmt, mv src/ clap/ and run fmt on build --- clap/args.zig | 71 +++++++++++ clap/comptime.zig | 143 ++++++++++++++++++++++ clap/streaming.zig | 348 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 562 insertions(+) create mode 100644 clap/args.zig create mode 100644 clap/comptime.zig create mode 100644 clap/streaming.zig (limited to 'clap') diff --git a/clap/args.zig b/clap/args.zig new file mode 100644 index 0000000..4234ada --- /dev/null +++ b/clap/args.zig @@ -0,0 +1,71 @@ +const builtin = @import("builtin"); +const std = @import("std"); + +const debug = std.debug; +const heap = std.heap; +const mem = std.mem; +const process = std.process; + +/// An example of what methods should be implemented on an arg iterator. +pub const ExampleArgIterator = struct { + const Error = error{}; + + pub fn next(iter: *ExampleArgIterator) Error!?[]const u8 { + return "2"; + } +}; + +/// An argument iterator which iterates over a slice of arguments. +/// This implementation does not allocate. +pub const SliceIterator = struct { + const Error = error{}; + + args: []const []const u8, + index: usize = 0, + + pub fn next(iter: *SliceIterator) Error!?[]const u8 { + if (iter.args.len <= iter.index) + return null; + + defer iter.index += 1; + return iter.args[iter.index]; + } +}; + +test "clap.args.SliceIterator" { + const args = [_][]const u8{ "A", "BB", "CCC" }; + var iter = SliceIterator{ .args = args }; + + for (args) |a| { + const b = try iter.next(); + debug.assert(mem.eql(u8, a, b.?)); + } +} + +/// An argument iterator which wraps the ArgIterator in ::std. +/// On windows, this iterator allocates. +pub const OsIterator = struct { + const Error = process.ArgIterator.NextError; + + arena: heap.ArenaAllocator, + args: process.ArgIterator, + + pub fn init(allocator: *mem.Allocator) OsIterator { + return OsIterator{ + .arena = heap.ArenaAllocator.init(allocator), + .args = process.args(), + }; + } + + pub fn deinit(iter: *OsIterator) void { + iter.arena.deinit(); + } + + pub fn next(iter: *OsIterator) Error!?[]const u8 { + if (builtin.os == builtin.Os.windows) { + return try iter.args.next(&iter.arena.allocator) orelse return null; + } else { + return iter.args.nextPosix(); + } + } +}; diff --git a/clap/comptime.zig b/clap/comptime.zig new file mode 100644 index 0000000..f5c2762 --- /dev/null +++ b/clap/comptime.zig @@ -0,0 +1,143 @@ +const clap = @import("../clap.zig"); +const std = @import("std"); + +const testing = std.testing; +const heap = std.heap; +const mem = std.mem; +const debug = std.debug; + +pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) type { + var flags: usize = 0; + var options: usize = 0; + var converted_params: []const clap.Param(usize) = [_]clap.Param(usize){}; + for (params) |param| { + var index: usize = 0; + if (param.names.long != null or param.names.short != null) { + const ptr = if (param.takes_value) &options else &flags; + index = ptr.*; + ptr.* += 1; + } + + const converted = clap.Param(usize){ + .id = index, + .names = param.names, + .takes_value = param.takes_value, + }; + converted_params = converted_params ++ [_]clap.Param(usize){converted}; + } + + return struct { + options: [options]?[]const u8, + flags: [flags]bool, + pos: []const []const u8, + allocator: *mem.Allocator, + + pub fn parse(allocator: *mem.Allocator, comptime ArgIter: type, iter: *ArgIter) !@This() { + var pos = std.ArrayList([]const u8).init(allocator); + var res = @This(){ + .options = [_]?[]const u8{null} ** options, + .flags = [_]bool{false} ** flags, + .pos = undefined, + .allocator = allocator, + }; + + var stream = clap.StreamingClap(usize, ArgIter){ + .params = converted_params, + .iter = iter, + }; + 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) { + // If we don't have any optional parameters, then this code should + // never be reached. + debug.assert(res.options.len != 0); + + // Hack: Utilize Zigs lazy analyzis to avoid a compiler error + if (res.options.len != 0) + res.options[param.id] = arg.value.?; + } else { + debug.assert(res.flags.len != 0); + if (res.flags.len != 0) + res.flags[param.id] = true; + } + } + + res.pos = pos.toOwnedSlice(); + return res; + } + + pub fn deinit(parser: *@This()) void { + parser.allocator.free(parser.pos); + parser.* = undefined; + } + + pub fn flag(parser: @This(), comptime name: []const u8) bool { + const param = comptime findParam(name); + if (param.takes_value) + @compileError(name ++ " is an option and not a flag."); + + return parser.flags[param.id]; + } + + pub fn option(parser: @This(), comptime name: []const u8) ?[]const u8 { + const param = comptime findParam(name); + if (!param.takes_value) + @compileError(name ++ " is a flag and not an option."); + + return parser.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 "clap.comptime.ComptimeClap" { + const Clap = ComptimeClap(clap.Help, comptime [_]clap.Param(clap.Help){ + clap.parseParam("-a, --aa ") catch unreachable, + clap.parseParam("-b, --bb ") catch unreachable, + clap.parseParam("-c, --cc ") catch unreachable, + clap.Param(clap.Help){ + .takes_value = true, + }, + }); + + var buf: [1024]u8 = undefined; + var fb_allocator = heap.FixedBufferAllocator.init(buf[0..]); + var iter = clap.args.SliceIterator{ + .args = [_][]const u8{ + "-a", "-c", "0", "something", + }, + }; + var args = try Clap.parse(&fb_allocator.allocator, clap.args.SliceIterator, &iter); + defer args.deinit(); + + testing.expect(args.flag("-a")); + testing.expect(args.flag("--aa")); + testing.expect(!args.flag("-b")); + testing.expect(!args.flag("--bb")); + testing.expectEqualSlices(u8, "0", args.option("-c").?); + testing.expectEqualSlices(u8, "0", args.option("--cc").?); + testing.expectEqual(usize(1), args.positionals().len); + testing.expectEqualSlices(u8, "something", args.positionals()[0]); +} diff --git a/clap/streaming.zig b/clap/streaming.zig new file mode 100644 index 0000000..fa7ce80 --- /dev/null +++ b/clap/streaming.zig @@ -0,0 +1,348 @@ +const builtin = @import("builtin"); +const clap = @import("../clap.zig"); +const std = @import("std"); + +const args = clap.args; +const testing = std.testing; +const heap = std.heap; +const mem = std.mem; +const os = std.os; + +/// The result returned from ::StreamingClap.next +pub fn Arg(comptime Id: type) type { + return struct { + const Self = @This(); + + param: *const clap.Param(Id), + value: ?[]const u8 = null, + }; +} + +/// 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 { + return struct { + const State = union(enum) { + Normal, + Chaining: Chaining, + + const Chaining = struct { + arg: []const u8, + index: usize, + }; + }; + + params: []const clap.Param(Id), + iter: *ArgIterator, + state: State = State.Normal, + + /// Get the next ::Arg that matches a ::Param. + pub fn next(parser: *@This()) !?Arg(Id) { + const ArgInfo = struct { + const Kind = enum { + Long, + Short, + Positional, + }; + + arg: []const u8, + kind: Kind, + }; + + switch (parser.state) { + .Normal => { + const full_arg = (try parser.iter.next()) orelse return null; + const arg_info = if (mem.eql(u8, full_arg, "--") or mem.eql(u8, full_arg, "-")) + ArgInfo{ .arg = full_arg, .kind = .Positional } + else if (mem.startsWith(u8, full_arg, "--")) + ArgInfo{ .arg = full_arg[2..], .kind = .Long } + else if (mem.startsWith(u8, full_arg, "-")) + ArgInfo{ .arg = full_arg[1..], .kind = .Short } + else + ArgInfo{ .arg = full_arg, .kind = .Positional }; + + const arg = arg_info.arg; + const kind = arg_info.kind; + const eql_index = mem.indexOfScalar(u8, arg, '='); + + switch (kind) { + ArgInfo.Kind.Long => { + for (parser.params) |*param| { + const match = param.names.long orelse continue; + 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){ .param = param }; + } + + const value = blk: { + if (maybe_value) |v| + break :blk v; + + break :blk (try parser.iter.next()) orelse return error.MissingValue; + }; + + return Arg(Id){ .param = param, .value = value }; + } + }, + ArgInfo.Kind.Short => { + return try parser.chainging(State.Chaining{ + .arg = full_arg, + .index = (full_arg.len - arg.len), + }); + }, + ArgInfo.Kind.Positional => { + for (parser.params) |*param| { + if (param.names.long) |_| + continue; + if (param.names.short) |_| + continue; + + return Arg(Id){ .param = param, .value = arg }; + } + }, + } + + return error.InvalidArgument; + }, + .Chaining => |state| return try parser.chainging(state), + } + } + + fn chainging(parser: *@This(), state: State.Chaining) !?Arg(Id) { + const arg = state.arg; + const index = state.index; + const next_index = index + 1; + + for (parser.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) { + parser.state = State.Normal; + } else { + parser.state = State{ + .Chaining = State.Chaining{ + .arg = arg, + .index = next_index, + }, + }; + } + } + + if (!param.takes_value) + return Arg(Id){ .param = param }; + + if (arg.len <= next_index) { + const value = (try parser.iter.next()) orelse return error.MissingValue; + return Arg(Id){ .param = param, .value = value }; + } + + if (arg[next_index] == '=') + return Arg(Id){ .param = param, .value = arg[next_index + 1 ..] }; + + return Arg(Id){ .param = param, .value = arg[next_index..] }; + } + + return error.InvalidArgument; + } + }; +} + +fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, results: []const Arg(u8)) void { + var iter = args.SliceIterator{ .args = args_strings }; + var c = StreamingClap(u8, args.SliceIterator){ + .params = params, + .iter = &iter, + }; + + for (results) |res| { + const arg = (c.next() catch unreachable) orelse unreachable; + testing.expectEqual(res.param, arg.param); + const expected_value = res.value orelse { + testing.expectEqual(@typeOf(arg.value)(null), arg.value); + continue; + }; + const actual_value = arg.value orelse unreachable; + testing.expectEqualSlices(u8, expected_value, actual_value); + } + + if (c.next() catch unreachable) |_| + unreachable; +} + +test "clap.streaming.StreamingClap: short params" { + const params = [_]clap.Param(u8){ + clap.Param(u8){ + .id = 0, + .names = clap.Names{ .short = 'a' }, + }, + clap.Param(u8){ + .id = 1, + .names = clap.Names{ .short = 'b' }, + }, + clap.Param(u8){ + .id = 2, + .names = clap.Names{ .short = 'c' }, + .takes_value = true, + }, + }; + + 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", + }, + [_]Arg(u8){ + Arg(u8){ .param = a }, + Arg(u8){ .param = b }, + Arg(u8){ .param = a }, + Arg(u8){ .param = b }, + Arg(u8){ .param = b }, + Arg(u8){ .param = a }, + Arg(u8){ .param = c, .value = "0" }, + Arg(u8){ .param = c, .value = "0" }, + Arg(u8){ .param = a }, + Arg(u8){ .param = c, .value = "0" }, + Arg(u8){ .param = a }, + Arg(u8){ .param = c, .value = "0" }, + }, + ); +} + +test "clap.streaming.StreamingClap: long params" { + const params = [_]clap.Param(u8){ + clap.Param(u8){ + .id = 0, + .names = clap.Names{ .long = "aa" }, + }, + clap.Param(u8){ + .id = 1, + .names = clap.Names{ .long = "bb" }, + }, + clap.Param(u8){ + .id = 2, + .names = clap.Names{ .long = "cc" }, + .takes_value = true, + }, + }; + + const aa = ¶ms[0]; + const bb = ¶ms[1]; + const cc = ¶ms[2]; + + testNoErr( + params, + [_][]const u8{ + "--aa", "--bb", + "--cc", "0", + "--cc=0", + }, + [_]Arg(u8){ + Arg(u8){ .param = aa }, + Arg(u8){ .param = bb }, + Arg(u8){ .param = cc, .value = "0" }, + Arg(u8){ .param = cc, .value = "0" }, + }, + ); +} + +test "clap.streaming.StreamingClap: positional params" { + const params = [_]clap.Param(u8){clap.Param(u8){ + .id = 0, + .takes_value = true, + }}; + + testNoErr( + params, + [_][]const u8{ "aa", "bb" }, + [_]Arg(u8){ + Arg(u8){ .param = ¶ms[0], .value = "aa" }, + Arg(u8){ .param = ¶ms[0], .value = "bb" }, + }, + ); +} + +test "clap.streaming.StreamingClap: all params" { + const params = [_]clap.Param(u8){ + clap.Param(u8){ + .id = 0, + .names = clap.Names{ + .short = 'a', + .long = "aa", + }, + }, + clap.Param(u8){ + .id = 1, + .names = clap.Names{ + .short = 'b', + .long = "bb", + }, + }, + clap.Param(u8){ + .id = 2, + .names = clap.Names{ + .short = 'c', + .long = "cc", + }, + .takes_value = true, + }, + clap.Param(u8){ + .id = 3, + .takes_value = true, + }, + }; + + const aa = ¶ms[0]; + const bb = ¶ms[1]; + const cc = ¶ms[2]; + const positional = ¶ms[3]; + + testNoErr( + params, + [_][]const u8{ + "-a", "-b", "-ab", "-ba", + "-c", "0", "-c=0", "-ac", + "0", "-ac=0", "--aa", "--bb", + "--cc", "0", "--cc=0", "something", + "--", "-", + }, + [_]Arg(u8){ + Arg(u8){ .param = aa }, + Arg(u8){ .param = bb }, + Arg(u8){ .param = aa }, + Arg(u8){ .param = bb }, + Arg(u8){ .param = bb }, + Arg(u8){ .param = aa }, + Arg(u8){ .param = cc, .value = "0" }, + Arg(u8){ .param = cc, .value = "0" }, + Arg(u8){ .param = aa }, + Arg(u8){ .param = cc, .value = "0" }, + Arg(u8){ .param = aa }, + Arg(u8){ .param = cc, .value = "0" }, + Arg(u8){ .param = aa }, + Arg(u8){ .param = bb }, + Arg(u8){ .param = cc, .value = "0" }, + Arg(u8){ .param = cc, .value = "0" }, + Arg(u8){ .param = positional, .value = "something" }, + Arg(u8){ .param = positional, .value = "--" }, + Arg(u8){ .param = positional, .value = "-" }, + }, + ); +} -- cgit v1.2.3