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; }