use core::hash::{Hash, Hasher}; pub fn default() -> T { T::default() } pub fn hash(value: U) -> u64 { let mut hasher: T = default(); value.hash(&mut hasher); hasher.finish() } pub struct DisplayFn core::fmt::Result>( pub F, ); impl core::fmt::Display for DisplayFn where F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result, { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { self.0(f) } } pub mod cli { //! Simple utilities for parsing arguments and generating command line help use crate::default; use core::str::FromStr; #[derive(Debug)] pub enum Error { InvalidParamFormat { param: &'static str, expected: &'static str, }, MissingParamFor(&'static str), UnknownParam(String), SingleDashMustBeSingleCharacter(String), } pub trait Value { fn assign(&mut self, param: &str) -> Result<(), &'static str>; } trait FromStrValue: FromStr { fn help() -> &'static str; } macro_rules! impl_from_str_value { ($t:ty) => { impl FromStrValue for $t { fn help() -> &'static str { stringify!($t) } } }; } impl_from_str_value!(String); impl_from_str_value!(std::path::PathBuf); impl_from_str_value!(u8); impl_from_str_value!(u16); impl_from_str_value!(u32); impl_from_str_value!(u64); impl_from_str_value!(i8); impl_from_str_value!(i16); impl_from_str_value!(i32); impl_from_str_value!(i64); impl Value for T where T: FromStrValue, { fn assign(&mut self, param: &str) -> Result<(), &'static str> { *self = param.parse().map_err(|_| Self::help())?; Ok(()) } } /// Struct-of-array-style storage struct Soa<'a, T: ?Sized> { ts: Vec<&'a mut T>, longs: Vec<&'static str>, descriptions: Vec>, // Shorts are not not 1:1 in length with // bindings, so we create a second linked vec that maps indices in // shorts to indices in bindings shorts: Vec, shorts_indices: Vec, } impl<'a, T: ?Sized> Soa<'a, T> { fn new() -> Self { Self { ts: Vec::new(), longs: default(), descriptions: default(), shorts: default(), shorts_indices: default(), } } fn add( &mut self, t: &'a mut T, long: &'static str, short: Option, description: Option<&'static str>, ) { let next = self.ts.len(); self.ts.push(t); self.longs.push(long); self.descriptions.push(description); if let Some(c) = short { self.shorts.push(c); self.shorts_indices.push(next); } } fn find_for_long( &mut self, long: &str, ) -> Option<(&'static str, &mut &'a mut T)> { self.longs .iter() .enumerate() .find(|(_, s)| *s == &long) .map(|(i, s)| (*s, &mut self.ts[i])) } fn find_for_short( &mut self, short: char, ) -> Option<(&'static str, &mut &'a mut T)> { self.shorts .iter() .zip(self.shorts_indices.iter().copied()) .find(|(s, _)| **s == short) .map(|(_, i)| (self.longs[i], &mut self.ts[i])) } } type Params<'a> = Soa<'a, dyn Value + 'a>; type Flags<'a> = Soa<'a, bool>; pub struct Cli<'a> { name: &'static str, description: Option<&'static str>, version: Option<&'static str>, params: Params<'a>, flags: Flags<'a>, errors: Vec, } impl<'a> Cli<'a> { pub fn new(name: &'static str) -> Self { Self { name, description: default(), version: default(), params: Soa::new(), flags: Soa::new(), errors: default(), } } pub fn description(&mut self, d: &'static str) -> &mut Self { self.description = Some(d); self } pub fn version(&mut self, v: &'static str) -> &mut Self { self.version = Some(v); self } pub fn bind(&mut self, pb: ParamBuilder<'a>) { match pb.kind { Kind::Param(p) => { self.params.add(p, pb.long, pb.short, pb.description) }, Kind::Flag(f) => { self.flags.add(f, pb.long, pb.short, pb.description) }, } } pub fn parse<'b, A: AsRef, B: From<&'b str>>( &mut self, args: &'b [A], mut out: Option<&mut Vec>, ) { let mut it = args.iter(); fn next_param<'a, A: AsRef + 'a>( it: &mut impl Iterator, name: &'static str, ) -> Result<&'a str, Error> { it.next() .map(|s| s.as_ref()) .ok_or(Error::MissingParamFor(name)) } while let Some(arg) = it.next() { let mut go = || { if let Some(arg) = arg.as_ref().strip_prefix("--") { if let Some((n, b)) = self.params.find_for_long(arg) { return b.assign(next_param(&mut it, n)?).map_err( |e| Error::InvalidParamFormat { param: n, expected: e, }, ); }; match self.flags.find_for_long(arg) { Some((_, b)) => { **b = true; Ok(()) }, None => Err(Error::UnknownParam(arg.to_string())), } } else if let Some(arg) = arg.as_ref().strip_prefix('-') { if arg.len() > 1 { return Err( Error::SingleDashMustBeSingleCharacter( arg.to_string(), ), ); } let arg = arg.chars().next().unwrap(); if let Some((n, b)) = self.params.find_for_short(arg) { return b.assign(next_param(&mut it, n)?).map_err( |e| Error::InvalidParamFormat { param: n, expected: e, }, ); }; match self.flags.find_for_short(arg) { Some((_, b)) => { **b = true; Ok(()) }, None => Err(Error::UnknownParam(arg.to_string())), } } else { if let Some(out) = out.as_mut() { out.push(arg.as_ref().into()); } Ok(()) } }; if let Some(e) = go().err() { self.errors.push(e) } } } pub fn parse_env(&mut self, out: Option<&mut Vec>) { let env_args = std::env::args().collect::>(); self.parse(env_args.as_slice(), out); } } enum Kind<'a> { Param(&'a mut dyn Value), Flag(&'a mut bool), } pub struct ParamBuilder<'a> { kind: Kind<'a>, long: &'static str, description: Option<&'static str>, short: Option, } pub fn param<'a>( long: &'static str, v: &'a mut dyn Value, ) -> ParamBuilder<'a> { ParamBuilder { kind: Kind::Param(v), long, description: default(), short: default(), } } pub fn flag<'a>(long: &'static str, v: &'a mut bool) -> ParamBuilder<'a> { ParamBuilder { kind: Kind::Flag(v), long, description: default(), short: default(), } } impl<'a> ParamBuilder<'a> { pub fn short(mut self, s: char) -> Self { self.short = Some(s); self } pub fn description(mut self, d: &'static str) -> Self { self.description = Some(d); self } pub fn bind(self, cli: &mut Cli<'a>) { cli.bind(self); } } #[cfg(test)] mod test { use super::*; #[test] fn basic() { let mut cli = Cli::new("test"); cli.description("A simple test CLI"); cli.version("0.0.0"); let mut foo = String::new(); param("foo", &mut foo) .short('f') .description("foo") .bind(&mut cli); let mut bar: u64 = 0; param("bar", &mut bar).description("bar").bind(&mut cli); let mut baz: bool = false; flag("baz", &mut baz) .short('b') .description("baz") .bind(&mut cli); let mut args: Vec<&str> = Vec::new(); cli.parse( &["--foo", "foo", "--bar", "854", "-b", "arg"], Some(&mut args), ); assert_eq!(foo, "foo"); assert_eq!(bar, 854); assert!(baz); assert_eq!(args, &["arg"]); } } }