Begin bringing Tokens into Opt

I've made an executive decision: Opt will be in charge of displaying the
usage (help) information for the program. That will involve bringing the
functionality delivered by Token into Opt. I've begun that process now.
This commit is contained in:
Zach Dziura 2015-06-04 15:37:59 -04:00
parent 346c17c517
commit c0d0a99c25
5 changed files with 198 additions and 194 deletions

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
use std::fmt::{self, Display, Formatter};
use errors::{Error, ErrorKind};
pub fn collect(input: &[&str]) -> Result<Vec<Token>, Error> {
let mut vector: Vec<Token> = 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<Token, Error> {
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)
}
}

View file

@ -25,7 +25,7 @@ use std::env::Args;
pub use errors::{Error, ErrorKind}; pub use errors::{Error, ErrorKind};
pub use matches::Matches; pub use matches::Matches;
pub use lexer::{analyze, collect, Token}; pub use lexer::{analyze, collect, Token};
use opts::Opts; use opts::{opts, Opts};
pub fn parse(mut args: Args, options: &[&'static str]) -> Result<Matches, Error> { pub fn parse(mut args: Args, options: &[&'static str]) -> Result<Matches, Error> {
let mut matches: Matches = Matches::new(); let mut matches: Matches = Matches::new();
@ -42,36 +42,44 @@ pub fn parse(mut args: Args, options: &[&'static str]) -> Result<Matches, Error>
let mut next_arg = args.next(); let mut next_arg = args.next();
while next_arg.is_some() { while next_arg.is_some() {
let mut current_arg = next_arg.unwrap(); let mut current_arg = next_arg.unwrap();
let arg: String; let mut arg_vec: Vec<String> = Vec::new();
if &current_arg[..1] == "-" { // Probably a opt // Determine if current opt is in short, long, or arg form
if current_arg.len() == 2 { // Short form opt if &current_arg[..1] == "-" {
arg = String::from(&current_arg[1..]); if &current_arg[..2] == "--" { // Long form opt
} else { // Assuming it's a long form opt arg_vec.push(String::from(&current_arg[2..]));
//TODO: Handle cases where it may be a opt group } else { // Short form opt
arg = String::from(&current_arg[2..]); // 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();
if opts.contains_opt(&arg) { s.push(c);
let has_arg: bool = *opts.get_opt(&arg).unwrap(); arg_vec.push(s);
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, &current_arg);
} else {
matches.insert(&arg, "");
} }
} 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, &current_arg);
} else {
matches.insert(&arg, "");
}
} else {
let arg_ = (*arg).clone();
return Err(Error::new(ErrorKind::InvalidArgument, arg_));
}
}
} else { // Probably a required arg } else { // Probably a required arg
let arg_name: String = opts.get_arg().unwrap(); let arg_name: String = opts.get_arg().unwrap();
matches.insert(&arg_name, &current_arg); matches.insert(&arg_name, &current_arg);
@ -86,20 +94,10 @@ pub fn parse(mut args: Args, options: &[&'static str]) -> Result<Matches, Error>
} }
} }
fn opts(opts: &Vec<lexer::Token>) -> Opts { pub fn help(tokens: &Vec<lexer::Token>, program_name: &str, program_desc: &str) {
let mut options = Opts::new(); println!("{} - {}", program_name, program_desc);
for opt in opts.iter() { for token in tokens.iter() {
if opt.is_arg { println!("{}", token);
options.insert_arg(opt.name());
} else {
options.insert_opt(opt.name(), opt.has_arg);
}
} }
// Push the obligatory "-h/--help" options
options.insert_opt("h", false);
options.insert_opt("help", false);
options
} }

View file

@ -18,11 +18,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::hash_map::Keys; use std::collections::hash_map::Keys;
use lexer;
pub struct Matches { pub struct Matches {
matches: HashMap<String, String>, matches: HashMap<String, String>
pub tokens: Vec<lexer::Token>
} }
impl Matches { impl Matches {

View file

@ -18,21 +18,33 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::VecDeque; use std::collections::VecDeque;
use lexer;
pub struct Opts { pub struct Opts {
pub opts: HashMap<String, bool>, pub opts: HashMap<String, bool>,
pub args: VecDeque<String>, pub args: VecDeque<String>,
} }
impl Opts { impl Opts {
pub fn new() -> Opts { pub fn new(options: &[&'static str]) -> Opts {
Opts { let opts: Hashmap<String, bool> = HashMap::new();
opts: HashMap::new(), let args: VecDeque<String> = VecDeque::new();
args: 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> { pub fn get_opt(&self, opt_name: &String) -> Option<&bool> {
@ -43,10 +55,6 @@ impl Opts {
self.opts.contains_key(opt) 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<String> { pub fn get_arg(&mut self) -> Option<String> {
self.args.pop_front() self.args.pop_front()
} }
@ -54,4 +62,4 @@ impl Opts {
pub fn arg_len(&self) -> usize { pub fn arg_len(&self) -> usize {
self.args.len() self.args.len()
} }
} }

137
src/token.rs Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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<Token, Error> {
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
}