Cli changes + Buf

This commit is contained in:
soup 2024-05-07 22:21:18 -04:00 committed by code
parent 928d4ce0ba
commit 7e44e93125

View file

@ -22,14 +22,138 @@ where
} }
} }
pub mod buf {
use core::mem::MaybeUninit;
pub struct Buf<T> {
storage: Box<[MaybeUninit<T>]>,
len: usize,
}
impl<T> Buf<T> {
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<T> core::ops::Index<usize> for Buf<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
&self.as_slice()[index]
}
}
impl<T> Push<T> for Buf<T> {
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<T> Push<T> for Vec<T> {
fn push_checked(&mut self, item: T) -> Result<(), OutOfSpace> {
self.push(item);
Ok(())
}
}
impl<T> Push<T> for () {
fn push(&mut self, _: T) {
}
}
impl<T> Push<T> for Option<T> {
fn push(&mut self, item: T) {
*self = Some(item);
}
}
pub struct OutOfSpace;
pub trait Push<T> {
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 { pub mod cli {
//! Simple utilities for parsing arguments and generating command line help //! Simple utilities for parsing arguments and generating command line help
use crate::default; use crate::{buf::Push, default};
use core::str::FromStr; use core::str::FromStr;
#[derive(Debug)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum Error { pub enum ParseError {
InvalidParamFormat { InvalidParamFormat {
param: &'static str, param: &'static str,
expected: &'static str, expected: &'static str,
@ -39,6 +163,12 @@ pub mod cli {
SingleDashMustBeSingleCharacter(String), SingleDashMustBeSingleCharacter(String),
} }
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum BindError {
DuplicateShort(char),
DuplicateLong(&'static str),
}
pub trait Value { pub trait Value {
fn assign(&mut self, param: &str) -> Result<(), &'static str>; fn assign(&mut self, param: &str) -> Result<(), &'static str>;
} }
@ -106,7 +236,16 @@ pub mod cli {
long: &'static str, long: &'static str,
short: Option<char>, short: Option<char>,
description: Option<&'static str>, 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(); let next = self.ts.len();
self.ts.push(t); self.ts.push(t);
@ -117,6 +256,8 @@ pub mod cli {
self.shorts.push(c); self.shorts.push(c);
self.shorts_indices.push(next); self.shorts_indices.push(next);
} }
Ok(())
} }
fn find_for_long( fn find_for_long(
@ -152,9 +293,8 @@ pub mod cli {
params: Params<'a>, params: Params<'a>,
flags: Flags<'a>, flags: Flags<'a>,
errors: Vec<Error>,
} }
impl<'a> Cli<'a> { impl<'a> Cli<'a> {
pub fn new(name: &'static str) -> Self { pub fn new(name: &'static str) -> Self {
Self { Self {
@ -164,8 +304,6 @@ pub mod cli {
params: Soa::new(), params: Soa::new(),
flags: Soa::new(), flags: Soa::new(),
errors: default(),
} }
} }
@ -179,7 +317,7 @@ pub mod cli {
self self
} }
pub fn bind(&mut self, pb: ParamBuilder<'a>) { pub fn bind(&mut self, pb: ParamBuilder<'a>) -> Result<(), BindError> {
match pb.kind { match pb.kind {
Kind::Param(p) => { Kind::Param(p) => {
self.params.add(p, pb.long, pb.short, pb.description) self.params.add(p, pb.long, pb.short, pb.description)
@ -190,19 +328,25 @@ pub mod cli {
} }
} }
pub fn parse<'b, A: AsRef<str>, B: From<&'b str>>( pub fn parse<
'b,
A: AsRef<str>,
B: From<&'b str>,
P: Push<ParseError>,
>(
&mut self, &mut self,
args: &'b [A], args: &'b [A],
mut out: Option<&mut Vec<B>>, mut out: Option<&mut Vec<B>>,
mut errors: Option<&mut P>,
) { ) {
let mut it = args.iter(); let mut it = args.iter();
fn next_param<'a, A: AsRef<str> + 'a>( fn next_param<'a, A: AsRef<str> + 'a>(
it: &mut impl Iterator<Item = &'a A>, it: &mut impl Iterator<Item = &'a A>,
name: &'static str, name: &'static str,
) -> Result<&'a str, Error> { ) -> Result<&'a str, ParseError> {
it.next() it.next()
.map(|s| s.as_ref()) .map(|s| s.as_ref())
.ok_or(Error::MissingParamFor(name)) .ok_or(ParseError::MissingParamFor(name))
} }
while let Some(arg) = it.next() { 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(arg) = arg.as_ref().strip_prefix("--") {
if let Some((n, b)) = self.params.find_for_long(arg) { if let Some((n, b)) = self.params.find_for_long(arg) {
return b.assign(next_param(&mut it, n)?).map_err( return b.assign(next_param(&mut it, n)?).map_err(
|e| Error::InvalidParamFormat { |e| ParseError::InvalidParamFormat {
param: n, param: n,
expected: e, expected: e,
}, },
@ -222,12 +366,14 @@ pub mod cli {
**b = true; **b = true;
Ok(()) 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('-') { } else if let Some(arg) = arg.as_ref().strip_prefix('-') {
if arg.len() > 1 { if arg.len() > 1 {
return Err( return Err(
Error::SingleDashMustBeSingleCharacter( ParseError::SingleDashMustBeSingleCharacter(
arg.to_string(), arg.to_string(),
), ),
); );
@ -237,7 +383,7 @@ pub mod cli {
if let Some((n, b)) = self.params.find_for_short(arg) { if let Some((n, b)) = self.params.find_for_short(arg) {
return b.assign(next_param(&mut it, n)?).map_err( return b.assign(next_param(&mut it, n)?).map_err(
|e| Error::InvalidParamFormat { |e| ParseError::InvalidParamFormat {
param: n, param: n,
expected: e, expected: e,
}, },
@ -249,7 +395,9 @@ pub mod cli {
**b = true; **b = true;
Ok(()) Ok(())
}, },
None => Err(Error::UnknownParam(arg.to_string())), None => {
Err(ParseError::UnknownParam(arg.to_string()))
},
} }
} else { } else {
if let Some(out) = out.as_mut() { if let Some(out) = out.as_mut() {
@ -259,15 +407,20 @@ pub mod cli {
} }
}; };
if let Some(e) = go().err() { if let (Some(err), Some(errors)) = (go().err(), errors.as_mut())
self.errors.push(e) {
errors.push(err);
} }
} }
} }
pub fn parse_env(&mut self, out: Option<&mut Vec<String>>) { pub fn parse_env<P: Push<ParseError>>(
&mut self,
out: Option<&mut Vec<String>>,
errors: Option<&mut P>,
) {
let env_args = std::env::args().collect::<Vec<_>>(); let env_args = std::env::args().collect::<Vec<_>>();
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>) { 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)] #[cfg(test)]
mod test { mod test {
use super::*; use crate::{
buf::Buf,
cli::{flag, param, Cli},
};
#[test] #[test]
fn basic() { fn basic() {
@ -344,15 +504,24 @@ pub mod cli {
.description("baz") .description("baz")
.bind(&mut cli); .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 args: Vec<&str> = Vec::new();
let mut errors = Buf::new(1);
cli.parse( cli.parse(
&["--foo", "foo", "--bar", "854", "-b", "arg"], &["--foo", "foo", "--bar", "854", "-b", "arg"],
Some(&mut args), Some(&mut args),
Some(&mut errors),
); );
assert_eq!(foo, "foo"); assert_eq!(foo, "foo");
assert_eq!(bar, 854); assert_eq!(bar, 854);
assert!(baz); assert!(baz);
assert_eq!(args, &["arg"]); assert_eq!(args, &["arg"]);
assert!(errors.is_empty());
} }
} }
} }