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