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