| // Std |
| use std::io::Write; |
| |
| // Internal |
| use app::parser::Parser; |
| use args::OptBuilder; |
| use completions; |
| |
| pub struct BashGen<'a, 'b> |
| where |
| 'a: 'b, |
| { |
| p: &'b Parser<'a, 'b>, |
| } |
| |
| impl<'a, 'b> BashGen<'a, 'b> { |
| pub fn new(p: &'b Parser<'a, 'b>) -> Self { |
| BashGen { p: p } |
| } |
| |
| pub fn generate_to<W: Write>(&self, buf: &mut W) { |
| w!( |
| buf, |
| format!( |
| r#"_{name}() {{ |
| local i cur prev opts cmds |
| COMPREPLY=() |
| cur="${{COMP_WORDS[COMP_CWORD]}}" |
| prev="${{COMP_WORDS[COMP_CWORD-1]}}" |
| cmd="" |
| opts="" |
| |
| for i in ${{COMP_WORDS[@]}} |
| do |
| case "${{i}}" in |
| {name}) |
| cmd="{name}" |
| ;; |
| {subcmds} |
| *) |
| ;; |
| esac |
| done |
| |
| case "${{cmd}}" in |
| {name}) |
| opts="{name_opts}" |
| if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq 1 ]] ; then |
| COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") ) |
| return 0 |
| fi |
| case "${{prev}}" in |
| {name_opts_details} |
| *) |
| COMPREPLY=() |
| ;; |
| esac |
| COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") ) |
| return 0 |
| ;; |
| {subcmd_details} |
| esac |
| }} |
| |
| complete -F _{name} -o bashdefault -o default {name} |
| "#, |
| name = self.p.meta.bin_name.as_ref().unwrap(), |
| name_opts = self.all_options_for_path(self.p.meta.bin_name.as_ref().unwrap()), |
| name_opts_details = |
| self.option_details_for_path(self.p.meta.bin_name.as_ref().unwrap()), |
| subcmds = self.all_subcommands(), |
| subcmd_details = self.subcommand_details() |
| ) |
| .as_bytes() |
| ); |
| } |
| |
| fn all_subcommands(&self) -> String { |
| debugln!("BashGen::all_subcommands;"); |
| let mut subcmds = String::new(); |
| let scs = completions::all_subcommand_names(self.p); |
| |
| for sc in &scs { |
| subcmds = format!( |
| r#"{} |
| {name}) |
| cmd+="__{fn_name}" |
| ;;"#, |
| subcmds, |
| name = sc, |
| fn_name = sc.replace("-", "__") |
| ); |
| } |
| |
| subcmds |
| } |
| |
| fn subcommand_details(&self) -> String { |
| debugln!("BashGen::subcommand_details;"); |
| let mut subcmd_dets = String::new(); |
| let mut scs = completions::get_all_subcommand_paths(self.p, true); |
| scs.sort(); |
| scs.dedup(); |
| |
| for sc in &scs { |
| subcmd_dets = format!( |
| r#"{} |
| {subcmd}) |
| opts="{sc_opts}" |
| if [[ ${{cur}} == -* || ${{COMP_CWORD}} -eq {level} ]] ; then |
| COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") ) |
| return 0 |
| fi |
| case "${{prev}}" in |
| {opts_details} |
| *) |
| COMPREPLY=() |
| ;; |
| esac |
| COMPREPLY=( $(compgen -W "${{opts}}" -- "${{cur}}") ) |
| return 0 |
| ;;"#, |
| subcmd_dets, |
| subcmd = sc.replace("-", "__"), |
| sc_opts = self.all_options_for_path(&*sc), |
| level = sc.split("__").map(|_| 1).fold(0, |acc, n| acc + n), |
| opts_details = self.option_details_for_path(&*sc) |
| ); |
| } |
| |
| subcmd_dets |
| } |
| |
| fn option_details_for_path(&self, path: &str) -> String { |
| debugln!("BashGen::option_details_for_path: path={}", path); |
| let mut p = self.p; |
| for sc in path.split("__").skip(1) { |
| debugln!("BashGen::option_details_for_path:iter: sc={}", sc); |
| p = &find_subcmd!(p, sc).unwrap().p; |
| } |
| let mut opts = String::new(); |
| for o in p.opts() { |
| if let Some(l) = o.s.long { |
| opts = format!( |
| "{} |
| --{}) |
| COMPREPLY=({}) |
| return 0 |
| ;;", |
| opts, |
| l, |
| self.vals_for(o) |
| ); |
| } |
| if let Some(s) = o.s.short { |
| opts = format!( |
| "{} |
| -{}) |
| COMPREPLY=({}) |
| return 0 |
| ;;", |
| opts, |
| s, |
| self.vals_for(o) |
| ); |
| } |
| } |
| opts |
| } |
| |
| fn vals_for(&self, o: &OptBuilder) -> String { |
| debugln!("BashGen::vals_for: o={}", o.b.name); |
| use args::AnyArg; |
| if let Some(vals) = o.possible_vals() { |
| format!(r#"$(compgen -W "{}" -- "${{cur}}")"#, vals.join(" ")) |
| } else { |
| String::from(r#"$(compgen -f "${cur}")"#) |
| } |
| } |
| |
| fn all_options_for_path(&self, path: &str) -> String { |
| debugln!("BashGen::all_options_for_path: path={}", path); |
| let mut p = self.p; |
| for sc in path.split("__").skip(1) { |
| debugln!("BashGen::all_options_for_path:iter: sc={}", sc); |
| p = &find_subcmd!(p, sc).unwrap().p; |
| } |
| let mut opts = shorts!(p).fold(String::new(), |acc, s| format!("{} -{}", acc, s)); |
| opts = format!( |
| "{} {}", |
| opts, |
| longs!(p).fold(String::new(), |acc, l| format!("{} --{}", acc, l)) |
| ); |
| opts = format!( |
| "{} {}", |
| opts, |
| p.positionals |
| .values() |
| .fold(String::new(), |acc, p| format!("{} {}", acc, p)) |
| ); |
| opts = format!( |
| "{} {}", |
| opts, |
| p.subcommands |
| .iter() |
| .fold(String::new(), |acc, s| format!("{} {}", acc, s.p.meta.name)) |
| ); |
| for sc in &p.subcommands { |
| if let Some(ref aliases) = sc.p.meta.aliases { |
| opts = format!( |
| "{} {}", |
| opts, |
| aliases |
| .iter() |
| .map(|&(n, _)| n) |
| .fold(String::new(), |acc, a| format!("{} {}", acc, a)) |
| ); |
| } |
| } |
| opts |
| } |
| } |