use std::{collections::HashMap, str::FromStr};
pub const HELP_LONG: &str = "help";
pub const HELP_SHORT: &str = "h";
pub trait Flaggable {
fn expects_value(&self) -> bool { true }
fn parse_from(&mut self, s: &str) -> Result<(), String>;
}
impl<T: FromStr> Flaggable for Option<T> {
fn parse_from(&mut self, s: &str) -> Result<(), String> {
match T::from_str(s) {
Err(_) => Err(s.to_string()),
Ok(v) => {
self.replace(v);
Ok(())
},
}
}
}
#[derive(Debug)]
pub struct Preset<T>(pub T);
impl<T> From<T> for Preset<T> {
fn from(t: T) -> Self { Preset(t) }
}
impl<T> Preset<T> {
#[inline]
pub fn into_inner(self) -> T { self.0 }
}
impl<T: FromStr> Flaggable for Preset<T> {
fn parse_from(&mut self, s: &str) -> Result<(), String> {
match T::from_str(s) {
Err(_) => Err(s.to_string()),
Ok(v) => {
self.0 = v;
Ok(())
},
}
}
}
impl Flaggable for bool {
fn expects_value(&self) -> bool { false }
fn parse_from(&mut self, _: &str) -> Result<(), String> {
*self = !*self;
Ok(())
}
}
#[derive(Default)]
pub struct FlagSet<'a> {
mappings: HashMap<&'static str, &'a mut dyn Flaggable>,
help_info: HashMap<&'static str, &'static str>,
}
fn show_help(h: &HashMap<&str, &str>) {
eprintln!("Usage:");
h.iter().for_each(|(flag, info)| {
eprintln!(" -{}", flag);
eprintln!("\t {}", info);
});
}
impl<'a> FlagSet<'a> {
pub fn new() -> Self {
Self {
mappings: HashMap::new(),
help_info: HashMap::new(),
}
}
pub fn add<F: Flaggable>(&mut self, name: &'static str, help: &'static str, f: &'a mut F) {
self.mappings.insert(name, f);
self.help_info.insert(name, help);
}
pub fn parse<I>(&mut self, mut i: I) -> Result<Vec<String>, ParseError>
where
I: Iterator<Item = String>, {
let mut out = vec![];
while let Some(v) = i.next() {
if !v.starts_with('-') {
out.push(v);
continue;
}
let v = v.trim_start_matches('-');
match self.mappings.get_mut(&*v) {
Some(ref mut flag) => {
if !flag.expects_value() {
flag
.parse_from("")
.map_err(|e| ParseError::ParseFromFailure(v.to_string(), e))?;
continue;
}
let flag_val = match i.next() {
None => return Err(ParseError::MissingValue(v.to_string())),
Some(flag_val) => flag_val,
};
flag
.parse_from(&flag_val)
.map_err(|e| ParseError::ParseFromFailure(v.to_string(), e))?;
},
None if v == HELP_LONG || v == HELP_SHORT => return Err(ParseError::HelpRequested),
None => return Err(ParseError::UnknownFlag(v.to_string())),
};
}
Ok(out)
}
pub fn parse_args(&mut self) -> Vec<String> {
use std::env::args;
const OK: i32 = 0;
const FAILURE: i32 = 1;
match self.parse(args().skip(1)) {
Ok(rem) => rem,
Err(e) => {
let status = match e {
ParseError::HelpRequested => OK,
ParseError::ParseFromFailure(f, v) => {
eprintln!("Invalid value \"{}\" for flag -{}", f, v);
FAILURE
},
ParseError::UnknownFlag(f) => {
eprintln!("flag provided but not defined: -{}", f);
FAILURE
},
ParseError::MissingValue(f) => {
eprintln!("Missing value for flag: -{}", f);
FAILURE
},
};
show_help(&self.help_info);
std::process::exit(status);
},
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseError {
ParseFromFailure(String, String),
MissingValue(String),
HelpRequested,
UnknownFlag(String),
}
use std::fmt;
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self) }
}