1#[allow(deprecated, unused_imports)]
3use std::{ascii::AsciiExt, io::Write};
4
5use crate::{
7 app::{parser::Parser, App},
8 args::{AnyArg, ArgSettings},
9 completions, INTERNAL_ERROR_MSG,
10};
11
12pub struct ZshGen<'a, 'b>
13where
14 'a: 'b,
15{
16 p: &'b Parser<'a, 'b>,
17}
18
19impl<'a, 'b> ZshGen<'a, 'b> {
20 pub fn new(p: &'b Parser<'a, 'b>) -> Self {
21 debugln!("ZshGen::new;");
22 ZshGen { p }
23 }
24
25 pub fn generate_to<W: Write>(&self, buf: &mut W) {
26 debugln!("ZshGen::generate_to;");
27 w!(
28 buf,
29 format!(
30 "\
31#compdef {name}
32
33autoload -U is-at-least
34
35_{name}() {{
36 typeset -A opt_args
37 typeset -a _arguments_options
38 local ret=1
39
40 if is-at-least 5.2; then
41 _arguments_options=(-s -S -C)
42 else
43 _arguments_options=(-s -C)
44 fi
45
46 local context curcontext=\"$curcontext\" state line
47 {initial_args}
48 {subcommands}
49}}
50
51{subcommand_details}
52
53_{name} \"$@\"",
54 name = self.p.meta.bin_name.as_ref().unwrap(),
55 initial_args = get_args_of(self.p),
56 subcommands = get_subcommands_of(self.p),
57 subcommand_details = subcommand_details(self.p)
58 )
59 .as_bytes()
60 );
61 }
62}
63
64fn subcommand_details(p: &Parser) -> String {
92 debugln!("ZshGen::subcommand_details;");
93 let mut ret = vec![format!(
95 "\
96(( $+functions[_{bin_name_underscore}_commands] )) ||
97_{bin_name_underscore}_commands() {{
98 local commands; commands=(
99 {subcommands_and_args}
100 )
101 _describe -t commands '{bin_name} commands' commands \"$@\"
102}}",
103 bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"),
104 bin_name = p.meta.bin_name.as_ref().unwrap(),
105 subcommands_and_args = subcommands_of(p)
106 )];
107
108 let mut all_subcommands = completions::all_subcommands(p);
110 all_subcommands.sort();
111 all_subcommands.dedup();
112 for &(_, ref bin_name) in &all_subcommands {
113 debugln!("ZshGen::subcommand_details:iter: bin_name={}", bin_name);
114 ret.push(format!(
115 "\
116(( $+functions[_{bin_name_underscore}_commands] )) ||
117_{bin_name_underscore}_commands() {{
118 local commands; commands=(
119 {subcommands_and_args}
120 )
121 _describe -t commands '{bin_name} commands' commands \"$@\"
122}}",
123 bin_name_underscore = bin_name.replace(" ", "__"),
124 bin_name = bin_name,
125 subcommands_and_args = subcommands_of(parser_of(p, bin_name))
126 ));
127 }
128
129 ret.join("\n")
130}
131
132fn subcommands_of(p: &Parser) -> String {
144 debugln!("ZshGen::subcommands_of;");
145 let mut ret = vec![];
146 fn add_sc(sc: &App, n: &str, ret: &mut Vec<String>) {
147 debugln!("ZshGen::add_sc;");
148 let s = format!(
149 "\"{name}:{help}\" \\",
150 name = n,
151 help =
152 sc.p.meta
153 .about
154 .unwrap_or("")
155 .replace("[", "\\[")
156 .replace("]", "\\]")
157 );
158 if !s.is_empty() {
159 ret.push(s);
160 }
161 }
162
163 for sc in p.subcommands() {
165 debugln!("ZshGen::subcommands_of:iter: subcommand={}", sc.p.meta.name);
166 add_sc(sc, &sc.p.meta.name, &mut ret);
167 if let Some(ref v) = sc.p.meta.aliases {
168 for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) {
169 add_sc(sc, alias, &mut ret);
170 }
171 }
172 }
173
174 ret.join("\n")
175}
176
177fn get_subcommands_of(p: &Parser) -> String {
207 debugln!("get_subcommands_of;");
208
209 debugln!(
210 "get_subcommands_of: Has subcommands...{:?}",
211 p.has_subcommands()
212 );
213 if !p.has_subcommands() {
214 return String::new();
215 }
216
217 let sc_names = completions::subcommands_of(p);
218
219 let mut subcmds = vec![];
220 for &(ref name, ref bin_name) in &sc_names {
221 let mut v = vec![format!("({})", name)];
222 let subcommand_args = get_args_of(parser_of(p, &*bin_name));
223 if !subcommand_args.is_empty() {
224 v.push(subcommand_args);
225 }
226 let subcommands = get_subcommands_of(parser_of(p, &*bin_name));
227 if !subcommands.is_empty() {
228 v.push(subcommands);
229 }
230 v.push(String::from(";;"));
231 subcmds.push(v.join("\n"));
232 }
233
234 format!(
235 "case $state in
236 ({name})
237 words=($line[{pos}] \"${{words[@]}}\")
238 (( CURRENT += 1 ))
239 curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$line[{pos}]:\"
240 case $line[{pos}] in
241 {subcommands}
242 esac
243 ;;
244esac",
245 name = p.meta.name,
246 name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"),
247 subcommands = subcmds.join("\n"),
248 pos = p.positionals().len() + 1
249 )
250}
251
252fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> {
253 debugln!("parser_of: sc={}", sc);
254 if sc == p.meta.bin_name.as_ref().unwrap_or(&String::new()) {
255 return p;
256 }
257 &p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p
258}
259
260fn get_args_of(p: &Parser) -> String {
281 debugln!("get_args_of;");
282 let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")];
283 let opts = write_opts_of(p);
284 let flags = write_flags_of(p);
285 let positionals = write_positionals_of(p);
286 let sc_or_a = if p.has_subcommands() {
287 format!(
288 "\":: :_{name}_commands\" \\",
289 name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__")
290 )
291 } else {
292 String::new()
293 };
294 let sc = if p.has_subcommands() {
295 format!("\"*::: :->{name}\" \\", name = p.meta.name)
296 } else {
297 String::new()
298 };
299
300 if !opts.is_empty() {
301 ret.push(opts);
302 }
303 if !flags.is_empty() {
304 ret.push(flags);
305 }
306 if !positionals.is_empty() {
307 ret.push(positionals);
308 }
309 if !sc_or_a.is_empty() {
310 ret.push(sc_or_a);
311 }
312 if !sc.is_empty() {
313 ret.push(sc);
314 }
315 ret.push(String::from("&& ret=0"));
316
317 ret.join("\n")
318}
319
320fn escape_help(string: &str) -> String {
322 string
323 .replace("\\", "\\\\")
324 .replace("'", "'\\''")
325 .replace("[", "\\[")
326 .replace("]", "\\]")
327}
328
329fn escape_value(string: &str) -> String {
331 string
332 .replace("\\", "\\\\")
333 .replace("'", "'\\''")
334 .replace("(", "\\(")
335 .replace(")", "\\)")
336 .replace(" ", "\\ ")
337}
338
339fn write_opts_of(p: &Parser) -> String {
340 debugln!("write_opts_of;");
341 let mut ret = vec![];
342 for o in p.opts() {
343 debugln!("write_opts_of:iter: o={}", o.name());
344 let help = o.help().map_or(String::new(), escape_help);
345 let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG);
346 conflicts = if conflicts.is_empty() {
347 String::new()
348 } else {
349 format!("({})", conflicts)
350 };
351
352 let multiple = if o.is_set(ArgSettings::Multiple) {
353 "*"
354 } else {
355 ""
356 };
357 let pv = if let Some(pv_vec) = o.possible_vals() {
358 format!(
359 ": :({})",
360 pv_vec
361 .iter()
362 .map(|v| escape_value(*v))
363 .collect::<Vec<String>>()
364 .join(" ")
365 )
366 } else {
367 String::new()
368 };
369 if let Some(short) = o.short() {
370 let s = format!(
371 "'{conflicts}{multiple}-{arg}+[{help}]{possible_values}' \\",
372 conflicts = conflicts,
373 multiple = multiple,
374 arg = short,
375 possible_values = pv,
376 help = help
377 );
378
379 debugln!("write_opts_of:iter: Wrote...{}", &*s);
380 ret.push(s);
381 }
382 if let Some(long) = o.long() {
383 let l = format!(
384 "'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\",
385 conflicts = conflicts,
386 multiple = multiple,
387 arg = long,
388 possible_values = pv,
389 help = help
390 );
391
392 debugln!("write_opts_of:iter: Wrote...{}", &*l);
393 ret.push(l);
394 }
395 }
396
397 ret.join("\n")
398}
399
400fn write_flags_of(p: &Parser) -> String {
401 debugln!("write_flags_of;");
402 let mut ret = vec![];
403 for f in p.flags() {
404 debugln!("write_flags_of:iter: f={}", f.name());
405 let help = f.help().map_or(String::new(), escape_help);
406 let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG);
407 conflicts = if conflicts.is_empty() {
408 String::new()
409 } else {
410 format!("({})", conflicts)
411 };
412
413 let multiple = if f.is_set(ArgSettings::Multiple) {
414 "*"
415 } else {
416 ""
417 };
418 if let Some(short) = f.short() {
419 let s = format!(
420 "'{conflicts}{multiple}-{arg}[{help}]' \\",
421 multiple = multiple,
422 conflicts = conflicts,
423 arg = short,
424 help = help
425 );
426
427 debugln!("write_flags_of:iter: Wrote...{}", &*s);
428 ret.push(s);
429 }
430
431 if let Some(long) = f.long() {
432 let l = format!(
433 "'{conflicts}{multiple}--{arg}[{help}]' \\",
434 conflicts = conflicts,
435 multiple = multiple,
436 arg = long,
437 help = help
438 );
439
440 debugln!("write_flags_of:iter: Wrote...{}", &*l);
441 ret.push(l);
442 }
443 }
444
445 ret.join("\n")
446}
447
448fn write_positionals_of(p: &Parser) -> String {
449 debugln!("write_positionals_of;");
450 let mut ret = vec![];
451 for arg in p.positionals() {
452 debugln!("write_positionals_of:iter: arg={}", arg.b.name);
453 let a = format!(
454 "'{optional}:{name}{help}:{action}' \\",
455 optional = if !arg.b.is_set(ArgSettings::Required) {
456 ":"
457 } else {
458 ""
459 },
460 name = arg.b.name,
461 help = arg
462 .b
463 .help
464 .map_or("".to_owned(), |v| " -- ".to_owned() + v)
465 .replace("[", "\\[")
466 .replace("]", "\\]"),
467 action = arg.possible_vals().map_or("_files".to_owned(), |values| {
468 format!(
469 "({})",
470 values
471 .iter()
472 .map(|v| escape_value(*v))
473 .collect::<Vec<String>>()
474 .join(" ")
475 )
476 })
477 );
478
479 debugln!("write_positionals_of:iter: Wrote...{}", a);
480 ret.push(a);
481 }
482
483 ret.join("\n")
484}