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 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<Matches, Error> {
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();
while next_arg.is_some() {
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
if current_arg.len() == 2 { // Short form opt
arg = String::from(&current_arg[1..]);
} else { // Assuming it's a long form opt
//TODO: Handle cases where it may be a opt group
arg = String::from(&current_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, &current_arg);
} else {
matches.insert(&arg, "");
// Determine if current opt is in short, long, or arg form
if &current_arg[..1] == "-" {
if &current_arg[..2] == "--" { // Long form opt
arg_vec.push(String::from(&current_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, &current_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, &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 {
let mut options = Opts::new();
pub fn help(tokens: &Vec<lexer::Token>, 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
}

View file

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

View file

@ -18,21 +18,33 @@
use std::collections::HashMap;
use std::collections::VecDeque;
use lexer;
pub struct Opts {
pub opts: HashMap<String, bool>,
pub args: VecDeque<String>,
}
impl Opts {
pub fn new() -> Opts {
Opts {
opts: HashMap::new(),
args: VecDeque::new()
}
}
pub fn new(options: &[&'static str]) -> Opts {
let opts: Hashmap<String, bool> = HashMap::new();
let args: VecDeque<String> = VecDeque::new();
pub fn insert_opt(&mut self, key: &str, value: bool) {
self.opts.insert(String::from(key), value);
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 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<String> {
self.args.pop_front()
}

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
}