]>
Commit | Line | Data |
---|---|---|
8bb4bdeb XL |
1 | // Std |
2 | use std::io::Write; | |
3 | ||
4 | // Internal | |
5 | use app::parser::Parser; | |
6 | use args::{ArgSettings, OptBuilder}; | |
7 | use completions; | |
8 | ||
9 | pub struct BashGen<'a, 'b> | |
10 | where 'a: 'b | |
11 | { | |
12 | p: &'b Parser<'a, 'b>, | |
13 | } | |
14 | ||
15 | impl<'a, 'b> BashGen<'a, 'b> { | |
16 | pub fn new(p: &'b Parser<'a, 'b>) -> Self { BashGen { p: p } } | |
17 | ||
18 | pub fn generate_to<W: Write>(&self, buf: &mut W) { | |
19 | ||
20 | w!(buf, | |
21 | format!("_{name}() {{ | |
22 | local i cur prev opts cmds | |
23 | COMPREPLY=() | |
24 | cur=\"${{COMP_WORDS[COMP_CWORD]}}\" | |
25 | prev=\"${{COMP_WORDS[COMP_CWORD-1]}}\" | |
26 | cmd=\"\" | |
27 | opts=\"\" | |
28 | ||
29 | for i in ${{COMP_WORDS[@]}} | |
30 | do | |
31 | case \"${{i}}\" in | |
32 | {name}) | |
33 | cmd=\"{name}\" | |
34 | ;; | |
35 | {subcmds} | |
36 | *) | |
37 | ;; | |
38 | esac | |
39 | done | |
40 | ||
41 | case \"${{cmd}}\" in | |
42 | {name}) | |
43 | opts=\"{name_opts}\" | |
44 | if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then | |
45 | COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) ) | |
46 | return 0 | |
47 | fi | |
48 | case \"${{prev}}\" in | |
49 | {name_opts_details} | |
50 | *) | |
51 | COMPREPLY=() | |
52 | ;; | |
53 | esac | |
54 | COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) ) | |
55 | return 0 | |
56 | ;; | |
57 | {subcmd_details} | |
58 | esac | |
59 | }} | |
60 | ||
61 | complete -F _{name} -o bashdefault -o default {name} | |
62 | ", | |
63 | name = self.p.meta.bin_name.as_ref().unwrap(), | |
64 | name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()), | |
65 | name_opts_details = | |
66 | self.option_details_for_path(self.p.meta.bin_name.as_ref().unwrap()), | |
67 | subcmds = self.all_subcommands(), | |
68 | subcmd_details = self.subcommand_details()) | |
69 | .as_bytes()); | |
70 | } | |
71 | ||
72 | fn all_subcommands(&self) -> String { | |
041b39d2 | 73 | debugln!("BashGen::all_subcommands;"); |
8bb4bdeb XL |
74 | let mut subcmds = String::new(); |
75 | let scs = completions::all_subcommand_names(self.p); | |
76 | ||
77 | for sc in &scs { | |
78 | subcmds = format!("{} | |
79 | {name}) | |
041b39d2 | 80 | cmd+=\"__{name}\" |
8bb4bdeb XL |
81 | ;;", |
82 | subcmds, | |
041b39d2 | 83 | name = sc.replace("-", "__")); |
8bb4bdeb XL |
84 | } |
85 | ||
86 | subcmds | |
87 | } | |
88 | ||
89 | fn subcommand_details(&self) -> String { | |
041b39d2 | 90 | debugln!("BashGen::subcommand_details;"); |
8bb4bdeb XL |
91 | let mut subcmd_dets = String::new(); |
92 | let mut scs = completions::get_all_subcommand_paths(self.p, true); | |
93 | scs.sort(); | |
94 | scs.dedup(); | |
95 | ||
96 | for sc in &scs { | |
97 | subcmd_dets = format!("{} | |
98 | {subcmd}) | |
99 | opts=\"{sc_opts}\" | |
100 | if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then | |
101 | COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) ) | |
102 | return 0 | |
103 | fi | |
104 | case \"${{prev}}\" in | |
105 | {opts_details} | |
106 | *) | |
107 | COMPREPLY=() | |
108 | ;; | |
109 | esac | |
110 | COMPREPLY=( $(compgen -W \"${{opts}}\" -- ${{cur}}) ) | |
111 | return 0 | |
112 | ;;", | |
113 | subcmd_dets, | |
041b39d2 | 114 | subcmd = sc.replace("-", "__"), |
8bb4bdeb | 115 | sc_opts = self.all_options_for_path(&*sc), |
041b39d2 | 116 | level = sc.split("__").map(|_| 1).fold(0, |acc, n| acc + n), |
8bb4bdeb XL |
117 | opts_details = self.option_details_for_path(&*sc)); |
118 | } | |
119 | ||
120 | subcmd_dets | |
121 | } | |
122 | ||
123 | fn option_details_for_path(&self, path: &str) -> String { | |
041b39d2 | 124 | debugln!("BashGen::option_details_for_path: path={}", path); |
8bb4bdeb | 125 | let mut p = self.p; |
041b39d2 XL |
126 | for sc in path.split("__").skip(1) { |
127 | debugln!("BashGen::option_details_for_path:iter: sc={}", sc); | |
128 | p = &find_subcmd!(p, sc).unwrap().p; | |
8bb4bdeb XL |
129 | } |
130 | let mut opts = String::new(); | |
131 | for o in p.opts() { | |
132 | if let Some(l) = o.s.long { | |
133 | opts = format!("{} | |
134 | --{}) | |
135 | COMPREPLY=({}) | |
136 | return 0 | |
137 | ;;", | |
138 | opts, | |
139 | l, | |
140 | self.vals_for(o)); | |
141 | } | |
142 | if let Some(s) = o.s.short { | |
143 | opts = format!("{} | |
144 | -{}) | |
145 | COMPREPLY=({}) | |
146 | return 0 | |
147 | ;;", | |
148 | opts, | |
149 | s, | |
150 | self.vals_for(o)); | |
151 | } | |
152 | } | |
153 | opts | |
154 | } | |
155 | ||
156 | fn vals_for(&self, o: &OptBuilder) -> String { | |
041b39d2 | 157 | debugln!("BashGen::vals_for: o={}", o.b.name); |
8bb4bdeb XL |
158 | use args::AnyArg; |
159 | let mut ret = String::new(); | |
160 | let mut needs_quotes = true; | |
161 | if let Some(vals) = o.possible_vals() { | |
162 | needs_quotes = false; | |
163 | ret = format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" ")); | |
164 | } else if let Some(vec) = o.val_names() { | |
165 | let mut it = vec.iter().peekable(); | |
166 | while let Some((_, val)) = it.next() { | |
167 | ret = format!("{}<{}>{}", | |
168 | ret, | |
169 | val, | |
170 | if it.peek().is_some() { " " } else { "" }); | |
171 | } | |
172 | let num = vec.len(); | |
173 | if o.is_set(ArgSettings::Multiple) && num == 1 { | |
174 | ret = format!("{}...", ret); | |
175 | } | |
176 | } else if let Some(num) = o.num_vals() { | |
177 | let mut it = (0..num).peekable(); | |
178 | while let Some(_) = it.next() { | |
179 | ret = format!("{}<{}>{}", | |
180 | ret, | |
181 | o.name(), | |
182 | if it.peek().is_some() { " " } else { "" }); | |
183 | } | |
184 | if o.is_set(ArgSettings::Multiple) && num == 1 { | |
185 | ret = format!("{}...", ret); | |
186 | } | |
187 | } else { | |
188 | ret = format!("<{}>", o.name()); | |
189 | if o.is_set(ArgSettings::Multiple) { | |
190 | ret = format!("{}...", ret); | |
191 | } | |
192 | } | |
193 | if needs_quotes { | |
194 | ret = format!("\"{}\"", ret); | |
195 | } | |
196 | ret | |
197 | } | |
198 | fn all_options_for_path(&self, path: &str) -> String { | |
041b39d2 | 199 | debugln!("BashGen::all_options_for_path: path={}", path); |
8bb4bdeb | 200 | let mut p = self.p; |
041b39d2 XL |
201 | for sc in path.split("__").skip(1) { |
202 | debugln!("BashGen::all_options_for_path:iter: sc={}", sc); | |
203 | p = &find_subcmd!(p, sc).unwrap().p; | |
8bb4bdeb | 204 | } |
041b39d2 | 205 | let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s)); |
8bb4bdeb XL |
206 | opts = format!("{} {}", |
207 | opts, | |
041b39d2 | 208 | longs!(p).fold(String::new(), |acc, l| format!("{} --{}", acc, l))); |
8bb4bdeb XL |
209 | opts = format!("{} {}", |
210 | opts, | |
211 | p.positionals | |
212 | .values() | |
213 | .fold(String::new(), |acc, p| format!("{} {}", acc, p))); | |
214 | opts = format!("{} {}", | |
215 | opts, | |
216 | p.subcommands | |
217 | .iter() | |
218 | .fold(String::new(), |acc, s| format!("{} {}", acc, s.p.meta.name))); | |
219 | for sc in &p.subcommands { | |
220 | if let Some(ref aliases) = sc.p.meta.aliases { | |
221 | opts = format!("{} {}", | |
222 | opts, | |
223 | aliases.iter() | |
224 | .map(|&(n, _)| n) | |
225 | .fold(String::new(), |acc, a| format!("{} {}", acc, a))); | |
226 | } | |
227 | } | |
228 | opts | |
229 | } | |
230 | } |