Simplify + Cli
This commit is contained in:
parent
9d17672a76
commit
928d4ce0ba
166
Cargo.lock
generated
166
Cargo.lock
generated
|
|
@ -5,169 +5,3 @@ version = 3
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "e"
|
name = "e"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
|
||||||
"e-easy-default",
|
|
||||||
"e-easy-from",
|
|
||||||
"e-easy-get",
|
|
||||||
"e-impl-for-refs",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "e-easy-default"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"e-easy-default-core",
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "e-easy-default-core"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "e-easy-from"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"e-easy-from-core",
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "e-easy-from-core"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "e-easy-get"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"e-easy-get-core",
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "e-easy-get-core"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"e-easy-default",
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "e-impl-for-refs"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"e-impl-for-refs-core",
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "e-impl-for-refs-core"
|
|
||||||
version = "1.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-error-attr",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 1.0.109",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-error-attr"
|
|
||||||
version = "1.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro2"
|
|
||||||
version = "1.0.79"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
|
|
||||||
dependencies = [
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "quote"
|
|
||||||
version = "1.0.35"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "2.0.52"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-ident"
|
|
||||||
version = "1.0.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "version_check"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
|
||||||
|
|
|
||||||
33
Cargo.toml
33
Cargo.toml
|
|
@ -3,27 +3,18 @@ name = "e"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace]
|
# [workspace]
|
||||||
members = [
|
# members = []
|
||||||
"proc-macros/easy-default/core",
|
# resolver = "2"
|
||||||
"proc-macros/easy-default",
|
|
||||||
"proc-macros/easy-from/core",
|
|
||||||
"proc-macros/easy-from",
|
|
||||||
"proc-macros/easy-get/core",
|
|
||||||
"proc-macros/easy-get",
|
|
||||||
"proc-macros/impl-for-refs/core",
|
|
||||||
"proc-macros/impl-for-refs",
|
|
||||||
]
|
|
||||||
resolver = "2"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
e-easy-default = { path = "proc-macros/easy-default" }
|
# e-easy-default = { path = "proc-macros/easy-default" }
|
||||||
e-easy-from = { path = "proc-macros/easy-from" }
|
# e-easy-from = { path = "proc-macros/easy-from" }
|
||||||
e-easy-get = { path = "proc-macros/easy-get" }
|
# e-easy-get = { path = "proc-macros/easy-get" }
|
||||||
e-impl-for-refs = { path = "proc-macros/impl-for-refs" }
|
# e-impl-for-refs = { path = "proc-macros/impl-for-refs" }
|
||||||
|
|
||||||
[workspace.dependencies]
|
# [workspace.dependencies]
|
||||||
proc-macro-error = "1.0"
|
# proc-macro-error = "1.0"
|
||||||
proc-macro2 = "1"
|
# proc-macro2 = "1"
|
||||||
syn = { version = "2", features = ["full"] }
|
# syn = { version = "2", features = ["full"] }
|
||||||
quote = "1"
|
# quote = "1"
|
||||||
|
|
|
||||||
556
src/lib.rs
556
src/lib.rs
|
|
@ -1,224 +1,3 @@
|
||||||
/*
|
|
||||||
#[cfg(derive)]
|
|
||||||
pub mod derive {
|
|
||||||
pub use e_easy_default::EasyDefault;
|
|
||||||
pub use e_easy_from::EasyFrom;
|
|
||||||
pub use e_easy_get::EasyGet;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(meta)]
|
|
||||||
pub mod meta {
|
|
||||||
pub use e_impl_for_refs::impl_for_refs;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod args {
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use e_easy_get::EasyGet;
|
|
||||||
|
|
||||||
#[derive(Debug, EasyGet, Eq, PartialEq)]
|
|
||||||
pub struct MissingParamFor<T = &'static str> {
|
|
||||||
#[get_ref(pub)]
|
|
||||||
arg_name: T,
|
|
||||||
}
|
|
||||||
impl<T: core::fmt::Debug> core::fmt::Display for MissingParamFor<T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T: core::fmt::Debug> Error for MissingParamFor<T> {
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Arg<S> {
|
|
||||||
/// An arg that starts with a single `-` character. If multiple
|
|
||||||
/// characters follow the `-`, they will be returned as multiple,
|
|
||||||
/// separate Shorts
|
|
||||||
Short(char),
|
|
||||||
/// An arg that starts with `--`
|
|
||||||
Long(S),
|
|
||||||
/// An arg that has no preceding dashes
|
|
||||||
Param(S),
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ArgParserState {
|
|
||||||
ParsingSingle(std::vec::IntoIter<char>),
|
|
||||||
UseIterator,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ArgParser<I, S> {
|
|
||||||
iterator: I,
|
|
||||||
state: ArgParserState,
|
|
||||||
stored: Option<S>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<I, S> ArgParser<I, S>
|
|
||||||
where
|
|
||||||
I: Iterator<Item = S>,
|
|
||||||
S: AsRef<str>,
|
|
||||||
{
|
|
||||||
pub fn from_iterator(iterator: I) -> Self {
|
|
||||||
Self {
|
|
||||||
iterator,
|
|
||||||
state: ArgParserState::UseIterator,
|
|
||||||
stored: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::should_implement_trait)]
|
|
||||||
pub fn next(&mut self) -> Option<Arg<&str>> {
|
|
||||||
loop {
|
|
||||||
break match &mut self.state {
|
|
||||||
ArgParserState::ParsingSingle(it) => {
|
|
||||||
if let Some(c) = it.next() {
|
|
||||||
Some(Arg::Short(c))
|
|
||||||
} else {
|
|
||||||
self.state = ArgParserState::UseIterator;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ArgParserState::UseIterator => {
|
|
||||||
self.stored = Some(self.iterator.next()?);
|
|
||||||
|
|
||||||
let stored = self.stored.as_ref().unwrap();
|
|
||||||
let next = stored.as_ref();
|
|
||||||
if let Some(long) = next.strip_prefix("--") {
|
|
||||||
Some(Arg::Long(long))
|
|
||||||
} else if let Some(short) = next.strip_prefix('-') {
|
|
||||||
let mut chars = short.chars();
|
|
||||||
let out = chars.next()?;
|
|
||||||
let chars = chars.collect::<Vec<_>>().into_iter();
|
|
||||||
self.state = ArgParserState::ParsingSingle(chars);
|
|
||||||
|
|
||||||
Some(Arg::Short(out))
|
|
||||||
} else {
|
|
||||||
Some(Arg::Param(next))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_param_for<T>(
|
|
||||||
&mut self,
|
|
||||||
arg_name: T,
|
|
||||||
) -> Result<&str, MissingParamFor<T>> {
|
|
||||||
match self.next() {
|
|
||||||
Some(Arg::Param(p)) => Ok(p),
|
|
||||||
_ => Err(MissingParamFor { arg_name }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArgParser<std::env::Args, String> {
|
|
||||||
pub fn from_env() -> Self {
|
|
||||||
let it = std::env::args();
|
|
||||||
|
|
||||||
Self::from_iterator(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use std::num::ParseIntError;
|
|
||||||
|
|
||||||
use e_easy_default::EasyDefault;
|
|
||||||
use e_easy_from::EasyFrom;
|
|
||||||
|
|
||||||
use crate::args::{Arg, ArgParser, MissingParamFor};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_arg_parser() {
|
|
||||||
#[derive(EasyDefault, Debug, PartialEq, Eq)]
|
|
||||||
struct Args {
|
|
||||||
#[default("Hello world".to_string())]
|
|
||||||
string: String,
|
|
||||||
#[default(false)]
|
|
||||||
bool: bool,
|
|
||||||
#[default(8)]
|
|
||||||
u64: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(EasyFrom, Debug, PartialEq, Eq)]
|
|
||||||
#[from(infallible)]
|
|
||||||
enum Error {
|
|
||||||
#[from]
|
|
||||||
MissingParamFor(MissingParamFor<&'static str>),
|
|
||||||
#[from]
|
|
||||||
IntParseError(ParseIntError),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_args(strs: &[&str]) -> Result<Args, Error> {
|
|
||||||
let mut parser = ArgParser::from_iterator(strs.iter());
|
|
||||||
let mut args = Args::default();
|
|
||||||
|
|
||||||
while let Some(arg) = parser.next() {
|
|
||||||
match arg {
|
|
||||||
Arg::Long("string") => {
|
|
||||||
args.string =
|
|
||||||
parser.next_param_for("string")?.to_string()
|
|
||||||
},
|
|
||||||
Arg::Long("bool") => {
|
|
||||||
args.bool = true;
|
|
||||||
},
|
|
||||||
Arg::Long("u64") => {
|
|
||||||
args.u64 = parser.next_param_for("u64")?.parse()?
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
parse_args(&["--string"]),
|
|
||||||
Err(Error::MissingParamFor(MissingParamFor {
|
|
||||||
arg_name: "string"
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod str {
|
|
||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
#[derive(
|
|
||||||
Default, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Debug,
|
|
||||||
)]
|
|
||||||
pub struct NonEmptyStr<'a>(&'a str);
|
|
||||||
impl<'a> AsRef<str> for NonEmptyStr<'a> {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> Display for NonEmptyStr<'a> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait StrExt {
|
|
||||||
fn non_empty(&self) -> Option<NonEmptyStr>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> StrExt for T
|
|
||||||
where
|
|
||||||
T: AsRef<str>,
|
|
||||||
{
|
|
||||||
fn non_empty(&self) -> Option<NonEmptyStr> {
|
|
||||||
let s = self.as_ref();
|
|
||||||
|
|
||||||
if s.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(NonEmptyStr(s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
use core::hash::{Hash, Hasher};
|
use core::hash::{Hash, Hasher};
|
||||||
|
|
||||||
pub fn default<T: Default>() -> T {
|
pub fn default<T: Default>() -> T {
|
||||||
|
|
@ -242,3 +21,338 @@ where
|
||||||
self.0(f)
|
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"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue