Skip to main content

gor/cmd/
alias.rs

1//! Implementation of the `gor alias` subcommand.
2//!
3//! Provides command alias management for listing and setting aliases.
4//! Aliases are stored in the config file under the `aliases` key.
5
6#![allow(clippy::print_stdout)]
7
8use crate::cli::AliasCommand;
9use crate::config;
10use anyhow::Context;
11
12/// Run the `gor alias` subcommand.
13///
14/// # Errors
15///
16/// Returns an error if the command execution fails.
17pub fn run(cmd: AliasCommand) -> anyhow::Result<()> {
18    match cmd {
19        AliasCommand::List { hostname: _ } => list(),
20        AliasCommand::Set {
21            name,
22            command,
23            hostname: _,
24        } => set(&name, &command),
25        AliasCommand::Delete { name, hostname: _ } => delete(&name),
26    }
27}
28
29/// Execute `gor alias list`.
30///
31/// Lists all configured command aliases.
32///
33/// # Errors
34///
35/// Returns an error if the configuration cannot be read.
36fn list() -> anyhow::Result<()> {
37    let config = config::load().context("failed to load config")?;
38    let aliases = config.global.get("aliases");
39
40    let Some(alias_map) = aliases.and_then(|v| v.as_mapping()) else {
41        println!("No aliases configured.");
42        return Ok(());
43    };
44
45    let name_width = 20;
46    println!("{:<name_width$}  COMMAND", "ALIAS");
47    for (name, command) in alias_map {
48        let name_str = name.as_str().unwrap_or("?");
49        let cmd_str = command.as_str().unwrap_or("?");
50        let name_truncated = crate::cmd::util::truncate(name_str, name_width);
51        println!("{name_truncated:<name_width$}  {cmd_str}");
52    }
53    Ok(())
54}
55
56/// Execute `gor alias set`.
57///
58/// Sets a command alias. The alias maps a short name to a gor command
59/// with arguments.
60///
61/// # Errors
62///
63/// Returns an error if the configuration cannot be saved.
64fn set(name: &str, command: &[String]) -> anyhow::Result<()> {
65    if command.is_empty() {
66        anyhow::bail!("alias command is required");
67    }
68
69    let mut config = config::load().context("failed to load config")?;
70    let cmd_str = command.join(" ");
71
72    // Get or create the aliases map
73    let mut aliases = config
74        .global
75        .get("aliases")
76        .and_then(|v| v.as_mapping().cloned())
77        .unwrap_or_default();
78
79    aliases.insert(
80        serde_yaml_ng::Value::String(name.to_string()),
81        serde_yaml_ng::Value::String(cmd_str.clone()),
82    );
83
84    config.global.insert(
85        "aliases".to_string(),
86        serde_yaml_ng::Value::Mapping(aliases),
87    );
88
89    config::save(&config).context("failed to save config")?;
90
91    println!("Alias '{name}' set to '{cmd_str}'");
92    Ok(())
93}
94
95/// Execute `gor alias delete`.
96///
97/// Removes a configured alias by name. If the alias does not exist,
98/// prints a message and returns successfully.
99///
100/// # Errors
101///
102/// Returns an error if the configuration cannot be loaded or saved.
103fn delete(name: &str) -> anyhow::Result<()> {
104    let mut config = config::load().context("failed to load config")?;
105    let aliases = config
106        .global
107        .get("aliases")
108        .and_then(|v| v.as_mapping().cloned());
109
110    let Some(mut aliases) = aliases else {
111        println!("Alias '{name}' not found.");
112        return Ok(());
113    };
114
115    let key = serde_yaml_ng::Value::String(name.to_string());
116    if !aliases.contains_key(&key) {
117        println!("Alias '{name}' not found.");
118        return Ok(());
119    }
120
121    aliases.remove(&key);
122
123    config.global.insert(
124        "aliases".to_string(),
125        serde_yaml_ng::Value::Mapping(aliases),
126    );
127
128    config::save(&config).context("failed to save config")?;
129
130    println!("Alias '{name}' deleted.");
131    Ok(())
132}
133
134#[cfg(test)]
135#[allow(clippy::expect_used)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn set_requires_command() {
141        let result = std::panic::catch_unwind(|| {
142            let _ = set("test", &[]);
143        });
144        assert!(result.is_ok());
145    }
146
147    #[test]
148    fn delete_nonexistent_is_noop() {
149        // Should not panic or error when deleting a non-existent alias
150        let result = std::panic::catch_unwind(|| {
151            let _ = delete("nonexistent");
152        });
153        assert!(result.is_ok());
154    }
155}