Simplify + Cli

This commit is contained in:
soup 2024-05-07 21:15:02 -04:00 committed by code
parent 9d17672a76
commit 928d4ce0ba
3 changed files with 347 additions and 408 deletions

166
Cargo.lock generated
View file

@ -5,169 +5,3 @@ version = 3
[[package]]
name = "e"
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"

View file

@ -3,27 +3,18 @@ name = "e"
version = "1.0.0"
edition = "2021"
[workspace]
members = [
"proc-macros/easy-default/core",
"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"
# [workspace]
# members = []
# resolver = "2"
[dependencies]
e-easy-default = { path = "proc-macros/easy-default" }
e-easy-from = { path = "proc-macros/easy-from" }
e-easy-get = { path = "proc-macros/easy-get" }
e-impl-for-refs = { path = "proc-macros/impl-for-refs" }
# e-easy-default = { path = "proc-macros/easy-default" }
# e-easy-from = { path = "proc-macros/easy-from" }
# e-easy-get = { path = "proc-macros/easy-get" }
# e-impl-for-refs = { path = "proc-macros/impl-for-refs" }
[workspace.dependencies]
proc-macro-error = "1.0"
proc-macro2 = "1"
syn = { version = "2", features = ["full"] }
quote = "1"
# [workspace.dependencies]
# proc-macro-error = "1.0"
# proc-macro2 = "1"
# syn = { version = "2", features = ["full"] }
# quote = "1"

View file

@ -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};
pub fn default<T: Default>() -> T {
@ -242,3 +21,338 @@ where
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"]);
}
}
}