diff --git a/examples/covademo.zig b/examples/covademo.zig index 63451e5..60d681a 100644 --- a/examples/covademo.zig +++ b/examples/covademo.zig @@ -23,6 +23,7 @@ pub const CommandT = Command.Custom(.{ .global_help_prefix = "CovaDemo", .global_vals_mandatory = false, .help_category_order = &.{ .Prefix, .Header, .Aliases, .Values, .Options, .Commands }, + .allow_abbreviated_cmds = true, //.allow_arg_indices = false, .global_usage_fn = struct{ fn usage(self: anytype, writer: anytype, _: ?mem.Allocator) !void { diff --git a/src/Command.zig b/src/Command.zig index 8e9632c..3a77b24 100644 --- a/src/Command.zig +++ b/src/Command.zig @@ -176,6 +176,12 @@ pub const Config = struct { /// During parsing, mandate that Command instances of this Command Type, and their aliases, must be used in a case-sensitive manner. /// This will also affect Command Validation, but will NOT affect Tab-Completion. global_case_sensitive: bool = true, + /// During parsing, allow Abbreviated Command Names. (i.e. 'com' working for 'command') + /// This is seen in programs like `ip` on Linux, but may not be ideal in every use case. + /// Note, this does not check for uniqueness and will simply match on the first Command matching the abbreviation. + allow_abbreviated_cmds: bool = false, + /// The minimum length for an Abbreviated Command Name. + abbreviated_min_len: u16 = 3, /// Help Categories. @@ -320,6 +326,12 @@ pub fn Custom(comptime config: Config) type { /// Include Examples. /// Check (`Command.Config`) for details. pub const include_examples = config.include_examples; + /// Allow Abbreviated Commands. + /// Check (`Command.Config`) for details. + pub const allow_abbreviated_cmds = config.allow_abbreviated_cmds; + /// The minimum length for an Abbreviated Command Name. + /// Check (`Command.Config`) for details. + pub const abbreviated_min_len = config.abbreviated_min_len; /// The Root Allocator for this Command. diff --git a/src/cova.zig b/src/cova.zig index 8e05598..f2584c0 100644 --- a/src/cova.zig +++ b/src/cova.zig @@ -277,8 +277,26 @@ fn parseArgsCtx( checkCmds: for (cmds) |*sub_cmd| { const should_parse = shouldParse: { if (sub_cmd.case_sensitive) { - if (mem.eql(u8, sub_cmd.name, arg)) break :shouldParse true - else for (sub_cmd.alias_names orelse continue :checkCmds) |alias| if (mem.eql(u8, alias, arg)) break :shouldParse true; + if ( + mem.eql(u8, sub_cmd.name, arg) or + ( + CommandT.allow_abbreviated_cmds and + arg.len >= @min(sub_cmd.name.len, CommandT.abbreviated_min_len) and + mem.indexOf(u8, sub_cmd.name, arg) != null and sub_cmd.name[0] == arg[0] + ) + ) break :shouldParse true + else { + for (sub_cmd.alias_names orelse continue :checkCmds) |alias| { + if ( + mem.eql(u8, alias, arg) or + ( + CommandT.allow_abbreviated_cmds and + arg.len >= @min(alias.len, CommandT.abbreviated_min_len) and + mem.indexOf(u8, alias, arg) != null and alias[0] == arg[0] + ) + ) break :shouldParse true; + } + } } else { if (ascii.eqlIgnoreCase(sub_cmd.name, arg)) break :shouldParse true @@ -436,7 +454,10 @@ fn parseArgsCtx( if (matchOpt: { break :matchOpt if (opt.case_sensitive) mem.eql(u8, long_opt, long_name) or - (OptionT.allow_abbreviated_long_opts and mem.indexOf(u8, long_name, long_opt) != null and long_name[0] == long_opt[0]) + ( + OptionT.allow_abbreviated_long_opts and + mem.indexOf(u8, long_name, long_opt) != null and long_name[0] == long_opt[0] + ) else ascii.eqlIgnoreCase(long_opt, long_name) or (