1use std::io::Write;
3
4use 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}