Cli changes + Buf
This commit is contained in:
parent
928d4ce0ba
commit
7e44e93125
215
src/lib.rs
215
src/lib.rs
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue