359 lines
7.4 KiB
Rust
359 lines
7.4 KiB
Rust
use core::hash::{Hash, Hasher};
|
|
|
|
pub fn default<T: Default>() -> T {
|
|
T::default()
|
|
}
|
|
|
|
pub fn hash<T: Hasher + Default, U: Hash>(value: U) -> u64 {
|
|
let mut hasher: T = default();
|
|
value.hash(&mut hasher);
|
|
hasher.finish()
|
|
}
|
|
|
|
pub struct DisplayFn<F: Fn(&mut core::fmt::Formatter) -> core::fmt::Result>(
|
|
pub F,
|
|
);
|
|
impl<F> core::fmt::Display for DisplayFn<F>
|
|
where
|
|
F: Fn(&mut core::fmt::Formatter<'_>) -> core::fmt::Result,
|
|
{
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
self.0(f)
|
|
}
|
|
}
|
|
|
|
pub mod cli {
|
|
//! Simple utilities for parsing arguments and generating command line help
|
|
|
|
use crate::default;
|
|
use core::str::FromStr;
|
|
|
|
#[derive(Debug)]
|
|
pub enum Error {
|
|
InvalidParamFormat {
|
|
param: &'static str,
|
|
expected: &'static str,
|
|
},
|
|
MissingParamFor(&'static str),
|
|
UnknownParam(String),
|
|
SingleDashMustBeSingleCharacter(String),
|
|
}
|
|
|
|
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>,
|
|
) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
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>,
|
|
|
|
errors: Vec<Error>,
|
|
}
|
|
impl<'a> Cli<'a> {
|
|
pub fn new(name: &'static str) -> Self {
|
|
Self {
|
|
name,
|
|
description: default(),
|
|
version: default(),
|
|
|
|
params: Soa::new(),
|
|
flags: Soa::new(),
|
|
|
|
errors: default(),
|
|
}
|
|
}
|
|
|
|
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>) {
|
|
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>>(
|
|
&mut self,
|
|
args: &'b [A],
|
|
mut out: Option<&mut Vec<B>>,
|
|
) {
|
|
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, Error> {
|
|
it.next()
|
|
.map(|s| s.as_ref())
|
|
.ok_or(Error::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| Error::InvalidParamFormat {
|
|
param: n,
|
|
expected: e,
|
|
},
|
|
);
|
|
};
|
|
|
|
match self.flags.find_for_long(arg) {
|
|
Some((_, b)) => {
|
|
**b = true;
|
|
Ok(())
|
|
},
|
|
None => Err(Error::UnknownParam(arg.to_string())),
|
|
}
|
|
} else if let Some(arg) = arg.as_ref().strip_prefix('-') {
|
|
if arg.len() > 1 {
|
|
return Err(
|
|
Error::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| Error::InvalidParamFormat {
|
|
param: n,
|
|
expected: e,
|
|
},
|
|
);
|
|
};
|
|
|
|
match self.flags.find_for_short(arg) {
|
|
Some((_, b)) => {
|
|
**b = true;
|
|
Ok(())
|
|
},
|
|
None => Err(Error::UnknownParam(arg.to_string())),
|
|
}
|
|
} else {
|
|
if let Some(out) = out.as_mut() {
|
|
out.push(arg.as_ref().into());
|
|
}
|
|
Ok(())
|
|
}
|
|
};
|
|
|
|
if let Some(e) = go().err() {
|
|
self.errors.push(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn parse_env(&mut self, out: Option<&mut Vec<String>>) {
|
|
let env_args = std::env::args().collect::<Vec<_>>();
|
|
self.parse(env_args.as_slice(), out);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[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 args: Vec<&str> = Vec::new();
|
|
cli.parse(
|
|
&["--foo", "foo", "--bar", "854", "-b", "arg"],
|
|
Some(&mut args),
|
|
);
|
|
assert_eq!(foo, "foo");
|
|
assert_eq!(bar, 854);
|
|
assert!(baz);
|
|
assert_eq!(args, &["arg"]);
|
|
}
|
|
}
|
|
}
|