clap/completions/
bash.rs

1// Std
2use std::io::Write;
3
4// Internal
5use crate::{
6    app::parser::Parser,
7    args::{AnyArg, OptBuilder},
8    completions,
9};
10
11pub struct BashGen<'a, 'b>
12where
13    'a: 'b,
14{
15    p: &'b Parser<'a, 'b>,
16}
17
18impl<'a, 'b> BashGen<'a, 'b> {
19    pub fn new(p: &'b Parser<'a, 'b>) -> Self {
20        BashGen { p }
21    }
22
23    pub fn generate_to<W: Write>(&self, buf: &mut W) {
24        w!(
25            buf,
26            format!(
27                r#"_{name}() {{
28    local i cur prev opts cmds
29    COMPREPLY=()
30    cur="${{COMP_WORDS[COMP_CWORD]}}"
31    prev="${{COMP_WORDS[COMP_CWORD-1]}}"
32    cmd=""
33    opts=""
34
35    for i in ${{COMP_WORDS[@]}}
36    do
37        case "${{i}}" in
38            {name})
39                cmd="{name}"
40                ;;
41            {subcmds}
42            *)
43                ;;
44        esac
45    done
46
47    case "${{cmd}}" in
48        {name})
49            opts="{name_opts}"
50            if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then
51                COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
52                return 0
53            fi
54            case "${{prev}}" in
55                {name_opts_details}
56                *)
57                    COMPREPLY=()
58                    ;;
59            esac
60            COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
61            return 0
62            ;;
63        {subcmd_details}
64    esac
65}}
66
67complete -F _{name} -o bashdefault -o default {name}
68"#,
69                name = self.p.meta.bin_name.as_ref().unwrap(),
70                name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()),
71                name_opts_details =
72                    self.option_details_for_path(self.p.meta.bin_name.as_ref().unwrap()),
73                subcmds = self.all_subcommands(),
74                subcmd_details = self.subcommand_details()
75            )
76            .as_bytes()
77        );
78    }
79
80    fn all_subcommands(&self) -> String {
81        debugln!("BashGen::all_subcommands;");
82        let mut subcmds = String::new();
83        let scs = completions::all_subcommand_names(self.p);
84
85        for sc in &scs {
86            subcmds = format!(
87                r#"{}
88            {name})
89                cmd+="__{fn_name}"
90                ;;"#,
91                subcmds,
92                name = sc,
93                fn_name = sc.replace("-", "__")
94            );
95        }
96
97        subcmds
98    }
99
100    fn subcommand_details(&self) -> String {
101        debugln!("BashGen::subcommand_details;");
102        let mut subcmd_dets = String::new();
103        let mut scs = completions::get_all_subcommand_paths(self.p, true);
104        scs.sort();
105        scs.dedup();
106
107        for sc in &scs {
108            subcmd_dets = format!(
109                r#"{}
110        {subcmd})
111            opts="{sc_opts}"
112            if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then
113                COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
114                return 0
115            fi
116            case "${{prev}}" in
117                {opts_details}
118                *)
119                    COMPREPLY=()
120                    ;;
121            esac
122            COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") )
123            return 0
124            ;;"#,
125                subcmd_dets,
126                subcmd = sc.replace("-", "__"),
127                sc_opts = self.all_options_for_path(&*sc),
128                level = sc.split("__").count(),
129                opts_details = self.option_details_for_path(&*sc)
130            );
131        }
132
133        subcmd_dets
134    }
135
136    fn option_details_for_path(&self, path: &str) -> String {
137        debugln!("BashGen::option_details_for_path: path={}", path);
138        let mut p = self.p;
139        for sc in path.split("__").skip(1) {
140            debugln!("BashGen::option_details_for_path:iter: sc={}", sc);
141            p = &find_subcmd!(p, sc).unwrap().p;
142        }
143        let mut opts = String::new();
144        for o in p.opts() {
145            if let Some(l) = o.s.long {
146                opts = format!(
147                    "{}
148                --{})
149                    COMPREPLY=({})
150                    return 0
151                    ;;",
152                    opts,
153                    l,
154                    self.vals_for(o)
155                );
156            }
157            if let Some(s) = o.s.short {
158                opts = format!(
159                    "{}
160                    -{})
161                    COMPREPLY=({})
162                    return 0
163                    ;;",
164                    opts,
165                    s,
166                    self.vals_for(o)
167                );
168            }
169        }
170        opts
171    }
172
173    fn vals_for(&self, o: &OptBuilder) -> String {
174        debugln!("BashGen::vals_for: o={}", o.b.name);
175        if let Some(vals) = o.possible_vals() {
176            format!(r#"$(compgen -W "{}" -- "${{cur}}")"#, vals.join(" "))
177        } else {
178            String::from(r#"$(compgen -f "${cur}")"#)
179        }
180    }
181
182    fn all_options_for_path(&self, path: &str) -> String {
183        debugln!("BashGen::all_options_for_path: path={}", path);
184        let mut p = self.p;
185        for sc in path.split("__").skip(1) {
186            debugln!("BashGen::all_options_for_path:iter: sc={}", sc);
187            p = &find_subcmd!(p, sc).unwrap().p;
188        }
189        let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s));
190        opts = format!(
191            "{} {}",
192            opts,
193            longs!(p).fold(String::new(), |acc, l| format!("{} --{}", acc, l))
194        );
195        opts = format!(
196            "{} {}",
197            opts,
198            p.positionals
199                .values()
200                .fold(String::new(), |acc, p| format!("{} {}", acc, p))
201        );
202        opts = format!(
203            "{} {}",
204            opts,
205            p.subcommands
206                .iter()
207                .fold(String::new(), |acc, s| format!("{} {}", acc, s.p.meta.name))
208        );
209        for sc in &p.subcommands {
210            if let Some(ref aliases) = sc.p.meta.aliases {
211                opts = format!(
212                    "{} {}",
213                    opts,
214                    aliases
215                        .iter()
216                        .map(|&(n, _)| n)
217                        .fold(String::new(), |acc, a| format!("{} {}", acc, a))
218                );
219            }
220        }
221        opts
222    }
223}