1 // Std
2 use std::io::Write;
3 #[allow(deprecated, unused_imports)]
4 use std::ascii::AsciiExt;
5
6 // Internal
7 use app::App;
8 use app::parser::Parser;
9 use args::{AnyArg, ArgSettings};
10 use completions;
11 use INTERNAL_ERROR_MSG;
12
13 pub struct ZshGen<'a, 'b>
14 where
15 'a: 'b,
16 {
17 p: &'b Parser<'a, 'b>,
18 }
19
20 impl<'a, 'b> ZshGen<'a, 'b> {
new(p: &'b Parser<'a, 'b>) -> Self21 pub fn new(p: &'b Parser<'a, 'b>) -> Self {
22 debugln!("ZshGen::new;");
23 ZshGen { p: p }
24 }
25
generate_to<W: Write>(&self, buf: &mut W)26 pub fn generate_to<W: Write>(&self, buf: &mut W) {
27 debugln!("ZshGen::generate_to;");
28 w!(
29 buf,
30 format!(
31 "\
32 #compdef {name}
33
34 autoload -U is-at-least
35
36 _{name}() {{
37 typeset -A opt_args
38 typeset -a _arguments_options
39 local ret=1
40
41 if is-at-least 5.2; then
42 _arguments_options=(-s -S -C)
43 else
44 _arguments_options=(-s -C)
45 fi
46
47 local context curcontext=\"$curcontext\" state line
48 {initial_args}
49 {subcommands}
50 }}
51
52 {subcommand_details}
53
54 _{name} \"$@\"",
55 name = self.p.meta.bin_name.as_ref().unwrap(),
56 initial_args = get_args_of(self.p),
57 subcommands = get_subcommands_of(self.p),
58 subcommand_details = subcommand_details(self.p)
59 ).as_bytes()
60 );
61 }
62 }
63
64 // Displays the commands of a subcommand
65 // (( $+functions[_[bin_name_underscore]_commands] )) ||
66 // _[bin_name_underscore]_commands() {
67 // local commands; commands=(
68 // '[arg_name]:[arg_help]'
69 // )
70 // _describe -t commands '[bin_name] commands' commands "$@"
71 //
72 // Where the following variables are present:
73 // [bin_name_underscore]: The full space delineated bin_name, where spaces have been replaced by
74 // underscore characters
75 // [arg_name]: The name of the subcommand
76 // [arg_help]: The help message of the subcommand
77 // [bin_name]: The full space delineated bin_name
78 //
79 // Here's a snippet from rustup:
80 //
81 // (( $+functions[_rustup_commands] )) ||
82 // _rustup_commands() {
83 // local commands; commands=(
84 // 'show:Show the active and installed toolchains'
85 // 'update:Update Rust toolchains'
86 // # ... snip for brevity
87 // 'help:Prints this message or the help of the given subcommand(s)'
88 // )
89 // _describe -t commands 'rustup commands' commands "$@"
90 //
subcommand_details(p: &Parser) -> String91 fn subcommand_details(p: &Parser) -> String {
92 debugln!("ZshGen::subcommand_details;");
93 // First we do ourself
94 let mut ret = vec![
95 format!(
96 "\
97 (( $+functions[_{bin_name_underscore}_commands] )) ||
98 _{bin_name_underscore}_commands() {{
99 local commands; commands=(
100 {subcommands_and_args}
101 )
102 _describe -t commands '{bin_name} commands' commands \"$@\"
103 }}",
104 bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"),
105 bin_name = p.meta.bin_name.as_ref().unwrap(),
106 subcommands_and_args = subcommands_of(p)
107 ),
108 ];
109
110 // Next we start looping through all the children, grandchildren, etc.
111 let mut all_subcommands = completions::all_subcommands(p);
112 all_subcommands.sort();
113 all_subcommands.dedup();
114 for &(_, ref bin_name) in &all_subcommands {
115 debugln!("ZshGen::subcommand_details:iter: bin_name={}", bin_name);
116 ret.push(format!(
117 "\
118 (( $+functions[_{bin_name_underscore}_commands] )) ||
119 _{bin_name_underscore}_commands() {{
120 local commands; commands=(
121 {subcommands_and_args}
122 )
123 _describe -t commands '{bin_name} commands' commands \"$@\"
124 }}",
125 bin_name_underscore = bin_name.replace(" ", "__"),
126 bin_name = bin_name,
127 subcommands_and_args = subcommands_of(parser_of(p, bin_name))
128 ));
129 }
130
131 ret.join("\n")
132 }
133
134 // Generates subcommand completions in form of
135 //
136 // '[arg_name]:[arg_help]'
137 //
138 // Where:
139 // [arg_name]: the subcommand's name
140 // [arg_help]: the help message of the subcommand
141 //
142 // A snippet from rustup:
143 // 'show:Show the active and installed toolchains'
144 // 'update:Update Rust toolchains'
subcommands_of(p: &Parser) -> String145 fn subcommands_of(p: &Parser) -> String {
146 debugln!("ZshGen::subcommands_of;");
147 let mut ret = vec![];
148 fn add_sc(sc: &App, n: &str, ret: &mut Vec<String>) {
149 debugln!("ZshGen::add_sc;");
150 let s = format!(
151 "\"{name}:{help}\" \\",
152 name = n,
153 help = sc.p
154 .meta
155 .about
156 .unwrap_or("")
157 .replace("[", "\\[")
158 .replace("]", "\\]")
159 );
160 if !s.is_empty() {
161 ret.push(s);
162 }
163 }
164
165 // The subcommands
166 for sc in p.subcommands() {
167 debugln!(
168 "ZshGen::subcommands_of:iter: subcommand={}",
169 sc.p.meta.name
170 );
171 add_sc(sc, &sc.p.meta.name, &mut ret);
172 if let Some(ref v) = sc.p.meta.aliases {
173 for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) {
174 add_sc(sc, alias, &mut ret);
175 }
176 }
177 }
178
179 ret.join("\n")
180 }
181
182 // Get's the subcommand section of a completion file
183 // This looks roughly like:
184 //
185 // case $state in
186 // ([bin_name]_args)
187 // curcontext=\"${curcontext%:*:*}:[name_hyphen]-command-$words[1]:\"
188 // case $line[1] in
189 //
190 // ([name])
191 // _arguments -C -s -S \
192 // [subcommand_args]
193 // && ret=0
194 //
195 // [RECURSIVE_CALLS]
196 //
197 // ;;",
198 //
199 // [repeat]
200 //
201 // esac
202 // ;;
203 // esac",
204 //
205 // Where the following variables are present:
206 // [name] = The subcommand name in the form of "install" for "rustup toolchain install"
207 // [bin_name] = The full space delineated bin_name such as "rustup toolchain install"
208 // [name_hyphen] = The full space delineated bin_name, but replace spaces with hyphens
209 // [repeat] = From the same recursive calls, but for all subcommands
210 // [subcommand_args] = The same as zsh::get_args_of
211 fn get_subcommands_of(p: &Parser) -> String {
212 debugln!("get_subcommands_of;");
213
214 debugln!(
215 "get_subcommands_of: Has subcommands...{:?}",
216 p.has_subcommands()
217 );
218 if !p.has_subcommands() {
219 return String::new();
220 }
221
222 let sc_names = completions::subcommands_of(p);
223
224 let mut subcmds = vec![];
225 for &(ref name, ref bin_name) in &sc_names {
226 let mut v = vec![format!("({})", name)];
227 let subcommand_args = get_args_of(parser_of(p, &*bin_name));
228 if !subcommand_args.is_empty() {
229 v.push(subcommand_args);
230 }
231 let subcommands = get_subcommands_of(parser_of(p, &*bin_name));
232 if !subcommands.is_empty() {
233 v.push(subcommands);
234 }
235 v.push(String::from(";;"));
236 subcmds.push(v.join("\n"));
237 }
238
239 format!(
240 "case $state in
241 ({name})
242 words=($line[{pos}] \"${{words[@]}}\")
243 (( CURRENT += 1 ))
244 curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$line[{pos}]:\"
245 case $line[{pos}] in
246 {subcommands}
247 esac
248 ;;
249 esac",
250 name = p.meta.name,
251 name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"),
252 subcommands = subcmds.join("\n"),
253 pos = p.positionals().len() + 1
254 )
255 }
256
257 fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> {
258 debugln!("parser_of: sc={}", sc);
259 if sc == p.meta.bin_name.as_ref().unwrap_or(&String::new()) {
260 return p;
261 }
262 &p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p
263 }
264
265 // Writes out the args section, which ends up being the flags, opts and postionals, and a jump to
266 // another ZSH function if there are subcommands.
267 // The structer works like this:
268 // ([conflicting_args]) [multiple] arg [takes_value] [[help]] [: :(possible_values)]
269 // ^-- list '-v -h' ^--'*' ^--'+' ^-- list 'one two three'
270 //
271 // An example from the rustup command:
272 //
273 // _arguments -C -s -S \
274 // '(-h --help --verbose)-v[Enable verbose output]' \
275 // '(-V -v --version --verbose --help)-h[Prints help information]' \
276 // # ... snip for brevity
277 // ':: :_rustup_commands' \ # <-- displays subcommands
278 // '*::: :->rustup' \ # <-- displays subcommand args and child subcommands
279 // && ret=0
280 //
281 // The args used for _arguments are as follows:
282 // -C: modify the $context internal variable
283 // -s: Allow stacking of short args (i.e. -a -b -c => -abc)
284 // -S: Do not complete anything after '--' and treat those as argument values
285 fn get_args_of(p: &Parser) -> String {
286 debugln!("get_args_of;");
287 let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")];
288 let opts = write_opts_of(p);
289 let flags = write_flags_of(p);
290 let positionals = write_positionals_of(p);
291 let sc_or_a = if p.has_subcommands() {
292 format!(
293 "\":: :_{name}_commands\" \\",
294 name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__")
295 )
296 } else {
297 String::new()
298 };
299 let sc = if p.has_subcommands() {
300 format!("\"*::: :->{name}\" \\", name = p.meta.name)
301 } else {
302 String::new()
303 };
304
305 if !opts.is_empty() {
306 ret.push(opts);
307 }
308 if !flags.is_empty() {
309 ret.push(flags);
310 }
311 if !positionals.is_empty() {
312 ret.push(positionals);
313 }
314 if !sc_or_a.is_empty() {
315 ret.push(sc_or_a);
316 }
317 if !sc.is_empty() {
318 ret.push(sc);
319 }
320 ret.push(String::from("&& ret=0"));
321
322 ret.join("\n")
323 }
324
325 // Escape help string inside single quotes and brackets
326 fn escape_help(string: &str) -> String {
327 string
328 .replace("\\", "\\\\")
329 .replace("'", "'\\''")
330 .replace("[", "\\[")
331 .replace("]", "\\]")
332 }
333
334 // Escape value string inside single quotes and parentheses
335 fn escape_value(string: &str) -> String {
336 string
337 .replace("\\", "\\\\")
338 .replace("'", "'\\''")
339 .replace("(", "\\(")
340 .replace(")", "\\)")
341 .replace(" ", "\\ ")
342 }
343
344 fn write_opts_of(p: &Parser) -> String {
345 debugln!("write_opts_of;");
346 let mut ret = vec![];
347 for o in p.opts() {
348 debugln!("write_opts_of:iter: o={}", o.name());
349 let help = o.help().map_or(String::new(), escape_help);
350 let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG);
351 conflicts = if conflicts.is_empty() {
352 String::new()
353 } else {
354 format!("({})", conflicts)
355 };
356
357 let multiple = if o.is_set(ArgSettings::Multiple) {
358 "*"
359 } else {
360 ""
361 };
362 let pv = if let Some(pv_vec) = o.possible_vals() {
363 format!(": :({})", pv_vec.iter().map(
364 |v| escape_value(*v)).collect::<Vec<String>>().join(" "))
365 } else {
366 String::new()
367 };
368 if let Some(short) = o.short() {
369 let s = format!(
370 "'{conflicts}{multiple}-{arg}+[{help}]{possible_values}' \\",
371 conflicts = conflicts,
372 multiple = multiple,
373 arg = short,
374 possible_values = pv,
375 help = help
376 );
377
378 debugln!("write_opts_of:iter: Wrote...{}", &*s);
379 ret.push(s);
380 }
381 if let Some(long) = o.long() {
382 let l = format!(
383 "'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\",
384 conflicts = conflicts,
385 multiple = multiple,
386 arg = long,
387 possible_values = pv,
388 help = help
389 );
390
391 debugln!("write_opts_of:iter: Wrote...{}", &*l);
392 ret.push(l);
393 }
394 }
395
396 ret.join("\n")
397 }
398
399 fn write_flags_of(p: &Parser) -> String {
400 debugln!("write_flags_of;");
401 let mut ret = vec![];
402 for f in p.flags() {
403 debugln!("write_flags_of:iter: f={}", f.name());
404 let help = f.help().map_or(String::new(), escape_help);
405 let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG);
406 conflicts = if conflicts.is_empty() {
407 String::new()
408 } else {
409 format!("({})", conflicts)
410 };
411
412 let multiple = if f.is_set(ArgSettings::Multiple) {
413 "*"
414 } else {
415 ""
416 };
417 if let Some(short) = f.short() {
418 let s = format!(
419 "'{conflicts}{multiple}-{arg}[{help}]' \\",
420 multiple = multiple,
421 conflicts = conflicts,
422 arg = short,
423 help = help
424 );
425
426 debugln!("write_flags_of:iter: Wrote...{}", &*s);
427 ret.push(s);
428 }
429
430 if let Some(long) = f.long() {
431 let l = format!(
432 "'{conflicts}{multiple}--{arg}[{help}]' \\",
433 conflicts = conflicts,
434 multiple = multiple,
435 arg = long,
436 help = help
437 );
438
439 debugln!("write_flags_of:iter: Wrote...{}", &*l);
440 ret.push(l);
441 }
442 }
443
444 ret.join("\n")
445 }
446
447 fn write_positionals_of(p: &Parser) -> String {
448 debugln!("write_positionals_of;");
449 let mut ret = vec![];
450 for arg in p.positionals() {
451 debugln!("write_positionals_of:iter: arg={}", arg.b.name);
452 let a = format!(
453 "'{optional}:{name}{help}:{action}' \\",
454 optional = if !arg.b.is_set(ArgSettings::Required) { ":" } else { "" },
455 name = arg.b.name,
456 help = arg.b
457 .help
458 .map_or("".to_owned(), |v| " -- ".to_owned() + v)
459 .replace("[", "\\[")
460 .replace("]", "\\]"),
461 action = arg.possible_vals().map_or("_files".to_owned(), |values| {
462 format!("({})",
463 values.iter().map(|v| escape_value(*v)).collect::<Vec<String>>().join(" "))
464 })
465 );
466
467 debugln!("write_positionals_of:iter: Wrote...{}", a);
468 ret.push(a);
469 }
470
471 ret.join("\n")
472 }
473