summaryrefslogtreecommitdiff
path: root/clap.zig
diff options
context:
space:
mode:
authorGravatar Jimmi Holst Christensen2022-02-25 19:40:00 +0100
committerGravatar Jimmi Holst Christensen2022-02-25 19:40:00 +0100
commitcfaac64c404fb1c2e892880410aa3b7dd881ea58 (patch)
tree0dca149e43daaaef41f55fa61375ab361c36a39c /clap.zig
parentFix minor typos in README.md (diff)
downloadzig-clap-cfaac64c404fb1c2e892880410aa3b7dd881ea58.tar.gz
zig-clap-cfaac64c404fb1c2e892880410aa3b7dd881ea58.tar.xz
zig-clap-cfaac64c404fb1c2e892880410aa3b7dd881ea58.zip
Change clap into generating a struct
This changes - `.flag`, `.option`, `.options` and `.positionals` are now just fields you access. - Move the current `clap.parse` and friends into `clap.untyped.parse` - This is in preperation for `clap.typed.parse`
Diffstat (limited to 'clap.zig')
-rw-r--r--clap.zig265
1 files changed, 48 insertions, 217 deletions
diff --git a/clap.zig b/clap.zig
index 39bbef2..f10b61b 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 ComptimeClap = @import("clap/comptime.zig").ComptimeClap; 16pub const streaming = @import("clap/streaming.zig");
17pub const StreamingClap = @import("clap/streaming.zig").StreamingClap; 17pub const untyped = @import("clap/untyped.zig");
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,6 +23,23 @@ 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 };
26}; 43};
27 44
28/// Whether a param takes no value (a flag), one value, or can be specified multiple times. 45/// Whether a param takes no value (a flag), one value, or can be specified multiple times.
@@ -61,155 +78,6 @@ pub fn Param(comptime Id: type) type {
61 }; 78 };
62} 79}
63 80
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
213/// Optional diagnostics used for reporting useful errors 81/// Optional diagnostics used for reporting useful errors
214pub const Diagnostic = struct { 82pub const Diagnostic = struct {
215 arg: []const u8 = "", 83 arg: []const u8 = "",
@@ -306,7 +174,7 @@ test "Diagnostic.report" {
306pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type { 174pub fn Args(comptime Id: type, comptime params: []const Param(Id)) type {
307 return struct { 175 return struct {
308 arena: std.heap.ArenaAllocator, 176 arena: std.heap.ArenaAllocator,
309 clap: ComptimeClap(Id, params), 177 clap: untyped.Clap(Id, params),
310 exe_arg: ?[]const u8, 178 exe_arg: ?[]const u8,
311 179
312 pub fn deinit(a: *@This()) void { 180 pub fn deinit(a: *@This()) void {
@@ -342,43 +210,6 @@ pub const ParseOptions = struct {
342 diagnostic: ?*Diagnostic = null, 210 diagnostic: ?*Diagnostic = null,
343}; 211};
344 212
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
382/// Will print a help message in the following format: 213/// Will print a help message in the following format:
383/// -s, --long <valueText> helpText 214/// -s, --long <valueText> helpText
384/// -s, helpText 215/// -s, helpText
@@ -526,15 +357,15 @@ test "clap.help" {
526 try help( 357 try help(
527 slice_stream.writer(), 358 slice_stream.writer(),
528 comptime &.{ 359 comptime &.{
529 parseParam("-a Short flag.") catch unreachable, 360 untyped.parseParam("-a Short flag.") catch unreachable,
530 parseParam("-b <V1> Short option.") catch unreachable, 361 untyped.parseParam("-b <V1> Short option.") catch unreachable,
531 parseParam("--aa Long flag.") catch unreachable, 362 untyped.parseParam("--aa Long flag.") catch unreachable,
532 parseParam("--bb <V2> Long option.") catch unreachable, 363 untyped.parseParam("--bb <V2> Long option.") catch unreachable,
533 parseParam("-c, --cc Both flag.") catch unreachable, 364 untyped.parseParam("-c, --cc Both flag.") catch unreachable,
534 parseParam("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable, 365 untyped.parseParam("--complicate Flag with a complicated and\nvery long description that\nspans multiple lines.") catch unreachable,
535 parseParam("-d, --dd <V3> Both option.") catch unreachable, 366 untyped.parseParam("-d, --dd <V3> Both option.") catch unreachable,
536 parseParam("-d, --dd <V3>... Both repeated option.") catch unreachable, 367 untyped.parseParam("-d, --dd <V3>... Both repeated option.") catch unreachable,
537 parseParam( 368 untyped.parseParam(
538 "<P> Positional. This should not appear in the help message.", 369 "<P> Positional. This should not appear in the help message.",
539 ) catch unreachable, 370 ) catch unreachable,
540 }, 371 },
@@ -667,37 +498,37 @@ fn testUsage(expected: []const u8, params: []const Param(Help)) !void {
667test "usage" { 498test "usage" {
668 @setEvalBranchQuota(100000); 499 @setEvalBranchQuota(100000);
669 try testUsage("[-ab]", &.{ 500 try testUsage("[-ab]", &.{
670 try parseParam("-a"), 501 try untyped.parseParam("-a"),
671 try parseParam("-b"), 502 try untyped.parseParam("-b"),
672 }); 503 });
673 try testUsage("[-a <value>] [-b <v>]", &.{ 504 try testUsage("[-a <value>] [-b <v>]", &.{
674 try parseParam("-a <value>"), 505 try untyped.parseParam("-a <value>"),
675 try parseParam("-b <v>"), 506 try untyped.parseParam("-b <v>"),
676 }); 507 });
677 try testUsage("[--a] [--b]", &.{ 508 try testUsage("[--a] [--b]", &.{
678 try parseParam("--a"), 509 try untyped.parseParam("--a"),
679 try parseParam("--b"), 510 try untyped.parseParam("--b"),
680 }); 511 });
681 try testUsage("[--a <value>] [--b <v>]", &.{ 512 try testUsage("[--a <value>] [--b <v>]", &.{
682 try parseParam("--a <value>"), 513 try untyped.parseParam("--a <value>"),
683 try parseParam("--b <v>"), 514 try untyped.parseParam("--b <v>"),
684 }); 515 });
685 try testUsage("<file>", &.{ 516 try testUsage("<file>", &.{
686 try parseParam("<file>"), 517 try untyped.parseParam("<file>"),
687 }); 518 });
688 try testUsage( 519 try testUsage(
689 "[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>", 520 "[-ab] [-c <value>] [-d <v>] [--e] [--f] [--g <value>] [--h <v>] [-i <v>...] <file>",
690 &.{ 521 &.{
691 try parseParam("-a"), 522 try untyped.parseParam("-a"),
692 try parseParam("-b"), 523 try untyped.parseParam("-b"),
693 try parseParam("-c <value>"), 524 try untyped.parseParam("-c <value>"),
694 try parseParam("-d <v>"), 525 try untyped.parseParam("-d <v>"),
695 try parseParam("--e"), 526 try untyped.parseParam("--e"),
696 try parseParam("--f"), 527 try untyped.parseParam("--f"),
697 try parseParam("--g <value>"), 528 try untyped.parseParam("--g <value>"),
698 try parseParam("--h <v>"), 529 try untyped.parseParam("--h <v>"),
699 try parseParam("-i <v>..."), 530 try untyped.parseParam("-i <v>..."),
700 try parseParam("<file>"), 531 try untyped.parseParam("<file>"),
701 }, 532 },
702 ); 533 );
703} 534}