From 1813d2a662ed36a4ecbd32b3df900d41999741b3 Mon Sep 17 00:00:00 2001 From: Edward Langley Date: Wed, 8 Apr 2026 22:27:36 -0700 Subject: [PATCH] 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) --- src/command/cmd.rs | 25 +++++++++++++++++++++ src/command/parse.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/src/command/cmd.rs b/src/command/cmd.rs index 8e6a8b2..a67b606 100644 --- a/src/command/cmd.rs +++ b/src/command/cmd.rs @@ -113,15 +113,32 @@ struct CmdEntry { #[derive(Default)] pub struct CmdRegistry { entries: Vec, + aliases: Vec<(&'static str, &'static str)>, } impl CmdRegistry { pub fn new() -> Self { Self { 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. /// The name is derived from a prototype command instance. 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). pub fn parse(&self, name: &str, args: &[String]) -> Result, String> { + let name = self.resolve(name); for e in &self.entries { if e.name == name { return (e.parse)(args); @@ -179,6 +197,7 @@ impl CmdRegistry { args: &[String], ctx: &CmdContext, ) -> Result, String> { + let name = self.resolve(name); for e in &self.entries { if e.name == name { return (e.interactive)(args, ctx); @@ -2781,6 +2800,12 @@ pub fn default_registry() -> CmdRegistry { // ── Wizard ─────────────────────────────────────────────────────────── 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 } diff --git a/src/command/parse.rs b/src/command/parse.rs index ff6a78d..3955589 100644 --- a/src/command/parse.rs +++ b/src/command/parse.rs @@ -181,4 +181,56 @@ mod tests { assert!(parse_line("add-category").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()); + } }