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