1use anyhow::Result;
16use clap::{CommandFactory, ValueEnum};
17use clap_complete::{Shell as ClapShell, generate};
18
19#[derive(clap::Args, Debug)]
21pub struct CompletionsArgs {
22 #[arg(value_enum)]
24 pub shell: Shell,
25}
26
27#[derive(Clone, Copy, Debug, ValueEnum)]
29pub enum Shell {
30 Bash,
32 Zsh,
34 Fish,
36 #[value(name = "powershell")]
38 PowerShell,
39 Elvish,
41}
42
43impl From<Shell> for ClapShell {
44 fn from(shell: Shell) -> Self {
45 match shell {
46 Shell::Bash => ClapShell::Bash,
47 Shell::Zsh => ClapShell::Zsh,
48 Shell::Fish => ClapShell::Fish,
49 Shell::PowerShell => ClapShell::PowerShell,
50 Shell::Elvish => ClapShell::Elvish,
51 }
52 }
53}
54
55pub fn handle_completions(args: CompletionsArgs) -> Result<()> {
60 let mut cmd = crate::cli::Cli::command();
61 let shell: ClapShell = args.shell.into();
62 let bin_name = cmd.get_name().to_string();
63 generate(shell, &mut cmd, bin_name, &mut std::io::stdout());
64 Ok(())
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use clap::ValueEnum;
71
72 #[test]
73 fn shell_enum_parses_bash() {
74 let shell = Shell::from_str("bash", true).expect("parse bash");
75 assert!(matches!(shell, Shell::Bash));
76 }
77
78 #[test]
79 fn shell_enum_parses_zsh() {
80 let shell = Shell::from_str("zsh", true).expect("parse zsh");
81 assert!(matches!(shell, Shell::Zsh));
82 }
83
84 #[test]
85 fn shell_enum_parses_fish() {
86 let shell = Shell::from_str("fish", true).expect("parse fish");
87 assert!(matches!(shell, Shell::Fish));
88 }
89
90 #[test]
91 fn shell_enum_parses_powershell() {
92 let shell = Shell::from_str("powershell", true).expect("parse powershell");
93 assert!(matches!(shell, Shell::PowerShell));
94 }
95
96 #[test]
97 fn shell_enum_parses_elvish() {
98 let shell = Shell::from_str("elvish", true).expect("parse elvish");
99 assert!(matches!(shell, Shell::Elvish));
100 }
101
102 #[test]
103 fn shell_enum_rejects_invalid() {
104 let result = Shell::from_str("invalid", true);
105 assert!(result.is_err());
106 }
107
108 #[test]
109 fn shell_conversion_to_clap_shell() {
110 assert!(matches!(ClapShell::from(Shell::Bash), ClapShell::Bash));
111 assert!(matches!(ClapShell::from(Shell::Zsh), ClapShell::Zsh));
112 assert!(matches!(ClapShell::from(Shell::Fish), ClapShell::Fish));
113 assert!(matches!(
114 ClapShell::from(Shell::PowerShell),
115 ClapShell::PowerShell
116 ));
117 assert!(matches!(ClapShell::from(Shell::Elvish), ClapShell::Elvish));
118 }
119
120 #[test]
121 fn handle_completions_generates_non_empty_output() {
122 let mut output = Vec::new();
123 let mut cmd = crate::cli::Cli::command();
124 let bin_name = cmd.get_name().to_string();
125 generate(ClapShell::Bash, &mut cmd, bin_name, &mut output);
126
127 assert!(!output.is_empty(), "completion script should not be empty");
128 let output_str = String::from_utf8(output).expect("valid UTF-8");
129 assert!(
130 output_str.contains("ralph"),
131 "completion script should reference 'ralph'"
132 );
133 }
134}