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
147 lines
4.7 KiB
Zig
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;
|
|
}
|