clap_complete/aot/shells/
bash.rs
1use std::{fmt::Write as _, io::Write};
2
3use clap::{Arg, Command, ValueHint};
4
5use crate::generator::{utils, Generator};
6
7#[derive(Copy, Clone, PartialEq, Eq, Debug)]
9pub struct Bash;
10
11impl Generator for Bash {
12 fn file_name(&self, name: &str) -> String {
13 format!("{name}.bash")
14 }
15
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 let fn_name = bin_name.replace('-', "__");
22
23 write!(
24 buf,
25 "_{name}() {{
26 local i cur prev opts cmd
27 COMPREPLY=()
28 if [[ \"${{BASH_VERSINFO[0]}}\" -ge 4 ]]; then
29 cur=\"$2\"
30 else
31 cur=\"${{COMP_WORDS[COMP_CWORD]}}\"
32 fi
33 prev=\"$3\"
34 cmd=\"\"
35 opts=\"\"
36
37 for i in \"${{COMP_WORDS[@]:0:COMP_CWORD}}\"
38 do
39 case \"${{cmd}},${{i}}\" in
40 \",$1\")
41 cmd=\"{cmd}\"
42 ;;{subcmds}
43 *)
44 ;;
45 esac
46 done
47
48 case \"${{cmd}}\" in
49 {cmd})
50 opts=\"{name_opts}\"
51 if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then
52 COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") )
53 return 0
54 fi
55 case \"${{prev}}\" in{name_opts_details}
56 *)
57 COMPREPLY=()
58 ;;
59 esac
60 COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") )
61 return 0
62 ;;{subcmd_details}
63 esac
64}}
65
66if [[ \"${{BASH_VERSINFO[0]}}\" -eq 4 && \"${{BASH_VERSINFO[1]}}\" -ge 4 || \"${{BASH_VERSINFO[0]}}\" -gt 4 ]]; then
67 complete -F _{name} -o nosort -o bashdefault -o default {name}
68else
69 complete -F _{name} -o bashdefault -o default {name}
70fi
71",
72 name = bin_name,
73 cmd = fn_name,
74 name_opts = all_options_for_path(cmd, bin_name),
75 name_opts_details = option_details_for_path(cmd, bin_name),
76 subcmds = all_subcommands(cmd, &fn_name),
77 subcmd_details = subcommand_details(cmd)
78 ).expect("failed to write completion file");
79 }
80}
81
82fn all_subcommands(cmd: &Command, parent_fn_name: &str) -> String {
83 debug!("all_subcommands");
84
85 fn add_command(
86 parent_fn_name: &str,
87 cmd: &Command,
88 subcmds: &mut Vec<(String, String, String)>,
89 ) {
90 let fn_name = format!(
91 "{parent_fn_name}__{cmd_name}",
92 parent_fn_name = parent_fn_name,
93 cmd_name = cmd.get_name().to_string().replace('-', "__")
94 );
95 subcmds.push((
96 parent_fn_name.to_string(),
97 cmd.get_name().to_string(),
98 fn_name.clone(),
99 ));
100 for alias in cmd.get_visible_aliases() {
101 subcmds.push((
102 parent_fn_name.to_string(),
103 alias.to_string(),
104 fn_name.clone(),
105 ));
106 }
107 for subcmd in cmd.get_subcommands() {
108 add_command(&fn_name, subcmd, subcmds);
109 }
110 }
111 let mut subcmds = vec![];
112 for subcmd in cmd.get_subcommands() {
113 add_command(parent_fn_name, subcmd, &mut subcmds);
114 }
115 subcmds.sort();
116
117 let mut cases = vec![String::new()];
118 for (parent_fn_name, name, fn_name) in subcmds {
119 cases.push(format!(
120 "{parent_fn_name},{name})
121 cmd=\"{fn_name}\"
122 ;;",
123 ));
124 }
125
126 cases.join("\n ")
127}
128
129fn subcommand_details(cmd: &Command) -> String {
130 debug!("subcommand_details");
131
132 let mut subcmd_dets = vec![String::new()];
133 let mut scs = utils::all_subcommands(cmd)
134 .iter()
135 .map(|x| x.1.replace(' ', "__"))
136 .collect::<Vec<_>>();
137
138 scs.sort();
139 scs.dedup();
140
141 subcmd_dets.extend(scs.iter().map(|sc| {
142 format!(
143 "{subcmd})
144 opts=\"{sc_opts}\"
145 if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then
146 COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") )
147 return 0
148 fi
149 case \"${{prev}}\" in{opts_details}
150 *)
151 COMPREPLY=()
152 ;;
153 esac
154 COMPREPLY=( $(compgen -W \"${{opts}}\" -- \"${{cur}}\") )
155 return 0
156 ;;",
157 subcmd = sc.replace('-', "__"),
158 sc_opts = all_options_for_path(cmd, sc),
159 level = sc.split("__").map(|_| 1).sum::<u64>(),
160 opts_details = option_details_for_path(cmd, sc)
161 )
162 }));
163
164 subcmd_dets.join("\n ")
165}
166
167fn option_details_for_path(cmd: &Command, path: &str) -> String {
168 debug!("option_details_for_path: path={path}");
169
170 let p = utils::find_subcommand_with_path(cmd, path.split("__").skip(1).collect());
171 let mut opts = vec![String::new()];
172
173 for o in p.get_opts() {
174 let compopt = match o.get_value_hint() {
175 ValueHint::FilePath => Some("compopt -o filenames"),
176 ValueHint::DirPath => Some("compopt -o plusdirs"),
177 ValueHint::Other => Some("compopt -o nospace"),
178 _ => None,
179 };
180
181 if let Some(longs) = o.get_long_and_visible_aliases() {
182 opts.extend(longs.iter().map(|long| {
183 let mut v = vec![format!("--{})", long)];
184
185 if o.get_value_hint() == ValueHint::FilePath {
186 v.extend([
187 "local oldifs".to_string(),
188 r#"if [ -n "${IFS+x}" ]; then"#.to_string(),
189 r#" oldifs="$IFS""#.to_string(),
190 "fi".to_string(),
191 r#"IFS=$'\n'"#.to_string(),
192 format!("COMPREPLY=({})", vals_for(o)),
193 r#"if [ -n "${oldifs+x}" ]; then"#.to_string(),
194 r#" IFS="$oldifs""#.to_string(),
195 "fi".to_string(),
196 ]);
197 } else {
198 v.push(format!("COMPREPLY=({})", vals_for(o)));
199 }
200
201 if let Some(copt) = compopt {
202 v.extend([
203 r#"if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then"#.to_string(),
204 format!(" {copt}"),
205 "fi".to_string(),
206 ]);
207 }
208
209 v.extend(["return 0", ";;"].iter().map(|s| (*s).to_string()));
210 v.join("\n ")
211 }));
212 }
213
214 if let Some(shorts) = o.get_short_and_visible_aliases() {
215 opts.extend(shorts.iter().map(|short| {
216 let mut v = vec![format!("-{})", short)];
217
218 if o.get_value_hint() == ValueHint::FilePath {
219 v.extend([
220 "local oldifs".to_string(),
221 r#"if [ -n "${IFS+x}" ]; then"#.to_string(),
222 r#" oldifs="$IFS""#.to_string(),
223 "fi".to_string(),
224 r#"IFS=$'\n'"#.to_string(),
225 format!("COMPREPLY=({})", vals_for(o)),
226 r#"if [ -n "${oldifs+x}" ]; then"#.to_string(),
227 r#" IFS="$oldifs""#.to_string(),
228 "fi".to_string(),
229 ]);
230 } else {
231 v.push(format!("COMPREPLY=({})", vals_for(o)));
232 }
233
234 if let Some(copt) = compopt {
235 v.extend([
236 r#"if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then"#.to_string(),
237 format!(" {copt}"),
238 "fi".to_string(),
239 ]);
240 }
241
242 v.extend(["return 0", ";;"].iter().map(|s| (*s).to_string()));
243 v.join("\n ")
244 }));
245 }
246 }
247
248 opts.join("\n ")
249}
250
251fn vals_for(o: &Arg) -> String {
252 debug!("vals_for: o={}", o.get_id());
253
254 if let Some(vals) = utils::possible_values(o) {
255 format!(
256 "$(compgen -W \"{}\" -- \"${{cur}}\")",
257 vals.iter()
258 .filter(|pv| !pv.is_hide_set())
259 .map(|n| n.get_name())
260 .collect::<Vec<_>>()
261 .join(" ")
262 )
263 } else if o.get_value_hint() == ValueHint::DirPath {
264 String::from("") } else if o.get_value_hint() == ValueHint::Other {
266 String::from("\"${cur}\"")
267 } else {
268 String::from("$(compgen -f \"${cur}\")")
269 }
270}
271
272fn all_options_for_path(cmd: &Command, path: &str) -> String {
273 debug!("all_options_for_path: path={path}");
274
275 let p = utils::find_subcommand_with_path(cmd, path.split("__").skip(1).collect());
276
277 let mut opts = String::new();
278 for short in utils::shorts_and_visible_aliases(p) {
279 write!(&mut opts, "-{short} ").expect("writing to String is infallible");
280 }
281 for long in utils::longs_and_visible_aliases(p) {
282 write!(&mut opts, "--{long} ").expect("writing to String is infallible");
283 }
284 for pos in p.get_positionals() {
285 if let Some(vals) = utils::possible_values(pos) {
286 for value in vals {
287 write!(&mut opts, "{} ", value.get_name())
288 .expect("writing to String is infallible");
289 }
290 } else {
291 write!(&mut opts, "{pos} ").expect("writing to String is infallible");
292 }
293 }
294 for (sc, _) in utils::subcommands(p) {
295 write!(&mut opts, "{sc} ").expect("writing to String is infallible");
296 }
297 opts.pop();
298
299 opts
300}