diff --git a/src/lib.rs b/src/lib.rs index adffdfd..41456da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,522 +21,3 @@ where self.0(f) } } - -pub mod buf { - use core::mem::MaybeUninit; - - pub struct Buf { - storage: Box<[MaybeUninit]>, - len: usize, - } - impl Buf { - pub fn new(cap: usize) -> Self { - let mut storage = Vec::with_capacity(cap); - unsafe { - storage.set_len(storage.capacity()); - } - - Self { - storage: storage.into_boxed_slice(), - len: 0, - } - } - - pub const fn len(&self) -> usize { - self.len - } - - pub const fn is_empty(&self) -> bool { - self.len() == 0 - } - - pub const fn capacity(&self) -> usize { - self.storage.len() - } - - pub const fn full(&self) -> bool { - self.len() >= self.capacity() - } - - pub fn push_checked(&mut self, item: T) -> Result<(), OutOfSpace> { - if self.full() { - return Err(OutOfSpace); - } - - unsafe { self.push_unchecked(item) }; - Ok(()) - } - - pub fn push(&mut self, item: T) { - let _ = self.push_checked(item); - } - - /// # Safety - /// Must check that self.full() returns false before calling this - pub unsafe fn push_unchecked(&mut self, item: T) { - self.storage[self.len()].write(item); - self.len += 1; - } - - pub fn as_slice(&self) -> &[T] { - let items = &self.storage[0..self.len()]; - - unsafe { core::mem::transmute(items) } - } - } - - impl core::ops::Index for Buf { - type Output = T; - - fn index(&self, index: usize) -> &Self::Output { - &self.as_slice()[index] - } - } - - impl Push for Buf { - fn push_checked(&mut self, item: T) -> Result<(), OutOfSpace> { - self.push_checked(item) - } - - /// # Safety - /// See [`Self::push_unchecked`] - unsafe fn push_unchecked(&mut self, item: T) { - self.push_unchecked(item) - } - } - - impl Push for Vec { - fn push_checked(&mut self, item: T) -> Result<(), OutOfSpace> { - self.push(item); - Ok(()) - } - } - - impl Push for () { - fn push(&mut self, _: T) { - } - } - - impl Push for Option { - fn push(&mut self, item: T) { - *self = Some(item); - } - } - - impl<'a, R, T> Push for &'a mut R - where - R: Push, - { - fn push_checked(&mut self, item: T) -> Result<(), OutOfSpace> { - (**self).push_checked(item) - } - - fn push(&mut self, item: T) { - (**self).push(item) - } - - unsafe fn push_unchecked(&mut self, item: T) { - (**self).push(item) - } - } - - pub struct OutOfSpace; - pub trait Push { - fn push_checked(&mut self, item: T) -> Result<(), OutOfSpace> { - self.push(item); - Ok(()) - } - - fn push(&mut self, item: T) { - let _ = self.push_checked(item); - } - - /// # Safety - /// You must check that the preconditions for the method for - /// each individual implementor are checked before calling this method. - /// - /// The default implementation of this method _is_ safe, but DO NOT RELY - /// ON THIS as implementors may implement this unsafely - unsafe fn push_unchecked(&mut self, item: T) { - self.push(item); - } - } -} - -pub mod cli { - //! Simple utilities for parsing arguments and generating command line help - - use crate::{buf::Push, default}; - use core::str::FromStr; - - #[derive(Debug, Clone, Eq, PartialEq, Hash)] - pub enum ParseError { - InvalidParamFormat { - param: &'static str, - expected: &'static str, - }, - MissingParamFor(&'static str), - UnknownParam(String), - SingleDashMustBeSingleCharacter(String), - } - - #[derive(Debug, Clone, Eq, PartialEq, Hash)] - pub enum BindError { - DuplicateShort(char), - DuplicateLong(&'static str), - } - - 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>, - ) -> Result<(), BindError> { - if self.longs.iter().any(|l| *l == long) { - return Err(BindError::DuplicateLong(long)); - } - if let Some(short) = short { - if self.shorts.iter().any(|c| *c == short) { - return Err(BindError::DuplicateShort(short)); - } - } - - 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); - } - - Ok(()) - } - - 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>, - } - - impl<'a> Cli<'a> { - pub fn new(name: &'static str) -> Self { - Self { - name, - description: default(), - version: default(), - - params: Soa::new(), - flags: Soa::new(), - } - } - - 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>) -> Result<(), BindError> { - 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>, - PB: Push, - PE: Push, - >( - &mut self, - args: &'b [A], - mut out: PB, - mut errors: PE, - ) { - let mut it = args.iter(); - fn next_param<'a, A: AsRef + 'a>( - it: &mut impl Iterator, - name: &'static str, - ) -> Result<&'a str, ParseError> { - it.next() - .map(|s| s.as_ref()) - .ok_or(ParseError::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| ParseError::InvalidParamFormat { - param: n, - expected: e, - }, - ); - }; - - match self.flags.find_for_long(arg) { - Some((_, b)) => { - **b = true; - Ok(()) - }, - None => { - Err(ParseError::UnknownParam(arg.to_string())) - }, - } - } else if let Some(arg) = arg.as_ref().strip_prefix('-') { - if arg.len() > 1 { - return Err( - ParseError::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| ParseError::InvalidParamFormat { - param: n, - expected: e, - }, - ); - }; - - match self.flags.find_for_short(arg) { - Some((_, b)) => { - **b = true; - Ok(()) - }, - None => { - Err(ParseError::UnknownParam(arg.to_string())) - }, - } - } else { - out.push(arg.as_ref().into()); - Ok(()) - } - }; - - if let Some(err) = go().err() { - errors.push(err); - } - } - } - - pub fn parse_env, PE: Push>( - &mut self, - out: PS, - errors: PE, - ) { - let env_args = std::env::args().collect::>(); - self.parse(env_args.as_slice(), out, errors); - } - } - - 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).unwrap() - } - - pub fn bind_checked(self, cli: &mut Cli<'a>) -> Result<(), BindError> { - cli.bind(self) - } - } - - #[cfg(test)] - mod test { - use crate::{ - buf::Buf, - cli::{flag, param, Cli}, - }; - - #[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 quux: bool = false; - assert!(flag("quux", &mut quux) - .short('b') - .bind_checked(&mut cli) - .is_err()); - - let mut args: Vec<&str> = Vec::new(); - let mut errors = Buf::new(1); - cli.parse( - &["--foo", "foo", "--bar", "854", "-b", "arg"], - &mut args, - &mut errors, - ); - assert_eq!(foo, "foo"); - assert_eq!(bar, 854); - assert!(baz); - assert_eq!(args, &["arg"]); - assert!(errors.is_empty()); - } - } -}