Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278const std = @import("std");const Allocator = std.mem.Allocator;
pub const DetectError = error{ /// Multiple actions were detected. You can specify at most one /// action on the CLI otherwise the behavior desired is ambiguous. MultipleActions,
/// An unknown action was specified. InvalidAction,};
/// Detect the action from CLI args.pub fn detectArgs(comptime E: type, alloc: Allocator) !?E { var iter = try std.process.argsWithAllocator(alloc); defer iter.deinit(); return try detectIter(E, &iter);}
/// Detect the action from any iterator. Each iterator value should yield/// a CLI argument such as "--foo".////// The comptime type E must be an enum with the available actions./// If the type E has a decl `detectSpecialCase`, then it will be called/// for each argument to allow handling of special cases. The function/// signature for `detectSpecialCase` should be:////// fn detectSpecialCase(arg: []const u8) ?SpecialCase(E)///pub fn detectIter( comptime E: type, iter: anytype,) DetectError!?E { var fallback: ?E = null; var pending: ?E = null; while (iter.next()) |arg| { // Allow handling of special cases. if (@hasDecl(E, "detectSpecialCase")) special: { const special = E.detectSpecialCase(arg) orelse break :special; switch (special) { .action => |a| return a, .fallback => |a| fallback = a, .abort_if_no_action => if (pending == null) return null, } }
// Commands must start with "+" if (arg.len == 0 or arg[0] != '+') continue; if (pending != null) return DetectError.MultipleActions; pending = std.meta.stringToEnum(E, arg[1..]) orelse return DetectError.InvalidAction; }
// If we have an action, we always return that action, even if we've // seen "--help" or "-h" because the action may have its own help text. if (pending != null) return pending;
// If we have no action but we have a fallback, then we return that. if (fallback) |a| return a;
return null;}
/// The action enum E can implement the decl `detectSpecialCase` to/// return this enum in order to perform various special case actions.pub fn SpecialCase(comptime E: type) type { return union(enum) { /// Immediately return this action. action: E,
/// Return this action if no other action is found. fallback: E,
/// If there is no pending action (we haven't seen an action yet) /// then we should return no action. This is kind of weird but is /// a special case to allow "-e" in Ghostty. abort_if_no_action, };}
test "detect direct match" { const testing = std.testing; const alloc = testing.allocator; const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "+foo", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expectEqual(Enum.foo, result.?);}
test "detect invalid match" { const testing = std.testing; const alloc = testing.allocator; const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "+invalid", ); defer iter.deinit(); try testing.expectError( DetectError.InvalidAction, detectIter(Enum, &iter), );}
test "detect multiple actions" { const testing = std.testing; const alloc = testing.allocator; const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "+foo +bar", ); defer iter.deinit(); try testing.expectError( DetectError.MultipleActions, detectIter(Enum, &iter), );}
test "detect no match" { const testing = std.testing; const alloc = testing.allocator; const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "--some-flag", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expect(result == null);}
test "detect special case action" { const testing = std.testing; const alloc = testing.allocator; const Enum = enum { foo, bar,
fn detectSpecialCase(arg: []const u8) ?SpecialCase(@This()) { return if (std.mem.eql(u8, arg, "--special")) .{ .action = .foo } else null; } };
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "--special +bar", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expectEqual(Enum.foo, result.?); }
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "+bar --special", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expectEqual(Enum.foo, result.?); }
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "+bar", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expectEqual(Enum.bar, result.?); }}
test "detect special case fallback" { const testing = std.testing; const alloc = testing.allocator; const Enum = enum { foo, bar,
fn detectSpecialCase(arg: []const u8) ?SpecialCase(@This()) { return if (std.mem.eql(u8, arg, "--special")) .{ .fallback = .foo } else null; } };
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "--special", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expectEqual(Enum.foo, result.?); }
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "+bar --special", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expectEqual(Enum.bar, result.?); }
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "--special +bar", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expectEqual(Enum.bar, result.?); }}
test "detect special case abort_if_no_action" { const testing = std.testing; const alloc = testing.allocator; const Enum = enum { foo, bar,
fn detectSpecialCase(arg: []const u8) ?SpecialCase(@This()) { return if (std.mem.eql(u8, arg, "-e")) .abort_if_no_action else null; } };
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "-e", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expect(result == null); }
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "+foo -e", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expectEqual(Enum.foo, result.?); }
{ var iter = try std.process.ArgIteratorGeneral(.{}).init( alloc, "-e +bar", ); defer iter.deinit(); const result = try detectIter(Enum, &iter); try testing.expect(result == null); }}