help: implement formatted printer

Turns out the line wrapping logic is kind of ugly. I think probably a
couple of helper functions would make a big difference. But it appears
to work and even handles the edge cases I've currently encountered.
This commit is contained in:
torque 2023-04-05 01:45:21 -07:00
parent 910cdd8106
commit b0868744f6
Signed by: torque
SSH Key Fingerprint: SHA256:nCrXefBNo6EbjNSQhv0nXmEg/VuNq3sMF5b8zETw3Tk
2 changed files with 120 additions and 36 deletions

View File

@ -1,8 +1,9 @@
const std = @import("std"); const std = @import("std");
const NoclipError = @import("./errors.zig").NoclipError;
const ncmeta = @import("./meta.zig"); const ncmeta = @import("./meta.zig");
const parser = @import("./parser.zig");
const FixedCount = @import("./parameters.zig").FixedCount; const FixedCount = @import("./parameters.zig").FixedCount;
const parser = @import("./parser.zig");
const AlignablePair = struct { const AlignablePair = struct {
left: []const u8, left: []const u8,
@ -14,6 +15,105 @@ const OptionDescription = struct {
just: usize, just: usize,
}; };
pub fn StructuredPrinter(comptime Writer: type) type {
return struct {
wrap_width: usize = 100,
writer: Writer,
pub fn print_pair(self: *@This(), pair: AlignablePair, leading_indent: u8, tabstop: usize) !void {
try self.writer.writeByteNTimes(' ', leading_indent);
const left = std.mem.trim(u8, pair.left, " \n");
try self.writer.writeAll(left);
const offset: usize = leading_indent + left.len;
// TODO: lol return a real error
if (offset > tabstop) return NoclipError.UnexpectedFailure;
try self.writer.writeByteNTimes(' ', tabstop - offset);
try self.print_rewrap(std.mem.trim(u8, pair.right, " \n"), tabstop);
try self.writer.writeByte('\n');
}
pub fn print_pair_brief(self: *@This(), pair: AlignablePair, leading_indent: u8, tabstop: usize) !void {
const brief = ncmeta.partition(u8, pair.right, &[_][]const u8{"\n\n"})[0];
const simulacrum: AlignablePair = .{
.left = pair.left,
.right = brief,
};
try self.print_pair(simulacrum, leading_indent, tabstop);
}
pub fn print_wrapped(self: *@This(), text: []const u8, leading_indent: usize) !void {
try self.writer.writeByteNTimes(' ', leading_indent);
try self.print_rewrap(std.mem.trim(u8, text, "\n"), leading_indent);
}
fn print_rewrap(self: *@This(), text: []const u8, indent: usize) !void {
// TODO: lol return a real error
if (indent >= self.wrap_width) return NoclipError.UnexpectedFailure;
// this assumes output stream has already had the first line properly
// indented.
var splitter = std.mem.split(u8, text, "\n");
var location: usize = indent;
while (splitter.next()) |line| {
if (line.len == 0) {
// we have a trailing line that needs to be cleaned up
if (location > indent)
_ = try self.clear_line(indent);
location = try self.clear_line(indent);
continue;
}
var choppee = line;
var need_forced_break = false;
choppa: while (choppee.len > 0) {
const breakoff = self.wrap_width - location;
if (breakoff >= choppee.len) {
if (location > indent)
try self.writer.writeByte(' ');
try self.writer.writeAll(choppee);
location += choppee.len;
break;
}
var split = breakoff;
while (choppee[split] != ' ') : (split -= 1) {
if (split == 0) {
// we have encountered a word that is too long to break,
// so force breaking it
if (need_forced_break) {
split = breakoff;
break;
}
if (location != indent)
location = try self.clear_line(indent);
need_forced_break = true;
continue :choppa;
}
}
if (location > indent)
try self.writer.writeByte(' ');
try self.writer.writeAll(choppee[0..split]);
location = try self.clear_line(indent);
choppee = choppee[split + 1 ..];
}
}
}
fn clear_line(self: *@This(), indent: usize) !usize {
try self.writer.writeByte('\n');
try self.writer.writeByteNTimes(' ', indent);
return indent;
}
};
}
pub fn HelpBuilder(comptime command: anytype) type { pub fn HelpBuilder(comptime command: anytype) type {
const help_info = opt_info(command.generate()); const help_info = opt_info(command.generate());
@ -42,7 +142,8 @@ pub fn HelpBuilder(comptime command: anytype) type {
}, },
); );
try writer.writeAll(std.mem.trim(u8, command.description, " \n")); var printer = StructuredPrinter(@TypeOf(writer)){ .writer = writer };
try printer.print_wrapped(command.description, 2);
try writer.writeAll("\n\n"); try writer.writeAll("\n\n");
const arguments = try self.describe_arguments(); const arguments = try self.describe_arguments();
@ -53,48 +154,32 @@ pub fn HelpBuilder(comptime command: anytype) type {
if (arguments.pairs.len > 0) { if (arguments.pairs.len > 0) {
try writer.writeAll("Arguments:\n"); try writer.writeAll("Arguments:\n");
for (arguments.pairs) |pair| { for (arguments.pairs) |pair|
try writer.print( try printer.print_pair(pair, 2, max_just + 4);
" {[0]s: <[1]}{[2]s}\n",
.{ pair.left, max_just + 3, pair.right },
);
}
try writer.writeAll("\n"); try writer.writeAll("\n");
} }
if (options.pairs.len > 0) { if (options.pairs.len > 0) {
try writer.writeAll("Options:\n"); try writer.writeAll("Options:\n");
for (options.pairs) |pair| { for (options.pairs) |pair|
try writer.print( try printer.print_pair(pair, 2, max_just + 4);
" {[0]s: <[1]}{[2]s}\n",
.{ pair.left, max_just + 3, pair.right },
);
}
try writer.writeAll("\n"); try writer.writeAll("\n");
} }
if (env_vars.pairs.len > 0) { if (env_vars.pairs.len > 0) {
try writer.writeAll("Environment variables:\n"); try writer.writeAll("Environment variables:\n");
for (env_vars.pairs) |pair| { for (env_vars.pairs) |pair|
try writer.print( try printer.print_pair(pair, 2, max_just + 4);
" {[0]s: <[1]}{[2]s}\n",
.{ pair.left, max_just + 3, pair.right },
);
}
try writer.writeAll("\n"); try writer.writeAll("\n");
} }
if (subcs.pairs.len > 0) { if (subcs.pairs.len > 0) {
try writer.writeAll("Subcommands:\n"); try writer.writeAll("Subcommands:\n");
for (subcs.pairs) |pair| { for (subcs.pairs) |pair|
try writer.print( try printer.print_pair_brief(pair, 2, max_just + 4);
" {[0]s: <[1]}{[2]s}\n",
.{ pair.left, max_just + 3, pair.right },
);
}
try writer.writeAll("\n"); try writer.writeAll("\n");
} }
@ -116,7 +201,7 @@ pub fn HelpBuilder(comptime command: anytype) type {
for (comptime help_info.arguments) |arg| { for (comptime help_info.arguments) |arg| {
try writer.writeAll(" "); try writer.writeAll(" ");
if (!arg.required) try writer.writeAll("["); if (!arg.required) try writer.writeAll("[");
try writer.print("<{s}>", .{arg.name}); try writer.print("<{s}{s}>", .{ arg.name, if (arg.multi) " ..." else "" });
if (!arg.required) try writer.writeAll("]"); if (!arg.required) try writer.writeAll("]");
} }
@ -257,12 +342,9 @@ pub fn HelpBuilder(comptime command: anytype) type {
var just: usize = 0; var just: usize = 0;
var iter = subcommands.keyIterator(); var iter = subcommands.keyIterator();
while (iter.next()) |key| { while (iter.next()) |key| {
const subif = subcommands.get(key.*).?;
const short = ncmeta.partition(u8, subif.describe(), "\n");
const pair: AlignablePair = .{ const pair: AlignablePair = .{
.left = key.*, .left = key.*,
.right = short[0], .right = subcommands.get(key.*).?.describe(),
}; };
if (pair.left.len > just) just = pair.left.len; if (pair.left.len > just) just = pair.left.len;
try pairs.append(pair); try pairs.append(pair);

View File

@ -52,14 +52,16 @@ pub fn enum_length(comptime T: type) comptime_int {
return @typeInfo(T).Enum.fields.len; return @typeInfo(T).Enum.fields.len;
} }
pub fn partition(comptime T: type, input: []const T, wedge: []const T) [3][]const T { pub fn partition(comptime T: type, input: []const T, wedge: []const []const T) [3][]const T {
for (input, 0..) |candidate, idx| { var idx: usize = 0;
while (idx < input.len) : (idx += 1) {
for (wedge) |splitter| { for (wedge) |splitter| {
if (candidate == splitter) { if (input.len - idx < splitter.len) continue;
if (std.mem.eql(T, input[idx .. idx + splitter.len], splitter)) {
return [3][]const T{ return [3][]const T{
input[0..idx], input[0..idx],
input[idx..(idx + 1)], input[idx..(idx + splitter.len)],
input[(idx + 1)..], input[(idx + splitter.len)..],
}; };
} }
} }