Move JSON accessor functions to io.zig: - jsonGetString, jsonGetInt, jsonGetFloat, jsonGetBool - json.zig now re-exports from io.zig for backward compatibility Benefits: - Single location for all I/O related utilities - Consistent with terminal/color consolidation - Reduced file count Build passes successfully.
245 lines
7.9 KiB
Zig
245 lines
7.9 KiB
Zig
const std = @import("std");
|
|
|
|
// ============================================================================
|
|
// Terminal Detection and Utilities
|
|
// ============================================================================
|
|
|
|
/// Check if stdout is a TTY
|
|
pub fn isTTY() bool {
|
|
return std.posix.isatty(std.posix.STDOUT_FILENO);
|
|
}
|
|
|
|
/// Get terminal width from COLUMNS env var
|
|
pub fn getWidth() ?usize {
|
|
const allocator = std.heap.page_allocator;
|
|
if (std.process.getEnvVarOwned(allocator, "COLUMNS")) |cols| {
|
|
defer allocator.free(cols);
|
|
return std.fmt.parseInt(usize, cols, 10) catch null;
|
|
} else |_| {}
|
|
return null;
|
|
}
|
|
|
|
/// Get user's preferred pager from PAGER env var
|
|
pub fn getPager() ?[]const u8 {
|
|
const allocator = std.heap.page_allocator;
|
|
return std.process.getEnvVarOwned(allocator, "PAGER") catch null;
|
|
}
|
|
|
|
/// Table formatting mode
|
|
pub const TableMode = enum { truncate, wrap, auto };
|
|
|
|
/// Get table formatting mode from env var
|
|
pub fn getTableMode() TableMode {
|
|
const allocator = std.heap.page_allocator;
|
|
const mode_str = std.process.getEnvVarOwned(allocator, "ML_TABLE_MODE") catch return .truncate;
|
|
defer allocator.free(mode_str);
|
|
if (std.mem.eql(u8, mode_str, "wrap")) return .wrap;
|
|
if (std.mem.eql(u8, mode_str, "auto")) return .auto;
|
|
return .truncate;
|
|
}
|
|
|
|
// ============================================================================
|
|
// ANSI Color Codes
|
|
// ============================================================================
|
|
|
|
pub const reset = "\x1b[0m";
|
|
pub const red = "\x1b[31m";
|
|
pub const green = "\x1b[32m";
|
|
pub const yellow = "\x1b[33m";
|
|
pub const blue = "\x1b[34m";
|
|
pub const bold = "\x1b[1m";
|
|
|
|
/// Check if colors should be used based on: flag > NO_COLOR > CLICOLOR_FORCE > TTY
|
|
pub fn shouldUseColor(force_flag: ?bool) bool {
|
|
// Flag takes precedence
|
|
if (force_flag) |forced| return forced;
|
|
|
|
// Check NO_COLOR (any value disables colors)
|
|
if (std.process.getEnvVarOwned(std.heap.page_allocator, "NO_COLOR")) |_| {
|
|
return false;
|
|
} else |_| {}
|
|
|
|
// Check CLICOLOR_FORCE (any value enables colors)
|
|
if (std.process.getEnvVarOwned(std.heap.page_allocator, "CLICOLOR_FORCE")) |_| {
|
|
return true;
|
|
} else |_| {}
|
|
|
|
// Default: color if TTY
|
|
return isTTY();
|
|
}
|
|
|
|
/// Legacy function - uses auto-detection
|
|
pub const shouldUseColorAuto = shouldUseColor;
|
|
|
|
// ============================================================================
|
|
// stdout/stderr Writers
|
|
// ============================================================================
|
|
|
|
fn writeAllFd(fd: std.posix.fd_t, data: []const u8) std.Io.Writer.Error!void {
|
|
var off: usize = 0;
|
|
while (off < data.len) {
|
|
const n = std.posix.write(fd, data[off..]) catch return error.WriteFailed;
|
|
if (n == 0) return error.WriteFailed;
|
|
off += n;
|
|
}
|
|
}
|
|
|
|
fn drainStdout(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
|
|
_ = w;
|
|
if (data.len == 0) return 0;
|
|
var written: usize = 0;
|
|
for (data) |chunk| {
|
|
try writeAllFd(std.posix.STDOUT_FILENO, chunk);
|
|
written += chunk.len;
|
|
}
|
|
if (splat > 0) {
|
|
const last = data[data.len - 1];
|
|
var i: usize = 0;
|
|
while (i < splat) : (i += 1) {
|
|
try writeAllFd(std.posix.STDOUT_FILENO, last);
|
|
written += last.len;
|
|
}
|
|
}
|
|
return written;
|
|
}
|
|
|
|
fn drainStderr(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize {
|
|
_ = w;
|
|
if (data.len == 0) return 0;
|
|
var written: usize = 0;
|
|
for (data) |chunk| {
|
|
try writeAllFd(std.posix.STDERR_FILENO, chunk);
|
|
written += chunk.len;
|
|
}
|
|
if (splat > 0) {
|
|
const last = data[data.len - 1];
|
|
var i: usize = 0;
|
|
while (i < splat) : (i += 1) {
|
|
try writeAllFd(std.posix.STDERR_FILENO, last);
|
|
written += last.len;
|
|
}
|
|
}
|
|
return written;
|
|
}
|
|
|
|
const stdout_vtable = std.Io.Writer.VTable{ .drain = drainStdout };
|
|
const stderr_vtable = std.Io.Writer.VTable{ .drain = drainStderr };
|
|
|
|
pub fn stdoutWriter() std.Io.Writer {
|
|
return .{ .vtable = &stdout_vtable, .buffer = &[_]u8{}, .end = 0 };
|
|
}
|
|
|
|
pub fn stderrWriter() std.Io.Writer {
|
|
return .{ .vtable = &stderr_vtable, .buffer = &[_]u8{}, .end = 0 };
|
|
}
|
|
|
|
/// Write a JSON value to stdout
|
|
pub fn stdoutWriteJson(value: std.json.Value) !void {
|
|
var buf = std.ArrayList(u8).empty;
|
|
defer buf.deinit(std.heap.page_allocator);
|
|
try writeJSONValue(buf.writer(std.heap.page_allocator), value);
|
|
var stdout_file = std.fs.File{ .handle = std.posix.STDOUT_FILENO };
|
|
try stdout_file.writeAll(buf.items);
|
|
try stdout_file.writeAll("\n");
|
|
}
|
|
|
|
fn writeJSONValue(writer: anytype, v: std.json.Value) !void {
|
|
switch (v) {
|
|
.null => try writer.writeAll("null"),
|
|
.bool => |b| try writer.print("{}", .{b}),
|
|
.integer => |i| try writer.print("{d}", .{i}),
|
|
.float => |f| try writer.print("{d}", .{f}),
|
|
.string => |s| try writeJSONString(writer, s),
|
|
.array => |arr| {
|
|
try writer.writeAll("[");
|
|
for (arr.items, 0..) |item, idx| {
|
|
if (idx > 0) try writer.writeAll(",");
|
|
try writeJSONValue(writer, item);
|
|
}
|
|
try writer.writeAll("]");
|
|
},
|
|
.object => |obj| {
|
|
try writer.writeAll("{");
|
|
var first = true;
|
|
var it = obj.iterator();
|
|
while (it.next()) |entry| {
|
|
if (!first) try writer.writeAll(",");
|
|
first = false;
|
|
try writer.print("\"{s}\":", .{entry.key_ptr.*});
|
|
try writeJSONValue(writer, entry.value_ptr.*);
|
|
}
|
|
try writer.writeAll("}");
|
|
},
|
|
.number_string => |s| try writer.print("{s}", .{s}),
|
|
}
|
|
}
|
|
|
|
fn writeJSONString(writer: anytype, s: []const u8) !void {
|
|
try writer.writeAll("\"");
|
|
for (s) |c| {
|
|
switch (c) {
|
|
'"' => try writer.writeAll("\\\""),
|
|
'\\' => try writer.writeAll("\\\\"),
|
|
'\n' => try writer.writeAll("\\n"),
|
|
'\r' => try writer.writeAll("\\r"),
|
|
'\t' => try writer.writeAll("\\t"),
|
|
else => {
|
|
if (c < 0x20) {
|
|
var buf: [6]u8 = undefined;
|
|
buf[0] = '\\';
|
|
buf[1] = 'u';
|
|
buf[2] = '0';
|
|
buf[3] = '0';
|
|
buf[4] = hexDigit(@intCast((c >> 4) & 0x0F));
|
|
buf[5] = hexDigit(@intCast(c & 0x0F));
|
|
try writer.writeAll(&buf);
|
|
} else {
|
|
try writer.writeAll(&[_]u8{c});
|
|
}
|
|
},
|
|
}
|
|
}
|
|
try writer.writeAll("\"");
|
|
}
|
|
|
|
fn hexDigit(v: u8) u8 {
|
|
return if (v < 10) ('0' + v) else ('a' + (v - 10));
|
|
}
|
|
|
|
// ============================================================================
|
|
// JSON Object Accessor Utilities
|
|
// ============================================================================
|
|
|
|
/// Get a string value from a JSON object map
|
|
pub fn jsonGetString(obj: std.json.ObjectMap, key: []const u8) ?[]const u8 {
|
|
const v = obj.get(key) orelse return null;
|
|
if (v != .string) return null;
|
|
return v.string;
|
|
}
|
|
|
|
/// Get an integer value from a JSON object map
|
|
pub fn jsonGetInt(obj: std.json.ObjectMap, key: []const u8) ?i64 {
|
|
const v = obj.get(key) orelse return null;
|
|
switch (v) {
|
|
.integer => |i| return i,
|
|
.float => |f| return @intFromFloat(f),
|
|
else => return null,
|
|
}
|
|
}
|
|
|
|
/// Get a float value from a JSON object map
|
|
pub fn jsonGetFloat(obj: std.json.ObjectMap, key: []const u8) ?f64 {
|
|
const v = obj.get(key) orelse return null;
|
|
switch (v) {
|
|
.float => |f| return f,
|
|
.integer => |i| return @floatFromInt(i),
|
|
else => return null,
|
|
}
|
|
}
|
|
|
|
/// Get a boolean value from a JSON object map
|
|
pub fn jsonGetBool(obj: std.json.ObjectMap, key: []const u8) ?bool {
|
|
const v = obj.get(key) orelse return null;
|
|
if (v != .bool) return null;
|
|
return v.bool;
|
|
}
|