summaryrefslogtreecommitdiff
path: root/clap.zig
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2022-02-25 19:44:52 +0100
committerGravatar Jimmi Holst Christensen2022-02-25 19:44:52 +0100
commitc06c93608cb3befe77c78ba25c70b14db6f7b319 (patch)
tree3598bbd00512eb5baa6da004ae7e42cd8005c3eb /clap.zig
parentChange clap into generating a struct (diff)
downloadzig-clap-c06c93608cb3befe77c78ba25c70b14db6f7b319.tar.gz
zig-clap-c06c93608cb3befe77c78ba25c70b14db6f7b319.tar.xz
zig-clap-c06c93608cb3befe77c78ba25c70b14db6f7b319.zip
Revert "Change clap into generating a struct"
This reverts commit cfaac64c404fb1c2e892880410aa3b7dd881ea58.
Diffstat (limited to '')
-rw-r--r--clap.zig265
1 files changed, 217 insertions, 48 deletions
diff --git a/clap.zig b/clap.zig
index f10b61b..39bbef2 100644
--- a/clap.zig
+++ b/clap.zig
@@ -13,8 +13,8 @@ test "clap" {
13 testing.refAllDecls(@This()); 13 testing.refAllDecls(@This());
14} 14}
15 15
16pub const streaming = @import("clap/streaming.zig"); 16pub const ComptimeClap = @import("clap/comptime.zig").ComptimeClap;
17pub const untyped = @import("clap/untyped.zig"); 17pub const StreamingClap = @import("clap/streaming.zig").StreamingClap;
18 18
19/// The names a ::Param can have. 19/// The names a ::Param can have.
20pub const Names = struct { 20pub const Names = struct {
@@ -23,23 +23,6 @@ pub const Names = struct {
23 23
24 /// '--' prefix 24 /// '--' prefix
25 long: ?[]const u8 = null, 25 long: ?[]const u8 = null,
26
27 pub fn longest(names: *const Names) Longest {
28 if (names.long) |long|
29 return .{ .kind = .long, .name = long };
30 if (names.short) |*short| {
31 // TODO: Zig cannot figure out @as(*const [1]u8, short) in the ano literal
32 const casted: *const [1]u8 = short;
33 return .{ .kind = .short, .name = casted };
34 }
35
36 return .{ .kind = .positinal, .name = "" };
37 }
38
39 pub const Longest = struct {
40 kind: enum { long, short, positinal },
41 name: []const u8,
42 };
43}; 26};
44 27
45/// Whether a param takes no value (a flag), one value, or can be specified multiple times. 28/// Whether a param takes no value (a flag), one value, or can be specified multiple times.
@@ -78,6 +61,155 @@ pub fn Param(comptime Id: type) type {
78 }; 61 };
79} 62}
80 63
64/// Takes a string and parses it to a Param(Help).
65/// This is the reverse of 'help' but for at single parameter only.
66pub fn parseParam(line: []const u8) !Param(Help) {
67 // This function become a lot less ergonomic to use once you hit the eval branch quota. To
68 // avoid this we pick a sane default. Sadly, the only sane default is the biggest possible
69 // value. If we pick something a lot smaller and a user hits the quota after that, they have
70 // no way of overriding it, since we set it here.
71 // We can recosider this again if:
72 // * We get parseParams: https://github.com/Hejsil/zig-clap/issues/39
73 // * We get a larger default branch quota in the zig compiler (stage 2).
74 // * Someone points out how this is a really bad idea.
75 @setEvalBranchQuota(std.math.maxInt(u32));
76
77 var found_comma = false;
78 var it = mem.tokenize(u8, line, " \t");
79 var param_str = it.next() orelse return error.NoParamFound;
80
81 const short_name = if (!mem.startsWith(u8, param_str, "--") and
82 mem.startsWith(u8, param_str, "-"))
83 blk: {
84 found_comma = param_str[param_str.len - 1] == ',';
85 if (found_comma)
86 param_str = param_str[0 .. param_str.len - 1];
87
88 if (param_str.len != 2)
89 return error.InvalidShortParam;
90
91 const short_name = param_str[1];
92 if (!found_comma) {
93 var res = parseParamRest(it.rest());
94 res.names.short = short_name;
95 return res;
96 }
97
98 param_str = it.next() orelse return error.NoParamFound;
99 break :blk short_name;
100 } else null;
101
102 const long_name = if (mem.startsWith(u8, param_str, "--")) blk: {
103 if (param_str[param_str.len - 1] == ',')
104 return error.TrailingComma;
105
106 break :blk param_str[2..];
107 } else if (found_comma) {
108 return error.TrailingComma;
109 } else if (short_name == null) {
110 return parseParamRest(mem.trimLeft(u8, line, " \t"));
111 } else null;
112
113 var res = parseParamRest(it.rest());
114 res.names.long = long_name;
115 res.names.short = short_name;
116 return res;
117}
118
119fn parseParamRest(line: []const u8) Param(Help) {
120 if (mem.startsWith(u8, line, "<")) blk: {
121 const len = mem.indexOfScalar(u8, line, '>') orelse break :blk;
122 const takes_many = mem.startsWith(u8, line[len + 1 ..], "...");
123 const help_start = len + 1 + @as(usize, 3) * @boolToInt(takes_many);
124 return .{
125 .takes_value = if (takes_many) .many else .one,
126 .id = .{
127 .msg = mem.trim(u8, line[help_start..], " \t"),
128 .value = line[1..len],
129 },
130 };
131 }
132
133 return .{ .id = .{ .msg = mem.trim(u8, line, " \t") } };
134}
135
136fn expectParam(expect: Param(Help), actual: Param(Help)) !void {
137 try testing.expectEqualStrings(expect.id.msg, actual.id.msg);
138 try testing.expectEqualStrings(expect.id.value, actual.id.value);
139 try testing.expectEqual(expect.names.short, actual.names.short);
140 try testing.expectEqual(expect.takes_value, actual.takes_value);
141 if (expect.names.long) |long| {
142 try testing.expectEqualStrings(long, actual.names.long.?);
143 } else {
144 try testing.expectEqual(@as(?[]const u8, null), actual.names.long);
145 }
146}
147
148test "parseParam" {
149 try expectParam(Param(Help){
150 .id = .{ .msg = "Help text", .value = "value" },
151 .names = .{ .short = 's', .long = "long" },
152 .takes_value = .one,
153 }, try parseParam("-s, --long <value> Help text"));
154
155 try expectParam(Param(Help){
156 .id = .{ .msg = "Help text", .value = "value" },
157 .names = .{ .short = 's', .long = "long" },
158 .takes_value = .many,
159 }, try parseParam("-s, --long <value>... Help text"));
160
161 try expectParam(Param(Help){
162 .id = .{ .msg = "Help text", .value = "value" },
163 .names = .{ .long = "long" },
164 .takes_value = .one,
165 }, try parseParam("--long <value> Help text"));
166
167 try expectParam(Param(Help){
168 .id = .{ .msg = "Help text", .value = "value" },
169 .names = .{ .short = 's' },
170 .takes_value = .one,
171 }, try parseParam("-s <value> Help text"));
172
173 try expectParam(Param(Help){
174 .id = .{ .msg = "Help text" },
175 .names = .{ .short = 's', .long = "long" },
176 }, try parseParam("-s, --long Help text"));
177
178 try expectParam(Param(Help){
179 .id = .{ .msg = "Help text" },
180 .names = .{ .short = 's' },
181 }, try parseParam("-s Help text"));
182
183 try expectParam(Param(Help){
184 .id = .{ .msg = "Help text" },
185 .names = .{ .long = "long" },
186 }, try parseParam("--long Help text"));
187
188 try expectParam(Param(Help){
189 .id = .{ .msg = "Help text", .value = "A | B" },
190 .names = .{ .long = "long" },
191 .takes_value = .one,
192 }, try parseParam("--long <A | B> Help text"));
193
194 try expectParam(Param(Help){
195 .id = .{ .msg = "Help text", .value = "A" },
196 .names = .{},
197 .takes_value = .one,
198 }, try parseParam("<A> Help text"));
199
200 try expectParam(Param(Help){
201 .id = .{ .msg = "Help text", .value = "A" },
202 .names = .{},
203 .takes_value = .many,
204 }, try parseParam("<A>... Help text"));
205
206 try testing.expectError(error.TrailingComma, parseParam("--long, Help"));
207 try testing.expectError(error.TrailingComma, parseParam("-s, Help"));
208 try testing.expectError(error.InvalidShortParam, parseParam("-ss Help"));
209 try testing.expectError(error.InvalidShortParam, parseParam("-ss <value> Help"));
210 try testing.expectError(error.InvalidShortParam, parseParam("- Help"));
211}
212
81/// Optional diagnostics used for reporting useful errors 213/// Optional diagnostics used for reporting useful errors
82pub const Diagnostic = struct { 214pub const Diagnostic = struct {
83 arg: []const u8 = "", 215 arg: []const u8 = "",
@@ -174,7 +306,7 @@ test "Diagnostic.report" {
174pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { 306pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type {
175 return struct { 307 return struct {
176 arena: std.heap.ArenaAllocator, 308 arena: std.heap.ArenaAllocator,
177 clap: untyped.Clap(Id, params), 309 clap: ComptimeClap(Id, params),
178 exe_arg: ?[]const u8, 310 exe_arg: ?[]const u8,
179 311
180 pub fn deinit(a: *@This()) void { 312 pub fn deinit(a: *@This()) void {
@@ -210,6 +342,43 @@ pub const ParseOptions = struct {
210 diagnostic: ?*Diagnostic = null, 342 diagnostic: ?*Diagnostic = null,
211}; 343};
212 344
345/// Same as `parseEx` but uses the `args.OsIterator` by default.
346pub fn parse(
347 comptime Id: type,
348 comptime params: []const Param(Id),
349 opt: ParseOptions,
350) !Args(Id, params) {
351 var arena = heap.ArenaAllocator.init(opt.allocator);
352 errdefer arena.deinit();
353
354 var iter = try process.ArgIterator.initWithAllocator(arena.allocator());
355 const exe_arg = iter.next();
356
357 const clap = try parseEx(Id, params, &iter, .{
358 // Let's reuse the arena from the `OSIterator` since we already have it.
359 .allocator = arena.allocator(),
360 .diagnostic = opt.diagnostic,
361 });
362
363 return Args(Id, params){
364 .exe_arg = exe_arg,
365 .arena = arena,
366 .clap = clap,
367 };
368}
369
370/// Parses the command line arguments passed into the program based on an
371/// array of `Param`s.
372pub fn parseEx(
373 comptime Id: type,
374 comptime params: []const Param(Id),
375 iter: anytype,
376 opt: ParseOptions,
377) !ComptimeClap(Id, params) {
378 const Clap = ComptimeClap(Id, params);
379 return try Clap.parse(iter, opt);
380}
381
213/// Will print a help message in the following format: 382/// Will print a help message in the following format:
214/// -s, --long <valueText> helpText 383/// -s, --long <valueText> helpText
215/// -s, helpText 384/// -s, helpText
@@ -357,15 +526,15 @@ test "clap.help" {
357 try help( 526 try help(
358 slice_stream.writer(), 527 slice_stream.writer(),
359 comptime &.{ 528 comptime &.{
360 untyped.parseParam("-a Short flag.") catch unreachable, 529 parseParam("-a Short flag.") catch unreachable,
361 untyped.parseParam("-b <V1> Short option.") catch unreachable, 530 parseParam("-b <V1> Short option.") catch unreachable,
362 untyped.parseParam("--aa Long flag.") catch unreachable, 531 parseParam("--aa Long flag.") catch unreachable,
363 untyped.parseParam("--bb <V2> Long option.") catch unreachable, 532 parseParam("--bb <V2> Long option.") catch unreachable,
364 untyped.parseParam("-c, --cc Both flag.") catch unreachable, 533 parseParam("-c, --cc Both flag.") catch unreachable,
365 untyped.parseParam("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable, 534 parseParam("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable,
366 untyped.parseParam("-d, --dd <V3> Both option.") catch unreachable, 535 parseParam("-d, --dd <V3> Both option.") catch unreachable,
367 untyped.parseParam("-d, --dd <V3>... Both repeated option.") catch unreachable, 536 parseParam("-d, --dd <V3>... Both repeated option.") catch unreachable,
368 untyped.parseParam( 537 parseParam(
369 "<P> Positional. This should not appear in the help message.", 538 "<P> Positional. This should not appear in the help message.",
370 ) catch unreachable, 539 ) catch unreachable,
371 }, 540 },
@@ -498,37 +667,37 @@ fn testUsage(expected: []const u8, params: []const Param(Help)) !void {
498test "usage" { 667test "usage" {
499 @setEvalBranchQuota(100000); 668 @setEvalBranchQuota(100000);
500 try testUsage("[-ab]", &.{ 669 try testUsage("[-ab]", &.{
501 try untyped.parseParam("-a"), 670 try parseParam("-a"),
502 try untyped.parseParam("-b"), 671 try parseParam("-b"),
503 }); 672 });
504 try testUsage("[-a <value>] [-b <v>]", &.{ 673 try testUsage("[-a <value>] [-b <v>]", &.{
505 try untyped.parseParam("-a <value>"), 674 try parseParam("-a <value>"),
506 try untyped.parseParam("-b <v>"), 675 try parseParam("-b <v>"),
507 }); 676 });
508 try testUsage("[--a] [--b]", &.{ 677 try testUsage("[--a] [--b]", &.{
509 try untyped.parseParam("--a"), 678 try parseParam("--a"),
510 try untyped.parseParam("--b"), 679 try parseParam("--b"),
511 }); 680 });
512 try testUsage("[--a <value>] [--b <v>]", &.{ 681 try testUsage("[--a <value>] [--b <v>]", &.{
513 try untyped.parseParam("--a <value>"), 682 try parseParam("--a <value>"),
514 try untyped.parseParam("--b <v>"), 683 try parseParam("--b <v>"),
515 }); 684 });
516 try testUsage("<file>", &.{ 685 try testUsage("<file>", &.{
517 try untyped.parseParam("<file>"), 686 try parseParam("<file>"),
518 }); 687 });
519 try testUsage( 688 try testUsage(
520 "[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", 689 "[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>",
521 &.{ 690 &.{
522 try untyped.parseParam("-a"), 691 try parseParam("-a"),
523 try untyped.parseParam("-b"), 692 try parseParam("-b"),
524 try untyped.parseParam("-c <value>"), 693 try parseParam("-c <value>"),
525 try untyped.parseParam("-d <v>"), 694 try parseParam("-d <v>"),
526 try untyped.parseParam("--e"), 695 try parseParam("--e"),
527 try untyped.parseParam("--f"), 696 try parseParam("--f"),
528 try untyped.parseParam("--g <value>"), 697 try parseParam("--g <value>"),
529 try untyped.parseParam("--h <v>"), 698 try parseParam("--h <v>"),
530 try untyped.parseParam("-i <v>..."), 699 try parseParam("-i <v>..."),
531 try untyped.parseParam("<file>"), 700 try parseParam("<file>"),
532 }, 701 },
533 ); 702 );
534} 703}