extern crate libc;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::HashSet;
use std::env;
use std::path::Path;
use std::vec::IntoIter;
use args::{ ArgMatches, Arg, SubCommand };
use args::{FlagArg, FlagBuilder};
use args::{OptArg, OptBuilder};
use args::{PosArg, PosBuilder};
pub struct App<'a, 'v, 'ab, 'u> {
name: String,
author: Option<&'a str>,
version: Option<&'v str>,
about: Option<&'ab str>,
flags: HashMap<&'static str, FlagBuilder>,
opts: HashMap<&'static str, OptBuilder>,
positionals_idx: BTreeMap<u8, PosBuilder>,
subcommands: HashMap<String, Box<App<'a, 'v, 'ab, 'u>>>,
needs_long_help: bool,
needs_long_version: bool,
needs_short_help: bool,
needs_short_version: bool,
needs_subcmd_help: bool,
required: HashSet<&'static str>,
arg_list: HashSet<&'static str>,
short_list: HashSet<char>,
long_list: HashSet<&'static str>,
blacklist: HashSet<&'static str>,
usage_str: Option<&'u str>,
bin_name: Option<String>
}
impl<'a, 'v, 'ab, 'u> App<'a, 'v, 'ab, 'u>{
pub fn new<'n>(n: &'n str) -> App<'a, 'v, 'ab, 'u> {
App {
name: n.to_string(),
author: None,
about: None,
version: None,
flags: HashMap::new(),
opts: HashMap::new(),
positionals_idx: BTreeMap::new(),
subcommands: HashMap::new(),
needs_long_version: true,
needs_long_help: true,
needs_short_help: true,
needs_subcmd_help: true,
needs_short_version: true,
required: HashSet::new(),
arg_list: HashSet::new(),
short_list: HashSet::new(),
long_list: HashSet::new(),
usage_str: None,
blacklist: HashSet::new(),
bin_name: None,
}
}
pub fn author(mut self, a: &'a str) -> App<'a, 'v, 'ab, 'u> {
self.author = Some(a);
self
}
pub fn about(mut self, a: &'ab str) -> App<'a, 'v, 'ab, 'u > {
self.about = Some(a);
self
}
pub fn version(mut self, v: &'v str) -> App<'a, 'v, 'ab, 'u> {
self.version = Some(v);
self
}
pub fn usage(mut self, u: &'u str) -> App<'a, 'v, 'ab, 'u> {
self.usage_str = Some(u);
self
}
pub fn arg(mut self, a: Arg) -> App<'a, 'v, 'ab, 'u> {
if self.arg_list.contains(a.name) {
panic!("Argument name must be unique, \"{}\" is already in use", a.name);
} else {
self.arg_list.insert(a.name);
}
if let Some(ref s) = a.short {
if self.short_list.contains(s) {
panic!("Argument short must be unique, -{} is already in use", s);
} else {
self.short_list.insert(*s);
}
}
if let Some(ref l) = a.long {
if self.long_list.contains(l) {
panic!("Argument long must be unique, --{} is already in use", l);
} else {
self.long_list.insert(l);
}
}
if a.required {
self.required.insert(a.name);
}
if let Some(i) = a.index {
if a.short.is_some() || a.long.is_some() {
panic!("Argument \"{}\" has conflicting requirements, both index() and short(), or long(), were supplied", a.name);
}
if a.multiple {
panic!("Argument \"{}\" has conflicting requirements, both index() and multiple(true) were supplied",a.name);
}
if a.takes_value {
panic!("Argument \"{}\" has conflicting requirements, both index() and takes_value(true) were supplied", a.name);
}
self.positionals_idx.insert(i, PosBuilder {
name: a.name,
index: i,
required: a.required,
blacklist: a.blacklist,
requires: a.requires,
help: a.help,
});
} else if a.takes_value {
if a.short.is_none() && a.long.is_none() {
panic!("Argument \"{}\" has take_value(true), yet neither a short() or long() were supplied", a.name);
}
self.opts.insert(a.name, OptBuilder {
name: a.name,
short: a.short,
long: a.long,
multiple: a.multiple,
blacklist: a.blacklist,
help: a.help,
requires: a.requires,
required: a.required,
});
} else {
if let Some(ref l) = a.long {
if *l == "help" {
self.needs_long_help = false;
} else if *l == "version" {
self.needs_long_version = false;
}
}
if let Some(ref s) = a.short {
if *s == 'h' {
self.needs_short_help = false;
} else if *s == 'v' {
self.needs_short_version = false;
}
}
if a.short.is_none() && a.long.is_none() {
panic!("Argument \"{}\" must have either a short() and/or long() supplied since no index() or takes_value() were found", a.name);
}
if a.required {
panic!("Argument \"{}\" cannot be required(true) because it has no index() or takes_value(true)", a.name)
}
self.flags.insert(a.name, FlagBuilder {
name: a.name,
short: a.short,
long: a.long,
help: a.help,
blacklist: a.blacklist,
multiple: a.multiple,
requires: a.requires,
});
}
self
}
pub fn args(mut self, args: Vec<Arg>) -> App<'a, 'v, 'ab, 'u> {
for arg in args.into_iter() {
self = self.arg(arg);
}
self
}
pub fn subcommand(mut self, subcmd: App<'a, 'v, 'ab, 'u>) -> App<'a, 'v, 'ab, 'u> {
if subcmd.name == "help" { self.needs_subcmd_help = false; }
self.subcommands.insert(subcmd.name.clone(), Box::new(subcmd));
self
}
pub fn subcommands(mut self, subcmds: Vec<App<'a, 'v, 'ab, 'u>>) -> App<'a, 'v, 'ab, 'u> {
for subcmd in subcmds.into_iter() {
self = self.subcommand(subcmd);
}
self
}
fn print_usage(&self, more_info: bool) {
println!("USAGE:");
if let Some(u) = self.usage_str {
println!("\t{}",u);
} else {
let flags = ! self.flags.is_empty();
let pos = ! self.positionals_idx.is_empty();
let req_pos = self.positionals_idx.values().filter_map(|ref x| if x.required { Some(x.name) } else {None})
.fold(String::new(), |acc, ref name| acc + &format!("<{}> ", name)[..]);
let req_opts = self.opts.values().filter(|ref x| x.required)
.fold(String::new(), |acc, ref o| acc + &format!("{}{} ",if let Some(s) = o.short {
format!("-{} ", s)
} else {
format!("--{}=",o.long.unwrap())
},o.name));
let opts = ! self.opts.is_empty();
let subcmds = ! self.subcommands.is_empty();
print!("\t{} {} {} {} {}", if let Some(ref name) = self.bin_name { name } else { &self.name },
if flags {"[FLAGS]"} else {""},
if opts {
if req_opts.is_empty() { "[OPTIONS]" } else { &req_opts[..] }
} else { "" },
if pos {
if req_pos.is_empty() { "[POSITIONAL]"} else { &req_pos[..] }
} else {""},
if subcmds {"[SUBCOMMANDS]"} else {""});
}
if more_info {
println!("\nFor more information try --help");
}
}
fn print_help(&self) {
self.print_version(false);
let flags = ! self.flags.is_empty();
let pos = ! self.positionals_idx.is_empty();
let opts = ! self.opts.is_empty();
let subcmds = ! self.subcommands.is_empty();
if let Some(author) = self.author {
println!("{}", author);
}
if let Some(about) = self.about {
println!("{}", about);
}
println!("");
self.print_usage(false);
if flags || opts || pos || subcmds {
println!("");
}
if flags {
println!("");
println!("FLAGS:");
for v in self.flags.values() {
println!("\t{}{}\t{}",
if let Some(s) = v.short{format!("-{}",s)}else{format!(" ")},
if let Some(l) = v.long {format!(",--{}",l)}else {format!(" \t")},
if let Some(h) = v.help {h} else {" "} );
}
}
if opts {
println!("");
println!("OPTIONS:");
for v in self.opts.values() {
let mut needs_tab = false;
println!("\t{}{}{}\t{}",
if let Some(ref s) = v.short{format!("-{} ",s)}else{format!(" ")},
if let Some(ref l) = v.long {format!(",--{}=",l)}else {needs_tab = true; format!(" ")},
format!("{}", v.name),
if let Some(ref h) = v.help {if needs_tab {format!("\t{}", *h)} else { format!("{}", *h) } } else {format!(" ")} );
}
}
if pos {
println!("");
println!("POSITIONAL ARGUMENTS:");
for v in self.positionals_idx.values() {
println!("\t{}\t\t\t{}", v.name,
if let Some(h) = v.help {h} else {" "} );
}
}
if subcmds {
println!("");
println!("SUBCOMMANDS:");
for sc in self.subcommands.values() {
println!("\t{}\t\t{}", sc.name,
if let Some(a) = sc.about {a} else {" "} );
}
}
self.exit();
}
fn print_version(&self, quit: bool) {
println!("{} {}", self.name, if let Some(v) = self.version {v} else {""} );
if quit { self.exit(); }
}
fn exit(&self) {
unsafe { libc::exit(0); }
}
fn report_error(&self, msg: String, help: bool, quit: bool) {
println!("{}", msg);
if help { self.print_usage(true); }
if quit { env::set_exit_status(1); self.exit(); }
}
pub fn get_matches(mut self) -> ArgMatches {
let mut matches = ArgMatches::new();
let args = env::args().collect::<Vec<_>>();
let mut it = args.into_iter();
if let Some(name) = it.next() {
let p = Path::new(&name[..]);
if let Some(f) = p.file_name() {
match f.to_os_string().into_string() {
Ok(s) => self.bin_name = Some(s),
Err(_) => {}
}
}
}
self.get_matches_from(&mut matches, &mut it );
matches
}
fn get_matches_from(&mut self, matches: &mut ArgMatches, it: &mut IntoIter<String>) {
self.create_help_and_version();
let mut pos_only = false;
let mut subcmd_name: Option<String> = None;
let mut needs_val_of: Option<&'static str> = None;
let mut pos_counter = 1;
while let Some(arg) = it.next() {
let arg_slice = &arg[..];
let mut skip = false;
if !pos_only {
if let Some(nvo) = needs_val_of {
if let Some(ref opt) = self.opts.get(nvo) {
if let Some(ref mut o) = matches.opts.get_mut(opt.name) {
o.values.push(arg.clone());
o.occurrences = if opt.multiple { o.occurrences + 1 } else { 1 };
}
skip = true;
}
}
}
if skip {
needs_val_of = None;
continue;
}
if arg_slice.starts_with("--") && !pos_only {
if arg_slice.len() == 2 {
pos_only = true;
continue;
}
needs_val_of = self.parse_long_arg(matches, &arg);
} else if arg_slice.starts_with("-") && arg_slice.len() != 1 && ! pos_only {
needs_val_of = self.parse_short_arg(matches, &arg);
} else {
if self.subcommands.contains_key(&arg) {
if arg_slice == "help" {
self.print_help();
}
subcmd_name = Some(arg.clone());
break;
}
if self.positionals_idx.is_empty() {
self.report_error(
format!("Found positional argument {}, but {} doesn't accept any", arg, self.name),
true, true);
}
if let Some(ref p) = self.positionals_idx.get(&pos_counter) {
if self.blacklist.contains(p.name) {
self.report_error(format!("The argument \"{}\" is mutually exclusive with one or more other arguments", arg),
true, true);
}
matches.positionals.insert(p.name, PosArg{
name: p.name,
value: arg.clone(),
});
if let Some(ref bl) = p.blacklist {
for name in bl {
self.blacklist.insert(name);
}
}
if self.required.contains(p.name) {
self.required.remove(p.name);
}
if let Some(ref reqs) = p.requires {
for n in reqs {
if matches.positionals.contains_key(n) {continue;}
if matches.opts.contains_key(n) {continue;}
if matches.flags.contains_key(n) {continue;}
self.required.insert(n);
}
}
pos_counter += 1;
} else {
self.report_error(format!("Positional argument \"{}\" was found, but {} wasn't expecting any", arg, self.name), true, true);
}
}
}
match needs_val_of {
Some(ref a) => {
self.report_error(
format!("Argument \"{}\" requires a value but none was supplied", a),
true, true);
}
_ => {}
}
if ! self.required.is_empty() {
self.report_error("One or more required arguments were not supplied".to_string(),
true, true);
}
self.validate_blacklist(&matches);
if let Some(sc_name) = subcmd_name {
if let Some(ref mut sc) = self.subcommands.get_mut(&sc_name) {
let mut new_matches = ArgMatches::new();
sc.get_matches_from(&mut new_matches, it);
matches.subcommand = Some(Box::new(SubCommand{
name: sc.name.clone(),
matches: new_matches}));
}
}
}
fn create_help_and_version(&mut self) {
if self.needs_long_help {
self.flags.insert("clap_help", FlagBuilder {
name: "clap_help",
short: if self.needs_short_help { Some('h') } else { None },
long: Some("help"),
help: Some("Prints this message"),
blacklist: None,
multiple: false,
requires: None,
});
}
if self.needs_long_version {
self.flags.insert("clap_version", FlagBuilder {
name: "clap_version",
short: if self.needs_short_help { Some('v') } else { None },
long: Some("version"),
help: Some("Prints version information"),
blacklist: None,
multiple: false,
requires: None,
});
}
if self.needs_subcmd_help && !self.subcommands.is_empty() {
self.subcommands.insert("help".to_string(), Box::new(App::new("help").about("Prints this message")));
}
}
fn check_for_help_and_version(&self, arg: char) {
if arg == 'h' && self.needs_short_help {
self.print_help();
} else if arg == 'v' && self.needs_short_version {
self.print_version(true);
}
}
fn parse_long_arg(&mut self, matches: &mut ArgMatches ,full_arg: &String) -> Option<&'static str> {
let mut arg = full_arg.trim_left_matches(|c| c == '-');
if arg == "help" && self.needs_long_help {
self.print_help();
} else if arg == "version" && self.needs_long_version {
self.print_version(true);
}
let mut arg_val: Option<String> = None;
if arg.contains("=") {
let arg_vec: Vec<&str> = arg.split("=").collect();
arg = arg_vec[0];
if arg_vec[1].len() == 0 {
self.report_error(format!("Argument --{} requires a value, but none was supplied", arg), true, true);
}
arg_val = Some(arg_vec[1].to_string());
}
if let Some(v) = self.opts.values().filter(|&v| v.long.is_some()).filter(|&v| v.long.unwrap() == arg).nth(0) {
if self.blacklist.contains(v.name) {
self.report_error(format!("The argument --{} is mutually exclusive with one or more other arguments", arg),
true, true);
}
if matches.opts.contains_key(v.name) {
if !v.multiple {
self.report_error(format!("Argument --{} was supplied more than once, but does not support multiple values", arg), true, true);
}
if arg_val.is_some() {
if let Some(ref mut o) = matches.opts.get_mut(v.name) {
o.occurrences += 1;
o.values.push(arg_val.clone().unwrap());
}
}
} else {
matches.opts.insert(v.name, OptArg{
name: v.name,
occurrences: 1,
values: if arg_val.is_some() { vec![arg_val.clone().unwrap()]} else {vec![]}
});
}
if let Some(ref bl) = v.blacklist {
for name in bl {
self.blacklist.insert(name);
}
}
if self.required.contains(v.name) {
self.required.remove(v.name);
}
if let Some(ref reqs) = v.requires {
for n in reqs {
if matches.opts.contains_key(n) { continue; }
if matches.flags.contains_key(n) { continue; }
if matches.positionals.contains_key(n) { continue; }
self.required.insert(n);
}
}
match arg_val {
None => { return Some(v.name); },
_ => { return None; }
}
}
if let Some(v) = self.flags.values().filter(|&v| v.long.is_some()).filter(|&v| v.long.unwrap() == arg).nth(0) {
if self.blacklist.contains(v.name) {
self.report_error(format!("The argument --{} is mutually exclusive with one or more other arguments", arg),
true, true);
}
if matches.flags.contains_key(v.name) && !v.multiple {
self.report_error(format!("Argument --{} was supplied more than once, but does not support multiple values", arg), true, true);
}
let mut done = false;
if let Some(ref mut f) = matches.flags.get_mut(v.name) {
done = true;
f.occurrences = if v.multiple { f.occurrences + 1 } else { 1 };
}
if !done {
matches.flags.insert(v.name, FlagArg{
name: v.name,
occurrences: 1
});
}
if self.required.contains(v.name) {
self.required.remove(v.name);
}
if let Some(ref bl) = v.blacklist {
for name in bl {
self.blacklist.insert(name);
}
}
if let Some(ref reqs) = v.requires {
for n in reqs {
if matches.flags.contains_key(n) { continue; }
if matches.opts.contains_key(n) { continue; }
if matches.positionals.contains_key(n) { continue; }
self.required.insert(n);
}
}
return None;
}
self.report_error(format!("Argument --{} isn't valid", arg), true, true);
unreachable!();
}
fn parse_short_arg(&mut self, matches: &mut ArgMatches ,full_arg: &String) -> Option<&'static str> {
let arg = &full_arg[..].trim_left_matches(|c| c == '-');
if arg.len() > 1 {
for c in arg.chars() {
self.check_for_help_and_version(c);
if !self.parse_single_short_flag(matches, c) {
self.report_error(format!("Argument -{} isn't valid",arg), true, true);
}
}
return None;
}
let arg_c = arg.chars().nth(0).unwrap();
self.check_for_help_and_version(arg_c);
if self.parse_single_short_flag(matches, arg_c) { return None; }
if let Some(v) = self.opts.values().filter(|&v| v.short.is_some()).filter(|&v| v.short.unwrap() == arg_c).nth(0) {
if self.blacklist.contains(v.name) {
self.report_error(format!("The argument --{} is mutually exclusive with one or more other arguments", arg),
true, true);
}
if matches.opts.contains_key(v.name) {
if !v.multiple {
self.report_error(format!("Argument -{} was supplied more than once, but does not support multiple values", arg), true, true);
}
} else {
matches.opts.insert(v.name, OptArg{
name: v.name,
occurrences: 1,
values: vec![]
});
}
if let Some(ref bl) = v.blacklist {
for name in bl {
self.blacklist.insert(name);
}
}
if self.required.contains(v.name) {
self.required.remove(v.name);
}
if let Some(ref reqs) = v.requires {
for n in reqs {
if matches.opts.contains_key(n) { continue; }
if matches.flags.contains_key(n) { continue; }
if matches.positionals.contains_key(n) { continue; }
self.required.insert(n);
}
}
return Some(v.name)
}
self.report_error( format!("Argument -{} isn't valid",arg_c), true, true);
unreachable!();
}
fn parse_single_short_flag(&mut self, matches: &mut ArgMatches, arg: char) -> bool {
for v in self.flags.values().filter(|&v| v.short.is_some()).filter(|&v| v.short.unwrap() == arg) {
if self.blacklist.contains(v.name) {
self.report_error(format!("The argument -{} is mutually exclusive with one or more other arguments", arg),
true, true);
}
if matches.flags.contains_key(v.name) && !v.multiple {
self.report_error(format!("Argument -{} was supplied more than once, but does not support multiple values", arg), true, true);
}
let mut done = false;
if let Some(ref mut f) = matches.flags.get_mut(v.name) {
done = true;
f.occurrences = if v.multiple { f.occurrences + 1 } else { 1 };
}
if !done {
matches.flags.insert(v.name, FlagArg{
name: v.name,
occurrences: 1
});
}
if self.required.contains(v.name) {
self.required.remove(v.name);
}
if let Some(ref bl) = v.blacklist {
for name in bl {
self.blacklist.insert(name);
}
}
if let Some(ref reqs) = v.requires {
for n in reqs {
if matches.flags.contains_key(n) { continue; }
if matches.opts.contains_key(n) { continue; }
if matches.positionals.contains_key(n) { continue; }
self.required.insert(n);
}
}
return true;
}
false
}
fn validate_blacklist(&self, matches: &ArgMatches) {
for name in self.blacklist.iter() {
if matches.flags.contains_key(name) {
self.report_error(format!("The argument {} is mutually exclusive with one or more other arguments",
if let Some(s) = self.flags.get(name).unwrap().short {
format!("-{}", s)
} else if let Some(l) = self.flags.get(name).unwrap().long {
format!("--{}", l)
} else {
format!("\"{}\"", name)
}), true, true);
}
if matches.opts.contains_key(name) {
self.report_error(format!("The argument {} is mutually exclusive with one or more other arguments",
if let Some(s) = self.opts.get(name).unwrap().short {
format!("-{}", s)
} else if let Some(l) = self.opts.get(name).unwrap().long {
format!("--{}", l)
} else {
format!("\"{}\"", name)
}), true, true);
}
if matches.positionals.contains_key(name) {
self.report_error(format!("The argument \"{}\" is mutually exclusive with one or more other arguments",name),
false, true);
}
}
}
}