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 { |
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 | 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 nosort -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 | |
75 | 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 | )); |
118 | } |
119 | |
120 | cases.join(" \n " ) |
121 | } |
122 | |
123 | fn subcommand_details(cmd: &Command) -> String { |
124 | debug!("subcommand_details" ); |
125 | |
126 | let mut subcmd_dets = vec![String::new()]; |
127 | let mut scs = utils::all_subcommands(cmd) |
128 | .iter() |
129 | .map(|x| x.1.replace(' ' , "__" )) |
130 | .collect::<Vec<_>>(); |
131 | |
132 | scs.sort(); |
133 | |
134 | subcmd_dets.extend(scs.iter().map(|sc| { |
135 | format!( |
136 | " {subcmd}) |
137 | opts= \"{sc_opts}\" |
138 | if [[ $ {{cur }} == -* || $ {{COMP_CWORD }} -eq {level} ]] ; then |
139 | COMPREPLY=( $(compgen -W \"$ {{opts }}\" -- \"$ {{cur }}\") ) |
140 | return 0 |
141 | fi |
142 | case \"$ {{prev }}\" in {opts_details} |
143 | *) |
144 | COMPREPLY=() |
145 | ;; |
146 | esac |
147 | COMPREPLY=( $(compgen -W \"$ {{opts }}\" -- \"$ {{cur }}\") ) |
148 | return 0 |
149 | ;;" , |
150 | subcmd = sc.replace('-' , "__" ), |
151 | sc_opts = all_options_for_path(cmd, sc), |
152 | level = sc.split("__" ).map(|_| 1).sum::<u64>(), |
153 | opts_details = option_details_for_path(cmd, sc) |
154 | ) |
155 | })); |
156 | |
157 | subcmd_dets.join(" \n " ) |
158 | } |
159 | |
160 | fn option_details_for_path(cmd: &Command, path: &str) -> String { |
161 | debug!("option_details_for_path: path={path}" ); |
162 | |
163 | let p = utils::find_subcommand_with_path(cmd, path.split("__" ).skip(1).collect()); |
164 | let mut opts = vec![String::new()]; |
165 | |
166 | for o in p.get_opts() { |
167 | if let Some(longs) = o.get_long_and_visible_aliases() { |
168 | opts.extend(longs.iter().map(|long| { |
169 | format!( |
170 | "-- {}) |
171 | COMPREPLY=( {}) |
172 | return 0 |
173 | ;;" , |
174 | long, |
175 | vals_for(o) |
176 | ) |
177 | })); |
178 | } |
179 | |
180 | if let Some(shorts) = o.get_short_and_visible_aliases() { |
181 | opts.extend(shorts.iter().map(|short| { |
182 | format!( |
183 | "- {}) |
184 | COMPREPLY=( {}) |
185 | return 0 |
186 | ;;" , |
187 | short, |
188 | vals_for(o) |
189 | ) |
190 | })); |
191 | } |
192 | } |
193 | |
194 | opts.join(" \n " ) |
195 | } |
196 | |
197 | fn vals_for(o: &Arg) -> String { |
198 | debug!("vals_for: o={}" , o.get_id()); |
199 | |
200 | if let Some(vals: Vec) = crate::generator::utils::possible_values(o) { |
201 | format!( |
202 | "$(compgen -W \"{}\" -- \"$ {{cur }}\")" , |
203 | vals.iter() |
204 | .filter(|pv| !pv.is_hide_set()) |
205 | .map(|n| n.get_name()) |
206 | .collect::<Vec<_>>() |
207 | .join(" " ) |
208 | ) |
209 | } else if o.get_value_hint() == ValueHint::Other { |
210 | String::from(" \"${cur} \"" ) |
211 | } else { |
212 | String::from("$(compgen -f \"${cur} \")" ) |
213 | } |
214 | } |
215 | |
216 | fn all_options_for_path(cmd: &Command, path: &str) -> String { |
217 | debug!("all_options_for_path: path={path}" ); |
218 | |
219 | let p = utils::find_subcommand_with_path(cmd, path.split("__" ).skip(1).collect()); |
220 | |
221 | let mut opts = String::new(); |
222 | for short in utils::shorts_and_visible_aliases(p) { |
223 | write!(&mut opts, "- {short} " ).unwrap(); |
224 | } |
225 | for long in utils::longs_and_visible_aliases(p) { |
226 | write!(&mut opts, "-- {long} " ).unwrap(); |
227 | } |
228 | for pos in p.get_positionals() { |
229 | if let Some(vals) = utils::possible_values(pos) { |
230 | for value in vals { |
231 | write!(&mut opts, " {} " , value.get_name()).unwrap(); |
232 | } |
233 | } else { |
234 | write!(&mut opts, " {pos} " ).unwrap(); |
235 | } |
236 | } |
237 | for (sc, _) in utils::subcommands(p) { |
238 | write!(&mut opts, " {sc} " ).unwrap(); |
239 | } |
240 | opts.pop(); |
241 | |
242 | opts |
243 | } |
244 | |