aimcal-cli 0.12.1

AIM - Analyze. Interact. Manage Your Time, with calendar support
Documentation
// SPDX-FileCopyrightText: 2025-2026 Zexin Yuan <aim@yzx9.xyz>
//
// SPDX-License-Identifier: Apache-2.0

use std::io;

use clap::{ArgMatches, Command, ValueEnum, arg, value_parser};
use clap_complete::generate;

use crate::Cli;

#[derive(Debug, Clone, Copy)]
pub struct CmdGenerateCompletion {
    pub shell: Shell,
}

impl CmdGenerateCompletion {
    pub const NAME: &str = "generate-completion";

    pub fn command() -> Command {
        Command::new(Self::NAME)
            .about("Generate shell completion for the specified shell")
            .hide(true)
            .arg(
                arg!(shell: <SHELL> "The shell generator to use")
                    .value_parser(value_parser!(Shell)),
            )
    }

    pub fn from(matches: &ArgMatches) -> Self {
        match matches.get_one::<Shell>("shell") {
            Some(shell) => Self { shell: *shell },
            _ => unreachable!(),
        }
    }

    pub fn run(self) {
        tracing::debug!(?self, "generating shell completion...");
        self.generate(&mut io::stdout());
    }

    pub fn generate(self, buf: &mut impl io::Write) {
        use clap_complete::Shell as ClapShell;

        let mut cmd = Cli::command();
        let name = cmd.get_name().to_string();
        match self.shell {
            Shell::Bash => generate(ClapShell::Bash, &mut cmd, name, buf),
            Shell::Elvish => generate(ClapShell::Elvish, &mut cmd, name, buf),
            Shell::Fish => generate(ClapShell::Fish, &mut cmd, name, buf),
            Shell::PowerShell => generate(ClapShell::PowerShell, &mut cmd, name, buf),
            Shell::Zsh => generate(ClapShell::Zsh, &mut cmd, name, buf),
            Shell::Nushell => generate(clap_complete_nushell::Nushell, &mut cmd, name, buf),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Shell {
    Bash,
    Elvish,
    Fish,
    Nushell,
    #[clap(name = "powershell")]
    #[expect(clippy::enum_variant_names)]
    PowerShell,
    Zsh,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parses_generate_completion_command() {
        let args = ["generate-completion", "bash"];
        let matches = CmdGenerateCompletion::command()
            .try_get_matches_from(args)
            .unwrap();
        let parsed = CmdGenerateCompletion::from(&matches);
        assert_eq!(parsed.shell, Shell::Bash);

        let mut output = vec![];
        parsed.generate(&mut output);
        assert!(!output.is_empty());
    }

    #[test]
    fn parses_all_shell_variants() {
        for (shell, expected) in [
            ("bash", Shell::Bash),
            ("elvish", Shell::Elvish),
            ("fish", Shell::Fish),
            ("nushell", Shell::Nushell),
            ("powershell", Shell::PowerShell),
            ("zsh", Shell::Zsh),
        ] {
            let cmd = Cli::command();
            let matches = cmd
                .try_get_matches_from(["aim", "generate-completion", shell])
                .unwrap_or_else(|e| panic!("Failed to parse for shell '{shell}': {e}"));
            let sub_matches = matches.subcommand_matches("generate-completion").unwrap();
            let parsed = CmdGenerateCompletion::from(sub_matches);
            assert_eq!(parsed.shell, expected);
        }
    }
}