diff --git a/src/lexer.rs b/src/lexer.rs deleted file mode 100644 index 823ed93..0000000 --- a/src/lexer.rs +++ /dev/null @@ -1,136 +0,0 @@ -/* Pirate - A command-line arrrrguments parser, written in Rust. - * Copyright (C) 2015 Zachary Dziura - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -use std::fmt::{self, Display, Formatter}; - -use errors::{Error, ErrorKind}; - -pub fn collect(input: &[&str]) -> Result, Error> { - let mut vector: Vec = Vec::new(); - for item in input.iter() { - match analyze(item) { - Err(why) => return Err(why), - Ok(item) => vector.push(item) - } - } - - Ok(vector) -} - -pub fn analyze(input: &str) -> Result { - let mut token = Token::new(); - - token.is_arg = match &input[..1] { - ":" => true, - _ => false - }; - - token.has_arg = match &input[(input.len() - 1)..] { - ":" => true, - _ => false - }; - - if token.is_arg && token.has_arg { - return Err(Error::new(ErrorKind::OptionFormat, String::from(input))); - } - - let option = if token.is_arg { - &input[1..] - } else if token.has_arg { - &input[..(input.len() - 1)] - } 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 => token.short_name.push(c), - AnalysisStage::LongName => token.long_name.push(c), - AnalysisStage::Description => token.description.push(c) - } - } - } - } - - if token.short_name.is_empty() && token.long_name.is_empty() { - token.is_group = true; - } - - Ok(token) -} - -enum AnalysisStage { - ShortName, - LongName, - Description -} - -#[derive(Clone)] -pub struct Token { - short_name: String, - long_name: String, - pub is_arg: bool, - pub has_arg: bool, - is_group: bool, - description: String -} - -impl Token { - pub fn new() -> Token { - Token { - short_name: String::new(), - long_name: String::new(), - is_arg: false, - has_arg: false, - is_group: false, - description: String::new() - } - } - - pub fn name(&self) -> &str { - if !self.short_name.is_empty() { - &self.short_name - } else if !self.long_name.is_empty() { - &self.long_name - } else { - "" - } - } - - pub fn fmt_with_padding(&self, padding: usize) -> String { - let mut name = format!("-{}, --{}", self.short_name, self.long_name); - - for _ in 0..padding { - name.push(' '); - } - - name - } -} - -impl Display for Token { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - let repr = format!("-{}, --{} {}", self.short_name, self.long_name, self.description); - write!(f, "{}", repr) - } -} diff --git a/src/lib.rs b/src/lib.rs index 76908b8..060c0ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ use std::env::Args; pub use errors::{Error, ErrorKind}; pub use matches::Matches; pub use lexer::{analyze, collect, Token}; -use opts::Opts; +use opts::{opts, Opts}; pub fn parse(mut args: Args, options: &[&'static str]) -> Result { let mut matches: Matches = Matches::new(); @@ -42,36 +42,44 @@ pub fn parse(mut args: Args, options: &[&'static str]) -> Result let mut next_arg = args.next(); while next_arg.is_some() { let mut current_arg = next_arg.unwrap(); - let arg: String; + let mut arg_vec: Vec = Vec::new(); - if ¤t_arg[..1] == "-" { // Probably a opt - if current_arg.len() == 2 { // Short form opt - arg = String::from(¤t_arg[1..]); - } else { // Assuming it's a long form opt - //TODO: Handle cases where it may be a opt group - arg = String::from(¤t_arg[2..]); - } - - 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)); - }, - Some(a) => a - }; - - matches.insert(&arg, ¤t_arg); - } else { - matches.insert(&arg, ""); + // 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); } - } else { - return Err(Error::new(ErrorKind::InvalidArgument, arg)); } + 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 => { + let arg_ = (*arg).clone(); + return Err(Error::new(ErrorKind::MissingArgument, arg_)); + }, + Some(a) => a + }; + + matches.insert(&arg, ¤t_arg); + } else { + matches.insert(&arg, ""); + } + } else { + let arg_ = (*arg).clone(); + return Err(Error::new(ErrorKind::InvalidArgument, arg_)); + } + } } else { // Probably a required arg let arg_name: String = opts.get_arg().unwrap(); matches.insert(&arg_name, ¤t_arg); @@ -86,20 +94,10 @@ pub fn parse(mut args: Args, options: &[&'static str]) -> Result } } -fn opts(opts: &Vec) -> Opts { - let mut options = Opts::new(); +pub fn help(tokens: &Vec, program_name: &str, program_desc: &str) { + println!("{} - {}", program_name, program_desc); - for opt in opts.iter() { - if opt.is_arg { - options.insert_arg(opt.name()); - } else { - options.insert_opt(opt.name(), opt.has_arg); - } + for token in tokens.iter() { + println!("{}", token); } - - // Push the obligatory "-h/--help" options - options.insert_opt("h", false); - options.insert_opt("help", false); - - options } diff --git a/src/matches.rs b/src/matches.rs index 9056ba7..d8ba706 100644 --- a/src/matches.rs +++ b/src/matches.rs @@ -18,11 +18,8 @@ use std::collections::HashMap; use std::collections::hash_map::Keys; -use lexer; - pub struct Matches { - matches: HashMap, - pub tokens: Vec + matches: HashMap } impl Matches { diff --git a/src/opts.rs b/src/opts.rs index df042e1..3063cb0 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -18,21 +18,33 @@ use std::collections::HashMap; use std::collections::VecDeque; +use lexer; + pub struct Opts { pub opts: HashMap, pub args: VecDeque, } impl Opts { - pub fn new() -> Opts { - Opts { - opts: HashMap::new(), - args: VecDeque::new() + pub fn new(options: &[&'static str]) -> Opts { + let opts: Hashmap = HashMap::new(); + let args: VecDeque = VecDeque::new(); + + for opt in opts.iter() { + if opt.is_arg { + args.push_back(String::from(opt.name())); + } else { + opts.insert(String::from(opt.name()), opt.has_arg); + } + } + + opts.insert(String::from("-h", false)); + opts.insert(String::from("--help", false)); + + Opts { + opts: opts, + args: args } - } - - pub fn insert_opt(&mut self, key: &str, value: bool) { - self.opts.insert(String::from(key), value); } pub fn get_opt(&self, opt_name: &String) -> Option<&bool> { @@ -43,10 +55,6 @@ impl Opts { self.opts.contains_key(opt) } - pub fn insert_arg(&mut self, arg: &str) { - self.args.push_back(String::from(arg)); - } - pub fn get_arg(&mut self) -> Option { self.args.pop_front() } @@ -54,4 +62,4 @@ impl Opts { pub fn arg_len(&self) -> usize { self.args.len() } -} +} \ No newline at end of file diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 0000000..d7d1043 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,137 @@ +/* Pirate - A command-line arrrrguments parser, written in Rust. + * Copyright (C) 2015 Zachary Dziura + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +use std::fmt::{self, Display, Formatter}; + +use errors::{Error, ErrorKind}; + +#[derive(Clone)] +pub struct Token { + short_name: String, + long_name: String, + pub is_arg: bool, + pub has_arg: bool, + is_group: bool, + description: String, + padding: usize +} + +impl Token { + pub fn new(input: &str, padding: usize) -> 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 + }; + + let has_arg = match &input[last_char..] { + ":" => true, + _ => false + }; + + if is_arg && has_arg { + return Err(Error::new(ErrorKind::OptionFormat, String::from(input))); + } + + 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: padding + }) + } + + pub fn name(&self) -> &str { + if !self.short_name.is_empty() { + &self.short_name + } else if !self.long_name.is_empty() { + &self.long_name + } else { + "" + } + } + + pub fn fmt_with_padding(&self, padding: usize) -> String { + let mut name = format!("-{}, --{}", self.short_name, self.long_name); + + for _ in 0..padding { + name.push(' '); + } + + name + } +} + +impl Display for Token { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + let mut spacing = String::new(); + for 0..self.padding { + spacing.push(' '); + } + + let repr = if self.is_group { + format!("{}:", self.description) + } else { + format!("-{}, --{}{}{}", self.short_name, self.long_name, spacing self.description) + }; + + write!(f, "{}", repr) + } +} + +enum AnalysisStage { + ShortName, + LongName, + Description +} \ No newline at end of file