• 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!("{name}.bash")
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         ));
118     }
119 
120     cases.join("\n            ")
121 }
122 
subcommand_details(cmd: &Command) -> String123 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 
option_details_for_path(cmd: &Command, path: &str) -> String160 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 
vals_for(o: &Arg) -> String197 fn vals_for(o: &Arg) -> String {
198     debug!("vals_for: o={}", o.get_id());
199 
200     if let Some(vals) = 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 {
210         String::from("$(compgen -f \"${cur}\")")
211     }
212 }
213 
all_options_for_path(cmd: &Command, path: &str) -> String214 fn all_options_for_path(cmd: &Command, path: &str) -> String {
215     debug!("all_options_for_path: path={}", path);
216 
217     let p = utils::find_subcommand_with_path(cmd, path.split("__").skip(1).collect());
218 
219     let mut opts = String::new();
220     for short in utils::shorts_and_visible_aliases(p) {
221         write!(&mut opts, "-{short} ").unwrap();
222     }
223     for long in utils::longs_and_visible_aliases(p) {
224         write!(&mut opts, "--{long} ").unwrap();
225     }
226     for pos in p.get_positionals() {
227         if let Some(vals) = utils::possible_values(pos) {
228             for value in vals {
229                 write!(&mut opts, "{} ", value.get_name()).unwrap();
230             }
231         } else {
232             write!(&mut opts, "{pos} ").unwrap();
233         }
234     }
235     for (sc, _) in utils::subcommands(p) {
236         write!(&mut opts, "{sc} ").unwrap();
237     }
238     opts.pop();
239 
240     opts
241 }
242