From d137415a69007a90569ebbf38a92424fba60b997 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Tue, 18 Dec 2018 00:39:15 +0100 Subject: Add argparse 0.2.2 as a dependency This patch adds the crate rust-argparse [0] in version 0.2.2 as a dependency, as discussed in issue #4. [0] https://github.com/tailhook/rust-argparse Import subrepo argparse/:argparse at 0de60a5e6d9ee1a3570d6089afd3ccd6ed7480c5 --- argparse/src/action.rs | 33 ++ argparse/src/bool.rs | 22 + argparse/src/custom.rs | 95 +++++ argparse/src/from_cli.rs | 100 +++++ argparse/src/generic.rs | 133 ++++++ argparse/src/help.rs | 93 ++++ argparse/src/lib.rs | 64 +++ argparse/src/num.rs | 58 +++ argparse/src/parser.rs | 967 ++++++++++++++++++++++++++++++++++++++++++ argparse/src/print.rs | 13 + argparse/src/test_bool.rs | 98 +++++ argparse/src/test_const.rs | 28 ++ argparse/src/test_enum.rs | 51 +++ argparse/src/test_env.rs | 43 ++ argparse/src/test_float.rs | 50 +++ argparse/src/test_help.rs | 154 +++++++ argparse/src/test_int.rs | 107 +++++ argparse/src/test_many.rs | 95 +++++ argparse/src/test_optional.rs | 54 +++ argparse/src/test_parser.rs | 64 +++ argparse/src/test_path.rs | 30 ++ argparse/src/test_pos.rs | 196 +++++++++ argparse/src/test_str.rs | 28 ++ argparse/src/test_usage.rs | 57 +++ 24 files changed, 2633 insertions(+) create mode 100644 argparse/src/action.rs create mode 100644 argparse/src/bool.rs create mode 100644 argparse/src/custom.rs create mode 100644 argparse/src/from_cli.rs create mode 100644 argparse/src/generic.rs create mode 100644 argparse/src/help.rs create mode 100644 argparse/src/lib.rs create mode 100644 argparse/src/num.rs create mode 100644 argparse/src/parser.rs create mode 100644 argparse/src/print.rs create mode 100644 argparse/src/test_bool.rs create mode 100644 argparse/src/test_const.rs create mode 100644 argparse/src/test_enum.rs create mode 100644 argparse/src/test_env.rs create mode 100644 argparse/src/test_float.rs create mode 100644 argparse/src/test_help.rs create mode 100644 argparse/src/test_int.rs create mode 100644 argparse/src/test_many.rs create mode 100644 argparse/src/test_optional.rs create mode 100644 argparse/src/test_parser.rs create mode 100644 argparse/src/test_path.rs create mode 100644 argparse/src/test_pos.rs create mode 100644 argparse/src/test_str.rs create mode 100644 argparse/src/test_usage.rs (limited to 'argparse/src') diff --git a/argparse/src/action.rs b/argparse/src/action.rs new file mode 100644 index 0000000..c3a0aff --- /dev/null +++ b/argparse/src/action.rs @@ -0,0 +1,33 @@ +use std::cell::RefCell; +use std::rc::Rc; + +pub enum ParseResult { + Parsed, + Help, + Exit, + Error(String), +} + + +pub enum Action<'a> { + Flag(Box), + Single(Box), + Push(Box), + Many(Box), +} + +pub trait TypedAction { + fn bind<'x>(&self, Rc>) -> Action<'x>; +} + +pub trait IFlagAction { + fn parse_flag(&self) -> ParseResult; +} + +pub trait IArgAction { + fn parse_arg(&self, arg: &str) -> ParseResult; +} + +pub trait IArgsAction { + fn parse_args(&self, args: &[&str]) -> ParseResult; +} diff --git a/argparse/src/bool.rs b/argparse/src/bool.rs new file mode 100644 index 0000000..6ada458 --- /dev/null +++ b/argparse/src/bool.rs @@ -0,0 +1,22 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::action::Action; +use super::action::TypedAction; +use super::action::Action::Flag; +use super::generic::StoreConstAction; +use super::{StoreTrue, StoreFalse}; + + +impl TypedAction for StoreTrue { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + return Flag(Box::new(StoreConstAction { cell: cell, value: true })); + } +} + +impl TypedAction for StoreFalse { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + return Flag(Box::new(StoreConstAction { cell: cell, value: false })); + } +} + diff --git a/argparse/src/custom.rs b/argparse/src/custom.rs new file mode 100644 index 0000000..d002ffe --- /dev/null +++ b/argparse/src/custom.rs @@ -0,0 +1,95 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use super::{Parse, ParseOption, ParseList, ParseCollect, FromCommandLine}; +use super::action::Action; +use super::action::{TypedAction, IArgAction, IArgsAction}; +use super::action::ParseResult; +use super::action::ParseResult::{Parsed, Error}; +use super::action::Action::{Single, Push, Many}; + +pub struct ParseAction<'a, T: 'a> { + pub cell: Rc>, +} + +pub struct ParseOptionAction<'a, T: 'a> { + cell: Rc>>, +} + +pub struct ParseListAction<'a, T: 'a> { + cell: Rc>>, +} + +impl TypedAction for Parse { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + return Single(Box::new(ParseAction { cell: cell })); + } +} + +impl TypedAction> for ParseOption { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Single(Box::new(ParseOptionAction { cell: cell })); + } +} + +impl TypedAction> for ParseList { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Many(Box::new(ParseListAction { cell: cell })); + } +} + +impl TypedAction> for ParseCollect + where T: 'static + FromCommandLine + Clone +{ + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Push(Box::new(ParseListAction { cell: cell })) + } +} + +impl<'a, T: FromCommandLine> IArgAction for ParseAction<'a, T> { + fn parse_arg(&self, arg: &str) -> ParseResult { + match FromCommandLine::from_argument(arg) { + Ok(x) => { + **self.cell.borrow_mut() = x; + return Parsed; + } + Err(error) => { + return Error(format!("Bad value {:?}: {}", arg, error)); + } + } + } +} + +impl<'a, T: FromCommandLine> IArgAction for ParseOptionAction<'a, T> { + fn parse_arg(&self, arg: &str) -> ParseResult { + match FromCommandLine::from_argument(arg) { + Ok(x) => { + **self.cell.borrow_mut() = Some(x); + return Parsed; + } + Err(error) => { + return Error(format!("Bad value {:?}: {}", arg, error)); + } + } + } +} + +impl<'a, T: FromCommandLine + Clone> IArgsAction for ParseListAction<'a, T> { + fn parse_args(&self, args: &[&str]) -> ParseResult { + let mut result = vec!(); + for arg in args.iter() { + match FromCommandLine::from_argument(*arg) { + Ok(x) => { + result.push(x); + } + Err(error) => { + return Error(format!("Bad value {:?}: {}", arg, error)); + } + } + } + **self.cell.borrow_mut() = result; + return Parsed; + } +} + + diff --git a/argparse/src/from_cli.rs b/argparse/src/from_cli.rs new file mode 100644 index 0000000..7ea6d58 --- /dev/null +++ b/argparse/src/from_cli.rs @@ -0,0 +1,100 @@ +use std::str::FromStr; +use std::path::PathBuf; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; + +use super::FromCommandLine; + + +impl FromCommandLine for PathBuf { + fn from_argument(s: &str) -> Result { + Ok(From::from(s)) + } +} + +impl FromCommandLine for f32 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for f64 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} + +// TODO(tailhook) implement various radices for integer values +impl FromCommandLine for isize { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for i8 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for i16 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for i32 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for i64 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for usize { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for u8 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for u16 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for u32 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for u64 { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for bool { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for String { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|_| unreachable!()) + } +} +impl FromCommandLine for Ipv4Addr { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for Ipv6Addr { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} +impl FromCommandLine for SocketAddr { + fn from_argument(s: &str) -> Result { + FromStr::from_str(s).map_err(|e| format!("{:?}", e)) + } +} diff --git a/argparse/src/generic.rs b/argparse/src/generic.rs new file mode 100644 index 0000000..1b1b7dc --- /dev/null +++ b/argparse/src/generic.rs @@ -0,0 +1,133 @@ +use std::cell::RefCell; +use std::str::FromStr; +use std::rc::Rc; + +use super::{StoreConst, Store, StoreOption, List, Collect, PushConst}; +use super::action::Action; +use super::action::{TypedAction, IFlagAction, IArgAction, IArgsAction}; +use super::action::ParseResult; +use super::action::ParseResult::{Parsed, Error}; +use super::action::Action::{Flag, Single, Push, Many}; + +pub struct StoreConstAction<'a, T: 'a> { + pub value: T, + pub cell: Rc>, +} + +pub struct PushConstAction<'a, T: 'a> { + pub value: T, + pub cell: Rc>>, +} + +pub struct StoreAction<'a, T: 'a> { + pub cell: Rc>, +} + +pub struct StoreOptionAction<'a, T: 'a> { + cell: Rc>>, +} + +pub struct ListAction<'a, T: 'a> { + cell: Rc>>, +} + +impl TypedAction for StoreConst { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + let StoreConst(ref val) = *self; + return Flag(Box::new(StoreConstAction { cell: cell, value: val.clone() })); + } +} + +impl TypedAction> for PushConst { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + let PushConst(ref val) = *self; + return Flag(Box::new(PushConstAction { cell: cell, value: val.clone() })); + } +} + +impl TypedAction for Store { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + return Single(Box::new(StoreAction { cell: cell })); + } +} + +impl TypedAction> for StoreOption { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Single(Box::new(StoreOptionAction { cell: cell })); + } +} + +impl TypedAction> for List { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Many(Box::new(ListAction { cell: cell })); + } +} + +impl TypedAction> for Collect { + fn bind<'x>(&self, cell: Rc>>) -> Action<'x> { + return Push(Box::new(ListAction { cell: cell })); + } +} + +impl<'a, T: Clone> IFlagAction for StoreConstAction<'a, T> { + fn parse_flag(&self) -> ParseResult { + let mut targ = self.cell.borrow_mut(); + **targ = self.value.clone(); + return Parsed; + } +} + +impl<'a, T: Clone> IFlagAction for PushConstAction<'a, T> { + fn parse_flag(&self) -> ParseResult { + let mut targ = self.cell.borrow_mut(); + targ.push(self.value.clone()); + return Parsed; + } +} + +impl<'a, T: FromStr> IArgAction for StoreAction<'a, T> { + fn parse_arg(&self, arg: &str) -> ParseResult { + match FromStr::from_str(arg) { + Ok(x) => { + **self.cell.borrow_mut() = x; + return Parsed; + } + Err(_) => { + return Error(format!("Bad value {}", arg)); + } + } + } +} + +impl<'a, T: FromStr> IArgAction for StoreOptionAction<'a, T> { + fn parse_arg(&self, arg: &str) -> ParseResult { + match FromStr::from_str(arg) { + Ok(x) => { + **self.cell.borrow_mut() = Some(x); + return Parsed; + } + Err(_) => { + return Error(format!("Bad value {}", arg)); + } + } + } +} + +impl<'a, T: FromStr + Clone> IArgsAction for ListAction<'a, T> { + fn parse_args(&self, args: &[&str]) -> ParseResult { + let mut result = vec!(); + for arg in args.iter() { + match FromStr::from_str(*arg) { + Ok(x) => { + result.push(x); + } + Err(_) => { + return Error(format!("Bad value {}", arg)); + } + } + } + **self.cell.borrow_mut() = result; + return Parsed; + } +} + diff --git a/argparse/src/help.rs b/argparse/src/help.rs new file mode 100644 index 0000000..c7145be --- /dev/null +++ b/argparse/src/help.rs @@ -0,0 +1,93 @@ +use std::str::CharIndices; +use std::io::Result as IoResult; +use std::io::Write; + +use super::action::{IFlagAction, ParseResult}; +use super::action::ParseResult::Help; + +pub struct HelpAction; + +impl IFlagAction for HelpAction { + fn parse_flag(&self) -> ParseResult { + return Help; + } +} + + +struct WordsIter<'a> { + data: &'a str, + iter: CharIndices<'a>, +} + +impl<'a> WordsIter<'a> { + fn new(data: &'a str) -> WordsIter<'a> { + return WordsIter { + data: data, + iter: data.char_indices(), + }; + } +} + +impl<'a> Iterator for WordsIter<'a> { + type Item = &'a str; + fn next(&mut self) -> Option<&'a str> { + let word_start; + loop { + let (idx, ch) = match self.iter.next() { + None => return None, + Some((idx, ch)) => ((idx, ch)), + }; + match ch { + ' ' | '\t' | '\r' | '\n' => continue, + _ => { + word_start = idx; + break; + } + } + } + loop { + let (idx, ch) = match self.iter.next() { + None => break, + Some((idx, ch)) => ((idx, ch)), + }; + match ch { + ' ' | '\t' | '\r' | '\n' => { + return Some(&self.data[word_start..idx]); + } + _ => continue, + } + } + return Some(&self.data[word_start..self.data.len()]); + } +} + +pub fn wrap_text(buf: &mut Write, data: &str, width: usize, indent: usize) + -> IoResult<()> +{ + let mut witer = WordsIter::new(data); + let mut off = indent; + match witer.next() { + None => { + return Ok(()); + } + Some(word) => { + try!(buf.write(word.as_bytes())); + off += word.len(); + } + } + for word in witer { + if off + word.len() + 1 > width { + try!(buf.write(b"\n")); + for _ in 0..indent { + try!(buf.write(b" ")); + } + off = indent; + } else { + try!(buf.write(b" ")); + off += 1; + } + try!(buf.write(word.as_bytes())); + off += word.len(); + } + return Ok(()); +} diff --git a/argparse/src/lib.rs b/argparse/src/lib.rs new file mode 100644 index 0000000..62b0cf9 --- /dev/null +++ b/argparse/src/lib.rs @@ -0,0 +1,64 @@ +#![crate_name = "argparse"] +#![crate_type = "lib"] + +pub use self::parser::{ArgumentParser, Ref}; + +pub mod action; +pub mod parser; +mod generic; +mod custom; +mod help; +mod print; + +mod bool; +mod num; +mod from_cli; + +pub trait FromCommandLine: Sized { + fn from_argument(s: &str) -> Result; +} + +// TODO(tailhook) make consts +pub struct StoreTrue; +pub struct StoreFalse; + +pub struct StoreConst(pub T); + +pub struct PushConst(pub T); + +pub struct Store; +pub struct Parse; + +pub struct StoreOption; +pub struct ParseOption; + +pub struct List; +pub struct ParseList; + +pub struct Collect; +pub struct ParseCollect; + +/// Print string and exit with status 0 +/// +/// Particularly useful for `--version` option and similar +pub struct Print(pub String); + +pub struct IncrBy(pub T); + +pub struct DecrBy(pub T); + + +#[cfg(test)] mod test_parser; +#[cfg(test)] mod test_bool; +#[cfg(test)] mod test_int; +#[cfg(test)] mod test_float; +#[cfg(test)] mod test_str; +#[cfg(test)] mod test_enum; +#[cfg(test)] mod test_pos; +#[cfg(test)] mod test_many; +#[cfg(test)] mod test_optional; +#[cfg(test)] mod test_usage; +#[cfg(test)] mod test_help; +#[cfg(test)] mod test_env; +#[cfg(test)] mod test_const; +#[cfg(test)] mod test_path; diff --git a/argparse/src/num.rs b/argparse/src/num.rs new file mode 100644 index 0000000..9f34de7 --- /dev/null +++ b/argparse/src/num.rs @@ -0,0 +1,58 @@ +use std::cell::RefCell; +use std::rc::Rc; +use std::ops::{Add, Sub}; + +use super::{IncrBy, DecrBy}; +use super::action::{TypedAction, Action, ParseResult}; +use super::action::ParseResult::Parsed; +use super::action::IFlagAction; +use super::action::Action::Flag; + +pub struct IncrByAction<'a, T: 'a> { + delta: T, + cell: Rc>, +} + +pub struct DecrByAction<'a, T: 'a> { + delta: T, + cell: Rc>, +} + +impl + Clone> TypedAction for IncrBy { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + let IncrBy(ref delta) = *self; + return Flag(Box::new(IncrByAction { cell: cell, delta: delta.clone() })); + } +} + +impl + Clone> TypedAction for DecrBy { + fn bind<'x>(&self, cell: Rc>) -> Action<'x> { + let DecrBy(ref delta) = *self; + return Flag(Box::new(DecrByAction { cell: cell, delta: delta.clone() })); + } +} + +impl<'a, T: Add + Clone> IFlagAction for IncrByAction<'a, T> { + fn parse_flag(&self) -> ParseResult { + let oldval = { + let targ = self.cell.borrow(); + targ.clone() + }; + let mut targ = self.cell.borrow_mut(); + **targ = oldval + self.delta.clone(); + return Parsed; + } +} + +impl<'a, T: Sub + Clone> IFlagAction for DecrByAction<'a, T> { + fn parse_flag(&self) -> ParseResult { + let oldval = { + let targ = self.cell.borrow(); + targ.clone() + }; + let mut targ = self.cell.borrow_mut(); + **targ = oldval - self.delta.clone(); + return Parsed; + } +} + diff --git a/argparse/src/parser.rs b/argparse/src/parser.rs new file mode 100644 index 0000000..ac7e3da --- /dev/null +++ b/argparse/src/parser.rs @@ -0,0 +1,967 @@ +use std::env; +use std::io::{Write}; +use std::io::Result as IoResult; +use std::io::{stdout, stderr}; +use std::rc::Rc; +use std::cell::RefCell; +use std::iter::Peekable; +use std::slice::Iter; +use std::hash::Hash; +use std::hash::Hasher; +use std::str::FromStr; +use std::process::exit; + +#[allow(unused_imports)] #[allow(deprecated)] +use std::ascii::AsciiExt; + +use std::collections::HashMap; +use std::collections::hash_map::Entry; +use std::collections::HashSet; + +use super::action::{Action, ParseResult}; +use super::action::ParseResult::{Parsed, Help, Exit, Error}; +use super::action::TypedAction; +use super::action::Action::{Flag, Single, Push, Many}; +use super::action::IArgAction; +use super::generic::StoreAction; +use super::help::{HelpAction, wrap_text}; +use action::IFlagAction; + +use self::ArgumentKind::{Positional, ShortOption, LongOption, Delimiter}; + + +static OPTION_WIDTH: usize = 24; +static TOTAL_WIDTH: usize = 79; + + +enum ArgumentKind { + Positional, + ShortOption, + LongOption, + Delimiter, // Barely "--" +} + + +impl ArgumentKind { + fn check(name: &str) -> ArgumentKind { + let mut iter = name.chars(); + let char1 = iter.next(); + let char2 = iter.next(); + let char3 = iter.next(); + return match char1 { + Some('-') => match char2 { + Some('-') => match char3 { + Some(_) => LongOption, // --opt + None => Delimiter, // just -- + }, + Some(_) => ShortOption, // -opts + None => Positional, // single dash + }, + Some(_) | None => Positional, + } + } +} + +struct GenericArgument<'parser> { + id: usize, + varid: usize, + name: &'parser str, + help: &'parser str, + action: Action<'parser>, +} + +struct GenericOption<'parser> { + id: usize, + varid: Option, + names: Vec<&'parser str>, + help: &'parser str, + action: Action<'parser>, +} + +struct EnvVar<'parser> { + varid: usize, + name: &'parser str, + action: Box, +} + +impl<'a> Hash for GenericOption<'a> { + fn hash(&self, state: &mut H) where H: Hasher { + self.id.hash(state); + } +} + +impl<'a> PartialEq for GenericOption<'a> { + fn eq(&self, other: &GenericOption<'a>) -> bool { + return self.id == other.id; + } +} + +impl<'a> Eq for GenericOption<'a> {} + +impl<'a> Hash for GenericArgument<'a> { + fn hash(&self, state: &mut H) where H: Hasher { + self.id.hash(state); + } +} + +impl<'a> PartialEq for GenericArgument<'a> { + fn eq(&self, other: &GenericArgument<'a>) -> bool { + return self.id == other.id; + } +} + +impl<'a> Eq for GenericArgument<'a> {} + +pub struct Var { + id: usize, + metavar: String, + required: bool, +} + +impl Hash for Var { + fn hash(&self, state: &mut H) where H: Hasher { + self.id.hash(state); + } +} + +impl PartialEq for Var { + fn eq(&self, other: &Var) -> bool { + return self.id == other.id; + } +} + +impl Eq for Var {} + +struct Context<'ctx, 'parser: 'ctx> { + parser: &'ctx ArgumentParser<'parser>, + set_vars: HashSet, + list_options: HashMap>, Vec<&'ctx str>>, + list_arguments: HashMap>, Vec<&'ctx str>>, + arguments: Vec<&'ctx str>, + iter: Peekable>, + stderr: &'ctx mut (Write + 'ctx), +} + +impl<'a, 'b> Context<'a, 'b> { + + fn parse_option(&mut self, opt: Rc>, + optarg: Option<&'a str>) + -> ParseResult + { + let value = match optarg { + Some(value) => value, + None => match self.iter.next() { + Some(value) => { + &value[..] + } + None => { + return match opt.action { + Many(_) => Parsed, + _ => Error(format!( + // TODO(tailhook) is {:?} ok? + "Option {:?} requires an argument", opt.names)), + }; + } + }, + }; + match opt.varid { + Some(varid) => { self.set_vars.insert(varid); } + None => {} + } + match opt.action { + Single(ref action) => { + return action.parse_arg(value); + } + Push(_) => { + (match self.list_options.entry(opt.clone()) { + Entry::Occupied(occ) => occ.into_mut(), + Entry::Vacant(vac) => vac.insert(Vec::new()), + }).push(value); + return Parsed; + } + Many(_) => { + let vec = match self.list_options.entry(opt.clone()) { + Entry::Occupied(occ) => occ.into_mut(), + Entry::Vacant(vac) => vac.insert(Vec::new()), + }; + vec.push(value); + match optarg { + Some(_) => return Parsed, + _ => {} + } + loop { + match self.iter.peek() { + None => { break; } + Some(arg) if arg.starts_with("-") => { + break; + } + Some(value) => { + vec.push(&value[..]); + } + } + self.iter.next(); + } + return Parsed; + } + _ => panic!(), + }; + } + + fn parse_long_option(&mut self, arg: &'a str) -> ParseResult { + let mut equals_iter = arg.splitn(2, '='); + let optname = equals_iter.next().unwrap(); + let valueref = equals_iter.next(); + let opt = self.parser.long_options.get(&optname.to_string()); + match opt { + Some(opt) => { + match opt.action { + Flag(ref action) => { + match valueref { + Some(_) => { + return Error(format!( + "Option {} does not accept an argument", + optname)); + } + None => { + match opt.varid { + Some(varid) => { + self.set_vars.insert(varid); + } + None => {} + } + return action.parse_flag(); + } + } + } + Single(_) | Push(_) | Many(_) => { + return self.parse_option(opt.clone(), valueref); + } + } + } + None => { + return Error(format!("Unknown option {}", arg)); + } + } + } + + fn parse_short_options<'x>(&'x mut self, arg: &'a str) -> ParseResult { + let mut iter = arg.char_indices(); + iter.next(); + for (idx, ch) in iter { + let opt = match self.parser.short_options.get(&ch) { + Some(opt) => { opt } + None => { + return Error(format!("Unknown short option \"{}\"", ch)); + } + }; + let res = match opt.action { + Flag(ref action) => { + match opt.varid { + Some(varid) => { self.set_vars.insert(varid); } + None => {} + } + action.parse_flag() + } + Single(_) | Push(_) | Many(_) => { + let value; + if idx + 1 < arg.len() { + value = Some(&arg[idx+1..arg.len()]); + } else { + value = None; + } + return self.parse_option(opt.clone(), value); + } + }; + match res { + Parsed => { continue; } + x => { return x; } + } + } + return Parsed; + } + + fn postpone_argument(&mut self, arg: &'a str) { + self.arguments.push(arg); + } + + fn parse_options(&mut self) -> ParseResult { + self.iter.next(); // Command name + loop { + let next = self.iter.next(); + let arg = match next { + Some(arg) => { arg } + None => { break; } + }; + let res = match ArgumentKind::check(&arg[..]) { + Positional => { + self.postpone_argument(&arg[..]); + if self.parser.stop_on_first_argument { + break; + } + continue; + } + LongOption => self.parse_long_option(&arg[..]), + ShortOption => self.parse_short_options(&arg[..]), + Delimiter => { + if !self.parser.silence_double_dash { + self.postpone_argument("--"); + } + break; + } + }; + match res { + Parsed => continue, + _ => return res, + } + } + + loop { + match self.iter.next() { + None => break, + Some(arg) => self.postpone_argument(&arg[..]), + } + } + return Parsed; + } + + fn parse_arguments(&mut self) -> ParseResult { + let mut pargs = self.parser.arguments.iter(); + for arg in self.arguments.iter() { + let opt; + loop { + match pargs.next() { + Some(option) => { + if self.set_vars.contains(&option.varid) { + continue; + } + opt = option; + break; + } + None => match self.parser.catchall_argument { + Some(ref option) => { + opt = option; + break; + } + None => return Error(format!( + "Unexpected argument {}", arg)), + } + }; + } + let res = match opt.action { + Single(ref act) => { + self.set_vars.insert(opt.varid); + act.parse_arg(*arg) + }, + Many(_) | Push(_) => { + (match self.list_arguments.entry(opt.clone()) { + Entry::Occupied(occ) => occ.into_mut(), + Entry::Vacant(vac) => vac.insert(Vec::new()), + }).push(*arg); + Parsed + }, + _ => unreachable!(), + }; + match res { + Parsed => continue, + _ => return res, + } + } + return Parsed; + } + + fn parse_list_vars(&mut self) -> ParseResult { + for (opt, lst) in self.list_options.iter() { + match opt.action { + Push(ref act) | Many(ref act) => { + let res = act.parse_args(&lst[..]); + match res { + Parsed => continue, + _ => return res, + } + } + _ => panic!(), + } + } + for (opt, lst) in self.list_arguments.iter() { + match opt.action { + Push(ref act) | Many(ref act) => { + let res = act.parse_args(&lst[..]); + match res { + Parsed => continue, + _ => return res, + } + } + _ => panic!(), + } + } + return Parsed; + } + + fn parse_env_vars(&mut self) -> ParseResult { + for evar in self.parser.env_vars.iter() { + match env::var(evar.name) { + Ok(val) => { + match evar.action.parse_arg(&val[..]) { + Parsed => { + self.set_vars.insert(evar.varid); + continue; + } + Error(err) => { + write!(self.stderr, + "WARNING: Environment variable {}: {}\n", + evar.name, err).ok(); + } + _ => unreachable!(), + } + } + Err(_) => {} + } + } + return Parsed; + } + + fn check_required(&mut self) -> ParseResult { + // Check for required arguments + for var in self.parser.vars.iter() { + if var.required && !self.set_vars.contains(&var.id) { + // First try positional arguments + for opt in self.parser.arguments.iter() { + if opt.varid == var.id { + return Error(format!( + "Argument {} is required", opt.name)); + } + } + // Then options + let mut all_options = vec!(); + for opt in self.parser.options.iter() { + match opt.varid { + Some(varid) if varid == var.id => {} + _ => { continue } + } + all_options.extend(opt.names.clone().into_iter()); + } + if all_options.len() > 1 { + return Error(format!( + "One of the options {:?} is required", all_options)); + } else if all_options.len() == 1 { + return Error(format!( + "Option {:?} is required", all_options)); + } + // Then envvars + for envvar in self.parser.env_vars.iter() { + if envvar.varid == var.id { + return Error(format!( + "Environment var {} is required", envvar.name)); + } + } + } + } + return Parsed; + } + + fn parse(parser: &ArgumentParser, args: &Vec, stderr: &mut Write) + -> ParseResult + { + let mut ctx = Context { + parser: parser, + iter: args.iter().peekable(), + set_vars: HashSet::new(), + list_options: HashMap::new(), + list_arguments: HashMap::new(), + arguments: Vec::new(), + stderr: stderr, + }; + + match ctx.parse_env_vars() { + Parsed => {} + x => { return x; } + } + + match ctx.parse_options() { + Parsed => {} + x => { return x; } + } + + match ctx.parse_arguments() { + Parsed => {} + x => { return x; } + } + + match ctx.parse_list_vars() { + Parsed => {} + x => { return x; } + } + + match ctx.check_required() { + Parsed => {} + x => { return x; } + } + + return Parsed; + } +} + +pub struct Ref<'parser:'refer, 'refer, T: 'parser> { + cell: Rc>, + varid: usize, + parser: &'refer mut ArgumentParser<'parser>, +} + +impl<'parser, 'refer, T> Ref<'parser, 'refer, T> { + + pub fn add_option<'x, A: TypedAction>(&'x mut self, + names: &[&'parser str], action: A, help: &'parser str) + -> &'x mut Ref<'parser, 'refer, T> + { + { + let var = &mut self.parser.vars[self.varid]; + if var.metavar.len() == 0 { + let mut longest_name = names[0]; + let mut llen = longest_name.len(); + for name in names.iter() { + if name.len() > llen { + longest_name = *name; + llen = longest_name.len(); + } + } + if llen > 2 { + var.metavar = longest_name[2..llen] + .to_ascii_uppercase().replace("-", "_"); + } + } + } + self.parser.add_option_for(Some(self.varid), names, + action.bind(self.cell.clone()), + help); + return self; + } + + pub fn add_argument<'x, A: TypedAction>(&'x mut self, + name: &'parser str, action: A, help: &'parser str) + -> &'x mut Ref<'parser, 'refer, T> + { + let act = action.bind(self.cell.clone()); + let opt = Rc::new(GenericArgument { + id: self.parser.arguments.len(), + varid: self.varid, + name: name, + help: help, + action: act, + }); + match opt.action { + Flag(_) => panic!("Flag arguments can't be positional"), + Many(_) | Push(_) => { + match self.parser.catchall_argument { + Some(ref y) => panic!(format!( + "Option {} conflicts with option {}", + name, y.name)), + None => {}, + } + self.parser.catchall_argument = Some(opt); + } + Single(_) => { + self.parser.arguments.push(opt); + } + } + { + let var = &mut self.parser.vars[self.varid]; + if var.metavar.len() == 0 { + var.metavar = name.to_string(); + } + } + return self; + } + + pub fn metavar<'x>(&'x mut self, name: &str) + -> &'x mut Ref<'parser, 'refer, T> + { + { + let var = &mut self.parser.vars[self.varid]; + var.metavar = name.to_string(); + } + return self; + } + + pub fn required<'x>(&'x mut self) + -> &'x mut Ref<'parser, 'refer, T> + { + { + let var = &mut self.parser.vars[self.varid]; + var.required = true; + } + return self; + } +} + +impl<'parser, 'refer, T: 'static + FromStr> Ref<'parser, 'refer, T> { + pub fn envvar<'x>(&'x mut self, varname: &'parser str) + -> &'x mut Ref<'parser, 'refer, T> + { + self.parser.env_vars.push(Rc::new(EnvVar { + varid: self.varid, + name: varname, + action: Box::new(StoreAction { cell: self.cell.clone() }), + })); + return self; + } +} + +/// The main argument parser class +pub struct ArgumentParser<'parser> { + description: &'parser str, + vars: Vec>, + options: Vec>>, + arguments: Vec>>, + env_vars: Vec>>, + catchall_argument: Option>>, + short_options: HashMap>>, + long_options: HashMap>>, + stop_on_first_argument: bool, + silence_double_dash: bool, +} + + + +impl<'parser> ArgumentParser<'parser> { + + /// Create an empty argument parser + pub fn new() -> ArgumentParser<'parser> { + + let mut ap = ArgumentParser { + description: "", + vars: Vec::new(), + env_vars: Vec::new(), + arguments: Vec::new(), + catchall_argument: None, + options: Vec::new(), + short_options: HashMap::new(), + long_options: HashMap::new(), + stop_on_first_argument: false, + silence_double_dash: true, + }; + ap.add_option_for(None, &["-h", "--help"], Flag(Box::new(HelpAction)), + "Show this help message and exit"); + return ap; + } + + /// Borrow mutable variable for an argument + /// + /// This returns `Ref` object which should be used configure the option + pub fn refer<'x, T>(&'x mut self, val: &'parser mut T) + -> Box> + { + let cell = Rc::new(RefCell::new(val)); + let id = self.vars.len(); + self.vars.push(Box::new(Var { + id: id, + required: false, + metavar: "".to_string(), + })); + return Box::new(Ref { + cell: cell.clone(), + varid: id, + parser: self, + }); + } + + /// Add option to argument parser + /// + /// This is only useful for options that don't store value. For + /// example `Print(...)` + pub fn add_option(&mut self, + names: &[&'parser str], action: F, help: &'parser str) + { + self.add_option_for(None, names, Flag(Box::new(action)), help); + } + + /// Set description of the command + pub fn set_description(&mut self, descr: &'parser str) { + self.description = descr; + } + + fn add_option_for(&mut self, var: Option, + names: &[&'parser str], + action: Action<'parser>, help: &'parser str) + { + let opt = Rc::new(GenericOption { + id: self.options.len(), + varid: var, + names: names.to_vec(), + help: help, + action: action, + }); + + if names.len() < 1 { + panic!("At least one name for option must be specified"); + } + for nameptr in names.iter() { + let name = *nameptr; + match ArgumentKind::check(name) { + Positional|Delimiter => { + panic!("Bad argument name {}", name); + } + LongOption => { + self.long_options.insert( + name.to_string(), opt.clone()); + } + ShortOption => { + if name.len() > 2 { + panic!("Bad short argument {}", name); + } + self.short_options.insert( + name.as_bytes()[1] as char, opt.clone()); + } + } + } + self.options.push(opt); + } + + /// Print help + /// + /// Usually command-line option is used for printing help, + /// this is here for any awkward cases + pub fn print_help(&self, name: &str, writer: &mut Write) -> IoResult<()> { + return HelpFormatter::print_help(self, name, writer); + } + + /// Print usage + /// + /// Usually printed into stderr on error of command-line parsing + pub fn print_usage(&self, name: &str, writer: &mut Write) -> IoResult<()> + { + return HelpFormatter::print_usage(self, name, writer); + } + + /// Parse arguments + /// + /// This is most powerful method. Usually you need `parse_args` + /// or `parse_args_or_exit` instead + pub fn parse(&self, args: Vec, + stdout: &mut Write, stderr: &mut Write) + -> Result<(), i32> + { + let name = if args.len() > 0 { &args[0][..] } else { "unknown" }; + match Context::parse(self, &args, stderr) { + Parsed => return Ok(()), + Exit => return Err(0), + Help => { + self.print_help(name, stdout).unwrap(); + return Err(0); + } + Error(message) => { + self.error(&name[..], &message[..], stderr); + return Err(2); + } + } + } + + /// Write an error similar to one produced by the library itself + /// + /// Only needed if you like to do some argument validation that is out + /// of scope of the argparse + pub fn error(&self, command: &str, message: &str, writer: &mut Write) { + self.print_usage(command, writer).unwrap(); + write!(writer, "{}: {}\n", command, message).ok(); + } + + /// Configure parser to ignore options when first non-option argument is + /// encountered. + /// + /// Useful for commands that want to pass following options to the + /// subcommand or subprocess, but need some options to be set before + /// command is specified. + pub fn stop_on_first_argument(&mut self, want_stop: bool) { + self.stop_on_first_argument = want_stop; + } + + /// Do not put double-dash (bare `--`) into argument + /// + /// The double-dash is used to stop parsing options and treat all the + /// following tokens as the arguments regardless of whether they start + /// with dash (minus) or not. + /// + /// The method only useful for `List` arguments. On by default. The method + /// allows to set option to `false` so that `cmd xx -- yy` will get + /// ``xx -- yy`` as arguments instead of ``xx yy`` by default. This is + /// useful if your ``--`` argument is meaningful. Only first double-dash + /// is ignored by default. + pub fn silence_double_dash(&mut self, silence: bool) { + self.silence_double_dash = silence; + } + + /// Convenience method to parse arguments + /// + /// On error returns error code that is supposed to be returned by + /// an application. (i.e. zero on `--help` and `2` on argument error) + pub fn parse_args(&self) -> Result<(), i32> { + // TODO(tailhook) can we get rid of collect? + return self.parse(env::args().collect(), + &mut stdout(), &mut stderr()); + } + + /// The simplest conveninece method + /// + /// The method returns only in case of successful parsing or exits with + /// appropriate code (including successful on `--help`) otherwise. + pub fn parse_args_or_exit(&self) { + // TODO(tailhook) can we get rid of collect? + self.parse(env::args().collect(), &mut stdout(), &mut stderr()) + .map_err(|c| exit(c)) + .ok(); + } +} + +struct HelpFormatter<'a, 'b: 'a> { + name: &'a str, + parser: &'a ArgumentParser<'b>, + buf: &'a mut (Write + 'a), +} + +impl<'a, 'b> HelpFormatter<'a, 'b> { + pub fn print_usage(parser: &ArgumentParser, name: &str, writer: &mut Write) + -> IoResult<()> + { + return HelpFormatter { parser: parser, name: name, buf: writer } + .write_usage(); + } + + pub fn print_help(parser: &ArgumentParser, name: &str, writer: &mut Write) + -> IoResult<()> + { + return HelpFormatter { parser: parser, name: name, buf: writer } + .write_help(); + } + + pub fn print_argument(&mut self, arg: &GenericArgument<'b>) + -> IoResult<()> + { + let mut num = 2; + try!(write!(self.buf, " {}", arg.name)); + num += arg.name.len(); + if num >= OPTION_WIDTH { + try!(write!(self.buf, "\n")); + for _ in 0..OPTION_WIDTH { + try!(write!(self.buf, " ")); + } + } else { + for _ in num..OPTION_WIDTH { + try!(write!(self.buf, " ")); + } + } + try!(wrap_text(self.buf, arg.help, TOTAL_WIDTH, OPTION_WIDTH)); + try!(write!(self.buf, "\n")); + return Ok(()); + } + + pub fn print_option(&mut self, opt: &GenericOption<'b>) -> IoResult<()> { + let mut num = 2; + try!(write!(self.buf, " ")); + let mut niter = opt.names.iter(); + let name = niter.next().unwrap(); + try!(write!(self.buf, "{}", name)); + num += name.len(); + for name in niter { + try!(write!(self.buf, ",")); + try!(write!(self.buf, "{}", name)); + num += name.len() + 1; + } + match opt.action { + Flag(_) => {} + Single(_) | Push(_) | Many(_) => { + try!(write!(self.buf, " ")); + let var = &self.parser.vars[opt.varid.unwrap()]; + try!(write!(self.buf, "{}", &var.metavar[..])); + num += var.metavar.len() + 1; + } + } + if num >= OPTION_WIDTH { + try!(write!(self.buf, "\n")); + for _ in 0..OPTION_WIDTH { + try!(write!(self.buf, " ")); + } + } else { + for _ in num..OPTION_WIDTH { + try!(write!(self.buf, " ")); + } + } + try!(wrap_text(self.buf, opt.help, TOTAL_WIDTH, OPTION_WIDTH)); + try!(write!(self.buf, "\n")); + return Ok(()); + } + + fn write_help(&mut self) -> IoResult<()> { + try!(self.write_usage()); + try!(write!(self.buf, "\n")); + if self.parser.description.len() > 0 { + try!(wrap_text(self.buf, self.parser.description,TOTAL_WIDTH, 0)); + try!(write!(self.buf, "\n")); + } + if self.parser.arguments.len() > 0 + || self.parser.catchall_argument.is_some() + { + try!(write!(self.buf, "\nPositional arguments:\n")); + for arg in self.parser.arguments.iter() { + try!(self.print_argument(&**arg)); + } + match self.parser.catchall_argument { + Some(ref opt) => { + try!(self.print_argument(&**opt)); + } + None => {} + } + } + if self.parser.short_options.len() > 0 + || self.parser.long_options.len() > 0 + { + try!(write!(self.buf, "\nOptional arguments:\n")); + for opt in self.parser.options.iter() { + try!(self.print_option(&**opt)); + } + } + return Ok(()); + } + + fn write_usage(&mut self) -> IoResult<()> { + try!(write!(self.buf, "Usage:\n ")); + try!(write!(self.buf, "{}", self.name)); + if self.parser.options.len() != 0 { + if self.parser.short_options.len() > 1 + || self.parser.long_options.len() > 1 + { + try!(write!(self.buf, " [OPTIONS]")); + } + for opt in self.parser.arguments.iter() { + let var = &self.parser.vars[opt.varid]; + try!(write!(self.buf, " ")); + if !var.required { + try!(write!(self.buf, "[")); + } + try!(write!(self.buf, "{}", + &opt.name.to_ascii_uppercase()[..])); + if !var.required { + try!(write!(self.buf, "]")); + } + } + match self.parser.catchall_argument { + Some(ref opt) => { + let var = &self.parser.vars[opt.varid]; + try!(write!(self.buf, " ")); + if !var.required { + try!(write!(self.buf, "[")); + } + try!(write!(self.buf, "{}", + &opt.name.to_ascii_uppercase()[..])); + if !var.required { + try!(write!(self.buf, " ...]")); + } else { + try!(write!(self.buf, " [...]")); + } + } + None => {} + } + } + try!(write!(self.buf, "\n")); + return Ok(()); + } + +} diff --git a/argparse/src/print.rs b/argparse/src/print.rs new file mode 100644 index 0000000..d0335c5 --- /dev/null +++ b/argparse/src/print.rs @@ -0,0 +1,13 @@ +use Print; +use action::{IFlagAction, ParseResult}; + +impl IFlagAction for Print { + fn parse_flag(&self) -> ParseResult { + if self.0.ends_with("\n") { + print!("{}", self.0); + } else { + println!("{}", self.0); + } + return ParseResult::Exit; + } +} diff --git a/argparse/src/test_bool.rs b/argparse/src/test_bool.rs new file mode 100644 index 0000000..a30cf3c --- /dev/null +++ b/argparse/src/test_bool.rs @@ -0,0 +1,98 @@ +use parser::ArgumentParser; +use super::Store; +use super::{StoreTrue, StoreFalse}; +use test_parser::{check_ok}; + +fn store_true(args: &[&str]) -> bool { + let mut verbose = false; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut verbose) + .add_option(&["-t", "--true"], StoreTrue, + "Store true action"); + check_ok(&ap, args); + } + return verbose; +} + +#[test] +fn test_store_true() { + assert!(!store_true(&["./argparse_test"])); + assert!(store_true(&["./argparse_test", "--true"])); +} + +fn store_false(args: &[&str]) -> bool { + let mut verbose = true; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut verbose) + .add_option(&["-f", "--false"], StoreFalse, + "Store false action"); + check_ok(&ap, args); + } + return verbose; +} +#[test] +fn test_store_false() { + assert!(store_false(&["./argparse_test"])); + assert!(!store_false(&["./argparse_test", "--false"])); +} + +fn store_bool(args: &[&str]) -> bool { + let mut verbose = false; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut verbose) + .add_option(&["-f", "--false"], StoreFalse, + "Store false action") + .add_option(&["-t", "--true"], StoreTrue, + "Store false action"); + check_ok(&ap, args); + } + return verbose; +} + +#[test] +fn test_bool() { + assert!(!store_bool(&["./argparse_test"])); + assert!(store_bool(&["./argparse_test", "-t"])); + assert!(!store_bool(&["./argparse_test", "-f"])); + assert!(store_bool(&["./argparse_test", "-fft"])); + assert!(!store_bool(&["./argparse_test", "-fffft", "-f"])); + assert!(store_bool(&["./argparse_test", "--false", "-fffft", "-f", + "--true"])); +} + +fn set_bool(args: &[&str]) -> bool { + let mut verbose = false; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut verbose) + .add_option(&["-s", "--set"], Store, + "Set boolean value"); + check_ok(&ap, args); + } + return verbose; +} + + +#[test] +fn test_set_bool() { + assert!(!set_bool(&["./argparse_test"])); + assert!(set_bool(&["./argparse_test", "-strue"])); + assert!(!set_bool(&["./argparse_test", "-sfalse"])); + + // Unfortunately other values do not work +} + +#[test] +#[should_panic(expected="Bad value yes")] +fn test_bad_bools1() { + assert!(!set_bool(&["./argparse_test", "-syes"])); +} + +#[test] +#[should_panic(expected="Bad value no")] +fn test_bad_bools2() { + assert!(!set_bool(&["./argparse_test", "-sno"])); +} diff --git a/argparse/src/test_const.rs b/argparse/src/test_const.rs new file mode 100644 index 0000000..b12e3d8 --- /dev/null +++ b/argparse/src/test_const.rs @@ -0,0 +1,28 @@ +use parser::ArgumentParser; +use super::{PushConst}; +use test_parser::{check_ok}; + + +fn push_const(args: &[&str]) -> Vec { + let mut res = vec!(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut res) + .add_option(&["-o", "--one"], PushConst(1), + "Add one to the list") + .add_option(&["-t", "--two"], PushConst(2), + "Add two to the list") + .add_option(&["-3", "--three"], PushConst(3), + "Add three to the list"); + check_ok(&ap, args); + } + return res; +} + +#[test] +fn test_push() { + assert_eq!(push_const(&["./argparse_test"]), vec!()); + assert_eq!(push_const(&["./argparse_test", "--one"]), vec!(1)); + assert_eq!(push_const(&["./argparse_test", "-3"]), vec!(3)); + assert_eq!(push_const(&["./argparse_test", "-oo3tt"]), vec!(1, 1, 3, 2, 2)); +} diff --git a/argparse/src/test_enum.rs b/argparse/src/test_enum.rs new file mode 100644 index 0000000..5205738 --- /dev/null +++ b/argparse/src/test_enum.rs @@ -0,0 +1,51 @@ +use std::str::FromStr; + +use parser::ArgumentParser; +use super::Store; +use test_parser::{check_ok}; + +use self::Greeting::{Hello, Hi, NoGreeting}; + + +#[derive(PartialEq, Eq, Debug)] +enum Greeting { + Hello, + Hi, + NoGreeting, +} + +impl FromStr for Greeting { + type Err = (); + fn from_str(src: &str) -> Result { + return match src { + "hello" => Ok(Hello), + "hi" => Ok(Hi), + _ => Err(()), + }; + } +} + +fn parse_enum(args: &[&str]) -> Greeting { + let mut val = NoGreeting; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-g"], Store, + "Greeting"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_parse_enum() { + assert_eq!(parse_enum(&["./argparse_test"]), NoGreeting); + assert_eq!(parse_enum(&["./argparse_test", "-ghello"]), Hello); + assert_eq!(parse_enum(&["./argparse_test", "-ghi"]), Hi); +} + +#[test] +#[should_panic] +fn test_parse_error() { + parse_enum(&["./argparse_test", "-ghell"]); +} diff --git a/argparse/src/test_env.rs b/argparse/src/test_env.rs new file mode 100644 index 0000000..2e1b649 --- /dev/null +++ b/argparse/src/test_env.rs @@ -0,0 +1,43 @@ +use std::env; + +use parser::ArgumentParser; +use super::Store; +use test_parser::{check_ok}; + + +fn required(args: &[&str]) -> (isize, isize) { + let mut val1 = 1isize; + let mut val2 = 2isize; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val1) + .add_option(&["--v1"], Store, "The value 1") + .add_argument("v1", Store, "The value 1") + .envvar("TEST_ENV_REQUIRED_V1") + .required(); + ap.refer(&mut val2) + .add_argument("v2", Store, "The value 2"); + check_ok(&ap, args); + } + return (val1, val2) +} + +#[test] +#[should_panic] +fn test_required() { + env::set_var("TEST_ENV_REQUIRED_V1", "some_crap"); + required(&["./argparse_test"]); + env::remove_var("TEST_ENV_REQUIRED_V1"); +} + +#[test] +fn test_req() { + env::set_var("TEST_ENV_REQUIRED_V1", "some_crap"); + assert_eq!(required(&["./argparse_test", "10"]), (10, 2)); + assert_eq!(required(&["./argparse_test", "11", "21"]), (11, 21)); + assert_eq!(required(&["./argparse_test", "--v1=7"]), (7, 2)); + env::set_var("TEST_ENV_REQUIRED_V1", "9"); + assert_eq!(required(&["./argparse_test", "10"]), (9, 10)); + assert_eq!(required(&["./argparse_test", "7", "--v1=15"]), (15, 7)); + env::remove_var("TEST_ENV_REQUIRED_V1"); +} diff --git a/argparse/src/test_float.rs b/argparse/src/test_float.rs new file mode 100644 index 0000000..9ef0f56 --- /dev/null +++ b/argparse/src/test_float.rs @@ -0,0 +1,50 @@ +use parser::ArgumentParser; +use super::{IncrBy,DecrBy}; +use super::Store; +use test_parser::{check_ok}; + +fn incr_decr(args: &[&str]) -> f32 { + let mut val = 0f32; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-d", "--decr"], DecrBy(0.25f32), + "Decrement value") + .add_option(&["-i", "--incr"], IncrBy(0.5f32), + "Increment value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_incr_decr() { + assert_eq!(incr_decr(&["./argparse_test", + "--incr", "-iii"]), 2.0); + assert_eq!(incr_decr(&["./argparse_test", + "-iiddd", "--incr", "-iii"]), 2.25); +} + +#[test] +fn test_float() { + let mut val = 0.1; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-s", "--set"], Store, + "Set float value"); + check_ok(&ap, &["./argparse_test", "-s", "15.125"]); + } + assert_eq!(val, 15.125); +} + +#[test] +#[should_panic] +fn test_fail() { + let mut val = 0.1; + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-s", "--set"], Store, + "Set float value"); + check_ok(&ap, &["./argparse_test", "-s", "test"]); +} diff --git a/argparse/src/test_help.rs b/argparse/src/test_help.rs new file mode 100644 index 0000000..f23b14b --- /dev/null +++ b/argparse/src/test_help.rs @@ -0,0 +1,154 @@ +use std::str::from_utf8; + +use parser::ArgumentParser; +use super::{Store, List}; + +#[test] +fn test_empty() { + let mut ap = ArgumentParser::new(); + let mut buf = Vec::::new(); + ap.set_description("Test program"); + assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n".to_string() + + " ./argparse_test\n" + + "\n" + + "Test program\n" + + "\n" + + "Optional arguments:\n" + + " -h,--help Show this help message and exit\n" + , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_options() { + let mut val = 0; + let mut val2 = 0; + let mut ap = ArgumentParser::new(); + ap.set_description("Test program. The description of the program is ought + to be very long, because we want to test how word wrapping works for + it. So some more text would be ok for the test"); + ap.refer(&mut val) + .add_option(&["--value"], Store, + "Set integer value"); + ap.refer(&mut val2) + .add_option(&["-L", "--long-option"], Store, + "Long option value"); + let mut buf = Vec::::new(); + assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n".to_string() + + " ./argparse_test [OPTIONS] + +Test program. The description of the program is ought to be very long, because +we want to test how word wrapping works for it. So some more text would be ok +for the test\n" + + "\n" + + "Optional arguments:\n" + + " -h,--help Show this help message and exit\n" + + " --value VALUE Set integer value\n" + + " -L,--long-option LONG_OPTION\n" + + " Long option value\n" + , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_argument() { + let mut val = 0; + let mut ap = ArgumentParser::new(); + ap.set_description("Test program"); + ap.refer(&mut val) + .add_argument("value", Store, + "Integer value"); + let mut buf = Vec::::new(); + assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n".to_string() + + " ./argparse_test [VALUE]\n" + + "\n" + + "Test program\n" + + "\n" + + "Positional arguments:\n" + + " value Integer value\n" + + "\n" + + "Optional arguments:\n" + + " -h,--help Show this help message and exit\n" + , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_arguments() { + let mut v1 = 0; + let mut v2 = Vec::::new(); + let mut ap = ArgumentParser::new(); + ap.set_description("Test program"); + ap.refer(&mut v1) + .add_argument("v1", Store, + "Integer value 1"); + ap.refer(&mut v2) + .add_argument("v2", List, + "More values"); + let mut buf = Vec::::new(); + assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n".to_string() + + " ./argparse_test [V1] [V2 ...]\n" + + "\n" + + "Test program\n" + + "\n" + + "Positional arguments:\n" + + " v1 Integer value 1\n" + + " v2 More values\n" + + "\n" + + "Optional arguments:\n" + + " -h,--help Show this help message and exit\n" + , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_req_arguments() { + let mut v1 = 0; + let mut v2 = Vec::::new(); + let mut ap = ArgumentParser::new(); + ap.set_description("Test program"); + ap.refer(&mut v1) + .add_argument("v1", Store, + "Integer value 1") + .required(); + ap.refer(&mut v2) + .add_argument("v2", List, + "More values") + .required(); + let mut buf = Vec::::new(); + assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n".to_string() + + " ./argparse_test V1 V2 [...]\n" + + "\n" + + "Test program\n" + + "\n" + + "Positional arguments:\n" + + " v1 Integer value 1\n" + + " v2 More values\n" + + "\n" + + "Optional arguments:\n" + + " -h,--help Show this help message and exit\n" + , from_utf8(&buf[..]).unwrap().to_string()); +} + +#[test] +fn test_metavar() { + let mut val2 = 0; + let mut ap = ArgumentParser::new(); + ap.set_description("Test program."); + ap.refer(&mut val2) + .add_option(&["-L", "--long-option"], Store, + "Long option value") + .metavar("VAL"); + let mut buf = Vec::::new(); + assert!(ap.print_help("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n".to_string() + + " ./argparse_test [OPTIONS]\n" + + "\n" + + "Test program.\n" + + "\n" + + "Optional arguments:\n" + + " -h,--help Show this help message and exit\n" + + " -L,--long-option VAL Long option value\n" + , from_utf8(&buf[..]).unwrap().to_string()); +} diff --git a/argparse/src/test_int.rs b/argparse/src/test_int.rs new file mode 100644 index 0000000..1826eaf --- /dev/null +++ b/argparse/src/test_int.rs @@ -0,0 +1,107 @@ +use parser::ArgumentParser; +use super::{IncrBy,DecrBy}; +use super::Store; +use test_parser::{check_ok}; + +fn incr_int(args: &[&str]) -> usize { + let mut val = 0; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-i", "--incr"], IncrBy(1usize), + "Increment value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_incr_int() { + assert_eq!(incr_int(&["./argparse_test"]), 0); + assert_eq!(incr_int(&["./argparse_test", "--incr"]), 1); + assert_eq!(incr_int(&["./argparse_test", "-iiiii"]), 5); +} + +fn decr_int(args: &[&str]) -> isize { + let mut val = 5; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-d", "--decr"], DecrBy(1isize), + "Decrement value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_decr_int() { + assert_eq!(decr_int(&["./argparse_test"]), 5); + assert_eq!(decr_int(&["./argparse_test", "--decr"]), 4); + assert_eq!(decr_int(&["./argparse_test", "-dddddd"]), -1); +} + +#[test] +fn test_incr_decr() { + let mut val = 0; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-d", "--decr"], DecrBy(1isize), + "Decrement value") + .add_option(&["-i", "--incr"], IncrBy(1isize), + "Increment value"); + check_ok(&ap, &["./argparse_test", + "-iiddd", "--incr", "-iii"]); + } + assert_eq!(val, 3); +} + +fn set_int(args: &[&str]) -> isize { + let mut val = 0; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-s", "--set"], Store, + "Set integer value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_set_int() { + assert_eq!(set_int(&["./argparse_test", "-s", "10"]), 10); + assert_eq!(set_int(&["./argparse_test", "--set", "99"]), 99); + assert_eq!(set_int(&["./argparse_test", "-s", "7", "-s77"]), 77); + assert_eq!(set_int(&["./argparse_test", "-s333", "--set=123"]), 123); +} + +#[test] +#[should_panic(expected="Bad value 1.5")] +fn test_set_int_bad() { + set_int(&["./argparse_test", "-s1.5"]); +} + +fn set_i16(args: &[&str]) -> i16 { + let mut val = 0; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-s", "--set"], Store, + "Set integer value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_i16() { + assert_eq!(set_i16(&["./argparse_test", "-s", "124"]), 124); +} + +#[test] +#[should_panic(expected="Bad value 1000000")] +fn test_i16_big() { + set_i16(&["./argparse_test", "-s", "1000000"]); +} diff --git a/argparse/src/test_many.rs b/argparse/src/test_many.rs new file mode 100644 index 0000000..68e2cec --- /dev/null +++ b/argparse/src/test_many.rs @@ -0,0 +1,95 @@ +use parser::ArgumentParser; +use super::{List, Store, Collect}; +use test_parser::{check_ok}; + +fn pos_list(args: &[&str]) -> (isize, Vec) { + let mut val1 = 1; + let mut val2 = Vec::new(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val1).add_argument("v1", Store, "The value 1"); + ap.refer(&mut val2).add_argument("v2", List, "The list of vals"); + check_ok(&ap, args); + } + return (val1, val2); +} + +#[test] +fn test_pos_list() { + assert_eq!(pos_list(&["./argparse_test", "10"]), (10, vec!())); + assert_eq!(pos_list(&["./argparse_test", "11", "21"]), (11, vec!(21))); + assert_eq!(pos_list(&["./argparse_test", "10", "20", "30"]), + (10, vec!(20, 30))); +} + +fn pos_collect(args: &[&str]) -> Vec { + let mut lst = Vec::new(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut lst) + .add_argument("v", Collect, "The list of vals"); + check_ok(&ap, args); + } + return lst; +} + +#[test] +fn test_pos_collect() { + assert_eq!(pos_collect(&["./argparse_test", "10"]), vec!(10)); + assert_eq!(pos_collect(&["./argparse_test", "11", "21"]), vec!(11, 21)); + assert_eq!(pos_collect(&["./argparse_test", "10", "20", "30"]), + vec!(10, 20, 30)); +} + +#[test] +#[should_panic] +fn wrong_type() { + pos_collect(&["./argparse_test", "10", "20", "test"]); +} + +fn collect(args: &[&str]) -> Vec { + let mut lst = Vec::new(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut lst).add_option(&["-a", "--add"], Collect, + "The list of vals"); + check_ok(&ap, args); + } + return lst; +} + +#[test] +fn test_collect() { + assert_eq!(collect(&["./argparse_test", "-a10"]), vec!(10)); + assert_eq!(collect(&["./argparse_test", "--add=11", "-a", "21"]), + vec!(11, 21)); + assert_eq!(collect(&["./argparse_test", + "-a", "10", "--add=20", "--add", "30"]), vec!(10, 20, 30)); +} + +#[test] +#[should_panic] +fn test_extra() { + collect(&["./argparse_test", "-a", "10", "20", "30"]); +} + +fn list(args: &[&str]) -> Vec { + let mut vec = Vec::new(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut vec).add_option(&["-a", "--add"], List, + "The list of vals"); + check_ok(&ap, args); + } + return vec; +} + +#[test] +#[should_panic] +fn test_list() { + assert_eq!(list(&["./argparse_test", "-a10"]), vec!(10)); + assert_eq!(list(&["./argparse_test", "--add", "11", "21"]), vec!(11, 21)); + assert_eq!(list(&["./argparse_test", "-a", "10", "20", "30"]), + vec!(10, 20, 30)); + assert_eq!(list(&["./argparse_test", "10", "20", "30"]), vec!(10, 20, 30)); +} diff --git a/argparse/src/test_optional.rs b/argparse/src/test_optional.rs new file mode 100644 index 0000000..5f4c35e --- /dev/null +++ b/argparse/src/test_optional.rs @@ -0,0 +1,54 @@ +use parser::ArgumentParser; +use super::StoreOption; +use test_parser::{check_ok}; + +fn opt(args: &[&str]) -> Option { + let mut val = None; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-s", "--set"], StoreOption, + "Set int value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_opt() { + assert_eq!(opt(&["./argparse_test"]), None); + assert_eq!(opt(&["./argparse_test", "-s", "10"]), Some(10)); + assert_eq!(opt(&["./argparse_test", "--set", "11"]), Some(11)); +} + +#[test] +#[should_panic] +fn test_opt_no_arg() { + opt(&["./argparse_test", "--set"]); +} + +fn optstr(args: &[&str]) -> Option { + let mut val = None; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-s", "--set"], StoreOption, + "Set string value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_str() { + assert_eq!(optstr(&["./argparse_test"]), None); + assert_eq!(optstr(&["./argparse_test", "-s", "10"]), Some(10.to_string())); + assert_eq!(optstr(&["./argparse_test", "--set", "11"]), + Some(11.to_string())); +} + +#[test] +#[should_panic] +fn test_str_no_art() { + optstr(&["./argparse_test", "--set"]); +} diff --git a/argparse/src/test_parser.rs b/argparse/src/test_parser.rs new file mode 100644 index 0000000..f60aa64 --- /dev/null +++ b/argparse/src/test_parser.rs @@ -0,0 +1,64 @@ + +use parser::ArgumentParser; + +pub fn check_ok(ap: &ArgumentParser, args: &[&str]) { + let mut stdout = Vec::::new(); + let mut stderr = Vec::::new(); + let mut owned_args = Vec::new(); + for x in args.iter() { + owned_args.push(x.to_string()); + } + let res = ap.parse(owned_args, &mut stdout, &mut stderr); + match res { + Ok(()) => return, + Err(x) => panic!( + String::from_utf8(stderr).unwrap() + + &format!("Expected ok, but found Exit({})", x)[..]), + } +} + +pub fn check_exit(ap: &ArgumentParser, args: &[&str]) { + let mut stdout = Vec::::new(); + let mut stderr = Vec::::new(); + let mut owned_args = Vec::new(); + for x in args.iter() { + owned_args.push(x.to_string()); + } + let res = ap.parse(owned_args, &mut stdout, &mut stderr); + match res { + Err(0) => return, + Err(x) => panic!(format!("Expected code {} got {}", 0usize, x)), + Ok(()) => panic!(format!("Expected failure, got success")), + } +} + +pub fn check_err(ap: &ArgumentParser, args: &[&str]) { + let mut stdout = Vec::::new(); + let mut stderr = Vec::::new(); + let mut owned_args = Vec::new(); + for x in args.iter() { + owned_args.push(x.to_string()); + } + let res = ap.parse(owned_args, &mut stdout, &mut stderr); + match res { + Err(2) => return, + Err(x) => panic!(format!("Expected code {} got {}", 2usize, x)), + Ok(()) => panic!(format!("Expected failure, got success")), + } +} + +#[test] +fn test_no_arg() { + let ap = ArgumentParser::new(); + check_ok(&ap, &["./argparse_test"]); + check_err(&ap, &["./argparse_test", "a"]); + check_err(&ap, &["./argparse_test", "-a"]); + check_err(&ap, &["./argparse_test", "--an-option"]); +} + +#[test] +fn test_help() { + let ap = ArgumentParser::new(); + check_ok(&ap, &["./argparse_test"]); + check_exit(&ap, &["./argparse_test", "--help"]); +} diff --git a/argparse/src/test_path.rs b/argparse/src/test_path.rs new file mode 100644 index 0000000..868ae8c --- /dev/null +++ b/argparse/src/test_path.rs @@ -0,0 +1,30 @@ +use std::path::PathBuf; +use parser::ArgumentParser; +use super::Parse; +use test_parser::{check_ok}; + +fn parse_str(args: &[&str]) -> PathBuf { + let mut val: PathBuf = From::from(""); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-s", "--set"], Parse, + "Set path value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_path() { + assert_eq!(parse_str(&["./argparse_test", "-s", "/hello"]), + PathBuf::from("/hello")); + assert_eq!(parse_str(&["./argparse_test", "--set", "a///b/../c"]), + PathBuf::from("a/b/../c")); +} + +#[test] +#[should_panic] +fn test_err() { + parse_str(&["./argparse_test", "--set"]); +} diff --git a/argparse/src/test_pos.rs b/argparse/src/test_pos.rs new file mode 100644 index 0000000..2542050 --- /dev/null +++ b/argparse/src/test_pos.rs @@ -0,0 +1,196 @@ +use parser::ArgumentParser; +use super::{Store, List}; +use test_parser::{check_ok}; + +fn parse_pos(args: &[&str]) -> isize { + let mut val = 0; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_argument("value", Store, "The value"); + check_ok(&ap, args); + } + return val; +} + + +#[test] +fn test_argument() { + assert_eq!(parse_pos(&["./argparse_test", "10"]), 10); +} + +#[test] +#[should_panic] +fn too_much_args() { + parse_pos(&["./argparse_test", "10", "20"]); +} + +#[test] +#[should_panic] +fn wrong_value() { + parse_pos(&["./argparse_test", "test", "20"]); +} + +#[test] +#[should_panic] +fn float_value() { + parse_pos(&["./argparse_test", "1.5"]); +} + +fn parse_two(args: &[&str]) -> (isize, isize) { + let mut val1 = 1; + let mut val2 = 2; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val1) + .add_argument("v1", Store, "The value 1"); + ap.refer(&mut val2) + .add_argument("v2", Store, "The value 2"); + check_ok(&ap, args); + } + return (val1, val2); +} + +#[test] +fn test_two() { + assert_eq!(parse_two(&["./argparse_test", "10"]), (10, 2)); + assert_eq!(parse_two(&["./argparse_test", "11", "21"]), (11, 21)); +} + +#[test] +#[should_panic] +fn test_two_fail_many() { + parse_two(&["./argparse_test", "10", "20", "30"]); +} + +#[test] +#[should_panic] +fn test_two_fail_value() { + parse_two(&["./argparse_test", "test", "20"]); +} + +#[test] +#[should_panic] +fn test_two_fail_float() { + parse_two(&["./argparse_test", "1.5"]); +} + +fn parse_pos_opt(args: &[&str]) -> (isize, isize) { + let mut val1 = 1; + let mut val2 = 2; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val1) + .add_option(&["--v1"], Store, "The value 1") + .add_argument("v1", Store, "The value 1"); + ap.refer(&mut val2) + .add_argument("v2", Store, "The value 2"); + check_ok(&ap, args); + } + return (val1, val2); +} + +#[test] +fn test_positional_optional() { + assert_eq!(parse_pos_opt(&["./argparse_test", "10"]), (10, 2)); + assert_eq!(parse_pos_opt(&["./argparse_test", "11", "21"]), (11, 21)); + assert_eq!(parse_pos_opt(&["./argparse_test", "--v1=7", "8"]), (7, 8)); + assert_eq!(parse_pos_opt(&["./argparse_test", "10", "--v1=9"]), (9, 10)); +} + +#[test] +#[should_panic] +fn test_pos_opt_err() { + parse_pos_opt(&["./argparse_test", "--v1=10", "20", "30"]); +} + +fn parse_pos_req(args: &[&str]) -> (isize, isize) { + let mut val1 = 1; + let mut val2 = 2; + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val1) + .add_option(&["--v1"], Store, "The value 1") + .add_argument("v1", Store, "The value 1") + .required(); + ap.refer(&mut val2) + .add_argument("v2", Store, "The value 2"); + check_ok(&ap, args); + } + return (val1, val2); +} + +#[test] +fn test_positional_required() { + assert_eq!(parse_pos_req(&["./argparse_test", "10"]), (10, 2)); + assert_eq!(parse_pos_req(&["./argparse_test", "11", "21"]), (11, 21)); + assert_eq!(parse_pos_req(&["./argparse_test", "--v1=7"]), (7, 2)); + assert_eq!(parse_pos_req(&["./argparse_test", "10", "--v1=9"]), (9, 10)); +} + +#[test] +#[should_panic] +fn test_pos_extra() { + parse_pos_req(&["./argparse_test", "--v1=10", "20", "30"]); +} + +#[test] +#[should_panic] +fn test_pos_no_req() { + parse_pos_req(&["./argparse_test"]); +} + +fn pos_stop(args: &[&str]) -> (isize, Vec) { + let mut val1 = 1; + let mut val2 = Vec::new(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val1) + .add_option(&["--v1"], Store, "The value 1") + .add_argument("v1", Store, "The value 1") + .required(); + ap.refer(&mut val2) + .add_argument("v2", List, "The value 2"); + ap.stop_on_first_argument(true); + check_ok(&ap, args); + } + return (val1, val2); +} + +#[test] +fn test_pos_stop() { + assert_eq!(pos_stop(&["./argparse_test", "10"]), (10, vec!())); + assert_eq!(pos_stop(&["./argparse_test", "11", "21"]), + (11, vec!("21".to_string()))); + assert_eq!(pos_stop(&["./argparse_test", "--v1=7"]), (7, vec!())); + assert_eq!(pos_stop(&["./argparse_test", "10", "--v1=9", "--whatever"]), + (10, vec!("--v1=9".to_string(), "--whatever".to_string()))); +} + +#[test] +#[should_panic] +fn test_test() { + pos_stop(&["./argparse_test"]); +} + +fn pos_dash(args: &[&str], dash: bool) -> Vec { + let mut val = Vec::new(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_argument("v1", List, "The value"); + ap.silence_double_dash(dash); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_pos_dash() { + assert_eq!(pos_dash(&["./argparse_test", "1"], true), + vec!("1".to_string())); + assert_eq!(pos_dash(&["./argparse_test", "--", "1"], true), + vec!("1".to_string())); + assert_eq!(pos_dash(&["./argparse_test", "--", "1"], false), + vec!("--".to_string(), "1".to_string())); +} diff --git a/argparse/src/test_str.rs b/argparse/src/test_str.rs new file mode 100644 index 0000000..42db286 --- /dev/null +++ b/argparse/src/test_str.rs @@ -0,0 +1,28 @@ +use parser::ArgumentParser; +use super::Store; +use test_parser::{check_ok}; + +fn parse_str(args: &[&str]) -> String { + let mut val: String = "".to_string(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["-s", "--set"], Store, + "Set string value"); + check_ok(&ap, args); + } + return val; +} + +#[test] +fn test_str() { + assert_eq!(parse_str(&["./argparse_test", "-s", "10"]), "10".to_string()); + assert_eq!(parse_str(&["./argparse_test", "--set", "value"]), + "value".to_string()); +} + +#[test] +#[should_panic] +fn test_err() { + parse_str(&["./argparse_test", "--set"]); +} diff --git a/argparse/src/test_usage.rs b/argparse/src/test_usage.rs new file mode 100644 index 0000000..efe89fe --- /dev/null +++ b/argparse/src/test_usage.rs @@ -0,0 +1,57 @@ +use std::str::from_utf8; + +use parser::ArgumentParser; +use super::{Store, List}; + +#[test] +fn test_empty() { + let ap = ArgumentParser::new(); + let mut buf = Vec::::new(); + assert!(ap.print_usage("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n ./argparse_test\n", from_utf8(&buf[..]).unwrap()); +} + +#[test] +fn test_options() { + let mut val = 0; + let mut buf = Vec::::new(); + { + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_option(&["--value"], Store, + "Set integer value"); + assert!(ap.print_usage("./argparse_test", &mut buf).is_ok()); + } + assert_eq!("Usage:\n ./argparse_test [OPTIONS]\n", + from_utf8(&buf[..]).unwrap()); +} + +#[test] +fn test_argument() { + let mut val = 0; + let mut ap = ArgumentParser::new(); + ap.refer(&mut val) + .add_argument("value", Store, + "Integer value"); + let mut buf = Vec::::new(); + assert!(ap.print_usage("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n ./argparse_test [VALUE]\n", + from_utf8(&buf[..]).unwrap()); +} + +#[test] +fn test_arguments() { + let mut v1 = 0; + let mut v2 = Vec::::new(); + let mut ap = ArgumentParser::new(); + ap.refer(&mut v1) + .add_argument("v1", Store, + "Integer value 1"); + ap.refer(&mut v2) + .add_argument("v2", List, + "More values"); + let mut buf = Vec::::new(); + assert!(ap.print_usage("./argparse_test", &mut buf).is_ok()); + assert_eq!("Usage:\n ./argparse_test [V1] [V2 ...]\n", + from_utf8(&buf[..]).unwrap()); +} -- cgit v1.2.1