fetch_ml/cli/src/core/flags.zig
Jeremie Fraeys 2b7319dc2e
refactor(cli): Simplify output system and add terminal utilities
Remove colors dependency from output.zig

Add terminal.zig for TTY detection and terminal width

Update flags.zig with color flag support

Simplify colors.zig to basic ANSI codes

Update main.zig and utils.zig exports
2026-02-23 14:11:59 -05:00

147 lines
4.7 KiB
Zig

const std = @import("std");
/// Common flags supported by most commands
pub const CommonFlags = struct {
json: bool = false,
help: bool = false,
verbose: bool = false,
dry_run: bool = false,
color: ?bool = null, // null = auto, true = force on, false = disable
};
/// Parse common flags from command arguments
/// Returns remaining non-flag arguments
pub fn parseCommon(allocator: std.mem.Allocator, args: []const []const u8, flags: *CommonFlags) !std.ArrayList([]const u8) {
var remaining = std.ArrayList([]const u8).initCapacity(allocator, args.len) catch |err| {
return err;
};
errdefer remaining.deinit(allocator);
var i: usize = 0;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (std.mem.eql(u8, arg, "--json")) {
flags.json = true;
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
flags.help = true;
} else if (std.mem.eql(u8, arg, "--verbose") or std.mem.eql(u8, arg, "-v")) {
flags.verbose = true;
} else if (std.mem.eql(u8, arg, "--dry-run")) {
flags.dry_run = true;
} else if (std.mem.eql(u8, arg, "--no-color") or std.mem.eql(u8, arg, "--no-colour")) {
flags.color = false;
} else if (std.mem.startsWith(u8, arg, "--color=")) {
const val = arg[8..];
if (std.mem.eql(u8, val, "always") or std.mem.eql(u8, val, "yes")) {
flags.color = true;
} else if (std.mem.eql(u8, val, "never") or std.mem.eql(u8, val, "no")) {
flags.color = false;
} else if (std.mem.eql(u8, val, "auto")) {
flags.color = null;
}
} else if (std.mem.eql(u8, arg, "--")) {
// End of flags, rest are positional
i += 1;
while (i < args.len) : (i += 1) {
try remaining.append(allocator, args[i]);
}
break;
} else {
try remaining.append(allocator, arg);
}
}
return remaining;
}
/// Parse a key-value flag (--key=value or --key value)
pub fn parseKVFlag(args: []const []const u8, key: []const u8) ?[]const u8 {
const prefix = std.fmt.allocPrint(std.heap.page_allocator, "--{s}=", .{key}) catch return null;
defer std.heap.page_allocator.free(prefix);
for (args) |arg| {
if (std.mem.startsWith(u8, arg, prefix)) {
return arg[prefix.len..];
}
}
// Check for --key value format
var i: usize = 0;
const key_only = std.fmt.allocPrint(std.heap.page_allocator, "--{s}", .{key}) catch return null;
defer std.heap.page_allocator.free(key_only);
while (i < args.len) : (i += 1) {
if (std.mem.eql(u8, args[i], key_only)) {
if (i + 1 < args.len) {
return args[i + 1];
}
return null;
}
}
return null;
}
/// Parse a boolean flag
pub fn parseBoolFlag(args: []const []const u8, flag: []const u8) bool {
const full_flag = std.fmt.allocPrint(std.heap.page_allocator, "--{s}", .{flag}) catch return false;
defer std.heap.page_allocator.free(full_flag);
for (args) |arg| {
if (std.mem.eql(u8, arg, full_flag)) {
return true;
}
}
return false;
}
/// Parse numeric flag with default value
pub fn parseNumFlag(comptime T: type, args: []const []const u8, flag: []const u8, default: T) T {
const val_str = parseKVFlag(args, flag);
if (val_str) |s| {
return std.fmt.parseInt(T, s, 10) catch default;
}
return default;
}
/// Check if args contain any of the given flags
pub fn hasAnyFlag(args: []const []const u8, flags: []const []const u8) bool {
for (args) |arg| {
for (flags) |flag| {
if (std.mem.eql(u8, arg, flag)) {
return true;
}
}
}
return false;
}
/// Shift/pop first argument
pub fn shift(args: []const []const u8) ?[]const u8 {
if (args.len == 0) return null;
return args[0];
}
/// Get remaining arguments after first
pub fn rest(args: []const []const u8) []const []const u8 {
if (args.len <= 1) return &[]const u8{};
return args[1..];
}
/// Require subcommand, return error if missing
pub fn requireSubcommand(args: []const []const u8, comptime cmd_name: []const u8) ![]const u8 {
if (args.len == 0) {
std.log.err("Command '{s}' requires a subcommand", .{cmd_name});
return error.MissingSubcommand;
}
return args[0];
}
/// Match subcommand and return remaining args
pub fn matchSubcommand(args: []const []const u8, comptime sub: []const u8) ?[]const []const u8 {
if (args.len == 0) return null;
if (std.mem.eql(u8, args[0], sub)) {
return args[1..];
}
return null;
}