summaryrefslogtreecommitdiff
path: root/clap.zig
diff options
context:
space:
mode:
authorGravatar Jimmi HC2019-06-12 15:30:30 +0200
committerGravatar Jimmi HC2019-06-12 15:30:30 +0200
commit44ea38f25453fbf51de72505d7b3ddb0f3eec13c (patch)
tree2ffd0fc94f65270f9f94edab940641b388ff3f16 /clap.zig
parenttried to fix travis (diff)
downloadzig-clap-44ea38f25453fbf51de72505d7b3ddb0f3eec13c.tar.gz
zig-clap-44ea38f25453fbf51de72505d7b3ddb0f3eec13c.tar.xz
zig-clap-44ea38f25453fbf51de72505d7b3ddb0f3eec13c.zip
updated to newest version of zig
Diffstat (limited to 'clap.zig')
-rw-r--r--clap.zig226
1 files changed, 226 insertions, 0 deletions
diff --git a/clap.zig b/clap.zig
new file mode 100644
index 0000000..8823b59
--- /dev/null
+++ b/clap.zig
@@ -0,0 +1,226 @@
1const std = @import("std");
2
3const debug = std.debug;
4const io = std.io;
5const mem = std.mem;
6
7pub const @"comptime" = @import("src/comptime.zig");
8pub const args = @import("src/args.zig");
9pub const streaming = @import("src/streaming.zig");
10
11test "clap" {
12 _ = @"comptime";
13 _ = args;
14 _ = streaming;
15}
16
17pub const ComptimeClap = @"comptime".ComptimeClap;
18pub const StreamingClap = streaming.StreamingClap;
19
20/// The names a ::Param can have.
21pub const Names = struct {
22 /// '-' prefix
23 short: ?u8 = null,
24
25 /// '--' prefix
26 long: ?[]const u8 = null,
27};
28
29/// Represents a parameter for the command line.
30/// Parameters come in three kinds:
31/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
32/// * They can take a value three different ways.
33/// * "-a value"
34/// * "-a=value"
35/// * "-avalue"
36/// * They chain if they don't take values: "-abc".
37/// * The last given parameter can take a value in the same way that a single parameter can:
38/// * "-abc value"
39/// * "-abc=value"
40/// * "-abcvalue"
41/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
42/// can describe the paramter.
43/// * They can take a value two different ways.
44/// * "--long-param value"
45/// * "--long-param=value"
46/// * Positional: Should be used as the primary parameter of the program, like a filename or
47/// an expression to parse.
48/// * Positional parameters have both names.long and names.short == null.
49/// * Positional parameters must take a value.
50pub fn Param(comptime Id: type) type {
51 return struct {
52 id: Id = Id{},
53 names: Names = Names{},
54 takes_value: bool = false,
55 };
56}
57
58/// Will print a help message in the following format:
59/// -s, --long=value_text help_text
60/// -s, help_text
61/// --long help_text
62pub fn helpFull(
63 stream: var,
64 comptime Id: type,
65 params: []const Param(Id),
66 comptime Error: type,
67 context: var,
68 help_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
69 value_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
70) !void {
71 const max_spacing = blk: {
72 var res: usize = 0;
73 for (params) |param| {
74 var counting_stream = io.CountingOutStream(io.NullOutStream.Error).init(io.null_out_stream);
75 try printParam(&counting_stream.stream, Id, param, Error, context, value_text);
76 if (res < counting_stream.bytes_written)
77 res = counting_stream.bytes_written;
78 }
79
80 break :blk res;
81 };
82
83 for (params) |param| {
84 if (param.names.short == null and param.names.long == null)
85 continue;
86
87 var counting_stream = io.CountingOutStream(@typeOf(stream.*).Error).init(stream);
88 try stream.print("\t");
89 try printParam(&counting_stream.stream, Id, param, Error, context, value_text);
90 try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written);
91 try stream.print("\t{}\n", try help_text(context, param));
92 }
93}
94
95fn printParam(
96 stream: var,
97 comptime Id: type,
98 param: Param(Id),
99 comptime Error: type,
100 context: var,
101 value_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
102) @typeOf(stream.*).Error!void {
103 if (param.names.short) |s| {
104 try stream.print("-{c}", s);
105 } else {
106 try stream.print(" ");
107 }
108 if (param.names.long) |l| {
109 if (param.names.short) |_| {
110 try stream.print(", ");
111 } else {
112 try stream.print(" ");
113 }
114
115 try stream.print("--{}", l);
116 }
117 if (param.takes_value)
118 try stream.print("={}", value_text(context, param));
119}
120
121/// A wrapper around helpFull for simple help_text and value_text functions that
122/// cant return an error or take a context.
123pub fn helpEx(
124 stream: var,
125 comptime Id: type,
126 params: []const Param(Id),
127 help_text: fn (Param(Id)) []const u8,
128 value_text: fn (Param(Id)) []const u8,
129) !void {
130 const Context = struct {
131 help_text: fn (Param(Id)) []const u8,
132 value_text: fn (Param(Id)) []const u8,
133
134 pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 {
135 return c.help_text(p);
136 }
137
138 pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
139 return c.value_text(p);
140 }
141 };
142
143 return helpFull(
144 stream,
145 Id,
146 params,
147 error{},
148 Context{
149 .help_text = help_text,
150 .value_text = value_text,
151 },
152 Context.help,
153 Context.value,
154 );
155}
156
157/// A wrapper around helpEx that takes a Param([]const u8) and uses the string id
158/// as the help text for each paramter.
159pub fn help(stream: var, params: []const Param([]const u8)) !void {
160 try helpEx(stream, []const u8, params, getHelpSimple, getValueSimple);
161}
162
163fn getHelpSimple(param: Param([]const u8)) []const u8 {
164 return param.id;
165}
166
167fn getValueSimple(param: Param([]const u8)) []const u8 {
168 return "VALUE";
169}
170
171test "clap.help" {
172 var buf: [1024]u8 = undefined;
173 var slice_stream = io.SliceOutStream.init(buf[0..]);
174 try help(
175 &slice_stream.stream,
176 [_]Param([]const u8){
177 Param([]const u8){
178 .id = "Short flag.",
179 .names = Names{ .short = 'a' },
180 },
181 Param([]const u8){
182 .id = "Short option.",
183 .names = Names{ .short = 'b' },
184 .takes_value = true,
185 },
186 Param([]const u8){
187 .id = "Long flag.",
188 .names = Names{ .long = "aa" },
189 },
190 Param([]const u8){
191 .id = "Long option.",
192 .names = Names{ .long = "bb" },
193 .takes_value = true,
194 },
195 Param([]const u8){
196 .id = "Both flag.",
197 .names = Names{ .short = 'c', .long = "cc" },
198 },
199 Param([]const u8){
200 .id = "Both option.",
201 .names = Names{ .short = 'd', .long = "dd" },
202 .takes_value = true,
203 },
204 Param([]const u8){
205 .id = "Positional. This should not appear in the help message.",
206 .takes_value = true,
207 },
208 },
209 );
210
211 const expected = "" ++
212 "\t-a \tShort flag.\n" ++
213 "\t-b=VALUE \tShort option.\n" ++
214 "\t --aa \tLong flag.\n" ++
215 "\t --bb=VALUE\tLong option.\n" ++
216 "\t-c, --cc \tBoth flag.\n" ++
217 "\t-d, --dd=VALUE\tBoth option.\n";
218
219 if (!mem.eql(u8, slice_stream.getWritten(), expected)) {
220 debug.warn("============ Expected ============\n");
221 debug.warn("{}", expected);
222 debug.warn("============= Actual =============\n");
223 debug.warn("{}", slice_stream.getWritten());
224 return error.NoMatch;
225 }
226}