Clean up
This commit is contained in:
parent
2772f2c38b
commit
03630d905a
519
src/lib.rs
519
src/lib.rs
|
|
@ -21,522 +21,3 @@ where
|
|||
self.0(f)
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, R, T> Push<T> for &'a mut R
|
||||
where
|
||||
R: Push<T>,
|
||||
{
|
||||
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<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 {
|
||||
//! 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<T> 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<Option<&'static str>>,
|
||||
|
||||
// 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<char>,
|
||||
shorts_indices: Vec<usize>,
|
||||
}
|
||||
|
||||
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<char>,
|
||||
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<str>,
|
||||
B: From<&'b str>,
|
||||
PB: Push<B>,
|
||||
PE: Push<ParseError>,
|
||||
>(
|
||||
&mut self,
|
||||
args: &'b [A],
|
||||
mut out: PB,
|
||||
mut errors: PE,
|
||||
) {
|
||||
let mut it = args.iter();
|
||||
fn next_param<'a, A: AsRef<str> + 'a>(
|
||||
it: &mut impl Iterator<Item = &'a A>,
|
||||
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<PS: Push<String>, PE: Push<ParseError>>(
|
||||
&mut self,
|
||||
out: PS,
|
||||
errors: PE,
|
||||
) {
|
||||
let env_args = std::env::args().collect::<Vec<_>>();
|
||||
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<char>,
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue