summaryrefslogtreecommitdiff
path: root/src/index.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/index.zig')
-rw-r--r--src/index.zig276
1 files changed, 0 insertions, 276 deletions
diff --git a/src/index.zig b/src/index.zig
deleted file mode 100644
index 40ad849..0000000
--- a/src/index.zig
+++ /dev/null
@@ -1,276 +0,0 @@
1const std = @import("std");
2
3const debug = std.debug;
4const io = std.io;
5const mem = std.mem;
6
7pub const @"comptime" = @import("comptime.zig");
8pub const args = @import("args.zig");
9pub const streaming = @import("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,
24
25 /// '--' prefix
26 long: ?[]const u8,
27
28 /// Initializes a short name
29 pub fn short(s: u8) Names {
30 return Names{
31 .short = s,
32 .long = null,
33 };
34 }
35
36 /// Initializes a long name
37 pub fn long(l: []const u8) Names {
38 return Names{
39 .short = null,
40 .long = l,
41 };
42 }
43
44 /// Initializes a name that is long and short, from the same string.
45 /// ::short is set to ::name[0], and ::long is set to ::name.
46 /// This function asserts that ::name.len != 0
47 pub fn both(name: []const u8) Names {
48 debug.assert(name.len != 0);
49
50 return Names{
51 .short = name[0],
52 .long = name,
53 };
54 }
55};
56
57/// Represents a parameter for the command line.
58/// Parameters come in three kinds:
59/// * Short ("-a"): Should be used for the most commonly used parameters in your program.
60/// * They can take a value three different ways.
61/// * "-a value"
62/// * "-a=value"
63/// * "-avalue"
64/// * They chain if they don't take values: "-abc".
65/// * The last given parameter can take a value in the same way that a single parameter can:
66/// * "-abc value"
67/// * "-abc=value"
68/// * "-abcvalue"
69/// * Long ("--long-param"): Should be used for less common parameters, or when no single character
70/// can describe the paramter.
71/// * They can take a value two different ways.
72/// * "--long-param value"
73/// * "--long-param=value"
74/// * Positional: Should be used as the primary parameter of the program, like a filename or
75/// an expression to parse.
76/// * Positional parameters have both names.long and names.short == null.
77/// * Positional parameters must take a value.
78pub fn Param(comptime Id: type) type {
79 return struct {
80 id: Id,
81 takes_value: bool,
82 names: Names,
83
84 pub fn flag(id: Id, names: Names) @This() {
85 return init(id, false, names);
86 }
87
88 pub fn option(id: Id, names: Names) @This() {
89 return init(id, true, names);
90 }
91
92 pub fn positional(id: Id) @This() {
93 return init(id, true, Names{ .short = null, .long = null });
94 }
95
96 pub fn init(id: Id, takes_value: bool, names: Names) @This() {
97 // Assert, that if the param have no name, then it has to take
98 // a value.
99 debug.assert(names.long != null or
100 names.short != null or
101 takes_value);
102
103 return @This(){
104 .id = id,
105 .takes_value = takes_value,
106 .names = names,
107 };
108 }
109 };
110}
111
112/// Will print a help message in the following format:
113/// -s, --long=value_text help_text
114/// -s, help_text
115/// --long help_text
116pub fn helpFull(
117 stream: var,
118 comptime Id: type,
119 params: []const Param(Id),
120 comptime Error: type,
121 context: var,
122 help_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
123 value_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
124) !void {
125 const max_spacing = blk: {
126 var res: usize = 0;
127 for (params) |param| {
128 var counting_stream = io.CountingOutStream(io.NullOutStream.Error).init(io.null_out_stream);
129 try printParam(&counting_stream.stream, Id, param, Error, context, value_text);
130 if (res < counting_stream.bytes_written)
131 res = counting_stream.bytes_written;
132 }
133
134 break :blk res;
135 };
136
137 for (params) |param| {
138 if (param.names.short == null and param.names.long == null)
139 continue;
140
141 var counting_stream = io.CountingOutStream(@typeOf(stream.*).Error).init(stream);
142 try stream.print("\t");
143 try printParam(&counting_stream.stream, Id, param, Error, context, value_text);
144 try stream.writeByteNTimes(' ', max_spacing - counting_stream.bytes_written);
145 try stream.print("\t{}\n", try help_text(context, param));
146 }
147}
148
149fn printParam(
150 stream: var,
151 comptime Id: type,
152 param: Param(Id),
153 comptime Error: type,
154 context: var,
155 value_text: fn (@typeOf(context), Param(Id)) Error![]const u8,
156) @typeOf(stream.*).Error!void {
157 if (param.names.short) |s| {
158 try stream.print("-{c}", s);
159 } else {
160 try stream.print(" ");
161 }
162 if (param.names.long) |l| {
163 if (param.names.short) |_| {
164 try stream.print(", ");
165 } else {
166 try stream.print(" ");
167 }
168
169 try stream.print("--{}", l);
170 }
171 if (param.takes_value)
172 try stream.print("={}", value_text(context, param));
173}
174
175/// A wrapper around helpFull for simple help_text and value_text functions that
176/// cant return an error or take a context.
177pub fn helpEx(
178 stream: var,
179 comptime Id: type,
180 params: []const Param(Id),
181 help_text: fn (Param(Id)) []const u8,
182 value_text: fn (Param(Id)) []const u8,
183) !void {
184 const Context = struct {
185 help_text: fn (Param(Id)) []const u8,
186 value_text: fn (Param(Id)) []const u8,
187
188 pub fn help(c: @This(), p: Param(Id)) error{}![]const u8 {
189 return c.help_text(p);
190 }
191
192 pub fn value(c: @This(), p: Param(Id)) error{}![]const u8 {
193 return c.value_text(p);
194 }
195 };
196
197 return helpFull(
198 stream,
199 Id,
200 params,
201 error{},
202 Context{
203 .help_text = help_text,
204 .value_text = value_text,
205 },
206 Context.help,
207 Context.value,
208 );
209}
210
211/// A wrapper around helpEx that takes a Param([]const u8) and uses the string id
212/// as the help text for each paramter.
213pub fn help(stream: var, params: []const Param([]const u8)) !void {
214 try helpEx(stream, []const u8, params, getHelpSimple, getValueSimple);
215}
216
217fn getHelpSimple(param: Param([]const u8)) []const u8 {
218 return param.id;
219}
220
221fn getValueSimple(param: Param([]const u8)) []const u8 {
222 return "VALUE";
223}
224
225test "clap.help" {
226 var buf: [1024]u8 = undefined;
227 var slice_stream = io.SliceOutStream.init(buf[0..]);
228 try help(
229 &slice_stream.stream,
230 []Param([]const u8){
231 Param([]const u8).flag(
232 "Short flag.",
233 Names.short('a'),
234 ),
235 Param([]const u8).option(
236 "Short option.",
237 Names.short('b'),
238 ),
239 Param([]const u8).flag(
240 "Long flag.",
241 Names.long("aa"),
242 ),
243 Param([]const u8).option(
244 "Long option.",
245 Names.long("bb"),
246 ),
247 Param([]const u8).flag(
248 "Both flag.",
249 Names.both("cc"),
250 ),
251 Param([]const u8).option(
252 "Both option.",
253 Names.both("dd"),
254 ),
255 Param([]const u8).positional(
256 "Positional. This should not appear in the help message.",
257 ),
258 },
259 );
260
261 const expected = "" ++
262 "\t-a \tShort flag.\n" ++
263 "\t-b=VALUE \tShort option.\n" ++
264 "\t --aa \tLong flag.\n" ++
265 "\t --bb=VALUE\tLong option.\n" ++
266 "\t-c, --cc \tBoth flag.\n" ++
267 "\t-d, --dd=VALUE\tBoth option.\n";
268
269 if (!mem.eql(u8, slice_stream.getWritten(), expected)) {
270 debug.warn("============ Expected ============\n");
271 debug.warn("{}", expected);
272 debug.warn("============= Actual =============\n");
273 debug.warn("{}", slice_stream.getWritten());
274 return error.NoMatch;
275 }
276}