diff options
| -rw-r--r-- | README.md | 87 | ||||
| -rw-r--r-- | build.zig | 12 | ||||
| -rw-r--r-- | build.zig.zon | 2 | ||||
| -rw-r--r-- | clap.zig | 128 | ||||
| -rw-r--r-- | clap/codepoint_counting_writer.zig | 68 | ||||
| -rw-r--r-- | clap/streaming.zig | 6 | ||||
| -rw-r--r-- | example/help.zig | 8 | ||||
| -rw-r--r-- | example/simple-ex.zig | 6 | ||||
| -rw-r--r-- | example/simple.zig | 6 | ||||
| -rw-r--r-- | example/streaming-clap.zig | 6 | ||||
| -rw-r--r-- | example/subcommands.zig | 13 | ||||
| -rw-r--r-- | example/usage.zig | 9 |
12 files changed, 201 insertions, 150 deletions
| @@ -5,8 +5,9 @@ A simple and easy to use command line argument parser library for Zig. | |||
| 5 | ## Installation | 5 | ## Installation |
| 6 | 6 | ||
| 7 | Developers tend to either use | 7 | Developers tend to either use |
| 8 | * The latest tagged release of Zig | 8 | |
| 9 | * The latest build of Zigs master branch | 9 | - The latest tagged release of Zig |
| 10 | - The latest build of Zigs master branch | ||
| 10 | 11 | ||
| 11 | Depending on which developer you are, you need to run different `zig fetch` commands: | 12 | Depending on which developer you are, you need to run different `zig fetch` commands: |
| 12 | 13 | ||
| @@ -29,22 +30,22 @@ exe.root_module.addImport("clap", clap.module("clap")); | |||
| 29 | 30 | ||
| 30 | ## Features | 31 | ## Features |
| 31 | 32 | ||
| 32 | * Short arguments `-a` | 33 | - Short arguments `-a` |
| 33 | * Chaining `-abc` where `a` and `b` does not take values. | 34 | - Chaining `-abc` where `a` and `b` does not take values. |
| 34 | * Multiple specifications are tallied (e.g. `-v -v`). | 35 | - Multiple specifications are tallied (e.g. `-v -v`). |
| 35 | * Long arguments `--long` | 36 | - Long arguments `--long` |
| 36 | * Supports both passing values using spacing and `=` (`-a 100`, `-a=100`) | 37 | - Supports both passing values using spacing and `=` (`-a 100`, `-a=100`) |
| 37 | * Short args also support passing values with no spacing or `=` (`-a100`) | 38 | - Short args also support passing values with no spacing or `=` (`-a100`) |
| 38 | * This all works with chaining (`-ba 100`, `-ba=100`, `-ba100`) | 39 | - This all works with chaining (`-ba 100`, `-ba=100`, `-ba100`) |
| 39 | * Supports options that can be specified multiple times (`-e 1 -e 2 -e 3`) | 40 | - Supports options that can be specified multiple times (`-e 1 -e 2 -e 3`) |
| 40 | * Print help message from parameter specification. | 41 | - Print help message from parameter specification. |
| 41 | * Parse help message to parameter specification. | 42 | - Parse help message to parameter specification. |
| 42 | 43 | ||
| 43 | ## API Reference | 44 | ## API Reference |
| 44 | 45 | ||
| 45 | Automatically generated API Reference for the project can be found at | 46 | Automatically generated API Reference for the project can be found at |
| 46 | https://Hejsil.github.io/zig-clap. Note that Zig autodoc is in beta; the website | 47 | https://Hejsil.github.io/zig-clap. Note that Zig autodoc is in beta; the website may be broken or |
| 47 | may be broken or incomplete. | 48 | incomplete. |
| 48 | 49 | ||
| 49 | ## Examples | 50 | ## Examples |
| 50 | 51 | ||
| @@ -76,7 +77,10 @@ pub fn main() !void { | |||
| 76 | .allocator = gpa.allocator(), | 77 | .allocator = gpa.allocator(), |
| 77 | }) catch |err| { | 78 | }) catch |err| { |
| 78 | // Report useful error and exit. | 79 | // Report useful error and exit. |
| 79 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 80 | var buf: [1024]u8 = undefined; |
| 81 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 82 | diag.report(&stderr.interface, err) catch {}; | ||
| 83 | try stderr.interface.flush(); | ||
| 80 | return err; | 84 | return err; |
| 81 | }; | 85 | }; |
| 82 | defer res.deinit(); | 86 | defer res.deinit(); |
| @@ -93,7 +97,6 @@ pub fn main() !void { | |||
| 93 | 97 | ||
| 94 | const clap = @import("clap"); | 98 | const clap = @import("clap"); |
| 95 | const std = @import("std"); | 99 | const std = @import("std"); |
| 96 | |||
| 97 | ``` | 100 | ``` |
| 98 | 101 | ||
| 99 | The result will contain an `args` field and a `positionals` field. `args` will have one field for | 102 | The result will contain an `args` field and a `positionals` field. `args` will have one field for |
| @@ -141,7 +144,10 @@ pub fn main() !void { | |||
| 141 | // allowed. | 144 | // allowed. |
| 142 | .assignment_separators = "=:", | 145 | .assignment_separators = "=:", |
| 143 | }) catch |err| { | 146 | }) catch |err| { |
| 144 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 147 | var buf: [1024]u8 = undefined; |
| 148 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 149 | diag.report(&stderr.interface, err) catch {}; | ||
| 150 | try stderr.interface.flush(); | ||
| 145 | return err; | 151 | return err; |
| 146 | }; | 152 | }; |
| 147 | defer res.deinit(); | 153 | defer res.deinit(); |
| @@ -160,7 +166,6 @@ pub fn main() !void { | |||
| 160 | 166 | ||
| 161 | const clap = @import("clap"); | 167 | const clap = @import("clap"); |
| 162 | const std = @import("std"); | 168 | const std = @import("std"); |
| 163 | |||
| 164 | ``` | 169 | ``` |
| 165 | 170 | ||
| 166 | ### Subcommands | 171 | ### Subcommands |
| @@ -212,7 +217,10 @@ pub fn main() !void { | |||
| 212 | // not fully consumed. It can then be reused to parse the arguments for subcommands. | 217 | // not fully consumed. It can then be reused to parse the arguments for subcommands. |
| 213 | .terminating_positional = 0, | 218 | .terminating_positional = 0, |
| 214 | }) catch |err| { | 219 | }) catch |err| { |
| 215 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 220 | var buf: [1024]u8 = undefined; |
| 221 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 222 | diag.report(&stderr.interface, err) catch {}; | ||
| 223 | try stderr.interface.flush(); | ||
| 216 | return err; | 224 | return err; |
| 217 | }; | 225 | }; |
| 218 | defer res.deinit(); | 226 | defer res.deinit(); |
| @@ -248,8 +256,11 @@ fn mathMain(gpa: std.mem.Allocator, iter: *std.process.ArgIterator, main_args: M | |||
| 248 | .diagnostic = &diag, | 256 | .diagnostic = &diag, |
| 249 | .allocator = gpa, | 257 | .allocator = gpa, |
| 250 | }) catch |err| { | 258 | }) catch |err| { |
| 251 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 259 | var buf: [1024]u8 = undefined; |
| 252 | return err; | 260 | var stderr = std.fs.File.stderr().writer(&buf); |
| 261 | diag.report(&stderr.interface, err) catch {}; | ||
| 262 | try stderr.interface.flush(); | ||
| 263 | return err; // propagate error | ||
| 253 | }; | 264 | }; |
| 254 | defer res.deinit(); | 265 | defer res.deinit(); |
| 255 | 266 | ||
| @@ -265,7 +276,6 @@ fn mathMain(gpa: std.mem.Allocator, iter: *std.process.ArgIterator, main_args: M | |||
| 265 | 276 | ||
| 266 | const clap = @import("clap"); | 277 | const clap = @import("clap"); |
| 267 | const std = @import("std"); | 278 | const std = @import("std"); |
| 268 | |||
| 269 | ``` | 279 | ``` |
| 270 | 280 | ||
| 271 | ### `streaming.Clap` | 281 | ### `streaming.Clap` |
| @@ -310,7 +320,10 @@ pub fn main() !void { | |||
| 310 | // Because we use a streaming parser, we have to consume each argument parsed individually. | 320 | // Because we use a streaming parser, we have to consume each argument parsed individually. |
| 311 | while (parser.next() catch |err| { | 321 | while (parser.next() catch |err| { |
| 312 | // Report useful error and exit. | 322 | // Report useful error and exit. |
| 313 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 323 | var buf: [1024]u8 = undefined; |
| 324 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 325 | diag.report(&stderr.interface, err) catch {}; | ||
| 326 | try stderr.interface.flush(); | ||
| 314 | return err; | 327 | return err; |
| 315 | }) |arg| { | 328 | }) |arg| { |
| 316 | // arg.param will point to the parameter which matched the argument. | 329 | // arg.param will point to the parameter which matched the argument. |
| @@ -329,10 +342,17 @@ pub fn main() !void { | |||
| 329 | 342 | ||
| 330 | const clap = @import("clap"); | 343 | const clap = @import("clap"); |
| 331 | const std = @import("std"); | 344 | const std = @import("std"); |
| 345 | ``` | ||
| 332 | 346 | ||
| 333 | ``` | 347 | ``` |
| 348 | $ zig-out/bin/streaming-clap --help --number=1 f=10 | ||
| 349 | Help! | ||
| 350 | --number = 1 | ||
| 351 | f=10 | ||
| 352 | ``` | ||
| 334 | 353 | ||
| 335 | Currently, this parser is the only parser that allows an array of `Param` that is generated at runtime. | 354 | Currently, this parser is the only parser that allows an array of `Param` that is generated at |
| 355 | runtime. | ||
| 336 | 356 | ||
| 337 | ### `help` | 357 | ### `help` |
| 338 | 358 | ||
| @@ -360,13 +380,16 @@ pub fn main() !void { | |||
| 360 | // where `Id` has a `description` and `value` method (`Param(Help)` is one such parameter). | 380 | // where `Id` has a `description` and `value` method (`Param(Help)` is one such parameter). |
| 361 | // The last argument contains options as to how `help` should print those parameters. Using | 381 | // The last argument contains options as to how `help` should print those parameters. Using |
| 362 | // `.{}` means the default options. | 382 | // `.{}` means the default options. |
| 363 | if (res.args.help != 0) | 383 | if (res.args.help != 0) { |
| 364 | return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{}); | 384 | var buf: [1024]u8 = undefined; |
| 385 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 386 | try clap.help(&stderr.interface, clap.Help, ¶ms, .{}); | ||
| 387 | return stderr.interface.flush(); | ||
| 388 | } | ||
| 365 | } | 389 | } |
| 366 | 390 | ||
| 367 | const clap = @import("clap"); | 391 | const clap = @import("clap"); |
| 368 | const std = @import("std"); | 392 | const std = @import("std"); |
| 369 | |||
| 370 | ``` | 393 | ``` |
| 371 | 394 | ||
| 372 | ``` | 395 | ``` |
| @@ -402,17 +425,19 @@ pub fn main() !void { | |||
| 402 | 425 | ||
| 403 | // `clap.usage` is a function that can print a simple help message. It can print any `Param` | 426 | // `clap.usage` is a function that can print a simple help message. It can print any `Param` |
| 404 | // where `Id` has a `value` method (`Param(Help)` is one such parameter). | 427 | // where `Id` has a `value` method (`Param(Help)` is one such parameter). |
| 405 | if (res.args.help != 0) | 428 | if (res.args.help != 0) { |
| 406 | return clap.usage(std.io.getStdErr().writer(), clap.Help, ¶ms); | 429 | var buf: [1024]u8 = undefined; |
| 430 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 431 | try clap.usage(&stderr.interface, clap.Help, ¶ms); | ||
| 432 | return stderr.interface.flush(); | ||
| 433 | } | ||
| 407 | } | 434 | } |
| 408 | 435 | ||
| 409 | const clap = @import("clap"); | 436 | const clap = @import("clap"); |
| 410 | const std = @import("std"); | 437 | const std = @import("std"); |
| 411 | |||
| 412 | ``` | 438 | ``` |
| 413 | 439 | ||
| 414 | ``` | 440 | ``` |
| 415 | $ zig-out/bin/usage --help | 441 | $ zig-out/bin/usage --help |
| 416 | [-hv] [--value <str>] | 442 | [-hv] [--value <str>] |
| 417 | ``` | 443 | ``` |
| 418 | |||
| @@ -24,12 +24,16 @@ pub fn build(b: *std.Build) void { | |||
| 24 | }) |example_name| { | 24 | }) |example_name| { |
| 25 | const example = b.addExecutable(.{ | 25 | const example = b.addExecutable(.{ |
| 26 | .name = example_name, | 26 | .name = example_name, |
| 27 | .root_source_file = b.path(b.fmt("example/{s}.zig", .{example_name})), | 27 | .root_module = b.createModule(.{ |
| 28 | .target = target, | 28 | .root_source_file = b.path(b.fmt("example/{s}.zig", .{example_name})), |
| 29 | .optimize = optimize, | 29 | .target = target, |
| 30 | .optimize = optimize, | ||
| 31 | .imports = &.{ | ||
| 32 | .{ .name = "clap", .module = clap_mod }, | ||
| 33 | }, | ||
| 34 | }), | ||
| 30 | }); | 35 | }); |
| 31 | const install_example = b.addInstallArtifact(example, .{}); | 36 | const install_example = b.addInstallArtifact(example, .{}); |
| 32 | example.root_module.addImport("clap", clap_mod); | ||
| 33 | example_step.dependOn(&example.step); | 37 | example_step.dependOn(&example.step); |
| 34 | example_step.dependOn(&install_example.step); | 38 | example_step.dependOn(&install_example.step); |
| 35 | } | 39 | } |
diff --git a/build.zig.zon b/build.zig.zon index 0fc81aa..883ed5e 100644 --- a/build.zig.zon +++ b/build.zig.zon | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | .{ | 1 | .{ |
| 2 | .name = .clap, | 2 | .name = .clap, |
| 3 | .version = "0.10.0", | 3 | .version = "0.10.0", |
| 4 | .minimum_zig_version = "0.14.0", | 4 | .minimum_zig_version = "0.15.0-dev.1147+69cf40da6", |
| 5 | .fingerprint = 0x65f99e6f07a316a0, | 5 | .fingerprint = 0x65f99e6f07a316a0, |
| 6 | .paths = .{ | 6 | .paths = .{ |
| 7 | "clap", | 7 | "clap", |
| @@ -542,7 +542,7 @@ pub const Diagnostic = struct { | |||
| 542 | 542 | ||
| 543 | /// Default diagnostics reporter when all you want is English with no colors. | 543 | /// Default diagnostics reporter when all you want is English with no colors. |
| 544 | /// Use this as a reference for implementing your own if needed. | 544 | /// Use this as a reference for implementing your own if needed. |
| 545 | pub fn report(diag: Diagnostic, stream: anytype, err: anyerror) !void { | 545 | pub fn report(diag: Diagnostic, stream: *std.Io.Writer, err: anyerror) !void { |
| 546 | var longest = diag.name.longest(); | 546 | var longest = diag.name.longest(); |
| 547 | if (longest.kind == .positional) | 547 | if (longest.kind == .positional) |
| 548 | longest.name = diag.arg; | 548 | longest.name = diag.arg; |
| @@ -567,9 +567,9 @@ pub const Diagnostic = struct { | |||
| 567 | 567 | ||
| 568 | fn testDiag(diag: Diagnostic, err: anyerror, expected: []const u8) !void { | 568 | fn testDiag(diag: Diagnostic, err: anyerror, expected: []const u8) !void { |
| 569 | var buf: [1024]u8 = undefined; | 569 | var buf: [1024]u8 = undefined; |
| 570 | var slice_stream = std.io.fixedBufferStream(&buf); | 570 | var writer = std.Io.Writer.fixed(&buf); |
| 571 | diag.report(slice_stream.writer(), err) catch unreachable; | 571 | diag.report(&writer, err) catch unreachable; |
| 572 | try std.testing.expectEqualStrings(expected, slice_stream.getWritten()); | 572 | try std.testing.expectEqualStrings(expected, writer.buffered()); |
| 573 | } | 573 | } |
| 574 | 574 | ||
| 575 | test "Diagnostic.report" { | 575 | test "Diagnostic.report" { |
| @@ -1263,9 +1263,9 @@ fn testErr( | |||
| 1263 | .diagnostic = &diag, | 1263 | .diagnostic = &diag, |
| 1264 | }) catch |err| { | 1264 | }) catch |err| { |
| 1265 | var buf: [1024]u8 = undefined; | 1265 | var buf: [1024]u8 = undefined; |
| 1266 | var fbs = std.io.fixedBufferStream(&buf); | 1266 | var writer = std.Io.Writer.fixed(&buf); |
| 1267 | diag.report(fbs.writer(), err) catch return error.TestFailed; | 1267 | diag.report(&writer, err) catch return error.TestFailed; |
| 1268 | try std.testing.expectEqualStrings(expected, fbs.getWritten()); | 1268 | try std.testing.expectEqualStrings(expected, writer.buffered()); |
| 1269 | return; | 1269 | return; |
| 1270 | }; | 1270 | }; |
| 1271 | 1271 | ||
| @@ -1366,7 +1366,7 @@ pub const HelpOptions = struct { | |||
| 1366 | /// The output can be constumized with the `opt` parameter. For default formatting `.{}` can | 1366 | /// The output can be constumized with the `opt` parameter. For default formatting `.{}` can |
| 1367 | /// be passed. | 1367 | /// be passed. |
| 1368 | pub fn help( | 1368 | pub fn help( |
| 1369 | writer: anytype, | 1369 | writer: *std.Io.Writer, |
| 1370 | comptime Id: type, | 1370 | comptime Id: type, |
| 1371 | params: []const Param(Id), | 1371 | params: []const Param(Id), |
| 1372 | opt: HelpOptions, | 1372 | opt: HelpOptions, |
| @@ -1374,8 +1374,9 @@ pub fn help( | |||
| 1374 | const max_spacing = blk: { | 1374 | const max_spacing = blk: { |
| 1375 | var res: usize = 0; | 1375 | var res: usize = 0; |
| 1376 | for (params) |param| { | 1376 | for (params) |param| { |
| 1377 | var cs = ccw.codepointCountingWriter(std.io.null_writer); | 1377 | var discarding = std.Io.Writer.Discarding.init(&.{}); |
| 1378 | try printParam(cs.writer(), Id, param); | 1378 | var cs = ccw.CodepointCountingWriter.init(&discarding.writer); |
| 1379 | try printParam(&cs.interface, Id, param); | ||
| 1379 | if (res < cs.codepoints_written) | 1380 | if (res < cs.codepoints_written) |
| 1380 | res = @intCast(cs.codepoints_written); | 1381 | res = @intCast(cs.codepoints_written); |
| 1381 | } | 1382 | } |
| @@ -1390,16 +1391,15 @@ pub fn help( | |||
| 1390 | var first_parameter: bool = true; | 1391 | var first_parameter: bool = true; |
| 1391 | for (params) |param| { | 1392 | for (params) |param| { |
| 1392 | if (!first_parameter) | 1393 | if (!first_parameter) |
| 1393 | try writer.writeByteNTimes('\n', opt.spacing_between_parameters); | 1394 | try writer.splatByteAll('\n', opt.spacing_between_parameters); |
| 1394 | 1395 | ||
| 1395 | first_parameter = false; | 1396 | first_parameter = false; |
| 1396 | try writer.writeByteNTimes(' ', opt.indent); | 1397 | try writer.splatByteAll(' ', opt.indent); |
| 1397 | 1398 | ||
| 1398 | var cw = ccw.codepointCountingWriter(writer); | 1399 | var cw = ccw.CodepointCountingWriter.init(writer); |
| 1399 | try printParam(cw.writer(), Id, param); | 1400 | try printParam(&cw.interface, Id, param); |
| 1400 | 1401 | ||
| 1401 | const Writer = DescriptionWriter(@TypeOf(writer)); | 1402 | var description_writer = DescriptionWriter{ |
| 1402 | var description_writer = Writer{ | ||
| 1403 | .underlying_writer = writer, | 1403 | .underlying_writer = writer, |
| 1404 | .indentation = description_indentation, | 1404 | .indentation = description_indentation, |
| 1405 | .printed_chars = @intCast(cw.codepoints_written), | 1405 | .printed_chars = @intCast(cw.codepoints_written), |
| @@ -1498,57 +1498,53 @@ pub fn help( | |||
| 1498 | } | 1498 | } |
| 1499 | } | 1499 | } |
| 1500 | 1500 | ||
| 1501 | fn DescriptionWriter(comptime UnderlyingWriter: type) type { | 1501 | const DescriptionWriter = struct { |
| 1502 | return struct { | 1502 | underlying_writer: *std.Io.Writer, |
| 1503 | pub const WriteError = UnderlyingWriter.Error; | ||
| 1504 | |||
| 1505 | underlying_writer: UnderlyingWriter, | ||
| 1506 | |||
| 1507 | indentation: usize, | ||
| 1508 | max_width: usize, | ||
| 1509 | printed_chars: usize, | ||
| 1510 | |||
| 1511 | pub fn writeWord(writer: *@This(), word: []const u8) !void { | ||
| 1512 | std.debug.assert(word.len != 0); | ||
| 1513 | 1503 | ||
| 1514 | var first_word = writer.printed_chars <= writer.indentation; | 1504 | indentation: usize, |
| 1515 | const chars_to_write = try std.unicode.utf8CountCodepoints(word) + @intFromBool(!first_word); | 1505 | max_width: usize, |
| 1516 | if (chars_to_write + writer.printed_chars > writer.max_width) { | 1506 | printed_chars: usize, |
| 1517 | // If the word does not fit on this line, then we insert a new line and print | ||
| 1518 | // it on that line. The only exception to this is if this was the first word. | ||
| 1519 | // If the first word does not fit on this line, then it will also not fit on the | ||
| 1520 | // next one. In that case, all we can really do is just output the word. | ||
| 1521 | if (!first_word) | ||
| 1522 | try writer.newline(); | ||
| 1523 | 1507 | ||
| 1524 | first_word = true; | 1508 | pub fn writeWord(writer: *@This(), word: []const u8) !void { |
| 1525 | } | 1509 | std.debug.assert(word.len != 0); |
| 1526 | 1510 | ||
| 1511 | var first_word = writer.printed_chars <= writer.indentation; | ||
| 1512 | const chars_to_write = try std.unicode.utf8CountCodepoints(word) + @intFromBool(!first_word); | ||
| 1513 | if (chars_to_write + writer.printed_chars > writer.max_width) { | ||
| 1514 | // If the word does not fit on this line, then we insert a new line and print | ||
| 1515 | // it on that line. The only exception to this is if this was the first word. | ||
| 1516 | // If the first word does not fit on this line, then it will also not fit on the | ||
| 1517 | // next one. In that case, all we can really do is just output the word. | ||
| 1527 | if (!first_word) | 1518 | if (!first_word) |
| 1528 | try writer.underlying_writer.writeAll(" "); | 1519 | try writer.newline(); |
| 1529 | 1520 | ||
| 1530 | try writer.ensureIndented(); | 1521 | first_word = true; |
| 1531 | try writer.underlying_writer.writeAll(word); | ||
| 1532 | writer.printed_chars += chars_to_write; | ||
| 1533 | } | 1522 | } |
| 1534 | 1523 | ||
| 1535 | pub fn newline(writer: *@This()) !void { | 1524 | if (!first_word) |
| 1536 | try writer.underlying_writer.writeAll("\n"); | 1525 | try writer.underlying_writer.writeAll(" "); |
| 1537 | writer.printed_chars = 0; | ||
| 1538 | } | ||
| 1539 | 1526 | ||
| 1540 | fn ensureIndented(writer: *@This()) !void { | 1527 | try writer.ensureIndented(); |
| 1541 | if (writer.printed_chars < writer.indentation) { | 1528 | try writer.underlying_writer.writeAll(word); |
| 1542 | const to_indent = writer.indentation - writer.printed_chars; | 1529 | writer.printed_chars += chars_to_write; |
| 1543 | try writer.underlying_writer.writeByteNTimes(' ', to_indent); | 1530 | } |
| 1544 | writer.printed_chars += to_indent; | 1531 | |
| 1545 | } | 1532 | pub fn newline(writer: *@This()) !void { |
| 1533 | try writer.underlying_writer.writeAll("\n"); | ||
| 1534 | writer.printed_chars = 0; | ||
| 1535 | } | ||
| 1536 | |||
| 1537 | fn ensureIndented(writer: *@This()) !void { | ||
| 1538 | if (writer.printed_chars < writer.indentation) { | ||
| 1539 | const to_indent = writer.indentation - writer.printed_chars; | ||
| 1540 | try writer.underlying_writer.splatByteAll(' ', to_indent); | ||
| 1541 | writer.printed_chars += to_indent; | ||
| 1546 | } | 1542 | } |
| 1547 | }; | 1543 | } |
| 1548 | } | 1544 | }; |
| 1549 | 1545 | ||
| 1550 | fn printParam( | 1546 | fn printParam( |
| 1551 | stream: anytype, | 1547 | stream: *std.Io.Writer, |
| 1552 | comptime Id: type, | 1548 | comptime Id: type, |
| 1553 | param: Param(Id), | 1549 | param: Param(Id), |
| 1554 | ) !void { | 1550 | ) !void { |
| @@ -1583,9 +1579,9 @@ fn testHelp(opt: HelpOptions, str: []const u8) !void { | |||
| 1583 | defer std.testing.allocator.free(params); | 1579 | defer std.testing.allocator.free(params); |
| 1584 | 1580 | ||
| 1585 | var buf: [2048]u8 = undefined; | 1581 | var buf: [2048]u8 = undefined; |
| 1586 | var fbs = std.io.fixedBufferStream(&buf); | 1582 | var writer = std.Io.Writer.fixed(&buf); |
| 1587 | try help(fbs.writer(), Help, params, opt); | 1583 | try help(&writer, Help, params, opt); |
| 1588 | try std.testing.expectEqualStrings(str, fbs.getWritten()); | 1584 | try std.testing.expectEqualStrings(str, writer.buffered()); |
| 1589 | } | 1585 | } |
| 1590 | 1586 | ||
| 1591 | test "clap.help" { | 1587 | test "clap.help" { |
| @@ -2015,9 +2011,9 @@ test "clap.help" { | |||
| 2015 | /// | 2011 | /// |
| 2016 | /// First all none value taking parameters, which have a short name are printed, then non | 2012 | /// First all none value taking parameters, which have a short name are printed, then non |
| 2017 | /// positional parameters and finally the positional. | 2013 | /// positional parameters and finally the positional. |
| 2018 | pub fn usage(stream: anytype, comptime Id: type, params: []const Param(Id)) !void { | 2014 | pub fn usage(stream: *std.Io.Writer, comptime Id: type, params: []const Param(Id)) !void { |
| 2019 | var cos = ccw.codepointCountingWriter(stream); | 2015 | var cos = ccw.CodepointCountingWriter.init(stream); |
| 2020 | const cs = cos.writer(); | 2016 | const cs = &cos.interface; |
| 2021 | for (params) |param| { | 2017 | for (params) |param| { |
| 2022 | const name = param.names.short orelse continue; | 2018 | const name = param.names.short orelse continue; |
| 2023 | if (param.takes_value != .none) | 2019 | if (param.takes_value != .none) |
| @@ -2060,7 +2056,7 @@ pub fn usage(stream: anytype, comptime Id: type, params: []const Param(Id)) !voi | |||
| 2060 | try cs.writeAll("..."); | 2056 | try cs.writeAll("..."); |
| 2061 | } | 2057 | } |
| 2062 | 2058 | ||
| 2063 | try cs.writeByte(']'); | 2059 | try cs.writeAll("]"); |
| 2064 | } | 2060 | } |
| 2065 | 2061 | ||
| 2066 | if (!has_positionals) | 2062 | if (!has_positionals) |
| @@ -2083,9 +2079,9 @@ pub fn usage(stream: anytype, comptime Id: type, params: []const Param(Id)) !voi | |||
| 2083 | 2079 | ||
| 2084 | fn testUsage(expected: []const u8, params: []const Param(Help)) !void { | 2080 | fn testUsage(expected: []const u8, params: []const Param(Help)) !void { |
| 2085 | var buf: [1024]u8 = undefined; | 2081 | var buf: [1024]u8 = undefined; |
| 2086 | var fbs = std.io.fixedBufferStream(&buf); | 2082 | var writer = std.Io.Writer.fixed(&buf); |
| 2087 | try usage(fbs.writer(), Help, params); | 2083 | try usage(&writer, Help, params); |
| 2088 | try std.testing.expectEqualStrings(expected, fbs.getWritten()); | 2084 | try std.testing.expectEqualStrings(expected, writer.buffered()); |
| 2089 | } | 2085 | } |
| 2090 | 2086 | ||
| 2091 | test "usage" { | 2087 | test "usage" { |
diff --git a/clap/codepoint_counting_writer.zig b/clap/codepoint_counting_writer.zig index c445c90..3518d48 100644 --- a/clap/codepoint_counting_writer.zig +++ b/clap/codepoint_counting_writer.zig | |||
| @@ -1,30 +1,39 @@ | |||
| 1 | /// A Writer that counts how many codepoints has been written to it. | 1 | /// A Writer that counts how many codepoints has been written to it. |
| 2 | /// Expects valid UTF-8 input, and does not validate the input. | 2 | /// Expects valid UTF-8 input, and does not validate the input. |
| 3 | pub fn CodepointCountingWriter(comptime WriterType: type) type { | 3 | pub const CodepointCountingWriter = struct { |
| 4 | return struct { | 4 | codepoints_written: u64 = 0, |
| 5 | codepoints_written: u64, | 5 | child_stream: *std.Io.Writer, |
| 6 | child_stream: WriterType, | 6 | interface: std.Io.Writer = .{ |
| 7 | 7 | .buffer = &.{}, | |
| 8 | pub const Error = WriterType.Error || error{Utf8InvalidStartByte}; | 8 | .vtable = &.{ .drain = drain }, |
| 9 | pub const Writer = std.io.Writer(*Self, Error, write); | 9 | }, |
| 10 | |||
| 11 | const Self = @This(); | ||
| 12 | |||
| 13 | pub fn init(child_stream: *std.Io.Writer) Self { | ||
| 14 | return .{ | ||
| 15 | .child_stream = child_stream, | ||
| 16 | }; | ||
| 17 | } | ||
| 10 | 18 | ||
| 11 | const Self = @This(); | 19 | fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { |
| 20 | const self: *Self = @fieldParentPtr("interface", w); | ||
| 21 | var n_bytes_written: usize = 0; | ||
| 22 | var i: usize = 0; | ||
| 12 | 23 | ||
| 13 | pub fn write(self: *Self, bytes: []const u8) Error!usize { | 24 | while (i < data.len + splat - 1) : (i += 1) { |
| 14 | const bytes_and_codepoints = try utf8CountCodepointsAllowTruncate(bytes); | 25 | const chunk = data[@min(i, data.len)]; |
| 26 | const bytes_and_codepoints = utf8CountCodepointsAllowTruncate(chunk) catch return std.Io.Writer.Error.WriteFailed; | ||
| 15 | // Might not be the full input, so the leftover bytes are written on the next call. | 27 | // Might not be the full input, so the leftover bytes are written on the next call. |
| 16 | const bytes_to_write = bytes[0..bytes_and_codepoints.bytes]; | 28 | const bytes_to_write = chunk[0..bytes_and_codepoints.bytes]; |
| 17 | const amt = try self.child_stream.write(bytes_to_write); | 29 | const amt = try self.child_stream.write(bytes_to_write); |
| 30 | n_bytes_written += amt; | ||
| 18 | const bytes_written = bytes_to_write[0..amt]; | 31 | const bytes_written = bytes_to_write[0..amt]; |
| 19 | self.codepoints_written += (try utf8CountCodepointsAllowTruncate(bytes_written)).codepoints; | 32 | self.codepoints_written += (utf8CountCodepointsAllowTruncate(bytes_written) catch return std.Io.Writer.Error.WriteFailed).codepoints; |
| 20 | return amt; | ||
| 21 | } | ||
| 22 | |||
| 23 | pub fn writer(self: *Self) Writer { | ||
| 24 | return .{ .context = self }; | ||
| 25 | } | 33 | } |
| 26 | }; | 34 | return n_bytes_written; |
| 27 | } | 35 | } |
| 36 | }; | ||
| 28 | 37 | ||
| 29 | // Like `std.unicode.utf8CountCodepoints`, but on truncated input, it returns | 38 | // Like `std.unicode.utf8CountCodepoints`, but on truncated input, it returns |
| 30 | // the number of codepoints up to that point. | 39 | // the number of codepoints up to that point. |
| @@ -58,44 +67,39 @@ fn utf8CountCodepointsAllowTruncate(s: []const u8) !struct { bytes: usize, codep | |||
| 58 | return .{ .bytes = i, .codepoints = len }; | 67 | return .{ .bytes = i, .codepoints = len }; |
| 59 | } | 68 | } |
| 60 | 69 | ||
| 61 | pub fn codepointCountingWriter(child_stream: anytype) CodepointCountingWriter(@TypeOf(child_stream)) { | ||
| 62 | return .{ .codepoints_written = 0, .child_stream = child_stream }; | ||
| 63 | } | ||
| 64 | |||
| 65 | const testing = std.testing; | 70 | const testing = std.testing; |
| 66 | 71 | ||
| 67 | test CodepointCountingWriter { | 72 | test CodepointCountingWriter { |
| 68 | var counting_stream = codepointCountingWriter(std.io.null_writer); | 73 | var discarding = std.Io.Writer.Discarding.init(&.{}); |
| 69 | const stream = counting_stream.writer(); | 74 | var counting_stream = CodepointCountingWriter.init(&discarding.writer); |
| 70 | 75 | ||
| 71 | const utf8_text = "blåhaj" ** 100; | 76 | const utf8_text = "blåhaj" ** 100; |
| 72 | stream.writeAll(utf8_text) catch unreachable; | 77 | counting_stream.interface.writeAll(utf8_text) catch unreachable; |
| 73 | const expected_count = try std.unicode.utf8CountCodepoints(utf8_text); | 78 | const expected_count = try std.unicode.utf8CountCodepoints(utf8_text); |
| 74 | try testing.expectEqual(expected_count, counting_stream.codepoints_written); | 79 | try testing.expectEqual(expected_count, counting_stream.codepoints_written); |
| 75 | } | 80 | } |
| 76 | 81 | ||
| 77 | test "handles partial UTF-8 writes" { | 82 | test "handles partial UTF-8 writes" { |
| 78 | var buf: [100]u8 = undefined; | 83 | var buf: [100]u8 = undefined; |
| 79 | var fbs = std.io.fixedBufferStream(&buf); | 84 | var fbs = std.Io.Writer.fixed(&buf); |
| 80 | var counting_stream = codepointCountingWriter(fbs.writer()); | 85 | var counting_stream = CodepointCountingWriter.init(&fbs); |
| 81 | const stream = counting_stream.writer(); | ||
| 82 | 86 | ||
| 83 | const utf8_text = "ååå"; | 87 | const utf8_text = "ååå"; |
| 84 | // `å` is represented as `\xC5\xA5`, write 1.5 `å`s. | 88 | // `å` is represented as `\xC5\xA5`, write 1.5 `å`s. |
| 85 | var wc = try stream.write(utf8_text[0..3]); | 89 | var wc = try counting_stream.interface.write(utf8_text[0..3]); |
| 86 | // One should have been written fully. | 90 | // One should have been written fully. |
| 87 | try testing.expectEqual("å".len, wc); | 91 | try testing.expectEqual("å".len, wc); |
| 88 | try testing.expectEqual(1, counting_stream.codepoints_written); | 92 | try testing.expectEqual(1, counting_stream.codepoints_written); |
| 89 | 93 | ||
| 90 | // Write the rest, continuing from the reported number of bytes written. | 94 | // Write the rest, continuing from the reported number of bytes written. |
| 91 | wc = try stream.write(utf8_text[wc..]); | 95 | wc = try counting_stream.interface.write(utf8_text[wc..]); |
| 92 | try testing.expectEqual(4, wc); | 96 | try testing.expectEqual(4, wc); |
| 93 | try testing.expectEqual(3, counting_stream.codepoints_written); | 97 | try testing.expectEqual(3, counting_stream.codepoints_written); |
| 94 | 98 | ||
| 95 | const expected_count = try std.unicode.utf8CountCodepoints(utf8_text); | 99 | const expected_count = try std.unicode.utf8CountCodepoints(utf8_text); |
| 96 | try testing.expectEqual(expected_count, counting_stream.codepoints_written); | 100 | try testing.expectEqual(expected_count, counting_stream.codepoints_written); |
| 97 | 101 | ||
| 98 | try testing.expectEqualSlices(u8, utf8_text, fbs.getWritten()); | 102 | try testing.expectEqualSlices(u8, utf8_text, fbs.buffered()); |
| 99 | } | 103 | } |
| 100 | 104 | ||
| 101 | const std = @import("std"); | 105 | const std = @import("std"); |
diff --git a/clap/streaming.zig b/clap/streaming.zig index 4a687a2..fa5ec70 100644 --- a/clap/streaming.zig +++ b/clap/streaming.zig | |||
| @@ -234,9 +234,9 @@ fn expectError( | |||
| 234 | 234 | ||
| 235 | while (parser.next() catch |err| { | 235 | while (parser.next() catch |err| { |
| 236 | var buf: [1024]u8 = undefined; | 236 | var buf: [1024]u8 = undefined; |
| 237 | var fbs = std.io.fixedBufferStream(&buf); | 237 | var fbs = std.Io.Writer.fixed(&buf); |
| 238 | diag.report(fbs.writer(), err) catch return error.TestFailed; | 238 | diag.report(&fbs, err) catch return error.TestFailed; |
| 239 | try std.testing.expectEqualStrings(expected, fbs.getWritten()); | 239 | try std.testing.expectEqualStrings(expected, fbs.buffered()); |
| 240 | return; | 240 | return; |
| 241 | }) |_| {} | 241 | }) |_| {} |
| 242 | 242 | ||
diff --git a/example/help.zig b/example/help.zig index b80ee35..676a56a 100644 --- a/example/help.zig +++ b/example/help.zig | |||
| @@ -17,8 +17,12 @@ pub fn main() !void { | |||
| 17 | // where `Id` has a `description` and `value` method (`Param(Help)` is one such parameter). | 17 | // where `Id` has a `description` and `value` method (`Param(Help)` is one such parameter). |
| 18 | // The last argument contains options as to how `help` should print those parameters. Using | 18 | // The last argument contains options as to how `help` should print those parameters. Using |
| 19 | // `.{}` means the default options. | 19 | // `.{}` means the default options. |
| 20 | if (res.args.help != 0) | 20 | if (res.args.help != 0) { |
| 21 | return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{}); | 21 | var buf: [1024]u8 = undefined; |
| 22 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 23 | try clap.help(&stderr.interface, clap.Help, ¶ms, .{}); | ||
| 24 | return stderr.interface.flush(); | ||
| 25 | } | ||
| 22 | } | 26 | } |
| 23 | 27 | ||
| 24 | const clap = @import("clap"); | 28 | const clap = @import("clap"); |
diff --git a/example/simple-ex.zig b/example/simple-ex.zig index 22f657f..a993868 100644 --- a/example/simple-ex.zig +++ b/example/simple-ex.zig | |||
| @@ -31,7 +31,11 @@ pub fn main() !void { | |||
| 31 | // allowed. | 31 | // allowed. |
| 32 | .assignment_separators = "=:", | 32 | .assignment_separators = "=:", |
| 33 | }) catch |err| { | 33 | }) catch |err| { |
| 34 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 34 | // Report useful error and exit. |
| 35 | var buf: [1024]u8 = undefined; | ||
| 36 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 37 | try diag.report(&stderr.interface, err); | ||
| 38 | try stderr.interface.flush(); | ||
| 35 | return err; | 39 | return err; |
| 36 | }; | 40 | }; |
| 37 | defer res.deinit(); | 41 | defer res.deinit(); |
diff --git a/example/simple.zig b/example/simple.zig index 2b7bf0a..ca6bd75 100644 --- a/example/simple.zig +++ b/example/simple.zig | |||
| @@ -21,8 +21,10 @@ pub fn main() !void { | |||
| 21 | .allocator = gpa.allocator(), | 21 | .allocator = gpa.allocator(), |
| 22 | }) catch |err| { | 22 | }) catch |err| { |
| 23 | // Report useful error and exit. | 23 | // Report useful error and exit. |
| 24 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 24 | var buf: [1024]u8 = undefined; |
| 25 | return err; | 25 | var stderr = std.fs.File.stderr().writer(&buf); |
| 26 | try diag.report(&stderr.interface, err); | ||
| 27 | return stderr.interface.flush(); | ||
| 26 | }; | 28 | }; |
| 27 | defer res.deinit(); | 29 | defer res.deinit(); |
| 28 | 30 | ||
diff --git a/example/streaming-clap.zig b/example/streaming-clap.zig index 054c401..d60167c 100644 --- a/example/streaming-clap.zig +++ b/example/streaming-clap.zig | |||
| @@ -34,8 +34,10 @@ pub fn main() !void { | |||
| 34 | // Because we use a streaming parser, we have to consume each argument parsed individually. | 34 | // Because we use a streaming parser, we have to consume each argument parsed individually. |
| 35 | while (parser.next() catch |err| { | 35 | while (parser.next() catch |err| { |
| 36 | // Report useful error and exit. | 36 | // Report useful error and exit. |
| 37 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 37 | var buf: [1024]u8 = undefined; |
| 38 | return err; | 38 | var stderr = std.fs.File.stderr().writer(&buf); |
| 39 | try diag.report(&stderr.interface, err); | ||
| 40 | return stderr.interface.flush(); | ||
| 39 | }) |arg| { | 41 | }) |arg| { |
| 40 | // arg.param will point to the parameter which matched the argument. | 42 | // arg.param will point to the parameter which matched the argument. |
| 41 | switch (arg.param.id) { | 43 | switch (arg.param.id) { |
diff --git a/example/subcommands.zig b/example/subcommands.zig index 8223f31..644e371 100644 --- a/example/subcommands.zig +++ b/example/subcommands.zig | |||
| @@ -41,8 +41,10 @@ pub fn main() !void { | |||
| 41 | // not fully consumed. It can then be reused to parse the arguments for subcommands. | 41 | // not fully consumed. It can then be reused to parse the arguments for subcommands. |
| 42 | .terminating_positional = 0, | 42 | .terminating_positional = 0, |
| 43 | }) catch |err| { | 43 | }) catch |err| { |
| 44 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 44 | var buf: [1024]u8 = undefined; |
| 45 | return err; | 45 | var stderr = std.fs.File.stderr().writer(&buf); |
| 46 | try diag.report(&stderr.interface, err); | ||
| 47 | return stderr.interface.flush(); | ||
| 46 | }; | 48 | }; |
| 47 | defer res.deinit(); | 49 | defer res.deinit(); |
| 48 | 50 | ||
| @@ -77,8 +79,11 @@ fn mathMain(gpa: std.mem.Allocator, iter: *std.process.ArgIterator, main_args: M | |||
| 77 | .diagnostic = &diag, | 79 | .diagnostic = &diag, |
| 78 | .allocator = gpa, | 80 | .allocator = gpa, |
| 79 | }) catch |err| { | 81 | }) catch |err| { |
| 80 | diag.report(std.io.getStdErr().writer(), err) catch {}; | 82 | var buf: [1024]u8 = undefined; |
| 81 | return err; | 83 | var stderr = std.fs.File.stderr().writer(&buf); |
| 84 | try diag.report(&stderr.interface, err); | ||
| 85 | try stderr.interface.flush(); | ||
| 86 | return err; // propagate error | ||
| 82 | }; | 87 | }; |
| 83 | defer res.deinit(); | 88 | defer res.deinit(); |
| 84 | 89 | ||
diff --git a/example/usage.zig b/example/usage.zig index 59ac84a..8bd25b7 100644 --- a/example/usage.zig +++ b/example/usage.zig | |||
| @@ -16,8 +16,13 @@ pub fn main() !void { | |||
| 16 | 16 | ||
| 17 | // `clap.usage` is a function that can print a simple help message. It can print any `Param` | 17 | // `clap.usage` is a function that can print a simple help message. It can print any `Param` |
| 18 | // where `Id` has a `value` method (`Param(Help)` is one such parameter). | 18 | // where `Id` has a `value` method (`Param(Help)` is one such parameter). |
| 19 | if (res.args.help != 0) | 19 | if (res.args.help != 0) { |
| 20 | return clap.usage(std.io.getStdErr().writer(), clap.Help, ¶ms); | 20 | var buf: [1024]u8 = undefined; |
| 21 | var stderr = std.fs.File.stderr().writer(&buf); | ||
| 22 | clap.usage(&stderr.interface, clap.Help, ¶ms) catch {}; | ||
| 23 | try stderr.interface.flush(); | ||
| 24 | return; | ||
| 25 | } | ||
| 21 | } | 26 | } |
| 22 | 27 | ||
| 23 | const clap = @import("clap"); | 28 | const clap = @import("clap"); |