clap/src/completions.rs
2016-09-05 17:03:45 -04:00

457 lines
15 KiB
Rust

// Std
use std::io::Write;
// Internal
use app::parser::Parser;
use args::{ArgSettings, OptBuilder};
use shell::Shell;
macro_rules! w {
($buf:expr, $to_w:expr) => {
match $buf.write_all($to_w) {
Ok(..) => (),
Err(..) => panic!("Failed to write to file completions file"),
}
};
}
pub struct ComplGen<'a, 'b>
where 'a: 'b
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> ComplGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
ComplGen { p: p }
}
pub fn generate<W: Write>(&self, for_shell: Shell, buf: &mut W) {
match for_shell {
Shell::Bash => self.gen_bash(buf),
Shell::Fish => self.gen_fish(buf),
}
}
fn gen_bash<W: Write>(&self, buf: &mut W) {
w!(buf,
format!("_{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} {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 {
let mut subcmds = String::new();
let scs = get_all_subcommands(self.p);
for sc in &scs {
subcmds = format!("{}
{name})
cmd+=\"_{name}\"
;;",
subcmds,
name = sc.replace("-", "_"));
}
subcmds
}
fn subcommand_details(&self) -> String {
let mut subcmd_dets = String::new();
let mut scs = get_all_subcommand_paths(self.p, true);
scs.sort();
scs.dedup();
for sc in &scs {
subcmd_dets = format!("{}
{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 all_options_for_path(&self, path: &str) -> String {
let mut p = self.p;
for sc in path.split('_').skip(1) {
debugln!("iter;sc={}", sc);
p = &p.subcommands
.iter()
.find(|s| {
s.p.meta.name == sc ||
(s.p.meta.aliases.is_some() &&
s.p
.meta
.aliases
.as_ref()
.unwrap()
.iter()
.any(|&(n, _)| n == sc))
})
.unwrap()
.p;
}
let mut opts = p.short_list.iter().fold(String::new(), |acc, s| format!("{} -{}", acc, s));
opts = format!("{} {}",
opts,
p.long_list
.iter()
.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
}
fn option_details_for_path(&self, path: &str) -> String {
let mut p = self.p;
for sc in path.split('_').skip(1) {
debugln!("iter;sc={}", sc);
p = &p.subcommands
.iter()
.find(|s| {
s.p.meta.name == sc ||
(s.p.meta.aliases.is_some() &&
s.p
.meta
.aliases
.as_ref()
.unwrap()
.iter()
.any(|&(n, _)| n == sc))
})
.unwrap()
.p;
}
let mut opts = String::new();
for o in &p.opts {
if let Some(l) = o.long {
opts = format!("{}
--{})
COMPREPLY=({})
return 0
;;",
opts,
l,
vals_for(o));
}
if let Some(s) = o.short {
opts = format!("{}
-{})
COMPREPLY=({})
return 0
;;",
opts,
s,
vals_for(o));
}
}
opts
}
fn gen_fish<W: Write>(&self, buf: &mut W) {
let command = self.p.meta.bin_name.as_ref().unwrap();
let subcommands: Vec<_> = get_all_subcommands(self.p);
let has_subcommands = subcommands.len() > 1;
// function to detect subcommand
let detect_subcommand_function = if has_subcommands {
format!(
r#"function __fish_{}_no_subcommand --description "Test if there isn't given a subcommand"
for i in (commandline -opc)
if contains -- $i {}
return 1
end
end
return 0
end
"#, command, subcommands.join(" "))
} else {
"".to_string()
};
let mut buffer = detect_subcommand_function;
gen_fish_inner(command, self, vec![], &mut buffer, has_subcommands);
w!(buf, buffer.as_bytes());
}
}
pub fn get_all_subcommands(p: &Parser) -> Vec<String> {
let mut subcmds = vec![];
if !p.has_subcommands() {
let mut ret = vec![p.meta.name.clone()];
if let Some(ref aliases) = p.meta.aliases {
for &(n, _) in aliases {
ret.push(n.to_owned());
}
}
return ret;
}
for sc in &p.subcommands {
if let Some(ref aliases) = sc.p.meta.aliases {
for &(n, _) in aliases {
subcmds.push(n.to_owned());
}
}
subcmds.push(sc.p.meta.name.clone());
}
for sc_v in p.subcommands.iter().map(|s| get_all_subcommands(&s.p)) {
subcmds.extend(sc_v);
}
subcmds.sort();
subcmds.dedup();
subcmds
}
pub fn get_all_subcommand_paths(p: &Parser, first: bool) -> Vec<String> {
let mut subcmds = vec![];
if !p.has_subcommands() {
if !first {
let name = &*p.meta.name;
let path = p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "_");
let mut ret = vec![path.clone()];
if let Some(ref aliases) = p.meta.aliases {
for &(n, _) in aliases {
ret.push(path.replace(name, n));
}
}
return ret;
}
return vec![];
}
for sc in &p.subcommands {
let name = &*sc.p.meta.name;
let path = sc.p.meta.bin_name.as_ref().unwrap().clone().replace(" ", "_");
subcmds.push(path.clone());
if let Some(ref aliases) = sc.p.meta.aliases {
for &(n, _) in aliases {
subcmds.push(path.replace(name, n));
}
}
}
for sc_v in p.subcommands.iter().map(|s| get_all_subcommand_paths(&s.p, false)) {
subcmds.extend(sc_v);
}
subcmds
}
fn vals_for(o: &OptBuilder) -> String {
use args::AnyArg;
let mut ret = String::new();
let mut needs_quotes = true;
if let Some(vals) = o.possible_vals() {
needs_quotes = false;
ret = format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" "));
} else if let Some(vec) = o.val_names() {
let mut it = vec.iter().peekable();
while let Some((_, val)) = it.next() {
ret = format!("{}<{}>{}",
ret,
val,
if it.peek().is_some() { " " } else { "" });
}
let num = vec.len();
if o.is_set(ArgSettings::Multiple) && num == 1 {
ret = format!("{}...", ret);
}
} else if let Some(num) = o.num_vals() {
let mut it = (0..num).peekable();
while let Some(_) = it.next() {
ret = format!("{}<{}>{}",
ret,
o.name(),
if it.peek().is_some() { " " } else { "" });
}
if o.is_set(ArgSettings::Multiple) && num == 1 {
ret = format!("{}...", ret);
}
} else {
ret = format!("<{}>", o.name());
if o.is_set(ArgSettings::Multiple) {
ret = format!("{}...", ret);
}
}
if needs_quotes {
ret = format!("\"{}\"", ret);
}
ret
}
fn gen_fish_inner(root_command: &str,
comp_gen: &ComplGen,
parent_cmds: Vec<&String>,
buffer: &mut String,
has_no_subcommand_fn: bool) {
// example :
//
// complete
// -c {command}
// -d "{description}"
// -s {short}
// -l {long}
// -a "{possible_arguments}"
// -r # if require parameter
// -f # don't use file completion
// -n "__fish_seen_subcommand_from install" # complete for subcommand "install"
let command = &comp_gen.p.meta.name;
let subcommands: Vec<_> = get_all_subcommands(comp_gen.p);
for option in &comp_gen.p.opts {
let mut template = format!("complete -c {}", root_command);
if !parent_cmds.is_empty() {
template.push_str(format!(" -n '__fish_seen_subcommand_from {}'", command).as_str());
} else if has_no_subcommand_fn {
template.push_str(format!(" -n '__fish_{}_no_subcommand'",
comp_gen.p.meta.bin_name.as_ref().unwrap())
.as_str());
}
if let Some(data) = option.short {
template.push_str(format!(" -s {}", data).as_str());
}
if let Some(data) = option.long {
template.push_str(format!(" -l {}", data).as_str());
}
if let Some(data) = option.help {
template.push_str(format!(" -d '{}'", data).as_str());
}
if let Some(ref data) = option.possible_vals {
template.push_str(format!(" -r -f -a '{}'", data.join(" ")).as_str());
}
buffer.push_str(template.as_str());
buffer.push_str("\n");
}
for flag in &comp_gen.p.flags {
let mut template = format!("complete -c {}", root_command);
if !parent_cmds.is_empty() {
template.push_str(format!(" -n '__fish_seen_subcommand_from {}'", command).as_str());
} else if has_no_subcommand_fn {
template.push_str(format!(" -n '__fish_{}_no_subcommand'",
comp_gen.p.meta.bin_name.as_ref().unwrap())
.as_str());
}
if let Some(data) = flag.short {
template.push_str(format!(" -s {}", data).as_str());
}
if let Some(data) = flag.long {
template.push_str(format!(" -l {}", data).as_str());
}
if let Some(data) = flag.help {
template.push_str(format!(" -d '{}'", data).as_str());
}
buffer.push_str(template.as_str());
buffer.push_str("\n");
}
if subcommands.len() > 1 {
for subcommand in subcommands {
let mut template = format!("complete -c {}", root_command);
if !parent_cmds.is_empty() {
template.push_str(format!(" -n '__fish_seen_subcommand_from {}'",
subcommand)
.as_str());
} else if has_no_subcommand_fn {
template.push_str(format!(" -n '__fish_{}_no_subcommand'",
comp_gen.p.meta.bin_name.as_ref().unwrap())
.as_str());
}
template.push_str(" -f");
template.push_str(format!(" -a '{}'", subcommand).as_str());
buffer.push_str(template.as_str());
buffer.push_str("\n");
}
}
// generate options of subcommands
for subcommand in &comp_gen.p.subcommands {
let sub_comp_gen = ComplGen::new(&subcommand.p);
let mut sub_parent_cmds = parent_cmds.clone();
sub_parent_cmds.push(command);
gen_fish_inner(root_command,
&sub_comp_gen,
sub_parent_cmds,
buffer,
has_no_subcommand_fn);
}
}