From afffd1ef917ad81d936f6ba24beaf12984d5c791 Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Tue, 18 Nov 2025 13:00:04 +1100 Subject: [PATCH] Split parse state validation and make parse context arg optional. --- jaarg-nostd/examples/basic_nostd.rs | 6 ++--- jaarg/examples/basic.rs | 10 ++++----- jaarg/examples/bin2h.rs | 8 +++---- jaarg/src/alloc.rs | 6 ++--- jaarg/src/argparse.rs | 34 +++++++++++++++++------------ 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/jaarg-nostd/examples/basic_nostd.rs b/jaarg-nostd/examples/basic_nostd.rs index de90ae6..044b46f 100644 --- a/jaarg-nostd/examples/basic_nostd.rs +++ b/jaarg-nostd/examples/basic_nostd.rs @@ -44,9 +44,9 @@ extern "C" fn safe_main(args: &[&str]) -> ExitCode { print!("{}", StandardFullHelpWriter::<'_, Arg>::new(ctx)); return Ok(ParseControl::Quit); } - Arg::Number => { number = str::parse(ctx.arg)?; } - Arg::File => { file = ctx.arg.into(); } - Arg::Out => { out = Some(ctx.arg.into()); } + Arg::Number => { number = str::parse(ctx.arg.unwrap())?; } + Arg::File => { file = ctx.arg.unwrap().into(); } + Arg::Out => { out = Some(ctx.arg.unwrap().into()); } } Ok(ParseControl::Continue) }, |program_name, error| { diff --git a/jaarg/examples/basic.rs b/jaarg/examples/basic.rs index 907792b..e48eb36 100644 --- a/jaarg/examples/basic.rs +++ b/jaarg/examples/basic.rs @@ -32,15 +32,15 @@ fn main() { OPTIONS.print_full_help(ctx.program_name); return Ok(ParseControl::Quit); } - Arg::Number => { number = str::parse(ctx.arg)?; } - Arg::File => { file = ctx.arg.into(); } - Arg::Out => { out = Some(ctx.arg.into()); } + Arg::Number => { number = str::parse(ctx.arg.unwrap().as_ref())?; } + Arg::File => { file = ctx.arg.unwrap().into(); } + Arg::Out => { out = Some(ctx.arg.unwrap().into()); } } Ok(ParseControl::Continue) }) { ParseResult::ContinueSuccess => (), - ParseResult::ExitSuccess => std::process::exit(0), - ParseResult::ExitFailure => std::process::exit(1), + ParseResult::ExitSuccess => std::process::exit(0), + ParseResult::ExitFailure => std::process::exit(1), } // Print the result variables diff --git a/jaarg/examples/bin2h.rs b/jaarg/examples/bin2h.rs index 1453082..baac1fa 100644 --- a/jaarg/examples/bin2h.rs +++ b/jaarg/examples/bin2h.rs @@ -212,10 +212,10 @@ pub fn main() -> ExitCode { as arrays and C strings respectively."); match OPTIONS.parse_easy(|ctx| { match ctx.id { - Arg::Out => { arguments.out = ctx.arg.into(); } - Arg::Bin => { jobs.push(Job { job_type: JobType::Binary, path: ctx.arg.into() }); } - Arg::Txt => { jobs.push(Job { job_type: JobType::Text, path: ctx.arg.into() }); } - Arg::Whitespace => { arguments.whitespace = ctx.arg.into(); } + Arg::Out => { arguments.out = ctx.arg.unwrap().into(); } + Arg::Bin => { jobs.push(Job { job_type: JobType::Binary, path: ctx.arg.unwrap().into() }); } + Arg::Txt => { jobs.push(Job { job_type: JobType::Text, path: ctx.arg.unwrap().into() }); } + Arg::Whitespace => { arguments.whitespace = ctx.arg.unwrap().into(); } Arg::Help => { OPTIONS.print_full_help(ctx.program_name); return Ok(ParseControl::Quit); diff --git a/jaarg/src/alloc.rs b/jaarg/src/alloc.rs index d6051ca..68ab2b3 100644 --- a/jaarg/src/alloc.rs +++ b/jaarg/src/alloc.rs @@ -5,9 +5,9 @@ extern crate alloc; -use alloc::collections::BTreeMap; -use alloc::string::String; use crate::{Opts, ParseControl, ParseError, ParseResult}; +use alloc::collections::BTreeMap; +use alloc::string::{String, ToString}; impl Opts<&'static str> { /// Parse an iterator of strings as arguments and return the results in a [`BTreeMap`]. @@ -22,7 +22,7 @@ impl Opts<&'static str> { help(program_name); Ok(ParseControl::Quit) } else { - out.insert(ctx.id, ctx.arg.into()); + out.insert(ctx.id, ctx.arg.unwrap().to_string()); Ok(ParseControl::Continue) } }, error) { diff --git a/jaarg/src/argparse.rs b/jaarg/src/argparse.rs index b627d4a..56b2e38 100644 --- a/jaarg/src/argparse.rs +++ b/jaarg/src/argparse.rs @@ -39,8 +39,8 @@ pub struct ParseHandlerContext<'a, ID: 'static> { /// The name of the argument parameter that was matched, /// for option parameters this is the token supplied by the user. pub name: &'a str, - /// The argument provided to positional arguments and value options, else "". - pub arg: &'a str, + /// The argument provided to positional arguments and value options (will always be Some), or None for flags. + pub arg: Option<&'a str>, } /// Result type used by the handler passed to the parser. @@ -151,6 +151,11 @@ impl Opts { } } + self.validate_state(program_name, state, error) + } + + fn validate_state(&self, program_name: &str, mut state: ParserState, error: impl FnOnce(&str, ParseError) + ) -> ParseResult { // Ensure that value options are provided a value if let Some((name, _)) = state.expects_arg.take() { error(program_name, ParseError::ExpectArgument(name)); @@ -188,7 +193,7 @@ impl Opts { // HACK: Ensure the string fields are set properly, because coerced // ParseIntError/ParseFloatError will have the string fields blanked. Err(ParseError::ArgumentError("", "", kind)) - => Err(ParseError::ArgumentError(name, value, kind)), + => Err(ParseError::ArgumentError(name, value.unwrap(), kind)), Err(err) => Err(err), Ok(ctl) => Ok(ctl), } @@ -198,7 +203,7 @@ impl Opts { // was matched and didn't have an equals sign separating a value, // then call the handler here. if let Some((name, option)) = state.expects_arg.take() { - call_handler(option, name, token) + call_handler(option, name, Some(token)) } else { // Check if the next argument token starts with an option flag if self.flag_chars.chars().any(|c| token.starts_with(c)) { @@ -230,9 +235,9 @@ impl Opts { match (&option.r#type, value_str) { // Call handler for flag-only options - (OptType::Flag, None) => call_handler(option, name, ""), + (OptType::Flag, None) => call_handler(option, name, None), // Value was provided this token, so call the handler right now - (OptType::Value, Some(value)) => call_handler(option, name, value), + (OptType::Value, Some(value)) => call_handler(option, name, Some(value)), // No value available in this token, delay handling to next token (OptType::Value, None) => { state.expects_arg = Some((name, option)); @@ -247,7 +252,7 @@ impl Opts { // Find the next positional argument for (i, option) in self.options[state.positional_index..].iter().enumerate() { if matches!(option.r#type, OptType::Positional) { - call_handler(option, option.first_name(), token)?; + call_handler(option, option.first_name(), Some(token))?; state.positional_index += i + 1; return Ok(ParseControl::Continue); } @@ -260,12 +265,13 @@ impl Opts { #[cfg(test)] mod tests { - extern crate alloc; - use alloc::string::String; use super::*; #[test] - fn test() { + fn test_parse() { + extern crate alloc; + use alloc::string::String; + enum ArgID { One, Two, Three, Four, Five } const OPTIONS: Opts = Opts::new(&[ Opt::positional(ArgID::One, "one"), @@ -284,11 +290,11 @@ mod tests { let mut five: Option = None; assert!(matches!(OPTIONS.parse("", ARGUMENTS.iter(), |ctx| { match ctx.id { - ArgID::One => { one = Some(ctx.arg.into()); } + ArgID::One => { one = Some(ctx.arg.unwrap().into()); } ArgID::Two => { two = true; } - ArgID::Three => { three = Some(ctx.arg.into()); } - ArgID::Four => { four = Some(ctx.arg.into()); } - ArgID::Five => { five = Some(ctx.arg.into()); } + ArgID::Three => { three = Some(ctx.arg.unwrap().into()); } + ArgID::Four => { four = Some(ctx.arg.unwrap().into()); } + ArgID::Five => { five = Some(ctx.arg.unwrap().into()); } } Ok(ParseControl::Continue) }, |_, error| {