a73x

9fd78ccd

refactor: help and version print from one place

a73x   2026-08-27 07:15

Commit message
refactor: help and version print from one place

Four binaries spelled the same bufPrint-and-write twice each — six arms,
and mux had two of each because `mux wall` answers for itself. cliflags
owns the two answers now; the version string still comes from the caller
because cliflags is layer 0 and build_options is not its to see.

Three of the four wrote with a bare posix.write, which may short-write a
usage page down a pipe. writeOut loops.

src/cli/flags.zig
Old New
@@ -125,6 +125,28 @@ fn aliasHit(comptime T: type, comptime field: []const u8, a: []const u8) bool {
125 return false; 125 return false;
126 } 126 }
127 127
128 pub fn help(usage: []const u8) u8 {
129 // stdout, unlike every refusal: a usage someone ASKED for is output,
130 // and they may well have piped it into a pager. version shares both
131 // the rule and the reason.
132 writeOut(usage);
133 return 0;
134 }
135
136 pub fn version(prog: []const u8, ver: []const u8) u8 {
137 // Both strings are the caller's: cliflags is layer 0 and cannot see
138 // build_options, where the version it prints lives.
139 var buf: [64]u8 = undefined;
140 writeOut(std.fmt.bufPrint(&buf, "{s} {s}\n", .{ prog, ver }) catch unreachable);
141 return 0;
142 }
143
144 /// A short write is not an error: a usage page down a pipe can take two.
145 fn writeOut(bytes: []const u8) void {
146 var off: usize = 0;
147 while (off < bytes.len) off += std.posix.write(std.posix.STDOUT_FILENO, bytes[off..]) catch return;
148 }
149
128 pub fn flagName(comptime field: []const u8) []const u8 { 150 pub fn flagName(comptime field: []const u8) []const u8 {
129 comptime var name: []const u8 = "--"; 151 comptime var name: []const u8 = "--";
130 inline for (field) |c| name = name ++ [_]u8{if (c == '_') '-' else c}; 152 inline for (field) |c| name = name ++ [_]u8{if (c == '_') '-' else c};
src/cli/main.zig
Old New
@@ -258,11 +258,7 @@ fn usageCode(u: Usage) u8 {
258 258
259 fn usageExit(u: Usage) u8 { 259 fn usageExit(u: Usage) u8 {
260 switch (u) { 260 switch (u) {
261 // stdout, unlike every refusal below: a usage someone asked for is 261 .help => return cliflags.help(usage),
262 // output, and they may well have piped it into a pager.
263 .help => {
264 _ = std.posix.write(std.posix.STDOUT_FILENO, usage) catch {};
265 },
266 .no_command => std.debug.print("{s}", .{usage}), 262 .no_command => std.debug.print("{s}", .{usage}),
267 .unknown_command => std.debug.print("{s}", .{usage}), 263 .unknown_command => std.debug.print("{s}", .{usage}),
268 .unknown_arg => |a| std.debug.print("unknown argument: {s}\n{s}", .{ a, usage }), 264 .unknown_arg => |a| std.debug.print("unknown argument: {s}\n{s}", .{ a, usage }),
@@ -374,12 +370,7 @@ pub fn main() !u8 {
374 // The socket path resolved above is unused here and unchecked (see 370 // The socket path resolved above is unused here and unchecked (see
375 // the length guard above): asking a binary its version must work 371 // the length guard above): asking a binary its version must work
376 // with no daemon and no runtime dir. 372 // with no daemon and no runtime dir.
377 .version => { 373 .version => return cliflags.version("muxd", build_options.version),
378 var vbuf: [64]u8 = undefined;
379 const s = std.fmt.bufPrint(&vbuf, "muxd {s}\n", .{build_options.version}) catch unreachable;
380 _ = std.posix.write(std.posix.STDOUT_FILENO, s) catch {};
381 return 0;
382 },
383 .help => return usageExit(.help), 374 .help => return usageExit(.help),
384 .keygen => return keygen(alloc), 375 .keygen => return keygen(alloc),
385 .start => return startCmd(alloc, sock_path, args[2..]), 376 .start => return startCmd(alloc, sock_path, args[2..]),
src/cli/mux_main.zig
Old New
@@ -321,18 +321,8 @@ pub fn main() !u8 {
321 } 321 }
322 322
323 switch (parsed) { 323 switch (parsed) {
324 .version => { 324 .version => return cliflags.version("mux", build_options.version),
325 var vbuf: [64]u8 = undefined; 325 .help => return cliflags.help(usage),
326 const s = std.fmt.bufPrint(&vbuf, "mux {s}\n", .{build_options.version}) catch unreachable;
327 _ = std.posix.write(std.posix.STDOUT_FILENO, s) catch {};
328 return 0;
329 },
330 .help => {
331 // stdout, unlike every refusal below: a usage someone asked for
332 // is output, and they may well have piped it into a pager.
333 _ = std.posix.write(std.posix.STDOUT_FILENO, usage) catch {};
334 return 0;
335 },
336 .usage_error => { 326 .usage_error => {
337 std.debug.print("{s}", .{usage}); 327 std.debug.print("{s}", .{usage});
338 return 2; 328 return 2;
@@ -523,18 +513,8 @@ fn wallMain(alloc: std.mem.Allocator, args: []const [:0]const u8) !u8 {
523 } 513 }
524 switch (outcome) { 514 switch (outcome) {
525 .ok => {}, 515 .ok => {},
526 .help => { 516 .help => return cliflags.help(usage),
527 // stdout, unlike the refusals below: a usage someone asked for 517 .version => return cliflags.version("mux", build_options.version),
528 // is output, and they may well have piped it into a pager.
529 _ = std.posix.write(std.posix.STDOUT_FILENO, usage) catch {};
530 return 0;
531 },
532 .version => {
533 var vbuf: [64]u8 = undefined;
534 const s = std.fmt.bufPrint(&vbuf, "mux {s}\n", .{build_options.version}) catch unreachable;
535 _ = std.posix.write(std.posix.STDOUT_FILENO, s) catch {};
536 return 0;
537 },
538 .missing_value, .bad_number => { 518 .missing_value, .bad_number => {
539 std.debug.print("{s}", .{usage}); 519 std.debug.print("{s}", .{usage});
540 return 2; 520 return 2;
src/cli/muxa.zig
Old New
@@ -1203,19 +1203,10 @@ pub fn main() !u8 {
1203 proto.writeAllFd(std.posix.STDERR_FILENO, usage) catch {}; 1203 proto.writeAllFd(std.posix.STDERR_FILENO, usage) catch {};
1204 return 2; 1204 return 2;
1205 }, 1205 },
1206 // A usage someone ASKED for is the answer, not a diagnostic, so it 1206 // Help and version go to stdout — the one place muxa otherwise
1207 // goes to stdout — the one place muxa otherwise reserves for JSON, 1207 // reserves for JSON, and the only two verbs allowed to spend it.
1208 // and the only two verbs that are allowed to spend it. 1208 error.Help => return cliflags.help(usage),
1209 error.Help => { 1209 error.Version => return cliflags.version("muxa", build_options.version),
1210 proto.writeAllFd(std.posix.STDOUT_FILENO, usage) catch {};
1211 return 0;
1212 },
1213 error.Version => {
1214 var vbuf: [64]u8 = undefined;
1215 const s = std.fmt.bufPrint(&vbuf, "muxa {s}\n", .{build_options.version}) catch unreachable;
1216 proto.writeAllFd(std.posix.STDOUT_FILENO, s) catch {};
1217 return 0;
1218 },
1219 }; 1210 };
1220 1211
1221 // Started BEFORE the connect, not after: over QUIC the handshake is 1212 // Started BEFORE the connect, not after: over QUIC the handshake is
src/cli/webhub_main.zig
Old New
@@ -141,18 +141,8 @@ pub fn main() !u8 {
141 else => |e| return e, 141 else => |e| return e,
142 }; 142 };
143 var parsed = switch (result) { 143 var parsed = switch (result) {
144 // stdout, unlike the refusal above: a usage someone asked for is 144 .help => return cliflags.help(usage),
145 // output, and they may well have piped it into a pager. 145 .version => return cliflags.version("muxweb", build_options.version),
146 .help => {
147 _ = std.posix.write(std.posix.STDOUT_FILENO, usage) catch {};
148 return 0;
149 },
150 .version => {
151 var vbuf: [64]u8 = undefined;
152 const s = std.fmt.bufPrint(&vbuf, "muxweb {s}\n", .{build_options.version}) catch unreachable;
153 _ = std.posix.write(std.posix.STDOUT_FILENO, s) catch {};
154 return 0;
155 },
156 .serve => |p| p, 146 .serve => |p| p,
157 }; 147 };
158 defer parsed.deinit(); 148 defer parsed.deinit();