use std::str::FromStr; use formula::Formula; pub mod formula; pub struct Scurvy { free: Vec, pairs: Vec, unknown: Vec<(String, String)>, print_help: bool, print_version: bool, } struct Pair { key: Argument, values: Vec, } impl Scurvy { pub fn make(args: Vec) -> Self { let mut free = vec![]; let mut pairs: Vec = args.into_iter().map(|a| a.into()).collect(); let mut unknown = vec![]; let mut print_help = false; let mut print_version = false; for arg in std::env::args().skip(1) { if arg == "-h" || arg == "--help" || arg == "help=" { print_help = true; } else if arg == "-V" || arg == "--version" || arg == "version=" { print_version = true; } match arg.split_once('=') { None => free.push(arg), Some((key, value)) => { if let Some(pair) = pairs.iter_mut().find(|p| p.key.matches(key)) { pair.values.push(value.to_owned()); } else { unknown.push((key.to_owned(), value.to_owned())); } } } } Self { free, pairs, unknown, print_help, print_version, } } pub fn should_print_help(&self) -> bool { self.print_help } pub fn should_print_version(&self) -> bool { self.print_version } pub fn free(&self) -> &[String] { &self.free } fn get_pair(&self, key: &str) -> Option<&Pair> { self.pairs.iter().find(|p| p.key.matches(key)) } pub fn get(&self, key: &str) -> Option<&str> { self.pairs .iter() .find(|p| p.key.matches(key)) .map(|p| p.values.first().map(|s| s.as_str())) .flatten() } pub fn parse>>(&self, key: &str, formula: F) -> Option { let formula = formula.into(); let Some(got) = self.get(key) else { return None; }; match got.parse::() { Ok(o) => { if let Some((fail, mut check)) = formula.check_fn { if !check(&o) { let pair = self.get_pair(key).unwrap(); let str = fail.replace("[opt]", pair.key.preferred_key()); eprintln!("{str}"); std::process::exit(-1); } return Some(o); }; Some(o) } Err(_e) => { eprintln!("{}", formula.failure); std::process::exit(-1); } } } pub fn parse_req>>(&self, key: &str, formula: F) -> T { let formula = formula.into(); let missing = formula.missing.clone(); match self.parse(key, formula) { None => match missing { None => { let pair = self.get_pair(key).unwrap(); eprintln!("An argument for '{}' is required", pair.key.preferred_key()); std::process::exit(-1); } Some(misstr) => { let pair = self.get_pair(key).unwrap(); let str = misstr.replace("[opt]", pair.key.preferred_key()); eprintln!("{str}"); std::process::exit(-1); } }, Some(o) => o, } } } pub struct Argument { keys: Vec, arg_name: Option<&'static str>, help: &'static str, } impl Argument { pub fn new<'s, K: Into>>(key: K) -> Self { let keys = match key.into() { SingleOrMultiple::Single(s) => vec![s], SingleOrMultiple::Multiple(m) => m.to_vec(), }; Self { keys: keys.into_iter().map(<_>::to_owned).collect(), arg_name: None, help: "", } } pub fn arg(mut self, name: &'static str) -> Self { self.arg_name = Some(name); self } pub fn help(mut self, help: &'static str) -> Self { self.help = help; self } fn matches(&self, key: &str) -> bool { self.keys.iter().find(|k| k.as_str() == key).is_some() } fn preferred_key(&self) -> &str { self.keys.first().unwrap().as_str() } } impl Into for Argument { fn into(self) -> Pair { Pair { key: self, values: vec![], } } } pub enum SingleOrMultiple<'s> { Single(&'s str), Multiple(&'s [&'s str]), } impl<'s> From<&'s str> for SingleOrMultiple<'s> { fn from(value: &'s str) -> Self { Self::Single(value) } } impl<'s> From<&'s [&'s str]> for SingleOrMultiple<'s> { fn from(value: &'s [&'s str]) -> Self { Self::Multiple(value) } } impl<'s, const N: usize> From<&'s [&'s str; N]> for SingleOrMultiple<'s> { fn from(value: &'s [&'s str; N]) -> Self { Self::Multiple(value.as_slice()) } }