#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png",
html_favicon_url = "https://www.rust-lang.org/favicon.ico",
html_root_url = "https://doc.rust-lang.org/")]
#![deny(missing_docs)]
#![cfg_attr(test, deny(warnings))]
#[macro_use] extern crate log;
extern crate getopts;
use getopts::{Fail,HasArg,Occur,Options};
use std::collections::BTreeMap;
use std::collections::btree_map::Iter;
use std::env;
use std::ffi::OsStr;
use std::fmt::{self,Display,Formatter};
use std::iter::IntoIterator;
use std::str::FromStr;
pub use self::errors::ArgsError;
use self::options::Opt;
use self::validations::Validation;
pub mod traits;
pub mod validations;
mod errors;
mod options;
#[cfg(test)] mod tst;
const COLUMN_WIDTH: usize = 20;
const SCOPE_PARSE: &'static str = "parse";
const SEPARATOR: &'static str = ",";
pub struct Args {
description: String,
options: Options,
opts: BTreeMap<String, Box<Opt>>,
opt_names: Vec<String>,
program_name: String,
values: BTreeMap<String, String>
}
impl Args {
pub fn new(program_name: &str, description: &str) -> Args {
debug!("Creating new args object for '{}'", program_name);
Args {
description: description.to_string(),
options: Options::new(),
opts: BTreeMap::new(),
opt_names: Vec::new(),
program_name: program_name.to_string(),
values: BTreeMap::new()
}
}
pub fn flag(&mut self,
short_name: &str,
long_name: &str,
desc: &str) -> &mut Args {
self.register_opt(
options::new(short_name,
long_name,
desc,
"",
HasArg::No,
Occur::Optional,
None
)
);
self
}
pub fn full_usage(&self) -> String {
format!("{}\n\n{}", self.short_usage(), self.usage())
}
pub fn has_options(&self) -> bool {
!self.opts.is_empty()
}
pub fn has_value(&self, opt_name: &str) -> bool {
self.values.get(opt_name).is_some()
}
pub fn iter(&self) -> Iter<String, String> {
self.values.iter()
}
pub fn option(&mut self,
short_name: &str,
long_name: &str,
desc: &str,
hint: &str,
occur: Occur,
default: Option<String>) -> &mut Args {
self.register_opt(
options::new(short_name,
long_name,
desc,
hint,
HasArg::Yes,
occur,
default
)
);
self
}
pub fn parse<C: IntoIterator>(&mut self, raw_args: C) -> Result<(), ArgsError> where C::Item: AsRef<OsStr> {
debug!("Parsing args for '{}'", self.program_name);
let matches = match self.options.parse(raw_args) {
Ok(matches) => { matches },
Err(error) => { return Err(ArgsError::new(SCOPE_PARSE, &error.to_string())) }
};
for opt_name in &self.opt_names {
let option = self.opts.get(opt_name);
if option.is_none() {
return Err(ArgsError::new(SCOPE_PARSE, &Fail::UnrecognizedOption(opt_name.to_string()).to_string()));
}
let opt = option.unwrap();
let value = opt.parse(&matches).unwrap_or("".to_string());
if !value.is_empty() {
self.values.insert(opt_name.to_string(), value);
} else {
if opt.is_required() {
return Err(ArgsError::new(SCOPE_PARSE, &Fail::ArgumentMissing(opt_name.to_string()).to_string()));
}
}
}
debug!("Args: {:?}", self.values);
Ok(())
}
pub fn parse_from_cli(&mut self) -> Result<(), ArgsError> {
let mut raw_args: Vec<String> = env::args().collect();
if !raw_args.is_empty() { raw_args.remove(0); }
self.parse(&mut raw_args)
}
pub fn short_usage(&self) -> String {
self.options.short_usage(&self.program_name)
}
pub fn usage(&self) -> String {
if !self.has_options() { return format!("{}\n", self.description); }
self.options.usage(&self.description)
}
pub fn optional_validated_value_of<T>(&self, opt_name: &str, validations: &[Box<Validation<T=T>>])
-> Result<Option<T>, ArgsError> where T: FromStr {
if self.has_value(opt_name) {
Ok(Some(try!(self.validated_value_of::<T>(opt_name, validations))))
} else {
Ok(None)
}
}
pub fn optional_value_of<T: FromStr>(&self, opt_name: &str) -> Result<Option<T>, ArgsError> {
if self.has_value(opt_name) {
Ok(Some(try!(self.value_of::<T>(opt_name))))
} else {
Ok(None)
}
}
pub fn validated_value_of<T>(&self, opt_name: &str, validations: &[Box<Validation<T=T>>])
-> Result<T, ArgsError> where T: FromStr {
self.value_of::<T>(opt_name).and_then(|value| {
for validation in validations {
if validation.is_invalid(&value) { return Err(validation.error(&value)); }
}
Ok(value)
})
}
pub fn value_of<T: FromStr>(&self, opt_name: &str) -> Result<T, ArgsError> {
self.values.get(opt_name).ok_or(
ArgsError::new(opt_name, "does not have a value")
).and_then(|value_string| {
T::from_str(value_string).or(
Err(ArgsError::new(opt_name, &format!("unable to parse '{}'", value_string)))
)
})
}
pub fn values_of<T: FromStr>(&self, opt_name: &str) -> Result<Vec<T>, ArgsError> {
self.values.get(opt_name).ok_or(
ArgsError::new(opt_name, "does not have a value")
).and_then(|values_str| {
values_str.split(SEPARATOR).map(|value| {
T::from_str(value).or(
Err(ArgsError::new(opt_name, &format!("unable to parse '{}'", value)))
)
}).collect()
})
}
pub fn get_option(&self, opt_name :&str) -> Option<Box<Opt>> {
self.opts.get(opt_name).map(|opt| opt.clone())
}
fn register_opt(&mut self, opt: Box<Opt>) {
if !self.opt_names.contains(&opt.name()) {
debug!("Registering {}", opt);
opt.register(&mut self.options);
self.opt_names.push(opt.name().to_string());
self.opts.insert(opt.name().to_string(), opt);
} else {
warn!("{} is already registered, ignoring", opt.name());
}
}
}
impl Display for Args {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut display = String::new();
display.push_str(&format!("{}\n{}",
to_column("Args"), column_underline()));
for (key, value) in self.values.clone() {
display.push_str(&format!("\n{}\t{}",
to_column(&key), to_column(&value)));
}
write!(f, "{}", display)
}
}
fn column_underline() -> String {
let mut underline = String::new();
for _ in 0..COLUMN_WIDTH { underline.push_str("="); }
underline
}
fn to_column(string: &str) -> String {
let mut string = string.to_string();
string = if string.len() > COLUMN_WIDTH {
string.truncate(COLUMN_WIDTH- 3);
format!("{}...", string)
} else { string };
let mut spaces = String::new();
for _ in 0..(COLUMN_WIDTH - string.len()) { spaces.push_str(" "); }
format!("{}{}", string, spaces)
}