1 use std::{fmt::Write as _, io::Write};
2
3 use clap::*;
4
5 use crate::generator::{utils, Generator};
6
7 /// Generate bash completion file
8 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
9 pub struct Bash;
10
11 impl Generator for Bash {
file_name(&self, name: &str) -> String12 fn file_name(&self, name: &str) -> String {
13 format!("{}.bash", name)
14 }
15
generate(&self, cmd: &Command, buf: &mut dyn Write)16 fn generate(&self, cmd: &Command, buf: &mut dyn Write) {
17 let bin_name = cmd
18 .get_bin_name()
19 .expect("crate::generate should have set the bin_name");
20
21 w!(
22 buf,
23 format!(
24 "_{name}() {{
25 local i cur prev opts cmd
26 COMPREPLY=()
27 cur=\"${{COMP_WORDS[COMP_CWORD]}}\"
28 prev=\"${{COMP_WORDS[COMP_CWORD-1]}}\"
29 cmd=\"\"
30 opts=\"\"
31
32 for i in ${{COMP_WORDS[@]}}
33 do
34 case \"${{cmd}},${{i}}\" in
35 \",$1\")
36 cmd=\"{cmd}\"
37 ;;{subcmds}
38 *)
39 ;;
40 esac
41 done
42
43 case \"${{cmd}}\" in
44 {cmd})
45 opts=\"{name_opts}\"
46 if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then
47 COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") )
48 return 0
49 fi
50 case \"${{prev}}\" in{name_opts_details}
51 *)
52 COMPREPLY=()
53 ;;
54 esac
55 COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") )
56 return 0
57 ;;{subcmd_details}
58 esac
59 }}
60
61 complete -F _{name} -o bashdefault -o default {name}
62 ",
63 name = bin_name,
64 cmd = bin_name.replace('-', "__"),
65 name_opts = all_options_for_path(cmd, bin_name),
66 name_opts_details = option_details_for_path(cmd, bin_name),
67 subcmds = all_subcommands(cmd),
68 subcmd_details = subcommand_details(cmd)
69 )
70 .as_bytes()
71 );
72 }
73 }
74
all_subcommands(cmd: &Command) -> String75 fn all_subcommands(cmd: &Command) -> String {
76 debug!("all_subcommands");
77
78 fn add_command(
79 parent_fn_name: &str,
80 cmd: &Command,
81 subcmds: &mut Vec<(String, String, String)>,
82 ) {
83 let fn_name = format!(
84 "{parent_fn_name}__{cmd_name}",
85 parent_fn_name = parent_fn_name,
86 cmd_name = cmd.get_name().to_string().replace('-', "__")
87 );
88 subcmds.push((
89 parent_fn_name.to_string(),
90 cmd.get_name().to_string(),
91 fn_name.clone(),
92 ));
93 for alias in cmd.get_visible_aliases() {
94 subcmds.push((
95 parent_fn_name.to_string(),
96 alias.to_string(),
97 fn_name.clone(),
98 ));
99 }
100 for subcmd in cmd.get_subcommands() {
101 add_command(&fn_name, subcmd, subcmds);
102 }
103 }
104 let mut subcmds = vec![];
105 let fn_name = cmd.get_name().replace('-', "__");
106 for subcmd in cmd.get_subcommands() {
107 add_command(&fn_name, subcmd, &mut subcmds);
108 }
109 subcmds.sort();
110
111 let mut cases = vec![String::new()];
112 for (parent_fn_name, name, fn_name) in subcmds {
113 cases.push(format!(
114 "{parent_fn_name},{name})
115 cmd=\"{fn_name}\"
116 ;;",
117 parent_fn_name = parent_fn_name,
118 name = name,
119 fn_name = fn_name,
120 ));
121 }
122
123 cases.join("\n ")
124 }
125
subcommand_details(cmd: &Command) -> String126 fn subcommand_details(cmd: &Command) -> String {
127 debug!("subcommand_details");
128
129 let mut subcmd_dets = vec![String::new()];
130 let mut scs = utils::all_subcommands(cmd)
131 .iter()
132 .map(|x| x.1.replace(' ', "__"))
133 .collect::<Vec<_>>();
134
135 scs.sort();
136
137 subcmd_dets.extend(scs.iter().map(|sc| {
138 format!(
139 "{subcmd})
140 opts=\"{sc_opts}\"
141 if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then
142 COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") )
143 return 0
144 fi
145 case \"${{prev}}\" in{opts_details}
146 *)
147 COMPREPLY=()
148 ;;
149 esac
150 COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") )
151 return 0
152 ;;",
153 subcmd = sc.replace('-', "__"),
154 sc_opts = all_options_for_path(cmd, sc),
155 level = sc.split("__").map(|_| 1).sum::<u64>(),
156 opts_details = option_details_for_path(cmd, sc)
157 )
158 }));
159
160 subcmd_dets.join("\n ")
161 }
162
option_details_for_path(cmd: &Command, path: &str) -> String163 fn option_details_for_path(cmd: &Command, path: &str) -> String {
164 debug!("option_details_for_path: path={}", path);
165
166 let p = utils::find_subcommand_with_path(cmd, path.split("__").skip(1).collect());
167 let mut opts = vec![String::new()];
168
169 for o in p.get_opts() {
170 if let Some(longs) = o.get_long_and_visible_aliases() {
171 opts.extend(longs.iter().map(|long| {
172 format!(
173 "--{})
174 COMPREPLY=({})
175 return 0
176 ;;",
177 long,
178 vals_for(o)
179 )
180 }));
181 }
182
183 if let Some(shorts) = o.get_short_and_visible_aliases() {
184 opts.extend(shorts.iter().map(|short| {
185 format!(
186 "-{})
187 COMPREPLY=({})
188 return 0
189 ;;",
190 short,
191 vals_for(o)
192 )
193 }));
194 }
195 }
196
197 opts.join("\n ")
198 }
199
vals_for(o: &Arg) -> String200 fn vals_for(o: &Arg) -> String {
201 debug!("vals_for: o={}", o.get_id());
202
203 if let Some(vals) = crate::generator::utils::possible_values(o) {
204 format!(
205 "$(compgen -W \"{}\" -- \"${{cur}}\")",
206 vals.iter()
207 .filter(|pv| !pv.is_hide_set())
208 .map(|n| n.get_name())
209 .collect::<Vec<_>>()
210 .join(" ")
211 )
212 } else {
213 String::from("$(compgen -f \"${cur}\")")
214 }
215 }
216
all_options_for_path(cmd: &Command, path: &str) -> String217 fn all_options_for_path(cmd: &Command, path: &str) -> String {
218 debug!("all_options_for_path: path={}", path);
219
220 let p = utils::find_subcommand_with_path(cmd, path.split("__").skip(1).collect());
221
222 let mut opts = String::new();
223 for short in utils::shorts_and_visible_aliases(p) {
224 write!(&mut opts, "-{} ", short).unwrap();
225 }
226 for long in utils::longs_and_visible_aliases(p) {
227 write!(&mut opts, "--{} ", long).unwrap();
228 }
229 for pos in p.get_positionals() {
230 if let Some(vals) = utils::possible_values(pos) {
231 for value in vals {
232 write!(&mut opts, "{} ", value.get_name()).unwrap();
233 }
234 } else {
235 write!(&mut opts, "{} ", pos).unwrap();
236 }
237 }
238 for (sc, _) in utils::subcommands(p) {
239 write!(&mut opts, "{} ", sc).unwrap();
240 }
241 opts.pop();
242
243 opts
244 }
245