From cdb67f070c3abd9c61848d57ef6c7c5182d6cd3e Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Fri, 24 Sep 2021 22:17:30 +0200 Subject: [PATCH] oh shit this might actually be quite usable --- README.md | 4 +- examples/compiler.rs | 28 +++------ src/lib.rs | 141 ++++++++++++++++++++++++++++++++++--------- src/macros.rs | 25 ++++++++ src/schema.rs | 71 ++++++++++++++++++++++ 5 files changed, 221 insertions(+), 48 deletions(-) create mode 100644 src/macros.rs create mode 100644 src/schema.rs diff --git a/README.md b/README.md index 0019620..aa89ab3 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,6 @@ A Rust fully type-safe argument parser without proc-macros Usability comes after that -oh and it's also not even close to being usable in any way at all so there's that \ No newline at end of file +oh and it's also not even close to being usable in any way at all so there's that + +oh and also 0 dependencies btw \ No newline at end of file diff --git a/examples/compiler.rs b/examples/compiler.rs index 8e20680..e4cd1f7 100644 --- a/examples/compiler.rs +++ b/examples/compiler.rs @@ -1,24 +1,14 @@ -use badargs::{CliArg, CliArgInfo, Template}; -use std::collections::HashMap; +use badargs::arg; +use badargs::CliArg; -#[derive(Default)] -struct OutFile; +arg!(OutFile: "output", "o" -> Option); +arg!(Force: "force", "f" -> bool); +arg!(OLevel: "optimize" -> usize); fn main() { - let args = badargs::badargs(Template { - options: { - let mut map = HashMap::new(); - map.insert( - Box::new(OutFile), - CliArgInfo { - name: "output".to_string(), - allow_short: true, - takes_value: true, - }, - ); - map - }, - }); + let args = badargs::badargs::<(OutFile, (Force, OLevel))>().unwrap(); - let outfile = args.get::(); + let _outfile = args.get::(); + let _force = args.get::(); + let _o_level = args.get::(); } diff --git a/src/lib.rs b/src/lib.rs index d0b71dc..081cbf5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,43 +1,128 @@ -use std::any::{Any, TypeId}; -use std::collections::HashMap; +mod macros; +mod schema; -#[derive(Debug, Clone)] -pub enum CliOption { - Flag(bool), - Value(String), +use crate::parse::CliArgs; +use crate::schema::{IntoSchema, SchemaKind}; + +pub use error::ArgError; +pub use macros::*; + +pub type Result = std::result::Result; + +pub trait CliReturnValue: sealed::SealedCliReturnValue { + fn kind() -> schema::SchemaKind; +} + +macro_rules! impl_cli_return { + ($(for $ty:ty => $type:ident);+) => {$( + impl CliReturnValue for $ty { + fn kind() -> SchemaKind { + SchemaKind::$type + } + } + )+}; +} + +impl_cli_return!( + for String => String; + for Option => OptionString; + for bool => Bool; + for isize => INum; + for usize => UNum +); + +mod sealed { + pub trait SealedCliReturnValue {} + macro_rules! impl_ { + ($($name:ty),+) => {$(impl SealedCliReturnValue for $name{})+}; + } + impl_!(String, Option, bool, usize, isize); } pub trait CliArg { - type Content; -} - -#[derive(Debug, Clone, Default)] -pub struct Template { - pub options: HashMap, CliArgInfo>, -} - -pub struct CliArgInfo { - pub name: String, - pub allow_short: bool, - pub takes_value: bool, + type Content: CliReturnValue; + fn long() -> &'static str; + fn short() -> Option<&'static str>; } #[derive(Debug, Clone, Default)] pub struct BadArgs { - options: HashMap, + args: CliArgs, } impl BadArgs { - pub fn get(&self) -> Option<&CliArgInfo> { - self.options.get(&T::type_id()) + pub fn get(&self) -> &T::Content + where + T: CliArg, + { + todo!() } } -pub fn badargs(template: Template) -> BadArgs { - let options = template - .options - .into_iter() - .map(|(key, value)| (key.type_id(), value)) - .collect(); - BadArgs { options } +pub fn badargs() -> Result +where + S: IntoSchema, +{ + let arg_schema = schema::parse_schema::()?; + + let args = CliArgs::from_args(arg_schema, std::env::args_os())?; + + Ok(BadArgs { args }) +} + +mod error { + #[derive(Debug, Clone)] + pub enum ArgError { + InvalidUtf8, + NameAlreadyExists(&'static str), + InvalidSchema(String), + IdkYet, + } +} + +mod parse { + use super::Result; + use crate::schema::Schema; + use std::collections::HashMap; + use std::ffi::OsString; + + #[derive(Debug, Clone, Default)] + pub struct CliArgs { + pub isize: HashMap<&'static str, isize>, + pub usize: HashMap<&'static str, isize>, + pub string: HashMap<&'static str, String>, + pub option_string: HashMap<&'static str, Option>, + pub bool: HashMap<&'static str, bool>, + } + + impl CliArgs { + pub fn from_args(_schema: Schema, args: impl Iterator) -> Result { + let mut result = Self::default(); + let mut args = args; + while let Some(_arg) = args.next() {} + + Ok(result) + } + } +} + +#[cfg(test)] +mod test { + use crate::CliArg; + + struct OutFile; + impl CliArg for OutFile { + type Content = Option; + + fn long() -> &'static str { + "output" + } + + fn short() -> Option<&'static str> { + Some("o") + } + } + + #[test] + fn get_single_schema() {} } diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..d1d9621 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,25 @@ +#[macro_export] +macro_rules! arg { + ($name:ident: $long:literal, $short:literal -> $result:ty) => { + arg!(@$name: ($long, ::std::option::Option::Some($short)) -> $result); + }; + ($name:ident: $long:literal -> $result:ty) => { + arg!(@$name: ($long, ::std::option::Option::None) -> $result); + }; + (@$name:ident: ($long:literal, $short:expr) -> $result:ty) => { + #[derive(Default)] + struct $name; + + impl ::badargs::CliArg for $name { + type Content = $result; + + fn long() -> &'static str { + $long + } + + fn short() -> Option<&'static str> { + $short + } + } + }; +} diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..1e4a191 --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,71 @@ +use super::Result; +use crate::{ArgError, CliArg, CliReturnValue}; +use std::collections::HashMap; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SchemaKind { + String, + OptionString, + Bool, + INum, + UNum, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SchemaCommand { + short: Option<&'static str>, + kind: SchemaKind, +} + +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub struct Schema { + commands: HashMap<&'static str, SchemaCommand>, +} + +impl Schema { + pub fn add_command(&mut self, name: &'static str, command: SchemaCommand) -> Result<()> { + if let Some(_) = self.commands.insert(name, command) { + Err(ArgError::NameAlreadyExists(name)) + } else { + Ok(()) + } + } +} + +pub trait IntoSchema { + fn add_schema(schema: &mut Schema) -> Result<()>; +} + +/// Allow using multiple schema values, these tuples can be nested :D +impl IntoSchema for (S1, S2) +where + S1: IntoSchema, + S2: IntoSchema, +{ + fn add_schema(schema: &mut Schema) -> Result<()> { + S1::add_schema(schema)?; + S2::add_schema(schema) + } +} + +pub fn parse_schema() -> Result +where + S: IntoSchema, +{ + let mut schema = Schema::default(); + S::add_schema(&mut schema)?; + Ok(schema) +} + +/// Create the Schema from the CliArg type +impl IntoSchema for T +where + T: CliArg, +{ + fn add_schema(schema: &mut Schema) -> Result<()> { + let kind = T::Content::kind(); + let name = T::long(); + let short = T::short(); + schema.add_command(name, SchemaCommand { short, kind }) + } +}