use std::io::Write;
use std::ascii::AsciiExt;
use app::App;
use app::parser::Parser;
use args::{ArgSettings, AnyArg};
use completions;
use INTERNAL_ERROR_MSG;
pub struct ZshGen<'a, 'b>
where 'a: 'b
{
p: &'b Parser<'a, 'b>,
}
impl<'a, 'b> ZshGen<'a, 'b> {
pub fn new(p: &'b Parser<'a, 'b>) -> Self {
debugln!("fn=ZshGen::new;");
ZshGen { p: p }
}
pub fn generate_to<W: Write>(&self, buf: &mut W) {
debugln!("fn=generate_to;");
w!(buf,
format!("\
#compdef {name}
_{name}() {{
typeset -A opt_args
local ret=1
local context curcontext=\"$curcontext\" state line
{initial_args}
{subcommands}
}}
{subcommand_details}
_{name} \"$@\"",
name = self.p.meta.bin_name.as_ref().unwrap(),
initial_args = get_args_of(self.p),
subcommands = get_subcommands_of(self.p),
subcommand_details = subcommand_details(self.p)).as_bytes());
}
}
fn subcommand_details(p: &Parser) -> String {
debugln!("fn=subcommand_details");
let mut ret = vec![format!("\
(( $+functions[_{bin_name_underscore}_commands] )) ||
_{bin_name_underscore}_commands() {{
local commands; commands=(
{subcommands_and_args}
)
_describe -t commands '{bin_name} commands' commands \"$@\"
}}",
bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "_"),
bin_name = p.meta.bin_name.as_ref().unwrap(),
subcommands_and_args = subcommands_and_args_of(p))];
let mut all_subcommands = completions::all_subcommands(p);
all_subcommands.sort();
all_subcommands.dedup();
for &(_, ref bin_name) in &all_subcommands {
debugln!("iter;bin_name={}", bin_name);
ret.push(format!("\
(( $+functions[_{bin_name_underscore}_commands] )) ||
_{bin_name_underscore}_commands() {{
local commands; commands=(
{subcommands_and_args}
)
_describe -t commands '{bin_name} commands' commands \"$@\"
}}",
bin_name_underscore = bin_name.replace(" ", "_"),
bin_name = bin_name,
subcommands_and_args = subcommands_and_args_of(parser_of(p, bin_name))));
}
ret.join("\n")
}
fn subcommands_and_args_of(p: &Parser) -> String {
debugln!("fn=subcommands_and_args_of;");
let mut ret = vec![];
fn add_sc(sc: &App, n: &str, ret: &mut Vec<String>) {
let s = format!("\"{name}:{help}\" \\",
name = n,
help = sc.p.meta.about.unwrap_or("").replace("[", "\\[").replace("]", "\\]"));
if !s.is_empty() {
ret.push(s);
}
}
for sc in p.subcommands() {
debugln!("iter;subcommand={}", sc.p.meta.name);
add_sc(sc, &sc.p.meta.name, &mut ret);
if let Some(ref v) = sc.p.meta.aliases {
for alias in v.iter().filter(|&&(_, vis)| vis).map(|&(n, _)| n) {
add_sc(sc, alias, &mut ret);
}
}
}
for arg in p.positionals() {
debugln!("iter;arg={}", arg.b.name);
let a = format!("\"{name}:{help}\" \\",
name = arg.b.name.to_ascii_uppercase(),
help = arg.b.help.unwrap_or("").replace("[", "\\[").replace("]", "\\]"));
if !a.is_empty() {
ret.push(a);
}
}
ret.join("\n")
}
fn get_subcommands_of(p: &Parser) -> String {
debugln!("fn=get_subcommands_of");
debug!("Has subcommands...");
if !p.has_subcommands() {
sdebugln!("No");
return String::new();
} else {
sdebugln!("Yes");
}
let sc_names = completions::subcommands_of(p);
let mut subcmds = vec![];
for &(ref name, ref bin_name) in &sc_names {
let mut v = vec![format!("({})", name)];
let subcommand_args = get_args_of(parser_of(p, &*bin_name));
if !subcommand_args.is_empty() {
v.push(subcommand_args);
}
let subcommands = get_subcommands_of(parser_of(p, &*bin_name));
if !subcommands.is_empty() {
v.push(subcommands);
}
v.push(String::from(";;"));
subcmds.push(v.join("\n"));
}
format!(
"case $state in
({name})
curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$words[1]:\"
case $line[1] in
{subcommands}
esac
;;
esac",
name = p.meta.name,
name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"),
subcommands = subcmds.join("\n"))
}
fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> {
debugln!("fn=parser_of;sc={}", sc);
if sc == p.meta.bin_name.as_ref().unwrap_or(&String::new()) {
return p;
}
&p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p
}
fn get_args_of(p: &Parser) -> String {
debugln!("fn=get_args_of");
let mut ret = vec![String::from("_arguments -s -S -C \\")];
let opts = write_opts_of(p);
let flags = write_flags_of(p);
let sc_or_a = if p.has_subcommands() || p.has_positionals() {
format!("\"1:: :_{name}_commands\" \\",
name = p.meta.bin_name.as_ref().unwrap().replace(" ", "_"))
} else {
String::new()
};
let sc = if p.has_subcommands() {
format!("\"*:: :->{name}\" \\", name = p.meta.name)
} else {
String::new()
};
if !opts.is_empty() {
ret.push(opts);
}
if !flags.is_empty() {
ret.push(flags);
}
if !sc_or_a.is_empty() {
ret.push(sc_or_a);
}
if !sc.is_empty() {
ret.push(sc);
}
ret.push(String::from("&& ret=0"));
ret.join("\n")
}
fn write_opts_of(p: &Parser) -> String {
debugln!("fn=write_opts_of;");
let mut ret = vec![];
for o in p.opts() {
debugln!("iter;o={}", o.name());
let help = o.help().unwrap_or("").replace("[", "\\[").replace("]", "\\]");
let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG);
conflicts = if conflicts.is_empty() {
String::new()
} else {
format!("({})", conflicts)
};
let multiple = if o.is_set(ArgSettings::Multiple) {
"*"
} else {
""
};
let pv = if let Some(pv_vec) = o.possible_vals() {
format!(": :({})", pv_vec.join(" "))
} else {
String::new()
};
if let Some(short) = o.short() {
let s = format!("\"{conflicts}{multiple}-{arg}+[{help}]{possible_values}\" \\",
conflicts = conflicts,
multiple = multiple,
arg = short,
possible_values = pv,
help = help);
debugln!("Wrote...{}", &*s);
ret.push(s);
}
if let Some(long) = o.long() {
let l = format!("\"{conflicts}{multiple}--{arg}+[{help}]{possible_values}\" \\",
conflicts = conflicts,
multiple = multiple,
arg = long,
possible_values = pv,
help = help);
debugln!("Wrote...{}", &*l);
ret.push(l);
}
}
ret.join("\n")
}
fn write_flags_of(p: &Parser) -> String {
debugln!("fn=write_flags_of;");
let mut ret = vec![];
for f in p.flags() {
debugln!("iter;f={}", f.name());
let help = f.help().unwrap_or("").replace("[", "\\[").replace("]", "\\]");
let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG);
conflicts = if conflicts.is_empty() {
String::new()
} else {
format!("({})", conflicts)
};
let multiple = if f.is_set(ArgSettings::Multiple) {
"*"
} else {
""
};
if let Some(short) = f.short() {
let s = format!("\"{conflicts}{multiple}-{arg}[{help}]\" \\",
multiple = multiple,
conflicts = conflicts,
arg = short,
help = help);
debugln!("Wrote...{}", &*s);
ret.push(s);
}
if let Some(long) = f.long() {
let l = format!("\"{conflicts}{multiple}--{arg}[{help}]\" \\",
conflicts = conflicts,
multiple = multiple,
arg = long,
help = help);
debugln!("Wrote...{}", &*l);
ret.push(l);
}
}
ret.join("\n")
}