From 346c8835cd12069586af6edd59720ddbc66c5a0e Mon Sep 17 00:00:00 2001 From: Nilstrieb Date: Sat, 25 Sep 2021 11:42:12 +0200 Subject: [PATCH] having problems with optionality --- examples/compiler.rs | 6 +-- src/lib.rs | 32 +++++++++----- src/parse.rs | 99 ++++++++++++++++++++++++++++++++++++-------- src/schema.rs | 53 ++++++++++++++++-------- 4 files changed, 142 insertions(+), 48 deletions(-) diff --git a/examples/compiler.rs b/examples/compiler.rs index a57d978..f2fe47b 100644 --- a/examples/compiler.rs +++ b/examples/compiler.rs @@ -7,7 +7,7 @@ arg!(OLevel: "optimize" -> usize); fn main() { let args = badargs::badargs::<(OutFile, (Force, OLevel))>().unwrap(); - let outfile = args.get::(); - let force = args.get::(); - let o_level = 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 ad2f736..2ad6317 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,10 +5,11 @@ mod schema; use crate::parse::CliArgs; use crate::schema::{IntoSchema, Schema, SchemaKind}; -pub use error::ArgError; +pub use error::SchemaError; pub use macros::*; +use std::any::Any; -pub type Result = std::result::Result; +pub type Result = std::result::Result; /// /// Parses the command line arguments based on the provided schema S @@ -18,7 +19,7 @@ where { let arg_schema = Schema::create::()?; - let args = CliArgs::from_args(&arg_schema, std::env::args())?; + let args = CliArgs::from_args(&arg_schema, std::env::args()).expect("todo"); Ok(BadArgs { args }) } @@ -33,7 +34,8 @@ where /// arg!(OutFile: "output", 'o' -> Option); /// // OutFile now implements CliArg /// ``` -pub trait CliArg { +// This trait requires any because some dynamic typing is done in the background +pub trait CliArg: Any { type Content: CliReturnValue; fn long() -> &'static str; @@ -41,7 +43,7 @@ pub trait CliArg { } /// The struct containing parsed argument information -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct BadArgs { args: CliArgs, } @@ -52,7 +54,10 @@ impl BadArgs { where T: CliArg, { - todo!() + let long_name = T::long(); + self.args + .get::(long_name) + .expect("it has been validated") } } @@ -89,14 +94,19 @@ mod sealed { } mod error { - /// The error type for `badargs` + /// Invalid schema #[derive(Debug, Clone, Eq, PartialEq)] - pub enum ArgError { - InvalidUtf8, + pub enum SchemaError { NameAlreadyExists(String), InvalidSchema(String), - IdkYet, - UnnamedArgument, + } + + /// Invalid arguments provided + #[derive(Debug, Clone, Eq, PartialEq)] + pub enum CallError { SingleMinus, + UnnamedArgument, + ShortFlagNotFound(char), + ExpectedValue(String), } } diff --git a/src/parse.rs b/src/parse.rs index da97231..23faa2b 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,16 +1,14 @@ -use super::Result; -use crate::schema::Schema; -use crate::ArgError; +use crate::error::CallError; +use crate::schema::{Schema, SchemaKind, SchemaKindType}; +use std::any::Any; use std::collections::HashMap; use std::iter::Peekable; -#[derive(Debug, Clone, Default)] +type Result = std::result::Result; + +#[derive(Debug, 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>, + args: HashMap<&'static str, Box>, } impl CliArgs { @@ -21,14 +19,25 @@ impl CliArgs { while let Some(arg) = args.next() { if let Some(shorts) = arg.strip_prefix('-') { parse_shorts(schema, &mut result, shorts, &mut args)?; - } else if let Some(longs) = arg.strip_prefix("--") { + } else if let Some(_longs) = arg.strip_prefix("--") { } else { - return Err(ArgError::UnnamedArgument); + return Err(CallError::UnnamedArgument); } } Ok(result) } + + /// Get a value from the map, expecting it to have type T + /// Important: T should never be Option, making thisfh sjfhsekld fjkdsaljföoilkaesdf jikasoeldöojfliköesdafjisdolkyafj idrs + pub fn get(&self, long: &str) -> Option<&T> { + let any = self.args.get(long)?; + any.downcast_ref() + } + + fn insert(&mut self, long: &'static str, value: Box) { + self.args.insert(long, value); + } } fn parse_shorts( @@ -37,16 +46,72 @@ fn parse_shorts( shorts: &str, args: &mut Peekable>, ) -> Result<()> { - if shorts.len() == 0 { - return Err(ArgError::SingleMinus); + // there are kinds of short arguments + // single shorts that takes values: `-o main` + // multiple flags combined: `-xzf` + // combining these is invalid: `-xo main` + + let mut chars = shorts.chars(); + + let first_flag = chars.next(); + + if let Some(flag) = first_flag { + let command = schema + .short(flag) + .ok_or_else(|| CallError::ShortFlagNotFound(flag))?; + + let inner_kind = match command.kind { + SchemaKind::Required(inner) => inner, + SchemaKind::Optional(inner) => inner, + }; + + match inner_kind { + SchemaKindType::String => { + let next = args + .next() + .ok_or_else(|| CallError::ExpectedValue(command.long.to_string()))?; + results.insert(command.long, Box::new(next)); + } + _ => todo!(), + } + } else { + return Err(CallError::SingleMinus); } - for flag_name in shorts.chars() {} + for _flag_name in chars {} Ok(()) } -fn expects_value_short(schema: &Schema, name: char) -> bool { - schema.short('5'); - true +#[cfg(test)] +mod test { + use super::*; + use crate::arg; + use crate::schema::Schema; + + arg!(OutFile: "output", 'o' -> Option); + arg!(Input: "input", 'i' -> String); + arg!(Force: "force", 'f' -> bool); + arg!(SetUpstream: "set-upstream" -> String); + + fn schema() -> Schema { + Schema::create::<((OutFile, Input), (Force, SetUpstream))>().unwrap() + } + + fn parse_args(args: &str) -> Result { + CliArgs::from_args(&schema(), args.split_whitespace().map(|s| s.to_owned())) + } + + #[test] + #[ignore] + fn single_short_flag() { + let args = parse_args("-f").unwrap(); + assert_eq!(args.get::("force"), Some(&true)) + } + + #[test] + fn single_string_arg() { + let args = parse_args("-i stdin").unwrap(); + assert_eq!(args.get::("input"), Some(&"stdin".to_string())) + } } diff --git a/src/schema.rs b/src/schema.rs index a49e862..c80dbc3 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -4,17 +4,22 @@ //! This makes the interface of this crate fully type-safe! (and kind of cursed) use super::Result; -use crate::{ArgError, CliArg, CliReturnValue}; +use crate::{CliArg, CliReturnValue, SchemaError}; use std::collections::HashMap; +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SchemaKind { + Required(SchemaKindType), + Optional(SchemaKindType), +} + /// /// The type of value the argument returns /// /// This could *maybe* also be solved with trait objects but lets keep this for now #[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum SchemaKind { +pub enum SchemaKindType { String, - OptionString, Bool, INum, UNum, @@ -24,7 +29,9 @@ pub enum SchemaKind { /// A single command in the schema #[derive(Debug, Clone, Eq, PartialEq, Copy)] pub struct SchemaCommand { - kind: SchemaKind, + pub kind: SchemaKind, + pub long: &'static str, + pub short: Option, } /// @@ -48,7 +55,7 @@ impl Schema { fn add_command(&mut self, long_name: &'static str, command: SchemaCommand) -> Result<()> { if let Some(_) = self.longs.insert(long_name, command) { - Err(ArgError::NameAlreadyExists(long_name.to_string())) + Err(SchemaError::NameAlreadyExists(long_name.to_string())) } else { Ok(()) } @@ -64,7 +71,7 @@ impl Schema { fn add_short_command(&mut self, short_name: char, command: SchemaCommand) -> Result<()> { if let Some(_) = self.shorts.insert(short_name, command) { - Err(ArgError::NameAlreadyExists(short_name.to_string())) + Err(SchemaError::NameAlreadyExists(short_name.to_string())) } else { Ok(()) } @@ -101,20 +108,20 @@ where { fn add_schema(schema: &mut Schema) -> Result<()> { let kind = T::Content::kind(); - let name = T::long(); + let long = T::long(); let short = T::short(); - let command = SchemaCommand { kind }; + let command = SchemaCommand { kind, long, short }; if let Some(short_name) = short { schema.add_short_command(short_name, command)?; } - schema.add_command(name, command) + schema.add_command(long, command) } } #[cfg(test)] mod test { - use crate::schema::{Schema, SchemaCommand, SchemaKind}; - use crate::{arg, ArgError}; + use crate::arg; + use crate::schema::{Schema, SchemaCommand, SchemaKind, SchemaKindType}; arg!(OutFile: "output", 'o' -> Option); arg!(Force: "force", 'f' -> bool); @@ -125,7 +132,9 @@ mod test { fn one_command_schema() { let schema = Schema::create::().unwrap(); let out_file = SchemaCommand { - kind: SchemaKind::OptionString, + kind: SchemaKind::Optional(SchemaKindType::String), + long: "output", + short: Some('o'), }; assert_eq!(schema.longs.get("output"), Some(&out_file)); assert_eq!(schema.shorts.get(&'o'), Some(&out_file)); @@ -136,10 +145,14 @@ mod test { fn two_command_schema() { let schema = Schema::create::<(OutFile, Force)>().unwrap(); let out_file = SchemaCommand { - kind: SchemaKind::OptionString, + kind: SchemaKind::Optional(SchemaKindType::String), + long: "output", + short: Some('o'), }; let force = SchemaCommand { - kind: SchemaKind::Bool, + kind: SchemaKind::Required(SchemaKindType::Bool), + long: "force", + short: Some('f'), }; assert_eq!(schema.longs.get("output"), Some(&out_file)); @@ -155,13 +168,19 @@ mod test { fn three_command_schema() { let schema = Schema::create::<(OutFile, (Force, SetUpstream))>().unwrap(); let out_file = SchemaCommand { - kind: SchemaKind::OptionString, + kind: SchemaKind::Optional(SchemaKindType::String), + long: "output", + short: Some('o'), }; let force = SchemaCommand { - kind: SchemaKind::Bool, + kind: SchemaKind::Required(SchemaKindType::Bool), + long: "force", + short: Some('f'), }; let set_upstream = SchemaCommand { - kind: SchemaKind::String, + kind: SchemaKind::Required(SchemaKindType::String), + long: "set-upstream", + short: None, }; assert_eq!(schema.longs.get("output"), Some(&out_file));