From 632850c58ccc8a37ab17469968c3a8d741a676bb Mon Sep 17 00:00:00 2001 From: Zach Dziura Date: Tue, 16 Jun 2015 14:28:14 -0400 Subject: [PATCH] Finally (FINALLY!) finished the Token integration --- src/lib.rs | 4 +- src/matches.rs | 142 ++++++++++++++++++++++++++++--------------------- src/token.rs | 136 ++++++++++++++++++++++------------------------ src/usage.rs | 2 +- src/vars.rs | 110 ++++++++++++++++++++++---------------- tests/main.rs | 36 ++++++------- 6 files changed, 230 insertions(+), 200 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1f9f054..fe38af6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,6 @@ mod token; mod usage; mod vars; -pub use matches::Matches; -pub use vars::Vars; +pub use matches::{Matches, matches}; +pub use vars::{Vars, vars}; pub use usage::usage; diff --git a/src/matches.rs b/src/matches.rs index ab74f79..ee0952b 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -17,84 +17,102 @@ use std::collections::HashMap; use std::collections::hash_map::Keys; -use std::env; use errors::{Error, ErrorKind}; use vars::Vars; pub struct Matches { - matches: HashMap, - program_name: String + matches: HashMap +} + +pub fn matches(vars: &mut Vars, env_args: &[String]) -> Result { + let mut matches: HashMap = HashMap::new(); + let mut args = env_args.iter(); + + args.next(); // Remove the program name + + let mut next_arg = args.next(); + while next_arg.is_some() { + let mut current_arg = next_arg.unwrap(); + let mut arg_vec: Vec = Vec::new(); + + // Determine if current opt is in short, long, or arg form + if ¤t_arg[..1] == "-" { + if ¤t_arg[..2] == "--" { // Long form opt + arg_vec.push(String::from(¤t_arg[2..])); + } else { // Short form opt + // Assuming it's a group of short-form vars; e.g. tar -xzf + for c in current_arg[1..].chars() { + let mut s = String::new(); + s.push(c); + arg_vec.push(s); + } + } + + for arg in arg_vec.iter() { + if vars.contains_opt(arg) { + let token = vars.get_opt(arg).unwrap(); + + if token.has_arg { + // NOTE: The corresponding arg MUST be immediately following + current_arg = match args.next() { + None => return Err(Error::new(ErrorKind::MissingArgument, arg.clone())), + Some(a) => a + }; + + matches.insert(token.name(), current_arg.clone()); + } else { + matches.insert(token.name(), String::new()); + } + } else { + return Err(Error::new(ErrorKind::InvalidArgument, arg.clone())); + } + } + } else { // Probably a required arg + let arg = vars.get_arg().unwrap(); + matches.insert(arg.name(), current_arg.clone()); + } + + next_arg = args.next(); + } + + match vars.arg_len() { + 0 => Ok(Matches { matches: matches }), + _ => Err(Error::new(ErrorKind::MissingArgument, vars.get_arg().unwrap().name())), + } } impl Matches { - pub fn new(opts: &mut Vars) -> Result { - let mut args = env::args(); - let mut matches: HashMap = HashMap::new(); - - args.next(); // Remove the program name - - let mut next_arg = args.next(); - while next_arg.is_some() { - let mut current_arg = next_arg.unwrap(); - let mut arg_vec: Vec = Vec::new(); - - // Determine if current opt is in short, long, or arg form - if ¤t_arg[..1] == "-" { - if ¤t_arg[..2] == "--" { // Long form opt - arg_vec.push(String::from(¤t_arg[2..])); - } else { // Short form opt - // Assuming it's a group of short-form opts; e.g. tar -xzf - for c in current_arg[1..].chars() { - let mut s = String::new(); - s.push(c); - arg_vec.push(s); - } - } - - for arg in arg_vec.iter() { - if opts.contains_opt(&arg) { - let has_arg: bool = *opts.get_opt(&arg).unwrap(); - - if has_arg { - // NOTE: The corresponding arg MUST be immediately following - current_arg = match args.next() { - None => return Err(Error::new(ErrorKind::MissingArgument, (*arg).clone())), - Some(a) => a - }; - - matches.insert(arg.clone(), current_arg); - } else { - matches.insert(arg.clone(), String::new()); - } - } else { - return Err(Error::new(ErrorKind::InvalidArgument, arg.clone())); - } - } - } else { // Probably a required arg - let arg_name: String = opts.get_arg().unwrap().clone(); - matches.insert(arg_name, current_arg); - } - - next_arg = args.next(); - } - - match opts.arg_len() { - 0 => Ok(Matches { matches: matches, program_name: program_name }), - _ => Err(Error::new(ErrorKind::MissingArgument, opts.get_arg().unwrap())), - } - } - pub fn get(&self, arg: &str) -> Option<&String> { self.matches.get(arg) } - pub fn has_arg(&self, arg: &str) -> bool { + pub fn has_match(&self, arg: &str) -> bool { let arg = String::from(arg); self.matches.contains_key(&arg) } - pub fn args(&self) -> Keys { + pub fn matches(&self) -> Keys { self.matches.keys() } } + +#[cfg(test)] +mod tests { + use super::Matches; + use super::super::errors::{Error, ErrorKind}; + use super::super::vars::Vars; + + #[test] + fn test_matches() { + let opts = vec!["o/opt(An option)", "a(Argument):"]; + let env_args = vec![String::from("test"), String::from("-a"), String::from("Test")]; + let mut vars = Vars::new("Test", &opts).unwrap(); + let matches = match Matches::new(&mut vars, &env_args) { + Ok(m) => m, + Err(why) => panic!("An error occurred: {}", why) + }; + + assert_eq!(matches.get("opt").unwrap(), &String::from("opt")); + } +} diff --git a/src/token.rs b/src/token.rs index 0498888..efe89f2 100644 --- a/src/token.rs +++ b/src/token.rs @@ -15,85 +15,83 @@ * along with this program. If not, see . */ -use std::ascii::AsciiExt; use std::fmt::{self, Display, Formatter}; use errors::{Error, ErrorKind}; #[derive(Clone, Debug, PartialEq)] pub struct Token { - short_name: String, - long_name: String, - description: String, + pub short_name: String, + pub long_name: String, + pub description: String, pub is_arg: bool, pub has_arg: bool, pub is_group: bool, - padding: usize + pub padding: usize } -impl Token { - pub fn new(input: &str) -> Result { - let mut short_name = String::new(); - let mut long_name = String::new(); - let mut description = String::new(); - let last_char = input.len() - 1; - - let is_arg = match &input[..1] { - ":" => true, - _ => false - }; +pub fn token(input: &str) -> Result { + let mut short_name = String::new(); + let mut long_name = String::new(); + let mut description = String::new(); + let last_char = input.len() - 1; - let has_arg = match &input[last_char..] { - ":" => true, - _ => false - }; + let is_arg = match &input[..1] { + ":" => true, + _ => false + }; - if is_arg && has_arg { - return Err(Error::new(ErrorKind::OptionFormat, String::from(input))); - } + let has_arg = match &input[last_char..] { + ":" => true, + _ => false + }; - let option = if is_arg { - &input[1..] - } else if has_arg { - &input[..last_char] - } else { - input - }; + if is_arg && has_arg { + return Err(Error::new(ErrorKind::TokenFormat, String::from(input))); + } - let mut current_stage = AnalysisStage::ShortName; - for c in option.chars() { - match c { - '/' => current_stage = AnalysisStage::LongName, - '(' => current_stage = AnalysisStage::Description, - ')' => (), - _ => { - match current_stage { - AnalysisStage::ShortName => short_name.push(c), - AnalysisStage::LongName => long_name.push(c), - AnalysisStage::Description => description.push(c) - } + let option = if is_arg { + &input[1..] + } else if has_arg { + &input[..last_char] + } else { + input + }; + + let mut current_stage = AnalysisStage::ShortName; + for c in option.chars() { + match c { + '/' => current_stage = AnalysisStage::LongName, + '(' => current_stage = AnalysisStage::Description, + ')' => (), + _ => { + match current_stage { + AnalysisStage::ShortName => short_name.push(c), + AnalysisStage::LongName => long_name.push(c), + AnalysisStage::Description => description.push(c) } } } - - let is_group = if short_name.is_empty() && long_name.is_empty() { - true - } else { - false - }; - - Ok(Token { - short_name: short_name, - long_name: long_name, - is_arg: is_arg, - has_arg: has_arg, - is_group: is_group, - description: description, - padding: 0 - }) } - - + + let is_group = if short_name.is_empty() && long_name.is_empty() { + true + } else { + false + }; + + Ok(Token { + short_name: short_name, + long_name: long_name, + is_arg: is_arg, + has_arg: has_arg, + is_group: is_group, + description: description, + padding: 0 + }) +} + +impl Token { pub fn adjust_padding(&mut self, padding: usize) { self.padding = padding; } @@ -115,13 +113,13 @@ impl Token { repr.len() } - pub fn name(&self) -> &str { + pub fn name(&self) -> String { if !self.long_name.is_empty() { - &self.long_name + self.long_name.clone() } else if !self.short_name.is_empty() { - &self.short_name + self.short_name.clone() } else { - "" + String::new() } } @@ -144,7 +142,6 @@ impl Token { if self.has_arg { let name = String::from(self.name()); - name.to_ascii_uppercase(); repr.push(' '); repr.push_str(&name); } @@ -152,7 +149,6 @@ impl Token { repr.push(']'); } else { let name = String::from(self.name()); - name.to_ascii_uppercase(); repr.push_str(&name); } @@ -207,7 +203,6 @@ mod tests { padding: 0 }; - println!("{}", token); assert_eq!(token, control_token); } @@ -228,7 +223,6 @@ mod tests { padding: 0 }; - println!("{}", token); assert_eq!(token, control_token); } @@ -249,7 +243,6 @@ mod tests { padding: 0 }; - println!("{}", token); assert_eq!(token, control_token); } @@ -270,7 +263,6 @@ mod tests { padding: 0 }; - println!("{}", token); assert_eq!(token, control_token); } @@ -286,8 +278,8 @@ mod tests { #[test] fn test_name() { - let short_name = "o/out"; - let long_name = "/out"; + let short_name = "o"; + let long_name = "o/out"; let group = "(Output)"; let short_token = Token::new(short_name).unwrap(); diff --git a/src/usage.rs b/src/usage.rs index 27a9afd..ece22fd 100644 --- a/src/usage.rs +++ b/src/usage.rs @@ -18,7 +18,7 @@ use vars::Vars; pub fn usage(vars: &Vars) { - for token in vars.tokens.iter() { + for token in vars.tokens() { println!("{}", token); } } \ No newline at end of file diff --git a/src/vars.rs b/src/vars.rs index d3e08a4..7881e1d 100644 --- a/src/vars.rs +++ b/src/vars.rs @@ -15,65 +15,81 @@ * along with this program. If not, see . */ -use std::collections::HashMap; -use std::collections::VecDeque; +use std::collections::{HashMap, VecDeque}; +use std::slice::Iter; use errors::Error; -use token::Token; +use token::{Token, token}; pub struct Vars { - pub opts: HashMap, - pub args: VecDeque, + opts: HashMap, + args: VecDeque, pub tokens: Vec, pub program_name: String } -impl Vars { - pub fn new(program_name: &str, options: &[&str]) -> Result { - let mut opts: HashMap = HashMap::new(); - let mut args: VecDeque = VecDeque::new(); - let mut tokens: Vec = Vec::new(); - let mut longest_token_len: usize = 0; - - for opt in options.iter() { - let token = match Token::new(opt) { - Ok(t) => t, - Err(why) => return Err(why) - }; - - if !token.is_group { - if token.is_arg { - args.push_back(String::from(token.name())); - } else { - opts.insert(String::from(token.name()), token.has_arg); +pub fn vars(program_name: &str, options: &[&str]) -> Result { + let mut opts: HashMap = HashMap::new(); + let mut args: VecDeque = VecDeque::new(); + let mut tokens: Vec = Vec::new(); + let mut longest_token_len: usize = 0; + + for opt in options.iter() { + let token = match token(opt) { + Ok(t) => t, + Err(why) => return Err(why) + }; + + if !token.is_group { + if token.is_arg { + args.push_back(token.clone()); + } else { + if !token.short_name.is_empty() { + opts.insert(token.short_name.clone(), token.clone()); } - - let token_len = token.len(); - if token_len > 0 { - if token_len > longest_token_len { - longest_token_len = token_len; - for t in tokens.iter_mut() { - let diff = longest_token_len - t.len(); - t.adjust_padding(diff); - } + + if !token.long_name.is_empty() { + opts.insert(token.long_name.clone(), token.clone()); + } + } + + let token_len = token.len(); + if token_len > 0 { + if token_len > longest_token_len { + longest_token_len = token_len; + for t in tokens.iter_mut() { + let diff = longest_token_len - t.len(); + t.adjust_padding(diff); } } } - tokens.push(token); } - - opts.insert(String::from("-h"), false); - opts.insert(String::from("--help"), false); - - Ok(Vars { - opts: opts, - args: args, - tokens: tokens, - program_name: program_name - }) + tokens.push(token); } + + let help_token = Token { + short_name: String::from("h"), + long_name: String::from("help"), + description: String::from("Display usage information"), + is_arg: false, + has_arg: false, + is_group: false, + padding: 0 + }; - pub fn get_opt(&self, opt_name: &String) -> Option<&bool> { + opts.insert(String::from("-h"), help_token.clone()); + opts.insert(String::from("--help"), help_token.clone()); + + Ok(Vars { + opts: opts, + args: args, + tokens: tokens, + program_name: String::from(program_name) + }) +} + +impl Vars { + pub fn get_opt(&self, opt_name: &String) -> Option<&Token> { self.opts.get(opt_name) } @@ -81,11 +97,15 @@ impl Vars { self.opts.contains_key(opt) } - pub fn get_arg(&mut self) -> Option { + pub fn get_arg(&mut self) -> Option { self.args.pop_front() } pub fn arg_len(&self) -> usize { self.args.len() } + + pub fn tokens(&self) -> Iter{ + self.tokens.iter() + } } diff --git a/tests/main.rs b/tests/main.rs index 25fcfb3..58789f6 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,26 +1,26 @@ extern crate pirate; -use pirate::{Matches, usage, Vars}; +use std::env; + +use pirate::{matches, vars}; fn main() { - let opts = vec!["n:", "b/boop", ":input"]; - let mut vars = match Vars::new(&opts) { - Ok(v) => v, - Err(why) => { - println!("{}", why); - return; - } - }; - let matches = match Matches::new(&mut vars) { + let env_args: Vec = env::args().collect(); + let opts = vec!["o/opt(An option)", "a(Argument):"]; + let mut vars = vars("A Test Program", &opts).unwrap(); + + let matches = match matches(&mut vars, &env_args) { Ok(m) => m, - Err(why) => { - println!("{}", why); - return; - } + Err(why) => panic!("An error occurred: {}", why) }; - - if matches.has_arg("-h") || matches.has_arg("--help") { - usage(&vars); - return; + + if matches.has_match("a") { + let m = matches.get("a").unwrap(); + println!("{}", m); + } + + match matches.get("opt") { + Some(_) => println!("Opt was passed to the program"), + None => println!("Opt was not passed to the program") } }