use std::fmt::{self, Display, Formatter};
use errors::{Error, ErrorKind};
#[derive(Clone, Debug, PartialEq)]
pub struct Token {
pub short_name: String,
pub long_name: String,
pub description: String,
pub is_arg: bool,
pub has_arg: bool,
pub is_group: bool,
pub padding: usize
}
pub fn token(input: &str) -> 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::TokenFormat, 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: 0
})
}
impl Token {
pub fn adjust_padding(&mut self, padding: usize) {
self.padding = padding;
}
pub fn len(&self) -> usize {
let short_name_empty = self.short_name.is_empty();
let long_name_empty = self.long_name.is_empty();
let repr = if !short_name_empty && !long_name_empty {
format!("-{}, --{}", self.short_name, self.long_name)
} else if !short_name_empty && long_name_empty {
format!("-{}", self.short_name)
} else if short_name_empty && !long_name_empty {
format!("--{}", self.long_name)
} else {
String::new()
};
repr.len()
}
pub fn name(&self) -> String {
if !self.long_name.is_empty() {
self.long_name.clone()
} else if !self.short_name.is_empty() {
self.short_name.clone()
} else {
self.description.clone()
}
}
pub fn usage(&self) -> Option<String> {
let mut repr = String::new();
if !self.is_group {
if !self.is_arg {
repr.push('[');
if !self.short_name.is_empty() {
repr.push('-');
repr.push_str(&self.short_name);
}
if !self.long_name.is_empty() {
repr.push_str("|--");
repr.push_str(&self.long_name);
}
if self.has_arg {
let name = String::from(self.name());
repr.push(' ');
repr.push_str(&name);
}
repr.push(']');
} else {
let name = String::from(self.name());
repr.push_str(&name);
}
Some(repr)
} else {
None
}
}
}
impl Display for Token {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let mut spacing = String::new();
for _ in 0..self.padding {
spacing.push(' ');
}
let short_name_empty = self.short_name.is_empty();
let long_name_empty = self.long_name.is_empty();
let repr = if self.is_group {
format!("\n{}:", self.description)
} else if !short_name_empty && !long_name_empty {
format!(" -{}, --{}{} {}", self.short_name, self.long_name, spacing, self.description)
} else if short_name_empty && !long_name_empty {
format!(" --{}{} {}", self.long_name, spacing, self.description)
} else {
format!(" -{}{} {}", self.short_name, spacing, self.description)
};
write!(f, "{}", repr)
}
}
enum AnalysisStage {
ShortName,
LongName,
Description
}
#[cfg(test)]
mod tests {
use super::{Token, token};
#[test]
fn test_new_token() {
let opt = "h/help#Display the program usage";
let token = match token(opt) {
Ok(t) => t,
Err(why) => panic!("Received error: {}", why)
};
let control_token = Token {
short_name: String::from("h"),
long_name: String::from("help"),
description: String::from("Display the program usage"),
is_arg: false,
has_arg: false,
is_group: false,
padding: 0
};
assert_eq!(token, control_token);
}
#[test]
fn test_new_group() {
let opt = "#This is a group";
let token = match token(opt) {
Ok(t) => t,
Err(why) => panic!("Received error: {}", why)
};
let control_token = Token {
short_name: String::new(),
long_name: String::new(),
description: String::from("This is a group"),
is_arg: false,
has_arg: false,
is_group: true,
padding: 0
};
assert_eq!(token, control_token);
}
#[test]
fn test_new_token_with_arg() {
let opt = "o/option#An option with an argument:";
let token = match token(opt) {
Ok(t) => t,
Err(why) => panic!("Received error: {}", why)
};
let control_token = Token {
short_name: String::from("o"),
long_name: String::from("option"),
description: String::from("An option with an argument"),
is_arg: false,
has_arg: true,
is_group: false,
padding: 0
};
assert_eq!(token, control_token);
}
#[test]
fn test_new_token_as_arg() {
let opt = ":a/arg#An argument";
let token = match token(opt) {
Ok(t) => t,
Err(why) => panic!("Received error: {}", why)
};
let control_token = Token {
short_name: String::from("a"),
long_name: String::from("arg"),
description: String::from("An argument"),
is_arg: true,
has_arg: false,
is_group: false,
padding: 0
};
assert_eq!(token, control_token);
}
#[test]
#[should_panic]
fn test_invalid_token_format() {
let input = ":w/wrong#Wrong format:";
match token(input) {
Ok(t) => t,
Err(why) => panic!("Received error: {}", why)
};
}
#[test]
fn test_name() {
let short_name = "o";
let long_name = "/out";
let group = "#Output";
let short_token = token(short_name).unwrap();
let long_token = token(long_name).unwrap();
let group_token = token(group).unwrap();
assert_eq!(short_token.name(), "o");
assert_eq!(long_token.name(), "out");
assert_eq!(group_token.name(), "Output");
}
}