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