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().unwrap().to_string(),
49 writer,
50 );
51 }
52 Ok(())
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn test_completion_no_binaries_succeeds() {
62 let cmd = CompletionCommand {
63 binaries: None,
64 shell: Shell::Bash,
65 };
66
67 let result = cmd.run(&mut std::io::sink());
69 assert!(
70 result.is_ok(),
71 "Expected command to succeed, got: {result:?}"
72 );
73 }
74
75 #[test]
76 fn test_completion_binaries_succeeds() {
77 let cmd = CompletionCommand {
78 binaries: Some(vec!["forest-cli".to_string(), "forest-tool".to_string()]),
79 shell: Shell::Bash,
80 };
81
82 let result = cmd.run(&mut std::io::sink());
83 assert!(
84 result.is_ok(),
85 "Expected command to succeed, got {result:?}"
86 );
87 }
88
89 #[test]
90 fn test_completion_binaries_fails() {
91 let cmd = CompletionCommand {
92 binaries: Some(vec!["non-existent-binary".to_string()]),
93 shell: Shell::Bash,
94 };
95
96 let result = cmd.run(&mut std::io::sink());
97 assert!(
98 result.is_err(),
99 "Expected command to fail, but it succeeded"
100 );
101
102 let err = result.unwrap_err().to_string();
103 assert!(
104 err.contains("Unknown binary") && err.contains("non-existent-binary"),
105 "Error message '{err}' did not contain expected text"
106 );
107 }
108
109 #[test]
110 fn test_completion_mixed_valid_invalid_fails() {
111 let cmd = CompletionCommand {
113 binaries: Some(vec![
114 "forest-cli".to_string(),
115 "non-existent-binary".to_string(),
116 ]),
117 shell: Shell::Bash,
118 };
119
120 let result = cmd.run(&mut std::io::sink());
121 assert!(
122 result.is_err(),
123 "Expected command to fail, but it succeeded"
124 );
125
126 let err = result.unwrap_err().to_string();
127 assert!(
128 err.contains("Unknown binary") && err.contains("non-existent-binary"),
129 "Error message '{err}' did not contain expected text"
130 );
131 }
132}