use std::collections::HashMap;
pub const DEFAULT_ALIASES: &[(&str, &str)] = &[
("q", "search"),
("query", "search"),
("find", "search"),
("s", "search"),
("h", "help"),
("?", "help"),
("c", "config"),
("cfg", "config"),
("r", "role"),
("g", "graph"),
("kg", "graph"),
("quit", "quit"),
("exit", "quit"),
("bye", "quit"),
("ac", "autocomplete"),
("th", "thesaurus"),
];
#[derive(Debug, Clone)]
pub struct AliasRegistry {
aliases: HashMap<String, String>,
}
impl AliasRegistry {
pub fn new() -> Self {
let mut aliases = HashMap::new();
for (alias, canonical) in DEFAULT_ALIASES {
aliases.insert(alias.to_string(), canonical.to_string());
}
Self { aliases }
}
pub fn empty() -> Self {
Self {
aliases: HashMap::new(),
}
}
pub fn add(&mut self, alias: impl Into<String>, canonical: impl Into<String>) {
self.aliases.insert(alias.into(), canonical.into());
}
pub fn remove(&mut self, alias: &str) -> Option<String> {
self.aliases.remove(alias)
}
pub fn expand(&self, input: &str) -> Option<&str> {
self.aliases.get(input).map(|s| s.as_str())
}
pub fn is_alias(&self, input: &str) -> bool {
self.aliases.contains_key(input)
}
pub fn aliases_for(&self, canonical: &str) -> Vec<&str> {
self.aliases
.iter()
.filter(|(_, v)| v.as_str() == canonical)
.map(|(k, _)| k.as_str())
.collect()
}
pub fn all(&self) -> &HashMap<String, String> {
&self.aliases
}
pub fn merge(&mut self, other: &AliasRegistry) {
for (alias, canonical) in &other.aliases {
self.aliases.insert(alias.clone(), canonical.clone());
}
}
pub fn from_config(config: &str) -> Result<Self, String> {
let mut registry = Self::empty();
for line in config.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((alias, canonical)) = line.split_once('=') {
let alias = alias.trim().trim_matches('"');
let canonical = canonical.trim().trim_matches('"');
registry.add(alias, canonical);
}
}
Ok(registry)
}
}
impl Default for AliasRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_aliases() {
let registry = AliasRegistry::new();
assert_eq!(registry.expand("q"), Some("search"));
assert_eq!(registry.expand("h"), Some("help"));
assert_eq!(registry.expand("?"), Some("help"));
assert_eq!(registry.expand("c"), Some("config"));
}
#[test]
fn test_custom_alias() {
let mut registry = AliasRegistry::new();
registry.add("ss", "sessions search");
assert_eq!(registry.expand("ss"), Some("sessions search"));
}
#[test]
fn test_aliases_for() {
let registry = AliasRegistry::new();
let search_aliases = registry.aliases_for("search");
assert!(search_aliases.contains(&"q"));
assert!(search_aliases.contains(&"query"));
assert!(search_aliases.contains(&"find"));
}
#[test]
fn test_from_config() {
let config = r#"
# Custom aliases
ss = "sessions search"
si = "sessions import"
"#;
let registry = AliasRegistry::from_config(config).unwrap();
assert_eq!(registry.expand("ss"), Some("sessions search"));
assert_eq!(registry.expand("si"), Some("sessions import"));
}
#[test]
fn test_merge() {
let mut base = AliasRegistry::new();
let mut custom = AliasRegistry::empty();
custom.add("custom", "mycommand");
base.merge(&custom);
assert_eq!(base.expand("custom"), Some("mycommand"));
assert_eq!(base.expand("q"), Some("search"));
}
}