• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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