forest/cli_shared/cli/
completion_cmd.rs1use crate::cli::subcommands::Cli as ForestCli;
4use crate::daemon::main::Cli as ForestDaemonCli;
5use crate::tool::subcommands::Cli as ForestToolCli;
6use crate::wallet::subcommands::Cli as ForestWalletCli;
7use ahash::HashMap;
8use clap::{Command, CommandFactory};
9use clap_complete::aot::{Shell, generate};
10use itertools::Itertools as _;
11
12#[derive(Debug, clap::Args)]
14pub struct CompletionCommand {
15 #[arg(value_delimiter = ',')]
18 binaries: Option<Vec<String>>,
19 #[arg(long, default_value = "bash")]
21 shell: Shell,
22}
23
24impl CompletionCommand {
25 pub fn run<W: std::io::Write>(self, writer: &mut W) -> anyhow::Result<()> {
26 let mut bin_cmd_map: HashMap<String, Command> = HashMap::from_iter([
27 ("forest".to_string(), ForestDaemonCli::command()),
28 ("forest-cli".to_string(), ForestCli::command()),
29 ("forest-wallet".to_string(), ForestWalletCli::command()),
30 ("forest-tool".to_string(), ForestToolCli::command()),
31 ]);
32
33 let valid_binaries = bin_cmd_map.keys().cloned().collect_vec();
34 let binaries = self.binaries.unwrap_or_else(|| valid_binaries.clone());
35
36 for b in binaries {
37 let cmd = bin_cmd_map.get_mut(&b).ok_or_else(|| {
38 anyhow::anyhow!(
39 "Unknown binary: '{}'. Valid binaries are: {:?}",
40 b,
41 valid_binaries.join(",")
42 )
43 })?;
44
45 generate(
46 self.shell,
47 cmd,
48 cmd.get_bin_name()
49 .expect("every CLI sets bin_name via its clap command attribute")
50 .to_string(),
51 writer,
52 );
53 }
54 Ok(())
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn test_completion_no_binaries_succeeds() {
64 let cmd = CompletionCommand {
65 binaries: None,
66 shell: Shell::Bash,
67 };
68
69 let result = cmd.run(&mut std::io::sink());
71 assert!(
72 result.is_ok(),
73 "Expected command to succeed, got: {result:?}"
74 );
75 }
76
77 #[test]
78 fn test_completion_binaries_succeeds() {
79 let cmd = CompletionCommand {
80 binaries: Some(vec!["forest-cli".to_string(), "forest-tool".to_string()]),
81 shell: Shell::Bash,
82 };
83
84 let result = cmd.run(&mut std::io::sink());
85 assert!(
86 result.is_ok(),
87 "Expected command to succeed, got {result:?}"
88 );
89 }
90
91 #[test]
92 fn test_completion_binaries_fails() {
93 let cmd = CompletionCommand {
94 binaries: Some(vec!["non-existent-binary".to_string()]),
95 shell: Shell::Bash,
96 };
97
98 let result = cmd.run(&mut std::io::sink());
99 assert!(
100 result.is_err(),
101 "Expected command to fail, but it succeeded"
102 );
103
104 let err = result.unwrap_err().to_string();
105 assert!(
106 err.contains("Unknown binary") && err.contains("non-existent-binary"),
107 "Error message '{err}' did not contain expected text"
108 );
109 }
110
111 #[test]
112 fn test_completion_mixed_valid_invalid_fails() {
113 let cmd = CompletionCommand {
115 binaries: Some(vec![
116 "forest-cli".to_string(),
117 "non-existent-binary".to_string(),
118 ]),
119 shell: Shell::Bash,
120 };
121
122 let result = cmd.run(&mut std::io::sink());
123 assert!(
124 result.is_err(),
125 "Expected command to fail, but it succeeded"
126 );
127
128 let err = result.unwrap_err().to_string();
129 assert!(
130 err.contains("Unknown binary") && err.contains("non-existent-binary"),
131 "Error message '{err}' did not contain expected text"
132 );
133 }
134}