clap/completions/
zsh.rs

1// Std
2use std::io::Write;
3#[allow(deprecated, unused_imports)]
4use std::ascii::AsciiExt;
5
6// Internal
7use app::App;
8use app::parser::Parser;
9use args::{AnyArg, ArgSettings};
10use completions;
11use INTERNAL_ERROR_MSG;
12
13pub struct ZshGen<'a, 'b>
14where
15    'a: 'b,
16{
17    p: &'b Parser<'a, 'b>,
18}
19
20impl<'a, 'b> ZshGen<'a, 'b> {
21    pub fn new(p: &'b Parser<'a, 'b>) -> Self {
22        debugln!("ZshGen::new;");
23        ZshGen { p: p }
24    }
25
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
34autoload -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//
91fn 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'
145fn 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
211fn 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    ;;
249esac",
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
257fn 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
285fn 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
326fn 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
335fn escape_value(string: &str) -> String {
336    string
337        .replace("\\", "\\\\")
338        .replace("'", "'\\''")
339        .replace("(", "\\(")
340        .replace(")", "\\)")
341        .replace(" ", "\\ ")
342}
343
344fn 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
399fn 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
447fn 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}