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:
parent
346c17c517
commit
c0d0a99c25
5 changed files with 198 additions and 194 deletions
136
src/lexer.rs
136
src/lexer.rs
|
@ -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)
|
||||
}
|
||||
}
|
80
src/lib.rs
80
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<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 ¤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<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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
32
src/opts.rs
32
src/opts.rs
|
@ -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
137
src/token.rs
Normal 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
|
||||
}
|
Loading…
Add table
Reference in a new issue