fetch_ml/cli/src/core/output.zig
Jeremie Fraeys 59a5433444
refactor(cli): remove remaining deprecated imports
Update remaining files using deprecated imports:
- core/output.zig: terminal.zig → io.zig
- net/ws/deps.zig: remove colors.zig export (available via io)

All tests pass.
2026-03-04 21:39:05 -05:00

137 lines
4.3 KiB
Zig

const std = @import("std");
const io = @import("../utils/io.zig");
/// Output mode: JSON for structured data, text for TSV
pub const Mode = enum { json, text };
pub var mode: Mode = .text;
pub fn setMode(m: Mode) void {
mode = m;
}
/// Escape a value for TSV output (replace tabs/newlines with spaces for xargs safety)
fn escapeTSV(val: []const u8) []const u8 {
// For xargs usability, we need single-line output with no tabs/newlines in values
// This returns the same slice if no escaping needed, but we process to ensure safety
// In practice, we just use the value directly since the caller should sanitize
return val;
}
/// Check if value needs TSV escaping
fn needsTSVEscape(val: []const u8) bool {
for (val) |c| {
if (c == '\t' or c == '\n' or c == '\r') return true;
}
return false;
}
/// Print error to stderr
pub fn err(msg: []const u8) void {
std.debug.print("Error: {s}\n", .{msg});
}
/// Print line in current mode (JSON or TSV)
pub fn line(values: []const []const u8) void {
switch (mode) {
.json => {
std.debug.print("{{", .{});
// Assume alternating key-value pairs
var i: usize = 0;
while (i < values.len) : (i += 2) {
if (i > 0) std.debug.print(",", .{});
const key = values[i];
const val = if (i + 1 < values.len) values[i + 1] else "";
std.debug.print("\"{s}\":\"{s}\"", .{ key, val });
}
std.debug.print("}}\n", .{});
},
.text => {
for (values, 0..) |val, i| {
if (i > 0) std.debug.print("\t", .{});
// For TSV/xargs safety: if value contains tabs/newlines, we need to handle it
// Simple approach: print as-is but replace internal tabs with spaces
if (needsTSVEscape(val)) {
for (val) |c| {
if (c == '\t' or c == '\n' or c == '\r') {
std.debug.print(" ", .{});
} else {
std.debug.print("{c}", .{c});
}
}
} else {
std.debug.print("{s}", .{val});
}
}
std.debug.print("\n", .{});
},
}
}
/// Print raw JSON array
pub fn jsonArray(items: []const []const u8) void {
std.debug.print("[", .{});
for (items, 0..) |item, i| {
if (i > 0) std.debug.print(",", .{});
std.debug.print("\"{s}\"", .{item});
}
std.debug.print("]\n", .{});
}
/// Print raw JSON object from key-value pairs
pub fn jsonObject(pairs: []const []const u8) void {
std.debug.print("{{", .{});
var i: usize = 0;
while (i < pairs.len) : (i += 2) {
if (i > 0) std.debug.print(",", .{});
const key = pairs[i];
const val = if (i + 1 < pairs.len) pairs[i + 1] else "";
std.debug.print("\"{s}\":\"{s}\"", .{ key, val });
}
std.debug.print("}}\n", .{});
}
/// Print success response (JSON only)
pub fn success(comptime cmd: []const u8) void {
if (mode == .json) {
std.debug.print("{{\"success\":true,\"command\":\"{s}\"}}\n", .{cmd});
}
}
/// Print success with data
pub fn successData(comptime cmd: []const u8, pairs: []const []const u8) void {
if (mode == .json) {
std.debug.print("{{\"success\":true,\"command\":\"{s}\",\"data\":{{", .{cmd});
var i: usize = 0;
while (i < pairs.len) : (i += 2) {
if (i > 0) std.debug.print(",", .{});
const key = pairs[i];
const val = if (i + 1 < pairs.len) pairs[i + 1] else "";
std.debug.print("\"{s}\":\"{s}\"", .{ key, val });
}
std.debug.print("}}}}\n", .{});
} else {
for (pairs, 0..) |val, i| {
if (i > 0) std.debug.print("\t", .{});
std.debug.print("{s}", .{val});
}
std.debug.print("\n", .{});
}
}
/// Print usage information
pub fn usage(comptime cmd: []const u8, comptime u: []const u8) void {
std.debug.print("Usage: {s} {s}\n", .{ cmd, u });
}
/// Print plain value (text mode only)
pub fn value(v: []const u8) void {
if (mode == .text) {
std.debug.print("{s}\n", .{v});
}
}
/// Get terminal width for formatting
pub fn getTerminalWidth() ?usize {
return io.getWidth();
}