diff --git a/Cargo.lock b/Cargo.lock index 9d8e828..da4d4ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 2bff39d..40afde2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/lib.rs b/src/lib.rs index 4a2be26..f564366 100644 --- a/src/lib.rs +++ b/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 { - #[get_ref(pub)] - arg_name: T, - } - impl core::fmt::Display for MissingParamFor { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } - } - impl Error for MissingParamFor { - } - - pub enum Arg { - /// 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), - UseIterator, - } - - pub struct ArgParser { - iterator: I, - state: ArgParserState, - stored: Option, - } - - impl ArgParser - where - I: Iterator, - S: AsRef, - { - 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> { - 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::>().into_iter(); - self.state = ArgParserState::ParsingSingle(chars); - - Some(Arg::Short(out)) - } else { - Some(Arg::Param(next)) - } - }, - }; - } - } - - pub fn next_param_for( - &mut self, - arg_name: T, - ) -> Result<&str, MissingParamFor> { - match self.next() { - Some(Arg::Param(p)) => Ok(p), - _ => Err(MissingParamFor { arg_name }), - } - } - } - - impl ArgParser { - 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 { - 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 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; - } - - impl StrExt for T - where - T: AsRef, - { - fn non_empty(&self) -> Option { - let s = self.as_ref(); - - if s.is_empty() { - None - } else { - Some(NonEmptyStr(s)) - } - } - } -} -*/ - use core::hash::{Hash, Hasher}; pub fn 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 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>, + + // 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, + shorts_indices: Vec, + } + + 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, + 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, + } + 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, B: From<&'b str>>( + &mut self, + args: &'b [A], + mut out: Option<&mut Vec>, + ) { + let mut it = args.iter(); + fn next_param<'a, A: AsRef + 'a>( + it: &mut impl Iterator, + 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>) { + let env_args = std::env::args().collect::>(); + 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, + } + + 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"]); + } + } +}