feat(command): implement command aliasing
Implement command aliasing in CmdRegistry and update command parsing to resolve aliases. - Added `aliases` field to `CmdRegistry` . - Added `alias()` method to register short names. - Added `resolve()` method to map aliases to canonical names. - Updated `parse()` and `interactive()` to use `resolve()` . - Added unit tests for alias resolution in `src/command/parse.rs` . Co-Authored-By: fiddlerwoaroof/git-smart-commit (unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q5_K_XL)
This commit is contained in:
@ -113,15 +113,32 @@ struct CmdEntry {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CmdRegistry {
|
pub struct CmdRegistry {
|
||||||
entries: Vec<CmdEntry>,
|
entries: Vec<CmdEntry>,
|
||||||
|
aliases: Vec<(&'static str, &'static str)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CmdRegistry {
|
impl CmdRegistry {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
|
aliases: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Register a short name that resolves to a canonical command name.
|
||||||
|
pub fn alias(&mut self, short: &'static str, canonical: &'static str) {
|
||||||
|
self.aliases.push((short, canonical));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve a command name through the alias table.
|
||||||
|
fn resolve<'a>(&'a self, name: &'a str) -> &'a str {
|
||||||
|
for (alias, canonical) in &self.aliases {
|
||||||
|
if *alias == name {
|
||||||
|
return canonical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name
|
||||||
|
}
|
||||||
|
|
||||||
/// Register a command with both a text parser and an interactive constructor.
|
/// Register a command with both a text parser and an interactive constructor.
|
||||||
/// The name is derived from a prototype command instance.
|
/// The name is derived from a prototype command instance.
|
||||||
pub fn register(&mut self, prototype: &dyn Cmd, parse: ParseFn, interactive: InteractiveFn) {
|
pub fn register(&mut self, prototype: &dyn Cmd, parse: ParseFn, interactive: InteractiveFn) {
|
||||||
@ -162,6 +179,7 @@ impl CmdRegistry {
|
|||||||
|
|
||||||
/// Construct a command from text arguments (script/headless).
|
/// Construct a command from text arguments (script/headless).
|
||||||
pub fn parse(&self, name: &str, args: &[String]) -> Result<Box<dyn Cmd>, String> {
|
pub fn parse(&self, name: &str, args: &[String]) -> Result<Box<dyn Cmd>, String> {
|
||||||
|
let name = self.resolve(name);
|
||||||
for e in &self.entries {
|
for e in &self.entries {
|
||||||
if e.name == name {
|
if e.name == name {
|
||||||
return (e.parse)(args);
|
return (e.parse)(args);
|
||||||
@ -179,6 +197,7 @@ impl CmdRegistry {
|
|||||||
args: &[String],
|
args: &[String],
|
||||||
ctx: &CmdContext,
|
ctx: &CmdContext,
|
||||||
) -> Result<Box<dyn Cmd>, String> {
|
) -> Result<Box<dyn Cmd>, String> {
|
||||||
|
let name = self.resolve(name);
|
||||||
for e in &self.entries {
|
for e in &self.entries {
|
||||||
if e.name == name {
|
if e.name == name {
|
||||||
return (e.interactive)(args, ctx);
|
return (e.interactive)(args, ctx);
|
||||||
@ -2781,6 +2800,12 @@ pub fn default_registry() -> CmdRegistry {
|
|||||||
// ── Wizard ───────────────────────────────────────────────────────────
|
// ── Wizard ───────────────────────────────────────────────────────────
|
||||||
r.register_nullary(|| Box::new(HandleWizardKey));
|
r.register_nullary(|| Box::new(HandleWizardKey));
|
||||||
|
|
||||||
|
// ── Aliases (short names for common commands) ────────────────────────
|
||||||
|
r.alias("add-cat", "add-category");
|
||||||
|
r.alias("formula", "add-formula");
|
||||||
|
r.alias("add-view", "create-view");
|
||||||
|
r.alias("q!", "force-quit");
|
||||||
|
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -181,4 +181,56 @@ mod tests {
|
|||||||
assert!(parse_line("add-category").is_err());
|
assert!(parse_line("add-category").is_err());
|
||||||
assert!(parse_line("set-cell 100").is_err());
|
assert!(parse_line("set-cell 100").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Alias resolution ────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_add_cat_resolves_to_add_category() {
|
||||||
|
let cmds = parse_line("add-cat Region").unwrap();
|
||||||
|
assert_eq!(cmds.len(), 1);
|
||||||
|
assert_eq!(cmds[0].name(), "add-category");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_formula_resolves_to_add_formula() {
|
||||||
|
let cmds = parse_line(r#"formula Product "Total = A + B""#).unwrap();
|
||||||
|
assert_eq!(cmds.len(), 1);
|
||||||
|
assert_eq!(cmds[0].name(), "add-formula");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_add_view_resolves_to_create_view() {
|
||||||
|
let cmds = parse_line("add-view MyView").unwrap();
|
||||||
|
assert_eq!(cmds.len(), 1);
|
||||||
|
assert_eq!(cmds[0].name(), "create-view");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_q_bang_resolves_to_force_quit() {
|
||||||
|
let cmds = parse_line("q!").unwrap();
|
||||||
|
assert_eq!(cmds.len(), 1);
|
||||||
|
assert_eq!(cmds[0].name(), "force-quit");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alias_does_not_interfere_with_canonical_q() {
|
||||||
|
let cmds = parse_line("q").unwrap();
|
||||||
|
assert_eq!(cmds.len(), 1);
|
||||||
|
assert_eq!(cmds[0].name(), "q");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── add-items command ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_add_items_multiple() {
|
||||||
|
let cmds = parse_line("add-items Region North South East").unwrap();
|
||||||
|
assert_eq!(cmds.len(), 1);
|
||||||
|
assert_eq!(cmds[0].name(), "add-items");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn add_items_requires_at_least_two_args() {
|
||||||
|
assert!(parse_line("add-items").is_err());
|
||||||
|
assert!(parse_line("add-items Region").is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user