use std::cell::RefCell;
use std::collections::{hash_map::Entry, HashMap};
use std::env;
use std::fmt;
use std::rc::Rc;
use crate::error::{Error, Result};
const OPT_PREFIX: char = '-';
const HELP_OPTION: &str = "-h";
const END_OF_OPTIONS: &str = "--";
const LONG_OPT_PREFIX: &str = END_OF_OPTIONS;
pub const POSITIONAL_HANDLER_OPT: char = '"';
pub const UNKNOWN_OPTION_HANDLER_OPT: char = '?';
const USAGE_PREFIX_SPACES: &str = " ";
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone, Copy)]
pub enum Need {
Nothing,
Argument,
}
impl Default for Need {
fn default() -> Self {
Need::Nothing
}
}
impl Need {
pub fn new() -> Self {
Need::default()
}
}
pub trait Handler {
fn handle(&mut self, arg: Arg) -> Result<()>;
}
impl<'a> fmt::Debug for dyn Handler + 'a {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Handler: {:p}", self)
}
}
impl<'a> PartialEq for Box<dyn Handler + 'a> {
fn eq(&self, other: &Box<dyn Handler + 'a>) -> bool {
self == other
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct Arg {
pub option: char,
pub needs: Need,
pub help: Option<String>,
pub required: bool,
pub value: Option<String>,
pub count: usize,
}
impl Arg {
pub fn new(option: char) -> Self {
Arg::default().option(option)
}
pub fn option(self, option: char) -> Self {
Arg { option, ..self }
}
pub fn needs(self, needs: Need) -> Self {
Arg { needs, ..self }
}
pub fn help(self, help: &str) -> Self {
Arg {
help: Some(help.into()),
..self
}
}
pub fn required(self) -> Self {
Arg {
required: true,
..self
}
}
}
impl fmt::Display for Arg {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = if self.needs == Need::Argument {
" <value>"
} else {
" "
};
let help: String = match &self.help {
Some(help) => format!(" # {}", help),
_ => "".into(),
};
write!(
f,
"{}{}{}{} {}",
USAGE_PREFIX_SPACES, OPT_PREFIX, self.option, value, help
)
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialOrd, Default, PartialEq)]
pub struct Settings {
ignore_unknown_options: bool,
ignore_unknown_posn_args: bool,
}
impl Settings {
pub fn new() -> Self {
Settings::default()
}
pub fn ignore_unknown_options(self) -> Self {
Settings {
ignore_unknown_options: true,
..self
}
}
pub fn ignore_unknown_posn_args(self) -> Self {
Settings {
ignore_unknown_posn_args: true,
..self
}
}
}
pub fn get_args() -> Vec<String> {
let mut args: Vec<String> = env::args().collect();
let _ = args.remove(0);
args
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Args {
entries: HashMap<char, Rc<RefCell<Arg>>>,
}
impl Args {
pub fn new() -> Self {
Args {
entries: HashMap::<char, Rc<RefCell<Arg>>>::new(),
}
}
fn len(&self) -> usize {
self.entries.len()
}
#[allow(dead_code)]
fn set(&mut self, args: Vec<Arg>) {
self.entries.clear();
for arg in args {
self.entries.insert(arg.option, Rc::new(RefCell::new(arg)));
}
}
pub fn add(&mut self, arg: Arg) {
self.entries.insert(arg.option, Rc::new(RefCell::new(arg)));
}
pub fn exists(&self, option: &char) -> bool {
self.entries.get(option).is_some()
}
pub fn get(&self, option: char) -> Option<Arg> {
self.entries.get(&option).map(|a| a.borrow().clone())
}
}
impl Default for Args {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Default, Debug, PartialEq)]
pub struct App<'a> {
name: String,
version: String,
summary: String,
help: String,
notes: String,
settings: Settings,
args: Args,
handler: Option<Rc<RefCell<Box<dyn Handler + 'a>>>>,
}
impl<'a> App<'a> {
pub fn new(name: &str) -> Self {
App::default().name(name)
}
fn name(self, name: &str) -> Self {
App {
name: name.into(),
..self
}
}
pub fn args(self, args: Args) -> Self {
App { args, ..self }
}
pub fn version(self, version: &str) -> Self {
App {
version: version.into(),
..self
}
}
pub fn summary(self, summary: &str) -> Self {
App {
summary: summary.into(),
..self
}
}
pub fn help(self, help: &str) -> Self {
App {
help: help.into(),
..self
}
}
pub fn notes(self, notes: &str) -> Self {
App {
notes: notes.into(),
..self
}
}
pub fn settings(self, settings: Settings) -> Self {
App { settings, ..self }
}
pub fn ignore_unknown_options(self) -> Self {
App {
settings: Settings {
ignore_unknown_options: true,
..Default::default()
},
..self
}
}
pub fn ignore_unknown_posn_args(self) -> Self {
App {
settings: Settings {
ignore_unknown_posn_args: true,
..Default::default()
},
..self
}
}
pub fn generate_help(&self) -> Result<()> {
println!("NAME:\n{}{}\n", USAGE_PREFIX_SPACES, self.name);
if !self.version.is_empty() {
println!("VERSION:\n{}\n", self.version);
}
if !self.summary.is_empty() {
println!("{}{}\n", USAGE_PREFIX_SPACES, self.summary);
}
let have_posn_handler = self.args.get(POSITIONAL_HANDLER_OPT).is_some();
let posn_args = match have_posn_handler {
true => " [ARGUMENT..]",
false => "",
};
println!("USAGE:");
println!(
"{}{} [FLAGS]{}\n",
USAGE_PREFIX_SPACES, self.name, posn_args
);
println!("FLAGS:");
let mut keys: Vec<char> = self
.args
.entries
.iter()
.map(|(key, _)| *key)
.filter(|k| *k != POSITIONAL_HANDLER_OPT)
.collect();
keys.sort_unstable();
for key in keys.clone() {
let arg_ref = self.args.entries.get(&key).unwrap();
let arg = arg_ref.borrow();
if arg.needs == Need::Nothing {
println!("{}", arg);
}
}
println!();
println!("OPTIONS:");
for key in keys {
let arg_ref = self.args.entries.get(&key).unwrap();
let arg = arg_ref.borrow();
if arg.needs != Need::Nothing {
println!("{}", arg);
}
}
if !self.notes.is_empty() {
println!("{}{}\n", USAGE_PREFIX_SPACES, self.notes);
}
println!();
Ok(())
}
pub fn parse_with_args(&mut self, cli_args: Vec<String>) -> Result<()> {
let mut need = Need::new();
let mut end_of_options = false;
let mut current_option: Option<char> = None;
for cli_arg in cli_args.iter() {
match cli_arg.as_str() {
HELP_OPTION => return self.generate_help(),
END_OF_OPTIONS => break,
_ => (),
}
}
if self.handler.is_none() {
return Err(Error::NoHandler);
}
if self.args.len() == 0 {
return Err(Error::NoArgs);
}
for (_i, cli_arg) in cli_args.iter().enumerate() {
if cli_arg.starts_with(OPT_PREFIX) && !end_of_options {
if cli_arg == END_OF_OPTIONS {
end_of_options = true;
continue;
}
if cli_arg.starts_with(LONG_OPT_PREFIX) {
return Err(Error::NoLongOpts);
}
let mut chars = cli_arg.chars();
if cli_arg.len() > 2 {
let option_name = *chars.nth(1).as_ref().unwrap();
if option_name.is_ascii_whitespace() {
return Err(Error::MissingOptName);
} else {
return Err(Error::NoBundling);
}
}
let option = chars.nth(1).ok_or(Error::MissingOptName)?;
if need == Need::Argument {
return Err(Error::MissingOptArg);
}
if let Entry::Occupied(entry) = self.args.entries.entry(option) {
let arg_ref = entry.get();
let mut arg = arg_ref.borrow_mut();
need = arg.needs;
if need == Need::Nothing {
if let Some(h) = self.handler.clone() {
arg.count += 1;
h.borrow_mut().handle(arg.clone())?;
}
} else if need == Need::Argument {
current_option = Some(arg.option);
}
} else if let Entry::Occupied(entry) =
self.args.entries.entry(UNKNOWN_OPTION_HANDLER_OPT)
{
let arg_ref = entry.get();
let mut arg = arg_ref.borrow_mut();
if let Some(h) = self.handler.clone() {
arg.count += 1;
h.borrow_mut().handle(arg.clone())?;
}
} else if self.settings.ignore_unknown_options {
continue;
} else {
return Err(Error::UnknownOpt);
}
} else if let Some(option) = current_option {
if let Entry::Occupied(entry) = self.args.entries.entry(option) {
let mut arg = entry.get().borrow_mut();
arg.value = Some(cli_arg.into());
if let Some(h) = self.handler.clone() {
arg.count += 1;
h.borrow_mut().handle(arg.clone())?;
}
} else {
return Err(Error::NoHandler);
}
need = Need::Nothing;
current_option = None;
} else {
if let Entry::Occupied(entry) = self.args.entries.entry(POSITIONAL_HANDLER_OPT) {
let arg_ref = entry.get();
let mut arg = arg_ref.borrow_mut();
arg.value = Some(cli_arg.into());
if let Some(h) = self.handler.clone() {
arg.count += 1;
h.borrow_mut().handle(arg.clone())?;
}
} else if !self.settings.ignore_unknown_posn_args {
if !end_of_options {
return Err(Error::NoPosnArgs);
}
}
need = Need::Nothing;
current_option = None;
}
}
for arg_ref in self.args.entries.values() {
let arg = arg_ref.borrow();
if let Some(option) = current_option {
if arg.needs == Need::Argument && option == arg.option {
return Err(Error::MissingOptArg);
}
}
if arg.required && arg.count == 0 {
return Err(Error::MissingReqOpt);
}
}
Ok(())
}
pub fn handler(self, boxed_handler: Box<dyn Handler + 'a>) -> Self {
let boxed = Rc::new(RefCell::new(boxed_handler));
App {
handler: Some(boxed),
..self
}
}
pub fn parse(&mut self) -> Result<()> {
let args = get_args();
self.parse_with_args(args)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_requirement() {
let r1 = Need::new();
let r2 = Need::default();
assert_eq!(r1, Need::Nothing);
assert_eq!(r1, r2);
}
#[test]
fn test_settings() {
let new_settings = Settings::new();
let def_settings = Settings::default();
assert_eq!(new_settings.ignore_unknown_options, false);
assert_eq!(new_settings.ignore_unknown_posn_args, false);
assert_eq!(new_settings, def_settings);
let settings = Settings::new()
.ignore_unknown_options()
.ignore_unknown_posn_args();
assert_eq!(settings.ignore_unknown_options, true);
assert_eq!(settings.ignore_unknown_posn_args, true);
}
#[test]
fn test_arg() {
let default_arg = Arg::default();
let expected_default = Arg {
option: '\u{0}',
needs: Need::Nothing,
help: None,
required: false,
value: None,
count: 0,
};
assert_eq!(default_arg, expected_default);
let new_arg = Arg::new('a');
let expected_new = Arg {
option: 'a',
needs: Need::Nothing,
help: None,
required: false,
value: None,
count: 0,
};
assert_eq!(new_arg, expected_new);
let option_arg = Arg::new('a').option('b');
assert_eq!(option_arg.option, 'b');
let def_option_arg = Arg::default().option('a');
assert_eq!(def_option_arg.option, 'a');
let needs_arg = Arg::new('a').needs(Need::Argument);
assert_eq!(needs_arg.needs, Need::Argument);
let help = "some help text\nfoo bar\nthe end";
let help_arg = Arg::new('a').help(help);
assert_eq!(help_arg.help, Some(help.into()));
let required_arg = Arg::new('a').required();
assert_eq!(required_arg.required, true);
}
#[test]
fn test_args() {
let new_args = Args::new();
let def_args = Args::default();
assert_eq!(new_args, def_args);
let mut args = Args::new();
assert_eq!(args.len(), 0);
assert!(!args.exists(&'a'));
assert!(!args.exists(&'b'));
assert_eq!(args.get('a'), None);
assert_eq!(args.get('b'), None);
let arg_1 = Arg::new('a');
let arg_2 = Arg::new('b');
args.add(arg_1);
assert_eq!(args.len(), 1);
assert!(args.exists(&'a'));
assert!(!args.exists(&'b'));
assert!(args.get('a').is_some());
assert_eq!(args.get('a').unwrap().option, 'a');
assert_eq!(args.get('b'), None);
args.add(arg_2);
assert_eq!(args.len(), 2);
assert!(args.exists(&'a'));
assert!(args.exists(&'b'));
assert!(args.get('a').is_some());
assert!(args.get('b').is_some());
assert_eq!(args.get('a').unwrap().option, 'a');
assert_eq!(args.get('b').unwrap().option, 'b');
}
#[derive(Clone, Debug, Default)]
struct OkHandler {}
impl Handler for &OkHandler {
fn handle(&mut self, arg: Arg) -> Result<()> {
assert!(arg.count > 0);
Ok(())
}
}
impl Handler for &mut OkHandler {
fn handle(&mut self, arg: Arg) -> Result<()> {
assert!(arg.count > 0);
Ok(())
}
}
#[derive(Clone, Debug, Default)]
struct ErrHandler {}
const TEST_ERR: &str = "dang";
impl Handler for &ErrHandler {
fn handle(&mut self, arg: Arg) -> Result<()> {
assert!(arg.count > 0);
Err(Error::GenericError(TEST_ERR.into()))
}
}
impl Handler for &mut ErrHandler {
fn handle(&mut self, _arg: Arg) -> Result<()> {
Err(Error::GenericError(TEST_ERR.into()))
}
}
#[derive(Clone, Debug, Default, PartialEq)]
struct ModifyHandler {
i: usize,
v: Vec<String>,
s: String,
count: usize,
a_count: usize,
b_count: usize,
d_count: usize,
e_count: usize,
r_count: usize,
}
const INT_INCREMENT: usize = 17;
const HANDLER_SET_MSG: &str = "set by handler";
impl Handler for &mut ModifyHandler {
fn handle(&mut self, arg: Arg) -> Result<()> {
assert!(arg.count > 0);
self.count += arg.count;
self.i += INT_INCREMENT;
self.s = HANDLER_SET_MSG.into();
if let Some(value) = arg.value {
self.v.push(value);
}
match arg.option {
'a' => self.a_count += 1,
'b' => self.b_count += 1,
'd' => self.d_count += 1,
'e' => self.e_count += 1,
'r' => self.r_count += 1,
_ => (),
};
Ok(())
}
}
#[test]
fn test_parse_with_args() {
#[derive(Debug)]
struct TestData<'a> {
cli_args: Vec<&'a str>,
args: Option<Vec<Arg>>,
use_handler: bool,
result: Result<()>,
}
let mut ok_handler = OkHandler::default();
let need_arg_opt_1 = Arg::new('a').needs(Need::Argument);
let need_arg_opt_2 = Arg::new('b').needs(Need::Argument);
let need_arg_opt_3 = Arg::new('3').needs(Need::Argument);
let flag_opt = Arg::new('d').needs(Need::Nothing);
let flag_opt_2 = Arg::new('人').needs(Need::Nothing);
let flag_opt_3 = Arg::new('0').needs(Need::Nothing);
let required_flag_opt = Arg::new('e').needs(Need::Nothing).required();
let required_need_arg_opt = Arg::new('r').needs(Need::Argument).required();
let tests = &[
TestData {
cli_args: vec![],
args: None,
use_handler: false,
result: Err(Error::NoHandler),
},
TestData {
cli_args: vec![],
args: None,
use_handler: true,
result: Err(Error::NoArgs),
},
TestData {
cli_args: vec![],
args: Some(vec![need_arg_opt_1.clone()]),
use_handler: true,
result: Ok(()),
},
TestData {
cli_args: vec![],
args: Some(vec![need_arg_opt_1.clone(), need_arg_opt_2.clone()]),
use_handler: true,
result: Ok(()),
},
TestData {
cli_args: vec![],
args: Some(vec![required_flag_opt.clone()]),
use_handler: true,
result: Err(Error::MissingReqOpt),
},
TestData {
cli_args: vec![],
args: Some(vec![need_arg_opt_1.clone(), required_flag_opt.clone()]),
use_handler: true,
result: Err(Error::MissingReqOpt),
},
TestData {
cli_args: vec![],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Err(Error::MissingReqOpt),
},
TestData {
cli_args: vec!["-"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptName),
},
TestData {
cli_args: vec!["- -"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptName),
},
TestData {
cli_args: vec!["- - "],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptName),
},
TestData {
cli_args: vec![" - "],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec![END_OF_OPTIONS],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Ok(()),
},
TestData {
cli_args: vec![" -"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec![" --"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec![" ---"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec![" --- "],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec!["---"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoLongOpts),
},
TestData {
cli_args: vec!["--notsupported"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoLongOpts),
},
TestData {
cli_args: vec!["--not-supported"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoLongOpts),
},
TestData {
cli_args: vec![],
args: Some(vec![required_flag_opt.clone()]),
use_handler: true,
result: Err(Error::MissingReqOpt),
},
TestData {
cli_args: vec![],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Err(Error::MissingReqOpt),
},
TestData {
cli_args: vec!["--", "-r"],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Err(Error::MissingReqOpt),
},
TestData {
cli_args: vec!["-r"],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptArg),
},
TestData {
cli_args: vec!["-r", "--"],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptArg),
},
TestData {
cli_args: vec!["-r", "-d", "--"],
args: Some(vec![required_need_arg_opt.clone(), flag_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptArg),
},
TestData {
cli_args: vec![],
args: Some(vec![required_flag_opt.clone()]),
use_handler: true,
result: Err(Error::MissingReqOpt),
},
TestData {
cli_args: vec!["-r"],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptArg),
},
TestData {
cli_args: vec!["-r", "--"],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptArg),
},
TestData {
cli_args: vec!["-r"],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Err(Error::MissingOptArg),
},
TestData {
cli_args: vec!["-r", "foo"],
args: Some(vec![required_need_arg_opt.clone()]),
use_handler: true,
result: Ok(()),
},
TestData {
cli_args: vec!["-3", "three", "-0"],
args: Some(vec![need_arg_opt_3.clone(), flag_opt_3.clone()]),
use_handler: true,
result: Ok(()),
},
TestData {
cli_args: vec!["-人"],
args: Some(vec![flag_opt_2.clone()]),
use_handler: true,
result: Err(Error::NoBundling),
},
TestData {
cli_args: vec!["-ab"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoBundling),
},
TestData {
cli_args: vec!["-abc"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoBundling),
},
TestData {
cli_args: vec!["--", "-abc"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Ok(()),
},
TestData {
cli_args: vec!["--", "abc", "def", "hello world", "--wibble", "-abc"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Ok(()),
},
TestData {
cli_args: vec!["--foo"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::NoLongOpts),
},
TestData {
cli_args: vec!["-y"],
args: Some(vec![flag_opt.clone()]),
use_handler: true,
result: Err(Error::UnknownOpt),
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let string_args = d.cli_args.clone().into_iter().map(String::from).collect();
let mut app: App;
if d.use_handler {
app = App::default().handler(Box::new(&mut ok_handler));
} else {
app = App::default();
}
let mut args = Args::default();
if let Some(a) = d.args.clone() {
args.set(a);
}
app = app.args(args);
let result = app.parse_with_args(string_args);
let msg = format!("{}, result: {:?}", msg, result);
if d.result.is_ok() {
assert!(result.is_ok(), "{}", msg);
continue;
}
assert!(result.is_err(), "{}", msg);
let expected_err = format!("{:?}", d.result.as_ref().err());
let actual_err = format!("{:?}", result.as_ref().err());
assert_eq!(actual_err, expected_err, "{}", msg);
}
}
#[test]
fn test_parse_with_handler() {
#[derive(Debug)]
struct TestData<'a> {
cli_args: Vec<&'a str>,
args: Vec<Arg>,
result: Result<()>,
handler_result: ModifyHandler,
}
let need_arg_opt_1 = Arg::new('a').needs(Need::Argument);
let flag_opt = Arg::new('d').needs(Need::Nothing);
let tests = &[
TestData {
cli_args: vec![],
args: vec![flag_opt.clone()],
result: Ok(()),
handler_result: ModifyHandler::default(),
},
TestData {
cli_args: vec!["-d"],
args: vec![flag_opt.clone()],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
s: HANDLER_SET_MSG.into(),
count: 1,
d_count: 1,
..ModifyHandler::default()
},
},
TestData {
cli_args: vec!["-a", "foo"],
args: vec![need_arg_opt_1.clone()],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
s: HANDLER_SET_MSG.into(),
v: vec!["foo".into()],
count: 1,
a_count: 1,
..ModifyHandler::default()
},
},
TestData {
cli_args: vec!["-a", "foo", "-a", "bar", "-a", "baz"],
args: vec![need_arg_opt_1.clone()],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT * 3,
s: HANDLER_SET_MSG.into(),
v: vec!["foo".into(), "bar".into(), "baz".into()],
count: (1 + 2 + 3),
a_count: 3,
..ModifyHandler::default()
},
},
TestData {
cli_args: vec!["-d", "-a", "foo", "-d", "-a", "bar", "-a", "baz"],
args: vec![flag_opt.clone(), need_arg_opt_1.clone()],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT * 5,
s: HANDLER_SET_MSG.into(),
v: vec!["foo".into(), "bar".into(), "baz".into()],
count: (1 + 2 + 3) + (1 + 2),
a_count: 3,
d_count: 2,
..ModifyHandler::default()
},
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let string_args: Vec<String> =
d.cli_args.clone().into_iter().map(String::from).collect();
let mut args = Args::default();
args.set(d.args.clone());
let mut handler = ModifyHandler::default();
let mut app = App::default().args(args).handler(Box::new(&mut handler));
let result = app.parse_with_args(string_args);
drop(app);
if result.is_err() {
assert!(d.result.is_err(), "{}", msg);
let expected_err = format!("{:?}", d.result.as_ref().err());
let actual_err = format!("{:?}", result.as_ref().err());
assert_eq!(expected_err, actual_err, "{}", msg);
continue;
}
assert!(result.is_ok(), "{}", msg);
assert_eq!(d.handler_result, handler, "{}", msg);
}
}
#[test]
fn test_parse_with_bad_handler() {
let flag_opt = Arg::new('d').needs(Need::Nothing);
let mut args = Args::default();
args.add(flag_opt);
let mut handler = ErrHandler::default();
let mut app = App::default().args(args).handler(Box::new(&mut handler));
let result = app.parse_with_args(vec!["-d".into()]);
assert!(result.is_err());
let expected_err = format!("{:?}", Error::GenericError(TEST_ERR.into()));
let actual_err = format!("{:?}", result.err().unwrap());
assert_eq!(expected_err, actual_err);
}
#[test]
fn test_ensure_handler_not_called_on_parse_fail() {
let mut handler = ModifyHandler::default();
let mut args = Args::default();
let need_arg_opt_1 = Arg::new('a').needs(Need::Argument);
let need_arg_opt_2 = Arg::new('b').needs(Need::Argument);
let flag_opt = Arg::new('d').needs(Need::Nothing);
args.add(flag_opt);
args.add(need_arg_opt_1);
args.add(need_arg_opt_2);
let cli_args = vec!["-d", "-a", "foo bar", "-d", "-a", "hello world", "-b", "-d"];
let string_args = cli_args.clone().into_iter().map(String::from).collect();
let mut app = App::default().args(args).handler(Box::new(&mut handler));
let result = app.parse_with_args(string_args);
assert!(result.is_err());
drop(app);
let expected_handler = ModifyHandler {
i: INT_INCREMENT * 4,
s: HANDLER_SET_MSG.into(),
v: vec!["foo bar".into(), "hello world".into()],
count: (1 + 2) + (1 + 2),
a_count: 2,
d_count: 2,
b_count: 0,
..ModifyHandler::default()
};
assert_eq!(expected_handler, handler);
}
#[test]
fn test_parse_positional_args() {
let posn_arg = Arg::new(POSITIONAL_HANDLER_OPT);
#[derive(Debug)]
struct TestData<'a> {
cli_args: Vec<&'a str>,
result: Result<()>,
handler_result: ModifyHandler,
}
let tests = &[
TestData {
cli_args: vec![],
result: Ok(()),
handler_result: ModifyHandler::default(),
},
TestData {
cli_args: vec!["foo"],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec!["foo".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec!["\\- -"],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec!["\\- -".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec!["\\- - "],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec!["\\- - ".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec![" - "],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec![" - ".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec![" - -"],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec![" - -".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec![" - - "],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec![" - - ".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec![" -"],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec![" -".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec![" --"],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec![" --".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec![" ---"],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec![" ---".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec![" --- "],
result: Ok(()),
handler_result: ModifyHandler {
i: INT_INCREMENT,
v: vec![" --- ".into()],
s: HANDLER_SET_MSG.into(),
count: 1,
..Default::default()
},
},
TestData {
cli_args: vec!["-d", "foo"],
result: Err(Error::UnknownOpt),
handler_result: ModifyHandler::default(),
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let string_args: Vec<String> =
d.cli_args.clone().into_iter().map(String::from).collect();
let mut args = Args::default();
args.add(posn_arg.clone());
let mut handler = ModifyHandler::default();
let mut app = App::default().args(args).handler(Box::new(&mut handler));
let result = app.parse_with_args(string_args);
let msg = format!("{}, result: {:?}", msg, result);
drop(app);
if result.is_err() {
assert!(d.result.is_err(), "{}", msg);
let expected_err = format!("{:?}", d.result.as_ref().err());
let actual_err = format!("{:?}", result.as_ref().err());
assert_eq!(expected_err, actual_err, "{}", msg);
continue;
}
assert!(result.is_ok(), "{}", msg);
assert_eq!(d.handler_result, handler, "{}", msg);
}
}
#[test]
fn test_parse_unknown_options() {
let unknown_opts_arg = Arg::new(UNKNOWN_OPTION_HANDLER_OPT);
let mut args = Args::default();
args.add(Arg::new('d'));
args.add(unknown_opts_arg);
let cli_args = vec!["-d", "-a", "-b", "-c", "-d"];
let string_args = cli_args.clone().into_iter().map(String::from).collect();
let mut handler = ModifyHandler::default();
assert_eq!(handler.count, 0);
let mut app = App::default()
.args(args)
.ignore_unknown_options()
.handler(Box::new(&mut handler));
let result = app.parse_with_args(string_args);
assert!(result.is_ok());
drop(app);
let expected_count = (1 + 2) + (1 + 2 + 3);
assert_eq!(handler.count, expected_count);
}
#[test]
fn test_intermingling_arguments() {
let mut handler = ModifyHandler::default();
let mut args = Args::default();
args.add(Arg::new('a').needs(Need::Argument));
args.add(Arg::new('d').needs(Need::Nothing));
args.add(Arg::new(POSITIONAL_HANDLER_OPT));
let mut app = App::default()
.help("some text")
.args(args)
.handler(Box::new(&mut handler));
let cli_args = vec![
"the start",
"-d",
"foo bar",
"-a",
"hello world",
"-d",
"alpha omega",
"one",
"two",
"-d",
"-a",
"moo bar haz",
"the end",
];
let string_args = cli_args.clone().into_iter().map(String::from).collect();
let result = app.parse_with_args(string_args);
assert!(result.is_ok());
drop(app);
let expected_handler = ModifyHandler {
i: 187,
v: vec![
"the start".into(),
"foo bar".into(),
"hello world".into(),
"alpha omega".into(),
"one".into(),
"two".into(),
"moo bar haz".into(),
"the end".into(),
],
s: "set by handler".into(),
count: 30,
a_count: 2,
d_count: 3,
..Default::default()
};
assert_eq!(handler, expected_handler);
}
#[test]
fn test_ignore_unknown_options() {
#[derive(Debug)]
struct TestData<'a> {
cli_args: Vec<&'a str>,
args: Vec<Arg>,
allow_unknown_options: bool,
result: Result<()>,
}
let need_arg_opt = Arg::new('a').needs(Need::Argument);
let flag_opt = Arg::new('d').needs(Need::Nothing);
let tests = &[
TestData {
cli_args: vec!["-z"],
args: vec![need_arg_opt.clone()],
allow_unknown_options: false,
result: Err(Error::UnknownOpt),
},
TestData {
cli_args: vec!["-z"],
args: vec![need_arg_opt.clone()],
allow_unknown_options: true,
result: Ok(()),
},
TestData {
cli_args: vec!["-z"],
args: vec![flag_opt.clone()],
allow_unknown_options: false,
result: Err(Error::UnknownOpt),
},
TestData {
cli_args: vec!["-z"],
args: vec![flag_opt.clone()],
allow_unknown_options: true,
result: Ok(()),
},
TestData {
cli_args: vec!["-z", "-a", "foo"],
args: vec![need_arg_opt.clone()],
allow_unknown_options: false,
result: Err(Error::UnknownOpt),
},
TestData {
cli_args: vec!["-a", "foo", "-z"],
args: vec![need_arg_opt.clone()],
allow_unknown_options: false,
result: Err(Error::UnknownOpt),
},
TestData {
cli_args: vec!["-z", "-a", "foo"],
args: vec![need_arg_opt.clone()],
allow_unknown_options: true,
result: Err(Error::UnknownOpt),
},
TestData {
cli_args: vec!["-a", "foo", "-z"],
args: vec![need_arg_opt.clone()],
allow_unknown_options: true,
result: Ok(()),
},
TestData {
cli_args: vec!["-z", "-d"],
args: vec![flag_opt.clone()],
allow_unknown_options: false,
result: Err(Error::UnknownOpt),
},
TestData {
cli_args: vec!["-d", "-z"],
args: vec![flag_opt.clone()],
allow_unknown_options: false,
result: Err(Error::UnknownOpt),
},
TestData {
cli_args: vec!["-z", "-d"],
args: vec![flag_opt.clone()],
allow_unknown_options: true,
result: Ok(()),
},
TestData {
cli_args: vec!["-d", "-z"],
args: vec![flag_opt.clone()],
allow_unknown_options: true,
result: Ok(()),
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let string_args: Vec<String> =
d.cli_args.clone().into_iter().map(String::from).collect();
let mut args = Args::default();
args.set(d.args.clone());
let mut handler = OkHandler::default();
let mut app = App::default().args(args).handler(Box::new(&mut handler));
if d.allow_unknown_options {
app = app.ignore_unknown_options();
}
let result = app.parse_with_args(string_args);
let msg = format!("{}, result: {:?}", msg, result);
if result.is_err() {
assert!(d.result.is_err(), "{}", msg);
let expected_err = format!("{:?}", d.result.as_ref().err());
let actual_err = format!("{:?}", result.as_ref().err());
assert_eq!(expected_err, actual_err, "{}", msg);
continue;
}
assert!(result.is_ok(), "{}", msg);
}
}
#[test]
fn test_ignore_unknown_posn_args() {
#[derive(Debug)]
struct TestData<'a> {
cli_args: Vec<&'a str>,
args: Vec<Arg>,
allow_unknown_posn_args: bool,
result: Result<()>,
}
let need_arg_opt = Arg::new('a').needs(Need::Argument);
let flag_opt = Arg::new('d').needs(Need::Nothing);
let tests = &[
TestData {
cli_args: vec!["foo bar"],
args: vec![need_arg_opt.clone()],
allow_unknown_posn_args: false,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec!["foo bar"],
args: vec![need_arg_opt.clone()],
allow_unknown_posn_args: true,
result: Ok(()),
},
TestData {
cli_args: vec!["foo bar"],
args: vec![flag_opt.clone()],
allow_unknown_posn_args: false,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec!["foo bar"],
args: vec![flag_opt.clone()],
allow_unknown_posn_args: true,
result: Ok(()),
},
TestData {
cli_args: vec!["foo bar", "-a", "foo"],
args: vec![need_arg_opt.clone()],
allow_unknown_posn_args: false,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec!["-a", "foo", "foo bar"],
args: vec![need_arg_opt.clone()],
allow_unknown_posn_args: false,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec!["foo bar", "-a", "foo"],
args: vec![need_arg_opt.clone()],
allow_unknown_posn_args: true,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec!["-a", "foo", "foo bar"],
args: vec![need_arg_opt.clone()],
allow_unknown_posn_args: true,
result: Ok(()),
},
TestData {
cli_args: vec!["foo bar", "-d"],
args: vec![flag_opt.clone()],
allow_unknown_posn_args: false,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec!["-d", "foo bar"],
args: vec![flag_opt.clone()],
allow_unknown_posn_args: false,
result: Err(Error::NoPosnArgs),
},
TestData {
cli_args: vec!["foo bar", "-d"],
args: vec![flag_opt.clone()],
allow_unknown_posn_args: true,
result: Ok(()),
},
TestData {
cli_args: vec!["-d", "foo bar"],
args: vec![flag_opt.clone()],
allow_unknown_posn_args: true,
result: Ok(()),
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let string_args: Vec<String> =
d.cli_args.clone().into_iter().map(String::from).collect();
let mut args = Args::default();
args.set(d.args.clone());
let mut handler = OkHandler::default();
let mut app = App::default().args(args).handler(Box::new(&mut handler));
if d.allow_unknown_posn_args {
app = app.ignore_unknown_posn_args();
}
let result = app.parse_with_args(string_args);
let msg = format!("{}, result: {:?}", msg, result);
if result.is_err() {
assert!(d.result.is_err(), "{}", msg);
let expected_err = format!("{:?}", d.result.as_ref().err());
let actual_err = format!("{:?}", result.as_ref().err());
assert_eq!(expected_err, actual_err, "{}", msg);
continue;
}
assert!(result.is_ok(), "{}", msg);
}
}
#[test]
fn test_app_creation() {
let new_app = App::new("foo bar");
let def_app = App::default();
let expected_def_app = App {
name: "".into(),
version: "".into(),
summary: "".into(),
help: "".into(),
notes: "".into(),
settings: Settings::default(),
args: Args::default(),
handler: None,
};
let expected_new_app = App {
name: "foo bar".into(),
..Default::default()
};
assert_eq!(def_app, expected_def_app);
assert_eq!(new_app, expected_new_app);
}
#[test]
fn test_app() {
let mut app = App::default();
assert_eq!(app.name, "");
let name = "foo bar";
app = app.name(name);
assert_eq!(app.name, name);
let version = "1.2.3-beta5";
assert_eq!(app.version, "");
app = app.version(version);
assert_eq!(app.version, version);
let summary = "my awesome app";
assert_eq!(app.summary, "");
app = app.summary(summary);
assert_eq!(app.summary, summary);
let help = "this app does something\nthe end\n";
assert_eq!(app.help, "");
app = app.help(help);
assert_eq!(app.help, help);
let notes = "a b c d e f# g";
assert_eq!(app.notes, "");
app = app.notes(notes);
assert_eq!(app.notes, notes);
}
}