diff --git a/src/lib.rs b/src/lib.rs index f564366..fc43d66 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,14 +22,138 @@ where } } +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); + } + } + + 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::default; + use crate::{buf::Push, default}; use core::str::FromStr; - #[derive(Debug)] - pub enum Error { + #[derive(Debug, Clone, Eq, PartialEq, Hash)] + pub enum ParseError { InvalidParamFormat { param: &'static str, expected: &'static str, @@ -39,6 +163,12 @@ pub mod cli { 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>; } @@ -106,7 +236,16 @@ pub mod cli { 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); @@ -117,6 +256,8 @@ pub mod cli { self.shorts.push(c); self.shorts_indices.push(next); } + + Ok(()) } fn find_for_long( @@ -152,9 +293,8 @@ pub mod cli { params: Params<'a>, flags: Flags<'a>, - - errors: Vec, } + impl<'a> Cli<'a> { pub fn new(name: &'static str) -> Self { Self { @@ -164,8 +304,6 @@ pub mod cli { params: Soa::new(), flags: Soa::new(), - - errors: default(), } } @@ -179,7 +317,7 @@ pub mod cli { self } - pub fn bind(&mut self, pb: ParamBuilder<'a>) { + 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) @@ -190,19 +328,25 @@ pub mod cli { } } - pub fn parse<'b, A: AsRef, B: From<&'b str>>( + pub fn parse< + 'b, + A: AsRef, + B: From<&'b str>, + P: Push, + >( &mut self, args: &'b [A], mut out: Option<&mut Vec>, + mut errors: Option<&mut P>, ) { let mut it = args.iter(); fn next_param<'a, A: AsRef + 'a>( it: &mut impl Iterator, name: &'static str, - ) -> Result<&'a str, Error> { + ) -> Result<&'a str, ParseError> { it.next() .map(|s| s.as_ref()) - .ok_or(Error::MissingParamFor(name)) + .ok_or(ParseError::MissingParamFor(name)) } while let Some(arg) = it.next() { @@ -210,7 +354,7 @@ pub mod cli { 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 { + |e| ParseError::InvalidParamFormat { param: n, expected: e, }, @@ -222,12 +366,14 @@ pub mod cli { **b = true; Ok(()) }, - None => Err(Error::UnknownParam(arg.to_string())), + None => { + Err(ParseError::UnknownParam(arg.to_string())) + }, } } else if let Some(arg) = arg.as_ref().strip_prefix('-') { if arg.len() > 1 { return Err( - Error::SingleDashMustBeSingleCharacter( + ParseError::SingleDashMustBeSingleCharacter( arg.to_string(), ), ); @@ -237,7 +383,7 @@ pub mod cli { if let Some((n, b)) = self.params.find_for_short(arg) { return b.assign(next_param(&mut it, n)?).map_err( - |e| Error::InvalidParamFormat { + |e| ParseError::InvalidParamFormat { param: n, expected: e, }, @@ -249,7 +395,9 @@ pub mod cli { **b = true; Ok(()) }, - None => Err(Error::UnknownParam(arg.to_string())), + None => { + Err(ParseError::UnknownParam(arg.to_string())) + }, } } else { if let Some(out) = out.as_mut() { @@ -259,15 +407,20 @@ pub mod cli { } }; - if let Some(e) = go().err() { - self.errors.push(e) + if let (Some(err), Some(errors)) = (go().err(), errors.as_mut()) + { + errors.push(err); } } } - pub fn parse_env(&mut self, out: Option<&mut Vec>) { + pub fn parse_env>( + &mut self, + out: Option<&mut Vec>, + errors: Option<&mut P>, + ) { let env_args = std::env::args().collect::>(); - self.parse(env_args.as_slice(), out); + self.parse(env_args.as_slice(), out, errors); } } @@ -315,13 +468,20 @@ pub mod cli { } pub fn bind(self, cli: &mut Cli<'a>) { - cli.bind(self); + cli.bind(self).unwrap() + } + + pub fn bind_checked(self, cli: &mut Cli<'a>) -> Result<(), BindError> { + cli.bind(self) } } #[cfg(test)] mod test { - use super::*; + use crate::{ + buf::Buf, + cli::{flag, param, Cli}, + }; #[test] fn basic() { @@ -344,15 +504,24 @@ pub mod cli { .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"], Some(&mut args), + Some(&mut errors), ); assert_eq!(foo, "foo"); assert_eq!(bar, 854); assert!(baz); assert_eq!(args, &["arg"]); + assert!(errors.is_empty()); } } }