use std::collections::HashMap;
use std::fmt;
use std::error;
#[derive(Debug)]
pub enum Error {
InvalidName(String),
MissingValue(String),
MissingHelpArg,
InvalidUnicode,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::InvalidName(msg) => write!(f, "Error: {}", msg),
Error::MissingValue(msg) => write!(f, "Error: {}", msg),
Error::MissingHelpArg => write!(f, "Error: missing argument for the help command"),
Error::InvalidUnicode => write!(f, "Error: arguments are not valid unicode strings"),
}
}
}
impl Error {
pub fn exit(self) -> ! {
eprintln!("{}.", self);
std::process::exit(1);
}
}
pub struct ArgParser {
helptext: Option<String>,
version: Option<String>,
options: Vec<Opt>,
option_map: HashMap<String, usize>,
flags: Vec<Flag>,
flag_map: HashMap<String, usize>,
commands: Vec<ArgParser>,
command_map: HashMap<String, usize>,
callback: Option<fn(&str, &ArgParser)>,
auto_help_cmd: bool,
pub args: Vec<String>,
pub cmd_name: Option<String>,
pub cmd_parser: Option<Box<ArgParser>>,
}
impl ArgParser {
pub fn new() -> ArgParser {
ArgParser {
helptext: None,
version: None,
args: Vec::new(),
options: Vec::new(),
option_map: HashMap::new(),
flags: Vec::new(),
flag_map: HashMap::new(),
commands: Vec::new(),
command_map: HashMap::new(),
cmd_name: None,
callback: None,
auto_help_cmd: false,
cmd_parser: None,
}
}
pub fn helptext<S>(mut self, text: S) -> Self where S: Into<String> {
self.helptext = Some(text.into());
self
}
pub fn version<S>(mut self, text: S) -> Self where S: Into<String> {
self.version = Some(text.into());
self
}
pub fn option(mut self, name: &str) -> Self {
self.options.push(Opt {
values: Vec::new(),
});
let index = self.options.len() - 1;
for alias in name.split_whitespace() {
self.option_map.insert(alias.to_string(), index);
}
self
}
pub fn flag(mut self, name: &str) -> Self {
self.flags.push(Flag {
count: 0,
});
let index = self.flags.len() - 1;
for alias in name.split_whitespace() {
self.flag_map.insert(alias.to_string(), index);
}
self
}
pub fn command(mut self, name: &str, cmd_parser: ArgParser) -> Self {
if cmd_parser.helptext.is_some() {
self.auto_help_cmd = true;
}
self.commands.push(cmd_parser);
let index = self.commands.len() - 1;
for alias in name.split_whitespace() {
self.command_map.insert(alias.to_string(), index);
}
self
}
pub fn callback(mut self, f: fn(&str, &ArgParser)) -> Self {
self.callback = Some(f);
self
}
pub fn help_command(mut self, enable: bool) -> Self {
self.auto_help_cmd = enable;
self
}
pub fn value(&self, name: &str) -> Result<Option<String>, Error> {
if let Some(index) = self.option_map.get(name) {
if let Some(value) = self.options[*index].values.last() {
return Ok(Some(value.to_string()));
}
return Ok(None);
}
Err(Error::InvalidName(format!("'{}' is not a registered option name", name)))
}
pub fn values(&self, name: &str) -> Result<Vec<String>, Error> {
if let Some(index) = self.option_map.get(name) {
return Ok(self.options[*index].values.clone());
}
Err(Error::InvalidName(format!("'{}' is not a registered option name", name)))
}
pub fn count(&self, name: &str) -> Result<usize, Error> {
if let Some(index) = self.flag_map.get(name) {
return Ok(self.flags[*index].count);
}
if let Some(index) = self.option_map.get(name) {
return Ok(self.options[*index].values.len());
}
Err(Error::InvalidName(format!("'{}' is not a registered flag or option name", name)))
}
pub fn found(&self, name: &str) -> Result<bool, Error> {
match self.count(name) {
Ok(count) => Ok(count > 0),
Err(err) => Err(err),
}
}
pub fn parse(&mut self) -> Result<(), Error> {
let mut strings = Vec::<String>::new();
for os_string in std::env::args_os().skip(1) {
if let Ok(string) = os_string.into_string() {
strings.push(string);
} else {
return Err(Error::InvalidUnicode);
}
}
let mut stream = ArgStream::new(strings);
self.parse_argstream(&mut stream)?;
Ok(())
}
pub fn parse_args(&mut self, args: Vec<&str>) -> Result<(), Error> {
let strings = args.iter().map(|s| s.to_string()).collect();
let mut stream = ArgStream::new(strings);
self.parse_argstream(&mut stream)?;
Ok(())
}
fn parse_argstream(&mut self, argstream: &mut ArgStream) -> Result<(), Error> {
let mut is_first_arg = true;
while argstream.has_next() {
let arg = argstream.next();
if arg == "--" {
while argstream.has_next() {
self.args.push(argstream.next());
}
}
else if arg.starts_with("--") {
if arg.contains("=") {
self.handle_equals_opt(&arg)?;
} else {
self.handle_long_opt(&arg, argstream)?;
}
}
else if arg.starts_with("-") {
if arg == "-" || arg.chars().nth(1).unwrap().is_numeric() {
self.args.push(arg);
} else if arg.contains("=") {
self.handle_equals_opt(&arg)?;
} else {
self.handle_short_opt(&arg, argstream)?;
}
}
else if is_first_arg && self.command_map.contains_key(&arg) {
let index = self.command_map.get(&arg).unwrap();
let mut cmd_parser = self.commands.remove(*index);
self.command_map.clear();
self.commands.clear();
cmd_parser.parse_argstream(argstream)?;
if let Some(callback) = cmd_parser.callback {
callback(&arg, &cmd_parser);
}
self.cmd_name = Some(arg);
self.cmd_parser = Some(Box::new(cmd_parser));
}
else if is_first_arg && arg == "help" && self.auto_help_cmd {
if argstream.has_next() {
let name = argstream.next();
if let Some(index) = self.command_map.get(&name) {
let cmd_parser = &mut self.commands[*index];
let helptext = cmd_parser.helptext.as_deref().unwrap_or("").trim();
println!("{}", helptext);
std::process::exit(0);
} else {
return Err(Error::InvalidName(
format!("'{}' is not a recognised command name", &name)
));
}
} else {
return Err(Error::MissingHelpArg);
}
}
else {
self.args.push(arg);
}
is_first_arg = false;
}
Ok(())
}
fn handle_long_opt(&mut self, arg: &str, argstream: &mut ArgStream) -> Result<(), Error> {
if let Some(index) = self.flag_map.get(&arg[2..]) {
self.flags[*index].count += 1;
} else if let Some(index) = self.option_map.get(&arg[2..]) {
if argstream.has_next() {
self.options[*index].values.push(argstream.next());
} else {
return Err(Error::MissingValue(format!("missing value for {}", arg)));
}
} else if arg == "--help" && self.helptext.is_some() {
println!("{}", self.helptext.as_ref().unwrap().trim());
std::process::exit(0);
} else if arg == "--version" && self.version.is_some() {
println!("{}", self.version.as_ref().unwrap().trim());
std::process::exit(0);
} else {
return Err(Error::InvalidName(
format!("{} is not a recognised flag or option name", arg)
));
}
Ok(())
}
fn handle_short_opt(&mut self, arg: &str, argstream: &mut ArgStream) -> Result<(), Error> {
for c in arg.chars().skip(1) {
if let Some(index) = self.flag_map.get(&c.to_string()) {
self.flags[*index].count += 1;
} else if let Some(index) = self.option_map.get(&c.to_string()) {
if argstream.has_next() {
self.options[*index].values.push(argstream.next());
} else {
let msg = if arg.chars().count() > 2 {
format!("missing value for '{}' in {}", c, arg)
} else {
format!("missing value for {}", arg)
};
return Err(Error::MissingValue(msg));
}
} else if c == 'h' && self.helptext.is_some() {
println!("{}", self.helptext.as_ref().unwrap().trim());
std::process::exit(0);
} else if c == 'v' && self.version.is_some() {
println!("{}", self.version.as_ref().unwrap().trim());
std::process::exit(0);
} else {
let msg = if arg.chars().count() > 2 {
format!("'{}' in {} is not a recognised flag or option name", c, arg)
} else {
format!("{} is not a recognised flag or option name", arg)
};
return Err(Error::InvalidName(msg));
}
}
Ok(())
}
fn handle_equals_opt(&mut self, arg: &str) -> Result<(), Error> {
let splits: Vec<&str> = arg.splitn(2, '=').collect();
let name = splits[0];
let value = splits[1];
if let Some(index) = self.option_map.get(name.trim_start_matches('-')) {
if value == "" {
return Err(Error::MissingValue(format!("missing value for {}", name)));
} else {
self.options[*index].values.push(value.to_string());
return Ok(());
}
}
return Err(Error::InvalidName(format!("{} is not a recognised option name", name)));
}
}
struct ArgStream {
args: Vec<String>,
index: usize,
}
impl ArgStream {
fn new(args: Vec<String>) -> ArgStream {
ArgStream {
args: args,
index: 0,
}
}
fn has_next(&self) -> bool {
self.index < self.args.len()
}
fn next(&mut self) -> String {
self.index += 1;
self.args[self.index - 1].clone()
}
}
struct Opt {
values: Vec<String>,
}
struct Flag {
count: usize,
}