clap/completions/
bash.rs

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