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