summaryrefslogtreecommitdiff
path: root/clap.zig
diff options
context:
space:
mode:
Diffstat (limited to 'clap.zig')
-rw-r--r--clap.zig778
1 files changed, 686 insertions, 92 deletions
diff --git a/clap.zig b/clap.zig
index 3fd791e..6432352 100644
--- a/clap.zig
+++ b/clap.zig
@@ -4,6 +4,7 @@ const builtin = std.builtin;
4const debug = std.debug; 4const debug = std.debug;
5const heap = std.heap; 5const heap = std.heap;
6const io = std.io; 6const io = std.io;
7const math = std.math;
7const mem = std.mem; 8const mem = std.mem;
8const process = std.process; 9const process = std.process;
9const testing = std.testing; 10const testing = std.testing;
@@ -81,10 +82,17 @@ pub fn Param(comptime Id: type) type {
81/// Takes a string and parses it into many Param(Help). Returned is a newly allocated slice 82/// Takes a string and parses it into many Param(Help). Returned is a newly allocated slice
82/// containing all the parsed params. The caller is responsible for freeing the slice. 83/// containing all the parsed params. The caller is responsible for freeing the slice.
83pub fn parseParams(allocator: mem.Allocator, str: []const u8) ![]Param(Help) { 84pub fn parseParams(allocator: mem.Allocator, str: []const u8) ![]Param(Help) {
85 var end: usize = undefined;
86 return parseParamsEx(allocator, str, &end);
87}
88
89/// Takes a string and parses it into many Param(Help). Returned is a newly allocated slice
90/// containing all the parsed params. The caller is responsible for freeing the slice.
91pub fn parseParamsEx(allocator: mem.Allocator, str: []const u8, end: *usize) ![]Param(Help) {
84 var list = std.ArrayList(Param(Help)).init(allocator); 92 var list = std.ArrayList(Param(Help)).init(allocator);
85 errdefer list.deinit(); 93 errdefer list.deinit();
86 94
87 try parseParamsIntoArrayList(&list, str); 95 try parseParamsIntoArrayListEx(&list, str, end);
88 return list.toOwnedSlice(); 96 return list.toOwnedSlice();
89} 97}
90 98
@@ -92,8 +100,16 @@ pub fn parseParams(allocator: mem.Allocator, str: []const u8) ![]Param(Help) {
92/// exactly the number of params that was parsed from `str`. A parse error becomes a compiler 100/// exactly the number of params that was parsed from `str`. A parse error becomes a compiler
93/// error. 101/// error.
94pub fn parseParamsComptime(comptime str: []const u8) [countParams(str)]Param(Help) { 102pub fn parseParamsComptime(comptime str: []const u8) [countParams(str)]Param(Help) {
103 var end: usize = undefined;
95 var res: [countParams(str)]Param(Help) = undefined; 104 var res: [countParams(str)]Param(Help) = undefined;
96 _ = parseParamsIntoSlice(&res, str) catch unreachable; 105 _ = parseParamsIntoSliceEx(&res, str, &end) catch {
106 const loc = std.zig.findLineColumn(str, end);
107 @compileError(std.fmt.comptimePrint("error:{}:{}: Failed to parse parameter:\n{s}", .{
108 loc.line + 1,
109 loc.column + 1,
110 loc.source_line,
111 }));
112 };
97 return res; 113 return res;
98} 114}
99 115
@@ -131,14 +147,39 @@ pub fn parseParamsIntoSlice(slice: []Param(Help), str: []const u8) ![]Param(Help
131 return list.items; 147 return list.items;
132} 148}
133 149
150/// Takes a string and parses it into many Param(Help), which are written to `slice`. A subslice
151/// is returned, containing all the parameters parsed. This function will fail if the input slice
152/// is to small.
153pub fn parseParamsIntoSliceEx(slice: []Param(Help), str: []const u8, end: *usize) ![]Param(Help) {
154 var null_alloc = heap.FixedBufferAllocator.init("");
155 var list = std.ArrayList(Param(Help)){
156 .allocator = null_alloc.allocator(),
157 .items = slice[0..0],
158 .capacity = slice.len,
159 };
160
161 try parseParamsIntoArrayListEx(&list, str, end);
162 return list.items;
163}
164
134/// Takes a string and parses it into many Param(Help), which are appended onto `list`. 165/// Takes a string and parses it into many Param(Help), which are appended onto `list`.
135pub fn parseParamsIntoArrayList(list: *std.ArrayList(Param(Help)), str: []const u8) !void { 166pub fn parseParamsIntoArrayList(list: *std.ArrayList(Param(Help)), str: []const u8) !void {
167 var end: usize = undefined;
168 return parseParamsIntoArrayListEx(list, str, &end);
169}
170
171/// Takes a string and parses it into many Param(Help), which are appended onto `list`.
172pub fn parseParamsIntoArrayListEx(list: *std.ArrayList(Param(Help)), str: []const u8, end: *usize) !void {
136 var i: usize = 0; 173 var i: usize = 0;
137 while (i != str.len) { 174 while (i != str.len) {
138 var end: usize = undefined; 175 var end_of_this: usize = undefined;
139 try list.append(try parseParamEx(str[i..], &end)); 176 errdefer end.* = i + end_of_this;
140 i += end; 177
178 try list.append(try parseParamEx(str[i..], &end_of_this));
179 i += end_of_this;
141 } 180 }
181
182 end.* = str.len;
142} 183}
143 184
144pub fn parseParam(str: []const u8) !Param(Help) { 185pub fn parseParam(str: []const u8) !Param(Help) {
@@ -182,8 +223,6 @@ pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
182 third_dot_of_multi_value, 223 third_dot_of_multi_value,
183 224
184 before_description, 225 before_description,
185 before_description_new_line,
186
187 rest_of_description, 226 rest_of_description,
188 rest_of_description_new_line, 227 rest_of_description_new_line,
189 } = .start; 228 } = .start;
@@ -208,7 +247,11 @@ pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
208 }, 247 },
209 .end_of_short_name => switch (c) { 248 .end_of_short_name => switch (c) {
210 ' ', '\t' => state = .before_long_name_or_value_or_description, 249 ' ', '\t' => state = .before_long_name_or_value_or_description,
211 '\n' => state = .before_description_new_line, 250 '\n' => {
251 start = i + 1;
252 end.* = i + 1;
253 state = .rest_of_description_new_line;
254 },
212 ',' => state = .before_long_name, 255 ',' => state = .before_long_name,
213 else => return error.InvalidParameter, 256 else => return error.InvalidParameter,
214 }, 257 },
@@ -237,7 +280,9 @@ pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
237 }, 280 },
238 '\n' => { 281 '\n' => {
239 res.names.long = str[start..i]; 282 res.names.long = str[start..i];
240 state = .before_description_new_line; 283 start = i + 1;
284 end.* = i + 1;
285 state = .rest_of_description_new_line;
241 }, 286 },
242 else => return error.InvalidParameter, 287 else => return error.InvalidParameter,
243 }, 288 },
@@ -278,7 +323,11 @@ pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
278 .end_of_one_value => switch (c) { 323 .end_of_one_value => switch (c) {
279 '.' => state = .second_dot_of_multi_value, 324 '.' => state = .second_dot_of_multi_value,
280 ' ', '\t' => state = .before_description, 325 ' ', '\t' => state = .before_description,
281 '\n' => state = .before_description_new_line, 326 '\n' => {
327 start = i + 1;
328 end.* = i + 1;
329 state = .rest_of_description_new_line;
330 },
282 else => { 331 else => {
283 start = i; 332 start = i;
284 state = .rest_of_description; 333 state = .rest_of_description;
@@ -298,17 +347,10 @@ pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
298 347
299 .before_description => switch (c) { 348 .before_description => switch (c) {
300 ' ', '\t' => {}, 349 ' ', '\t' => {},
301 '\n' => state = .before_description_new_line, 350 '\n' => {
302 else => { 351 start = i + 1;
303 start = i; 352 end.* = i + 1;
304 state = .rest_of_description; 353 state = .rest_of_description_new_line;
305 },
306 },
307 .before_description_new_line => switch (c) {
308 ' ', '\t', '\n' => {},
309 '-', '<' => {
310 end.* = i;
311 break;
312 }, 354 },
313 else => { 355 else => {
314 start = i; 356 start = i;
@@ -316,13 +358,16 @@ pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
316 }, 358 },
317 }, 359 },
318 .rest_of_description => switch (c) { 360 .rest_of_description => switch (c) {
319 '\n' => state = .rest_of_description_new_line, 361 '\n' => {
362 end.* = i;
363 state = .rest_of_description_new_line;
364 },
320 else => {}, 365 else => {},
321 }, 366 },
322 .rest_of_description_new_line => switch (c) { 367 .rest_of_description_new_line => switch (c) {
323 ' ', '\t', '\n' => {}, 368 ' ', '\t', '\n' => {},
324 '-', '<' => { 369 '-', '<' => {
325 res.id.desc = mem.trimRight(u8, str[start..i], " \t\n\r"); 370 res.id.desc = str[start..end.*];
326 end.* = i; 371 end.* = i;
327 break; 372 break;
328 }, 373 },
@@ -330,17 +375,15 @@ pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
330 }, 375 },
331 } 376 }
332 } else { 377 } else {
333 end.* = str.len; 378 defer end.* = str.len;
334 switch (state) { 379 switch (state) {
335 .rest_of_description, .rest_of_description_new_line => { 380 .rest_of_description => res.id.desc = str[start..],
336 res.id.desc = mem.trimRight(u8, str[start..], " \t\n\r"); 381 .rest_of_description_new_line => res.id.desc = str[start..end.*],
337 },
338 .rest_of_long_name => res.names.long = str[start..], 382 .rest_of_long_name => res.names.long = str[start..],
339 .end_of_short_name, 383 .end_of_short_name,
340 .end_of_one_value, 384 .end_of_one_value,
341 .before_value_or_description, 385 .before_value_or_description,
342 .before_description, 386 .before_description,
343 .before_description_new_line,
344 => {}, 387 => {},
345 else => return error.InvalidParameter, 388 else => return error.InvalidParameter,
346 } 389 }
@@ -350,7 +393,16 @@ pub fn parseParamEx(str: []const u8, end: *usize) !Param(Help) {
350} 393}
351 394
352fn testParseParams(str: []const u8, expected_params: []const Param(Help)) !void { 395fn testParseParams(str: []const u8, expected_params: []const Param(Help)) !void {
353 const actual_params = try parseParams(testing.allocator, str); 396 var end: usize = undefined;
397 const actual_params = parseParamsEx(testing.allocator, str, &end) catch |err| {
398 const loc = std.zig.findLineColumn(str, end);
399 std.debug.print("error:{}:{}: Failed to parse parameter:\n{s}\n", .{
400 loc.line + 1,
401 loc.column + 1,
402 loc.source_line,
403 });
404 return err;
405 };
354 defer testing.allocator.free(actual_params); 406 defer testing.allocator.free(actual_params);
355 407
356 try testing.expectEqual(expected_params.len, actual_params.len); 408 try testing.expectEqual(expected_params.len, actual_params.len);
@@ -389,10 +441,10 @@ test "parseParams" {
389 \\--long <A | B> Help text 441 \\--long <A | B> Help text
390 \\<A> Help text 442 \\<A> Help text
391 \\<A>... Help text 443 \\<A>... Help text
392 \\--aa This is 444 \\--aa
445 \\ This is
393 \\ help spanning multiple 446 \\ help spanning multiple
394 \\ lines 447 \\ lines
395 \\
396 \\--aa This msg should end and the newline cause of new param 448 \\--aa This msg should end and the newline cause of new param
397 \\--bb This should be a new param 449 \\--bb This should be a new param
398 \\ 450 \\
@@ -461,7 +513,7 @@ test "parseParams" {
461 .{ 513 .{
462 .id = .{ 514 .id = .{
463 .desc = 515 .desc =
464 \\This is 516 \\ This is
465 \\ help spanning multiple 517 \\ help spanning multiple
466 \\ lines 518 \\ lines
467 , 519 ,
@@ -969,13 +1021,71 @@ pub const Help = struct {
969 } 1021 }
970}; 1022};
971 1023
972/// Will print a help message in the following format: 1024pub const HelpOptions = struct {
973/// -s, --long <valueText> helpText 1025 /// Render the description of a parameter in a simular way to how markdown would render
974/// -s, helpText 1026 /// such a string. This means that single newlines wont be respected unless followed by
975/// -s <valueText> helpText 1027 /// bullet points or other markdown elements.
976/// --long helpText 1028 markdown_lite: bool = true,
977/// --long <valueText> helpText 1029
978pub fn help(stream: anytype, comptime Id: type, params: []const Param(Id)) !void { 1030 /// Wether `help` should print the description of a parameter on a new line instead of after
1031 /// the parameter names. This options works together with `description_indent` to change
1032 /// where descriptions are printed.
1033 ///
1034 /// description_on_new_line=false, description_indent=4
1035 ///
1036 /// -a, --aa <v> This is a description
1037 /// that is not placed on
1038 /// a new line.
1039 ///
1040 /// description_on_new_line=true, description_indent=4
1041 ///
1042 /// -a, --aa <v>
1043 /// This is a description
1044 /// that is placed on a
1045 /// new line.
1046 description_on_new_line: bool = true,
1047
1048 /// How much to indent descriptions. See `description_on_new_line` for examples of how this
1049 /// changes the output.
1050 description_indent: usize = 8,
1051
1052 /// How much to indent each paramter.
1053 ///
1054 /// indent=0, description_on_new_line=false, description_indent=4
1055 ///
1056 /// -a, --aa <v> This is a description
1057 /// that is not placed on
1058 /// a new line.
1059 ///
1060 /// indent=4, description_on_new_line=false, description_indent=4
1061 ///
1062 /// -a, --aa <v> This is a description
1063 /// that is not placed on
1064 /// a new line.
1065 ///
1066 indent: usize = 4,
1067
1068 /// The maximum width of the help message. `help` will try to break the description of
1069 /// paramters into multiple lines if they exeed this maximum. Setting this to the width
1070 /// of the terminal is a nice way of using this option.
1071 max_width: usize = std.math.maxInt(usize),
1072
1073 /// The number of empty lines between each printed parameter.
1074 spacing_between_parameters: usize = 1,
1075};
1076
1077/// Print a slice of `Param` formatted as a help string to `writer`. This function expects
1078/// `Id` to have the methods `description` and `value` which are used by `help` to describe
1079/// each parameter. Using `Help` as `Id` is good choice.
1080///
1081/// The output can be constumized with the `opt` parameter. For default formatting `.{}` can
1082/// be passed.
1083pub fn help(
1084 writer: anytype,
1085 comptime Id: type,
1086 params: []const Param(Id),
1087 opt: HelpOptions,
1088) !void {
979 const max_spacing = blk: { 1089 const max_spacing = blk: {
980 var res: usize = 0; 1090 var res: usize = 0;
981 for (params) |param| { 1091 for (params) |param| {
@@ -988,48 +1098,186 @@ pub fn help(stream: anytype, comptime Id: type, params: []const Param(Id)) !void
988 break :blk res; 1098 break :blk res;
989 }; 1099 };
990 1100
1101 const description_indentation = opt.indent +
1102 opt.description_indent +
1103 max_spacing * @boolToInt(!opt.description_on_new_line);
1104
1105 var first_paramter: bool = true;
991 for (params) |param| { 1106 for (params) |param| {
992 if (param.names.short == null and param.names.long == null) 1107 if (param.names.longest().kind == .positinal)
993 continue; 1108 continue;
1109 if (!first_paramter)
1110 try writer.writeByteNTimes('\n', opt.spacing_between_parameters);
1111
1112 first_paramter = false;
1113 try writer.writeByteNTimes(' ', opt.indent);
994 1114
995 var cs = io.countingWriter(stream); 1115 var cw = io.countingWriter(writer);
996 try stream.writeAll("\t"); 1116 try printParam(cw.writer(), Id, param);
997 try printParam(cs.writer(), Id, param); 1117
998 try stream.writeByteNTimes(' ', max_spacing - @intCast(usize, cs.bytes_written)); 1118 const Writer = DescriptionWriter(@TypeOf(writer));
1119 var description_writer = Writer{
1120 .underlying_writer = writer,
1121 .indentation = description_indentation,
1122 .printed_chars = @intCast(usize, cw.bytes_written),
1123 .max_width = opt.max_width,
1124 };
1125
1126 if (opt.description_on_new_line)
1127 try description_writer.newline();
1128
1129 const min_description_indent = blk: {
1130 const description = param.id.description();
1131
1132 var first_line = true;
1133 var res: usize = std.math.maxInt(usize);
1134 var it = mem.tokenize(u8, description, "\n");
1135 while (it.next()) |line| : (first_line = false) {
1136 const trimmed = mem.trimLeft(u8, line, " ");
1137 const indent = line.len - trimmed.len;
1138
1139 // If the first line has no indentation, then we ignore the indentation of the
1140 // first line. We do this as the parameter might have been parsed from:
1141 //
1142 // -a, --aa The first line
1143 // is not indented,
1144 // but the rest of
1145 // the lines are.
1146 //
1147 // In this case, we want to pretend that the first line has the same indentation
1148 // as the min_description_indent, even though it is not so in the string we get.
1149 if (first_line and indent == 0)
1150 continue;
1151 if (indent < res)
1152 res = indent;
1153 }
1154
1155 break :blk res;
1156 };
999 1157
1000 const description = param.id.description(); 1158 const description = param.id.description();
1001 var it = mem.split(u8, description, "\n"); 1159 var it = mem.split(u8, description, "\n");
1002 var indent_line = false; 1160 var first_line = true;
1003 while (it.next()) |line| : (indent_line = true) { 1161 var non_emitted_newlines: usize = 0;
1004 if (indent_line) { 1162 var last_line_indentation: usize = 0;
1005 try stream.writeAll("\t"); 1163 while (it.next()) |raw_line| : (first_line = false) {
1006 try stream.writeByteNTimes(' ', max_spacing); 1164 // First line might be special. See comment above.
1165 const indented_line = if (first_line and !mem.startsWith(u8, raw_line, " "))
1166 raw_line
1167 else
1168 raw_line[math.min(min_description_indent, raw_line.len)..];
1169
1170 const line = mem.trimLeft(u8, indented_line, " ");
1171 if (line.len == 0) {
1172 non_emitted_newlines += 1;
1173 continue;
1007 } 1174 }
1008 try stream.writeAll("\t"); 1175
1009 try stream.writeAll(mem.trimLeft(u8, line, " \t")); 1176 const line_indentation = indented_line.len - line.len;
1010 try stream.writeAll("\n"); 1177 description_writer.indentation = description_indentation + line_indentation;
1178
1179 if (opt.markdown_lite) {
1180 const new_paragraph = non_emitted_newlines > 1;
1181
1182 const does_not_have_same_indent_as_last_line =
1183 line_indentation != last_line_indentation;
1184
1185 const starts_with_control_char = mem.indexOfScalar(u8, "=*", line[0]) != null;
1186
1187 // Either the input contains 2 or more newlines, in which case we should start
1188 // a new paragraph.
1189 if (new_paragraph) {
1190 try description_writer.newline();
1191 try description_writer.newline();
1192 }
1193 // Or this line has a special control char or different indentation which means
1194 // we should output it on a new line as well.
1195 else if (starts_with_control_char or does_not_have_same_indent_as_last_line) {
1196 try description_writer.newline();
1197 }
1198 } else {
1199 // For none markdown like format, we just respect the newlines in the input
1200 // string and output them as is.
1201 var i: usize = 0;
1202 while (i < non_emitted_newlines) : (i += 1)
1203 try description_writer.newline();
1204 }
1205
1206 var words = mem.tokenize(u8, line, " ");
1207 while (words.next()) |word|
1208 try description_writer.writeWord(word);
1209
1210 // We have not emitted the end of this line yet.
1211 non_emitted_newlines = 1;
1212 last_line_indentation = line_indentation;
1011 } 1213 }
1214
1215 try writer.writeAll("\n");
1012 } 1216 }
1013} 1217}
1014 1218
1219fn DescriptionWriter(comptime UnderlyingWriter: type) type {
1220 return struct {
1221 pub const WriteError = UnderlyingWriter.Error;
1222
1223 underlying_writer: UnderlyingWriter,
1224
1225 indentation: usize,
1226 max_width: usize,
1227 printed_chars: usize,
1228
1229 pub fn writeWord(writer: *@This(), word: []const u8) !void {
1230 debug.assert(word.len != 0);
1231
1232 var first_word = writer.printed_chars <= writer.indentation;
1233 const chars_to_write = word.len + @boolToInt(!first_word);
1234 if (chars_to_write + writer.printed_chars > writer.max_width) {
1235 // If the word does not fit on this line, then we insert a new line and print
1236 // it on that line. The only exception to this is if this was the first word.
1237 // If the first word does not fit on this line, then it will also not fit on the
1238 // next one. In that case, all we can really do is just output the word.
1239 if (!first_word)
1240 try writer.newline();
1241
1242 first_word = true;
1243 }
1244
1245 if (!first_word)
1246 try writer.underlying_writer.writeAll(" ");
1247
1248 try writer.ensureIndented();
1249 try writer.underlying_writer.writeAll(word);
1250 writer.printed_chars += chars_to_write;
1251 }
1252
1253 pub fn newline(writer: *@This()) !void {
1254 try writer.underlying_writer.writeAll("\n");
1255 writer.printed_chars = 0;
1256 }
1257
1258 fn ensureIndented(writer: *@This()) !void {
1259 if (writer.printed_chars < writer.indentation) {
1260 const to_indent = writer.indentation - writer.printed_chars;
1261 try writer.underlying_writer.writeByteNTimes(' ', to_indent);
1262 writer.printed_chars += to_indent;
1263 }
1264 }
1265 };
1266}
1267
1015fn printParam( 1268fn printParam(
1016 stream: anytype, 1269 stream: anytype,
1017 comptime Id: type, 1270 comptime Id: type,
1018 param: Param(Id), 1271 param: Param(Id),
1019) !void { 1272) !void {
1020 if (param.names.short) |s| { 1273 try stream.writeAll(&[_]u8{
1021 try stream.writeAll(&[_]u8{ '-', s }); 1274 if (param.names.short) |_| '-' else ' ',
1022 } else { 1275 param.names.short orelse ' ',
1023 try stream.writeAll(" "); 1276 });
1024 }
1025 if (param.names.long) |l| {
1026 if (param.names.short) |_| {
1027 try stream.writeAll(", ");
1028 } else {
1029 try stream.writeAll(" ");
1030 }
1031 1277
1032 try stream.writeAll("--"); 1278 if (param.names.long) |l| {
1279 try stream.writeByte(if (param.names.short) |_| ',' else ' ');
1280 try stream.writeAll(" --");
1033 try stream.writeAll(l); 1281 try stream.writeAll(l);
1034 } 1282 }
1035 1283
@@ -1043,39 +1291,386 @@ fn printParam(
1043 try stream.writeAll("..."); 1291 try stream.writeAll("...");
1044} 1292}
1045 1293
1294fn testHelp(opt: HelpOptions, str: []const u8) !void {
1295 const params = try parseParams(testing.allocator, str);
1296 defer testing.allocator.free(params);
1297
1298 var buf: [2048]u8 = undefined;
1299 var fbs = io.fixedBufferStream(&buf);
1300 try help(fbs.writer(), Help, params, opt);
1301 try testing.expectEqualStrings(str, fbs.getWritten());
1302}
1303
1046test "clap.help" { 1304test "clap.help" {
1047 var buf: [1024]u8 = undefined; 1305 try testHelp(.{},
1048 var slice_stream = io.fixedBufferStream(&buf); 1306 \\ -a
1307 \\ Short flag.
1308 \\
1309 \\ -b <V1>
1310 \\ Short option.
1311 \\
1312 \\ --aa
1313 \\ Long flag.
1314 \\
1315 \\ --bb <V2>
1316 \\ Long option.
1317 \\
1318 \\ -c, --cc
1319 \\ Both flag.
1320 \\
1321 \\ --complicate
1322 \\ Flag with a complicated and very long description that spans multiple lines.
1323 \\
1324 \\ Paragraph number 2:
1325 \\ * Bullet point
1326 \\ * Bullet point
1327 \\
1328 \\ Example:
1329 \\ something something something
1330 \\
1331 \\ -d, --dd <V3>
1332 \\ Both option.
1333 \\
1334 \\ -d, --dd <V3>...
1335 \\ Both repeated option.
1336 \\
1337 );
1049 1338
1050 const params = comptime parseParamsComptime( 1339 try testHelp(.{ .markdown_lite = false },
1051 \\-a Short flag. 1340 \\ -a
1052 \\-b <V1> Short option. 1341 \\ Short flag.
1053 \\--aa Long flag. 1342 \\
1054 \\--bb <V2> Long option. 1343 \\ -b <V1>
1055 \\-c, --cc Both flag. 1344 \\ Short option.
1056 \\--complicate Flag with a complicated and 1345 \\
1057 \\ very long description that 1346 \\ --aa
1058 \\ spans multiple lines. 1347 \\ Long flag.
1059 \\-d, --dd <V3> Both option. 1348 \\
1060 \\-d, --dd <V3>... Both repeated option. 1349 \\ --bb <V2>
1061 \\<P> Positional. This should not appear in the help message. 1350 \\ Long option.
1351 \\
1352 \\ -c, --cc
1353 \\ Both flag.
1354 \\
1355 \\ --complicate
1356 \\ Flag with a complicated and
1357 \\ very long description that
1358 \\ spans multiple lines.
1359 \\
1360 \\ Paragraph number 2:
1361 \\ * Bullet point
1362 \\ * Bullet point
1363 \\
1364 \\
1365 \\ Example:
1366 \\ something something something
1367 \\
1368 \\ -d, --dd <V3>
1369 \\ Both option.
1370 \\
1371 \\ -d, --dd <V3>...
1372 \\ Both repeated option.
1062 \\ 1373 \\
1063 ); 1374 );
1064 1375
1065 try help(slice_stream.writer(), Help, &params); 1376 try testHelp(.{ .indent = 0 },
1066 const expected = "" ++ 1377 \\-a
1067 "\t-a \tShort flag.\n" ++ 1378 \\ Short flag.
1068 "\t-b <V1> \tShort option.\n" ++ 1379 \\
1069 "\t --aa \tLong flag.\n" ++ 1380 \\-b <V1>
1070 "\t --bb <V2> \tLong option.\n" ++ 1381 \\ Short option.
1071 "\t-c, --cc \tBoth flag.\n" ++ 1382 \\
1072 "\t --complicate\tFlag with a complicated and\n" ++ 1383 \\ --aa
1073 "\t \tvery long description that\n" ++ 1384 \\ Long flag.
1074 "\t \tspans multiple lines.\n" ++ 1385 \\
1075 "\t-d, --dd <V3> \tBoth option.\n" ++ 1386 \\ --bb <V2>
1076 "\t-d, --dd <V3>...\tBoth repeated option.\n"; 1387 \\ Long option.
1388 \\
1389 \\-c, --cc
1390 \\ Both flag.
1391 \\
1392 \\ --complicate
1393 \\ Flag with a complicated and very long description that spans multiple lines.
1394 \\
1395 \\ Paragraph number 2:
1396 \\ * Bullet point
1397 \\ * Bullet point
1398 \\
1399 \\ Example:
1400 \\ something something something
1401 \\
1402 \\-d, --dd <V3>
1403 \\ Both option.
1404 \\
1405 \\-d, --dd <V3>...
1406 \\ Both repeated option.
1407 \\
1408 );
1077 1409
1078 try testing.expectEqualStrings(expected, slice_stream.getWritten()); 1410 try testHelp(.{ .indent = 0 },
1411 \\-a
1412 \\ Short flag.
1413 \\
1414 \\-b <V1>
1415 \\ Short option.
1416 \\
1417 \\ --aa
1418 \\ Long flag.
1419 \\
1420 \\ --bb <V2>
1421 \\ Long option.
1422 \\
1423 \\-c, --cc
1424 \\ Both flag.
1425 \\
1426 \\ --complicate
1427 \\ Flag with a complicated and very long description that spans multiple lines.
1428 \\
1429 \\ Paragraph number 2:
1430 \\ * Bullet point
1431 \\ * Bullet point
1432 \\
1433 \\ Example:
1434 \\ something something something
1435 \\
1436 \\-d, --dd <V3>
1437 \\ Both option.
1438 \\
1439 \\-d, --dd <V3>...
1440 \\ Both repeated option.
1441 \\
1442 );
1443
1444 try testHelp(.{ .indent = 0, .max_width = 26 },
1445 \\-a
1446 \\ Short flag.
1447 \\
1448 \\-b <V1>
1449 \\ Short option.
1450 \\
1451 \\ --aa
1452 \\ Long flag.
1453 \\
1454 \\ --bb <V2>
1455 \\ Long option.
1456 \\
1457 \\-c, --cc
1458 \\ Both flag.
1459 \\
1460 \\ --complicate
1461 \\ Flag with a
1462 \\ complicated and
1463 \\ very long
1464 \\ description that
1465 \\ spans multiple
1466 \\ lines.
1467 \\
1468 \\ Paragraph number
1469 \\ 2:
1470 \\ * Bullet point
1471 \\ * Bullet point
1472 \\
1473 \\ Example:
1474 \\ something
1475 \\ something
1476 \\ something
1477 \\
1478 \\-d, --dd <V3>
1479 \\ Both option.
1480 \\
1481 \\-d, --dd <V3>...
1482 \\ Both repeated
1483 \\ option.
1484 \\
1485 );
1486
1487 try testHelp(.{
1488 .indent = 0,
1489 .max_width = 26,
1490 .description_indent = 6,
1491 },
1492 \\-a
1493 \\ Short flag.
1494 \\
1495 \\-b <V1>
1496 \\ Short option.
1497 \\
1498 \\ --aa
1499 \\ Long flag.
1500 \\
1501 \\ --bb <V2>
1502 \\ Long option.
1503 \\
1504 \\-c, --cc
1505 \\ Both flag.
1506 \\
1507 \\ --complicate
1508 \\ Flag with a
1509 \\ complicated and
1510 \\ very long
1511 \\ description that
1512 \\ spans multiple
1513 \\ lines.
1514 \\
1515 \\ Paragraph number 2:
1516 \\ * Bullet point
1517 \\ * Bullet point
1518 \\
1519 \\ Example:
1520 \\ something
1521 \\ something
1522 \\ something
1523 \\
1524 \\-d, --dd <V3>
1525 \\ Both option.
1526 \\
1527 \\-d, --dd <V3>...
1528 \\ Both repeated
1529 \\ option.
1530 \\
1531 );
1532
1533 try testHelp(.{
1534 .indent = 0,
1535 .max_width = 46,
1536 .description_on_new_line = false,
1537 },
1538 \\-a Short flag.
1539 \\
1540 \\-b <V1> Short option.
1541 \\
1542 \\ --aa Long flag.
1543 \\
1544 \\ --bb <V2> Long option.
1545 \\
1546 \\-c, --cc Both flag.
1547 \\
1548 \\ --complicate Flag with a
1549 \\ complicated and very
1550 \\ long description that
1551 \\ spans multiple lines.
1552 \\
1553 \\ Paragraph number 2:
1554 \\ * Bullet point
1555 \\ * Bullet point
1556 \\
1557 \\ Example:
1558 \\ something
1559 \\ something
1560 \\ something
1561 \\
1562 \\-d, --dd <V3> Both option.
1563 \\
1564 \\-d, --dd <V3>... Both repeated option.
1565 \\
1566 );
1567
1568 try testHelp(.{
1569 .indent = 0,
1570 .max_width = 46,
1571 .description_on_new_line = false,
1572 .description_indent = 4,
1573 },
1574 \\-a Short flag.
1575 \\
1576 \\-b <V1> Short option.
1577 \\
1578 \\ --aa Long flag.
1579 \\
1580 \\ --bb <V2> Long option.
1581 \\
1582 \\-c, --cc Both flag.
1583 \\
1584 \\ --complicate Flag with a complicated
1585 \\ and very long description
1586 \\ that spans multiple
1587 \\ lines.
1588 \\
1589 \\ Paragraph number 2:
1590 \\ * Bullet point
1591 \\ * Bullet point
1592 \\
1593 \\ Example:
1594 \\ something something
1595 \\ something
1596 \\
1597 \\-d, --dd <V3> Both option.
1598 \\
1599 \\-d, --dd <V3>... Both repeated option.
1600 \\
1601 );
1602
1603 try testHelp(.{
1604 .indent = 0,
1605 .max_width = 46,
1606 .description_on_new_line = false,
1607 .description_indent = 4,
1608 .spacing_between_parameters = 0,
1609 },
1610 \\-a Short flag.
1611 \\-b <V1> Short option.
1612 \\ --aa Long flag.
1613 \\ --bb <V2> Long option.
1614 \\-c, --cc Both flag.
1615 \\ --complicate Flag with a complicated
1616 \\ and very long description
1617 \\ that spans multiple
1618 \\ lines.
1619 \\
1620 \\ Paragraph number 2:
1621 \\ * Bullet point
1622 \\ * Bullet point
1623 \\
1624 \\ Example:
1625 \\ something something
1626 \\ something
1627 \\-d, --dd <V3> Both option.
1628 \\-d, --dd <V3>... Both repeated option.
1629 \\
1630 );
1631
1632 try testHelp(.{
1633 .indent = 0,
1634 .max_width = 46,
1635 .description_on_new_line = false,
1636 .description_indent = 4,
1637 .spacing_between_parameters = 2,
1638 },
1639 \\-a Short flag.
1640 \\
1641 \\
1642 \\-b <V1> Short option.
1643 \\
1644 \\
1645 \\ --aa Long flag.
1646 \\
1647 \\
1648 \\ --bb <V2> Long option.
1649 \\
1650 \\
1651 \\-c, --cc Both flag.
1652 \\
1653 \\
1654 \\ --complicate Flag with a complicated
1655 \\ and very long description
1656 \\ that spans multiple
1657 \\ lines.
1658 \\
1659 \\ Paragraph number 2:
1660 \\ * Bullet point
1661 \\ * Bullet point
1662 \\
1663 \\ Example:
1664 \\ something something
1665 \\ something
1666 \\
1667 \\
1668 \\-d, --dd <V3> Both option.
1669 \\
1670 \\
1671 \\-d, --dd <V3>... Both repeated option.
1672 \\
1673 );
1079} 1674}
1080 1675
1081/// Will print a usage message in the following format: 1676/// Will print a usage message in the following format:
@@ -1104,7 +1699,6 @@ pub fn usage(stream: anytype, comptime Id: type, params: []const Param(Id)) !voi
1104 continue; 1699 continue;
1105 1700
1106 const prefix = if (param.names.short) |_| "-" else "--"; 1701 const prefix = if (param.names.short) |_| "-" else "--";
1107
1108 const name = if (param.names.short) |*s| 1702 const name = if (param.names.short) |*s|
1109 // Seems the zig compiler is being a little wierd. I doesn't allow me to write 1703 // Seems the zig compiler is being a little wierd. I doesn't allow me to write
1110 // @as(*const [1]u8, s) 1704 // @as(*const [1]u8, s)