93 lines
3.7 KiB
Zig
93 lines
3.7 KiB
Zig
//! Best-effort stderr writers.
|
|
//!
|
|
//! All three functions (`print`, `progress`, `rateLimitWait`) are
|
|
//! non-throwing on purpose. A stderr-write failure shouldn't
|
|
//! propagate as an error to a CLI command's logic — the user's
|
|
//! command should still complete (or fail for its own reasons),
|
|
//! not get derailed because we couldn't paint a hint message.
|
|
//! Secondary failures get logged at debug level for forensics.
|
|
//!
|
|
//! Lives at the top level (not under `commands/`) so the portfolio
|
|
//! loader and the TUI can use it without a "TUI calls into
|
|
//! commands/" import smell.
|
|
//!
|
|
//! Under `zig build test` the writes are suppressed entirely:
|
|
//! tests that exercise error paths emit the same usage/hint
|
|
//! strings on every run, and that noise is more annoying than
|
|
//! useful. Real CLI users always reach the real stderr.
|
|
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const fmt = @import("format.zig");
|
|
|
|
/// Default muted-text color used for progress headers. Matches the
|
|
/// CLI / TUI palette used elsewhere; defined here so this module
|
|
/// has no dependency on `commands/common.zig`.
|
|
const CLR_MUTED = [3]u8{ 0x80, 0x80, 0x80 };
|
|
|
|
/// Default red used for rate-limit warnings.
|
|
const CLR_NEGATIVE = [3]u8{ 0xe0, 0x6c, 0x75 };
|
|
|
|
pub fn print(io: std.Io, msg: []const u8) void {
|
|
if (builtin.is_test) return;
|
|
var buf: [1024]u8 = undefined;
|
|
var writer = std.Io.File.stderr().writer(io, &buf);
|
|
const out = &writer.interface;
|
|
out.writeAll(msg) catch |err| {
|
|
std.log.debug("stderr.print writeAll failed: {t}", .{err});
|
|
return;
|
|
};
|
|
out.flush() catch |err| {
|
|
std.log.debug("stderr.print flush failed: {t}", .{err});
|
|
};
|
|
}
|
|
|
|
/// Print progress line to stderr: " [N/M] SYMBOL (status)".
|
|
pub fn progress(io: std.Io, symbol: []const u8, status: []const u8, current: usize, total: usize, color: bool) void {
|
|
if (builtin.is_test) return;
|
|
progressImpl(io, symbol, status, current, total, color) catch |err| {
|
|
std.log.debug("stderr.progress failed: {t}", .{err});
|
|
};
|
|
}
|
|
|
|
fn progressImpl(io: std.Io, symbol: []const u8, status: []const u8, current: usize, total: usize, color: bool) !void {
|
|
var buf: [256]u8 = undefined;
|
|
var writer = std.Io.File.stderr().writer(io, &buf);
|
|
const out = &writer.interface;
|
|
if (color) try fmt.ansiSetFg(out, CLR_MUTED[0], CLR_MUTED[1], CLR_MUTED[2]);
|
|
try out.print(" [{d}/{d}] ", .{ current, total });
|
|
if (color) try fmt.ansiReset(out);
|
|
try out.print("{s}", .{symbol});
|
|
if (color) try fmt.ansiSetFg(out, CLR_MUTED[0], CLR_MUTED[1], CLR_MUTED[2]);
|
|
try out.print("{s}\n", .{status});
|
|
if (color) try fmt.ansiReset(out);
|
|
try out.flush();
|
|
}
|
|
|
|
/// Print rate-limit wait message to stderr.
|
|
pub fn rateLimitWait(io: std.Io, wait_seconds: u64, color: bool) void {
|
|
if (builtin.is_test) return;
|
|
rateLimitWaitImpl(io, wait_seconds, color) catch |err| {
|
|
std.log.debug("stderr.rateLimitWait failed: {t}", .{err});
|
|
};
|
|
}
|
|
|
|
fn rateLimitWaitImpl(io: std.Io, wait_seconds: u64, color: bool) !void {
|
|
var buf: [256]u8 = undefined;
|
|
var writer = std.Io.File.stderr().writer(io, &buf);
|
|
const out = &writer.interface;
|
|
if (color) try fmt.ansiSetFg(out, CLR_NEGATIVE[0], CLR_NEGATIVE[1], CLR_NEGATIVE[2]);
|
|
if (wait_seconds >= 60) {
|
|
const mins = wait_seconds / 60;
|
|
const secs = wait_seconds % 60;
|
|
if (secs > 0) {
|
|
try out.print(" (rate limit -- waiting {d}m {d}s)\n", .{ mins, secs });
|
|
} else {
|
|
try out.print(" (rate limit -- waiting {d}m)\n", .{mins});
|
|
}
|
|
} else {
|
|
try out.print(" (rate limit -- waiting {d}s)\n", .{wait_seconds});
|
|
}
|
|
if (color) try fmt.ansiReset(out);
|
|
try out.flush();
|
|
}
|