From 56e7be2835311888ef43f403e5d6bc2118c953fe Mon Sep 17 00:00:00 2001 From: Jimmi HC Date: Fri, 21 Jun 2019 19:15:32 +0200 Subject: Embed examples in README during build fixes #11 --- README.md | 237 +++++++++++++++++++++++++++------------- build.zig | 27 +++++ clap.zig | 10 +- example/README.md.template | 77 +++++++++++++ example/comptime-clap-error.zig | 21 ++++ example/comptime-clap.zig | 7 +- example/help.zig | 25 +++++ src/comptime.zig | 19 +++- src/streaming.zig | 30 +++-- 9 files changed, 346 insertions(+), 107 deletions(-) create mode 100644 example/README.md.template create mode 100644 example/comptime-clap-error.zig create mode 100644 example/help.zig diff --git a/README.md b/README.md index 8081d84..c959009 100644 --- a/README.md +++ b/README.md @@ -18,27 +18,64 @@ A simple and easy to use command line argument parser library for Zig. The `StreamingClap` is the base of all the other parsers. It's a streaming parser that uses an `args.Iterator` to provide it with arguments lazily. -```rust -const params = []clap.Param(u8){ - clap.Param(u8).flag('h', clap.Names.both("help")), - clap.Param(u8).option('n', clap.Names.both("number")), - clap.Param(u8).positional('f'), -}; - -var iter = clap.args.OsIterator.init(allocator); -defer iter.deinit(); -const exe = try iter.next(); - -var parser = clap.StreamingClap(u8, clap.args.OsIterator).init(params, &iter); - -while (try parser.next()) |arg| { - switch (arg.param.id) { - 'h' => debug.warn("Help!\n"), - 'n' => debug.warn("--number = {}\n", arg.value.?), - 'f' => debug.warn("{}\n", arg.value.?), - else => unreachable, +```zig +const std = @import("std"); +const clap = @import("clap"); + +const debug = std.debug; + +pub fn main() !void { + var direct_allocator = std.heap.DirectAllocator.init(); + const allocator = &direct_allocator.allocator; + defer direct_allocator.deinit(); + + // First we specify what parameters our program can take. + const params = [_]clap.Param(u8){ + clap.Param(u8){ + .id = 'h', + .names = clap.Names{ .short = 'h', .long = "help" }, + }, + clap.Param(u8){ + .id = 'n', + .names = clap.Names{ .short = 'n', .long = "number" }, + .takes_value = true, + }, + clap.Param(u8){ + .id = 'f', + .takes_value = true, + }, + }; + + // We then initialize an argument iterator. We will use the OsIterator as it nicely + // wraps iterating over arguments the most efficient way on each os. + var iter = clap.args.OsIterator.init(allocator); + defer iter.deinit(); + + // Consume the exe arg. + const exe = try iter.next(); + + // Finally we initialize our streaming parser. + var parser = clap.StreamingClap(u8, clap.args.OsIterator){ + .params = params, + .iter = &iter, + }; + + // Because we use a streaming parser, we have to consume each argument parsed individually. + while (try parser.next()) |arg| { + // arg.param will point to the parameter which matched the argument. + switch (arg.param.id) { + 'h' => debug.warn("Help!\n"), + 'n' => debug.warn("--number = {}\n", arg.value.?), + + // arg.value == null, if arg.param.takes_value == false. + // Otherwise, arg.value is the value passed with the argument, such as "-a=10" + // or "-a 10". + 'f' => debug.warn("{}\n", arg.value.?), + else => unreachable, + } } } + ``` ### `ComptimeClap` @@ -46,57 +83,100 @@ while (try parser.next()) |arg| { The `ComptimeClap` is a wrapper for `StreamingClap`, which parses all the arguments and makes them available through three functions (`flag`, `option`, `positionals`). -```rust -const params = comptime []clap.Param(void){ - clap.Param(void).flag({}, clap.Names.both("help")), - clap.Param(void).option({}, clap.Names.both("number")), - clap.Param(void).positional({}), -}; - -var iter = clap.args.OsIterator.init(allocator); -defer iter.deinit(); -const exe = try iter.next(); - -var args = try clap.ComptimeClap(void, params).parse(allocator, clap.args.OsIterator, &iter); -defer args.deinit(); - -if (args.flag("--help")) - debug.warn("Help!\n"); -if (args.option("--number")) |n| - debug.warn("--number = {}\n", n); -for (args.positionals()) |pos| - debug.warn("{}\n", pos); +```zig +const std = @import("std"); +const clap = @import("clap"); + +const debug = std.debug; + +pub fn main() !void { + const stdout_file = try std.io.getStdOut(); + var stdout_out_stream = stdout_file.outStream(); + const stdout = &stdout_out_stream.stream; + + var direct_allocator = std.heap.DirectAllocator.init(); + const allocator = &direct_allocator.allocator; + defer direct_allocator.deinit(); + + // First we specify what parameters our program can take. + const params = [_]clap.Param([]const u8){ + clap.Param([]const u8){ + .id = "Display this help and exit.", + .names = clap.Names{ .short = 'h', .long = "help" }, + }, + clap.Param([]const u8){ + .id = "An option parameter, which takes a value.", + .names = clap.Names{ .short = 'n', .long = "number" }, + .takes_value = true, + }, + clap.Param([]const u8){ + .id = "", + .takes_value = true, + }, + }; + + // We then initialize an argument iterator. We will use the OsIterator as it nicely + // wraps iterating over arguments the most efficient way on each os. + var iter = clap.args.OsIterator.init(allocator); + defer iter.deinit(); + + // Consume the exe arg. + const exe = try iter.next(); + + // Finally we can parse the arguments + var args = try clap.ComptimeClap([]const u8, params).parse(allocator, clap.args.OsIterator, &iter); + defer args.deinit(); + + if (args.flag("--help")) + debug.warn("--help\n"); + if (args.option("--number")) |n| + debug.warn("--number = {}\n", n); + for (args.positionals()) |pos| + debug.warn("{}\n", pos); +} + ``` The data structure returned from this parser has lookup speed on par with array access (`arr[i]`) and validates that the strings you pass to `option` and `flag` are actually parameters that the program can take: -```rust -const params = comptime []clap.Param(void){ - clap.Param(void).flag({}, clap.Names.both("help")), -}; +```zig +const std = @import("std"); +const clap = @import("clap"); + +pub fn main() !void { + const params = [_]clap.Param(void){ + clap.Param(void){ + .names = clap.Names{ .short = 'h', .long = "help" } + }, + }; + + var direct_allocator = std.heap.DirectAllocator.init(); + const allocator = &direct_allocator.allocator; + defer direct_allocator.deinit(); -var iter = clap.args.OsIterator.init(allocator); -defer iter.deinit(); -const exe = try iter.next(); + var iter = clap.args.OsIterator.init(allocator); + defer iter.deinit(); + const exe = try iter.next(); -var args = try clap.ComptimeClap(void, params).parse(allocator, clap.args.OsIterator, &iter); -defer args.deinit(); + var args = try clap.ComptimeClap(void, params).parse(allocator, clap.args.OsIterator, &iter); + defer args.deinit(); + + _ = args.flag("--helps"); +} -if (args.flag("--helps")) - debug.warn("Help!\n"); ``` ``` -zig-clap/src/comptime.zig:103:17: error: --helps is not a parameter. +zig-clap/src/comptime.zig:116:17: error: --helps is not a parameter. @compileError(name ++ " is not a parameter."); ^ -zig-clap/src/comptime.zig:71:45: note: called from here +zig-clap/src/comptime.zig:84:45: note: called from here const param = comptime findParam(name); ^ -zig-clap/example/comptime-clap.zig:41:18: note: called from here - if (args.flag("--helps")) +zig-clap/example/comptime-clap-error.zig:22:18: note: called from here + _ = args.flag("--helps"); ^ ``` @@ -107,29 +187,38 @@ Ofc, this limits you to parameters that are comptime known. The `help`, `helpEx` and `helpFull` are functions for printing a simple list of all parameters the program can take. -```rust -const stderr_file = try std.io.getStdErr(); -var stderr_out_stream = stderr_file.outStream(); -const stderr = &stderr_out_stream.stream; - -try clap.help( - stderr, - []clap.Param([]const u8){ - clap.Param([]const u8).flag( - "Display this help and exit.", - clap.Names.both("help"), - ), - clap.Param([]const u8).flag( - "Output version information and exit.", - clap.Names.both("version"), - ), - }, -); +```zig +const std = @import("std"); +const clap = @import("clap"); + +pub fn main() !void { + const stderr_file = try std.io.getStdErr(); + var stderr_out_stream = stderr_file.outStream(); + const stderr = &stderr_out_stream.stream; + + // clap.help is a function that can print a simple help message, given a + // slice of Param([]const u8). There is also a helpEx, which can print a + // help message for any Param, but it is more verbose to call. + try clap.help( + stderr, + [_]clap.Param([]const u8){ + clap.Param([]const u8){ + .id = "Display this help and exit.", + .names = clap.Names{ .short = 'h', .long = "help" } + }, + clap.Param([]const u8){ + .id = "Output version information and exit.", + .names = clap.Names{ .short = 'v', .long = "version" } + }, + }, + ); +} + ``` ``` - -h, --help Display this help and exit. - -v, --version Output version information and exit. + -h, --help Display this help and exit. + -v, --version Output version information and exit. ``` The `help` function is the simplest to call. It only takes an `OutStream` and a slice of diff --git a/build.zig b/build.zig index cbf8214..ec8579b 100644 --- a/build.zig +++ b/build.zig @@ -10,7 +10,9 @@ pub fn build(b: *Builder) void { const example_step = b.step("examples", "Build examples"); inline for ([_][]const u8{ "comptime-clap", + //"comptime-clap-error", "streaming-clap", + "help", }) |example_name| { const example = b.addExecutable(example_name, "example/" ++ example_name ++ ".zig"); example.addPackagePath("clap", "clap.zig"); @@ -31,13 +33,38 @@ pub fn build(b: *Builder) void { test_all_step.dependOn(test_step); } + const readme_step = b.step("test", "Remake README."); + const readme = readMeStep(b); + readme.dependOn(example_step); + readme_step.dependOn(readme); + const all_step = b.step("all", "Build everything and runs all tests"); all_step.dependOn(test_all_step); all_step.dependOn(example_step); + all_step.dependOn(readme_step); b.default_step.dependOn(all_step); } +fn readMeStep(b: *Builder) *std.build.Step { + const s = b.allocator.create(std.build.Step) catch unreachable; + s.* = std.build.Step.init("ReadMeStep", b.allocator, struct { + fn make(step: *std.build.Step) anyerror!void { + @setEvalBranchQuota(10000); + const file = try std.fs.File.openWrite("README.md"); + const stream = &file.outStream().stream; + try stream.print( + @embedFile("example/README.md.template"), + @embedFile("example/streaming-clap.zig"), + @embedFile("example/comptime-clap.zig"), + @embedFile("example/comptime-clap-error.zig"), + @embedFile("example/help.zig"), + ); + } + }.make); + return s; +} + fn modeToString(mode: Mode) []const u8 { return switch (mode) { Mode.Debug => "debug", diff --git a/clap.zig b/clap.zig index 8823b59..c16570f 100644 --- a/clap.zig +++ b/clap.zig @@ -4,18 +4,16 @@ const debug = std.debug; const io = std.io; const mem = std.mem; -pub const @"comptime" = @import("src/comptime.zig"); pub const args = @import("src/args.zig"); -pub const streaming = @import("src/streaming.zig"); test "clap" { - _ = @"comptime"; _ = args; - _ = streaming; + _ = ComptimeClap; + _ = StreamingClap; } -pub const ComptimeClap = @"comptime".ComptimeClap; -pub const StreamingClap = streaming.StreamingClap; +pub const ComptimeClap = @import("src/comptime.zig").ComptimeClap; +pub const StreamingClap = @import("src/streaming.zig").StreamingClap; /// The names a ::Param can have. pub const Names = struct { diff --git a/example/README.md.template b/example/README.md.template new file mode 100644 index 0000000..88914fb --- /dev/null +++ b/example/README.md.template @@ -0,0 +1,77 @@ +# zig-clap + +A simple and easy to use command line argument parser library for Zig. + +## Features + +* Short arguments `-a` + * Chaining `-abc` where `a` and `b` does not take values. +* Long arguments `--long` +* Supports both passing values using spacing and `=` (`-a 100`, `-a=100`) + * Short args also support passing values with no spacing or `=` (`-a100`) + * This all works with chaining (`-ba 100`, `-ba=100`, `-ba100`) + +## Examples + +### `StreamingClap` + +The `StreamingClap` 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 +{} +``` + +### `ComptimeClap` + +The `ComptimeClap` is a wrapper for `StreamingClap`, which parses all the arguments and makes +them available through three functions (`flag`, `option`, `positionals`). + +```zig +{} +``` + +The data structure returned from this parser has lookup speed on par with array access (`arr[i]`) +and validates that the strings you pass to `option` and `flag` are actually parameters that the +program can take: + +```zig +{} +``` + +``` +zig-clap/src/comptime.zig:116:17: error: --helps is not a parameter. + @compileError(name ++ " is not a parameter."); + ^ +zig-clap/src/comptime.zig:84:45: note: called from here + const param = comptime findParam(name); + ^ +zig-clap/example/comptime-clap-error.zig:22:18: note: called from here + _ = args.flag("--helps"); + ^ +``` + +Ofc, this limits you to parameters that are comptime known. + +### `help` + +The `help`, `helpEx` and `helpFull` are functions for printing a simple list of all parameters the +program can take. + +```zig +{} +``` + +``` + -h, --help Display this help and exit. + -v, --version Output version information and exit. +``` + +The `help` function is the simplest to call. It only takes an `OutStream` and a slice of +`Param([]const u8)`. This function assumes that the id of each parameter is the help message. + +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. diff --git a/example/comptime-clap-error.zig b/example/comptime-clap-error.zig new file mode 100644 index 0000000..93c1af2 --- /dev/null +++ b/example/comptime-clap-error.zig @@ -0,0 +1,21 @@ +const std = @import("std"); +const clap = @import("clap"); + +pub fn main() !void { + const params = [_]clap.Param(void){clap.Param(void){ + .names = clap.Names{ .short = 'h', .long = "help" }, + }}; + + var direct_allocator = std.heap.DirectAllocator.init(); + const allocator = &direct_allocator.allocator; + defer direct_allocator.deinit(); + + var iter = clap.args.OsIterator.init(allocator); + defer iter.deinit(); + const exe = try iter.next(); + + var args = try clap.ComptimeClap(void, params).parse(allocator, clap.args.OsIterator, &iter); + defer args.deinit(); + + _ = args.flag("--helps"); +} diff --git a/example/comptime-clap.zig b/example/comptime-clap.zig index 935381f..695fa62 100644 --- a/example/comptime-clap.zig +++ b/example/comptime-clap.zig @@ -13,7 +13,7 @@ pub fn main() !void { defer direct_allocator.deinit(); // First we specify what parameters our program can take. - const params = comptime [_]clap.Param([]const u8){ + const params = [_]clap.Param([]const u8){ clap.Param([]const u8){ .id = "Display this help and exit.", .names = clap.Names{ .short = 'h', .long = "help" }, @@ -41,11 +41,8 @@ pub fn main() !void { var args = try clap.ComptimeClap([]const u8, params).parse(allocator, clap.args.OsIterator, &iter); defer args.deinit(); - // clap.help is a function that can print a simple help message, given a - // slice of Param([]const u8). 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 try clap.help(stdout, params); + debug.warn("--help\n"); if (args.option("--number")) |n| debug.warn("--number = {}\n", n); for (args.positionals()) |pos| diff --git a/example/help.zig b/example/help.zig new file mode 100644 index 0000000..35c0258 --- /dev/null +++ b/example/help.zig @@ -0,0 +1,25 @@ +const std = @import("std"); +const clap = @import("clap"); + +pub fn main() !void { + const stderr_file = try std.io.getStdErr(); + var stderr_out_stream = stderr_file.outStream(); + const stderr = &stderr_out_stream.stream; + + // clap.help is a function that can print a simple help message, given a + // slice of Param([]const u8). There is also a helpEx, which can print a + // help message for any Param, but it is more verbose to call. + try clap.help( + stderr, + [_]clap.Param([]const u8){ + clap.Param([]const u8){ + .id = "Display this help and exit.", + .names = clap.Names{ .short = 'h', .long = "help" }, + }, + clap.Param([]const u8){ + .id = "Output version information and exit.", + .names = clap.Names{ .short = 'v', .long = "version" }, + }, + }, + ); +} diff --git a/src/comptime.zig b/src/comptime.zig index b585598..d872b82 100644 --- a/src/comptime.zig +++ b/src/comptime.zig @@ -4,6 +4,7 @@ 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; @@ -56,11 +57,17 @@ pub fn ComptimeClap(comptime Id: type, comptime params: []const clap.Param(Id)) if (param.names.long == null and param.names.short == null) { try pos.append(arg.value.?); } else if (param.takes_value) { - // We slice before access to avoid false positive access out of bound - // compile error. - res.options[0..][param.id] = arg.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 { - res.flags[0..][param.id] = true; + debug.assert(res.flags.len != 0); + if (res.flags.len != 0) + res.flags[param.id] = true; } } @@ -118,13 +125,13 @@ test "clap.comptime.ComptimeClap" { .names = clap.Names{ .short = 'a', .long = "aa", - } + }, }, clap.Param(void){ .names = clap.Names{ .short = 'b', .long = "bb", - } + }, }, clap.Param(void){ .names = clap.Names{ diff --git a/src/streaming.zig b/src/streaming.zig index 9da120c..d23471c 100644 --- a/src/streaming.zig +++ b/src/streaming.zig @@ -174,7 +174,7 @@ fn testNoErr(params: []const clap.Param(u8), args_strings: []const []const u8, r var iter = args.SliceIterator{ .args = args_strings }; var c = StreamingClap(u8, args.SliceIterator){ .params = params, - .iter = &iter + .iter = &iter, }; for (results) |res| { @@ -217,9 +217,9 @@ test "clap.streaming.StreamingClap: short params" { testNoErr( params, [_][]const u8{ - "-a", "-b", "-ab", "-ba", - "-c", "0", "-c=0", "-ac", - "0", "-ac=0", + "-a", "-b", "-ab", "-ba", + "-c", "0", "-c=0", "-ac", + "0", "-ac=0", }, [_]Arg(u8){ Arg(u8){ .param = a }, @@ -262,8 +262,8 @@ test "clap.streaming.StreamingClap: long params" { testNoErr( params, [_][]const u8{ - "--aa", "--bb", - "--cc", "0", + "--aa", "--bb", + "--cc", "0", "--cc=0", }, [_]Arg(u8){ @@ -276,12 +276,10 @@ test "clap.streaming.StreamingClap: long params" { } test "clap.streaming.StreamingClap: positional params" { - const params = [_]clap.Param(u8){ - clap.Param(u8){ - .id = 0, - .takes_value = true, - }, - }; + const params = [_]clap.Param(u8){clap.Param(u8){ + .id = 0, + .takes_value = true, + }}; testNoErr( params, @@ -331,10 +329,10 @@ test "clap.streaming.StreamingClap: all params" { testNoErr( params, [_][]const u8{ - "-a", "-b", "-ab", "-ba", - "-c", "0", "-c=0", "-ac", - "0", "-ac=0", "--aa", "--bb", - "--cc", "0", "--cc=0", "something", + "-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 }, -- cgit v1.2.3