6 Commits
v0.2.2 ... main

Author SHA1 Message Date
44b2addecb Merge branch '0.2.x'
# Conflicts:
#	jaarg/src/argparse.rs
2025-11-26 15:40:14 +11:00
75e2bde5fb Rename test modules for previously included files. 2025-11-17 21:31:15 +11:00
14028ed2c8 Add simple argparse test for values 2025-11-17 21:29:15 +11:00
23b6402db6 Add homepage 2025-11-17 21:17:32 +11:00
4f1a01f81c Collect parse handler args into a struct with proper names. 2025-11-17 18:11:32 +11:00
a6abeff9f2 Normalise naming between ParseResult and ParseMapResult 2025-11-17 16:35:07 +11:00
6 changed files with 61 additions and 48 deletions

View File

@@ -37,17 +37,16 @@ extern "C" fn safe_main(args: &[&str]) -> ExitCode {
// Parse command-line arguments from argv // Parse command-line arguments from argv
match OPTIONS.parse( match OPTIONS.parse(
SimplePathBuf::from(*args.first().unwrap()).basename(), SimplePathBuf::from(*args.first().unwrap()).basename(),
args.iter().skip(1), args.iter().skip(1), |ctx| {
|program_name, id, _opt, _name, arg| { match ctx.id {
match id {
Arg::Help => { Arg::Help => {
let ctx = HelpWriterContext { options: &OPTIONS, program_name }; let ctx = HelpWriterContext { options: &OPTIONS, program_name: ctx.program_name };
print!("{}", StandardFullHelpWriter::<'_, Arg>::new(ctx)); print!("{}", StandardFullHelpWriter::<'_, Arg>::new(ctx));
return Ok(ParseControl::Quit); return Ok(ParseControl::Quit);
} }
Arg::Number => { number = str::parse(arg)?; } Arg::Number => { number = str::parse(ctx.arg)?; }
Arg::File => { file = arg.into(); } Arg::File => { file = ctx.arg.into(); }
Arg::Out => { out = Some(arg.into()); } Arg::Out => { out = Some(ctx.arg.into()); }
} }
Ok(ParseControl::Continue) Ok(ParseControl::Continue)
}, |program_name, error| { }, |program_name, error| {
@@ -57,7 +56,7 @@ extern "C" fn safe_main(args: &[&str]) -> ExitCode {
) { ) {
ParseResult::ContinueSuccess => (), ParseResult::ContinueSuccess => (),
ParseResult::ExitSuccess => { return ExitCode::SUCCESS; } ParseResult::ExitSuccess => { return ExitCode::SUCCESS; }
ParseResult::ExitError => { return ExitCode::FAILURE; } ParseResult::ExitFailure => { return ExitCode::FAILURE; }
} }
// Print the result variables // Print the result variables

View File

@@ -26,21 +26,21 @@ fn main() {
]).with_description("My simple utility."); ]).with_description("My simple utility.");
// Parse command-line arguments from `std::env::args()` // Parse command-line arguments from `std::env::args()`
match OPTIONS.parse_easy(|program_name, id, _opt, _name, arg| { match OPTIONS.parse_easy(|ctx| {
match id { match ctx.id {
Arg::Help => { Arg::Help => {
OPTIONS.print_full_help(program_name); OPTIONS.print_full_help(ctx.program_name);
return Ok(ParseControl::Quit); return Ok(ParseControl::Quit);
} }
Arg::Number => { number = str::parse(arg)?; } Arg::Number => { number = str::parse(ctx.arg)?; }
Arg::File => { file = arg.into(); } Arg::File => { file = ctx.arg.into(); }
Arg::Out => { out = Some(arg.into()); } Arg::Out => { out = Some(ctx.arg.into()); }
} }
Ok(ParseControl::Continue) Ok(ParseControl::Continue)
}) { }) {
ParseResult::ContinueSuccess => (), ParseResult::ContinueSuccess => (),
ParseResult::ExitSuccess => std::process::exit(0), ParseResult::ExitSuccess => std::process::exit(0),
ParseResult::ExitError => std::process::exit(1), ParseResult::ExitFailure => std::process::exit(1),
} }
// Print the result variables // Print the result variables

View File

@@ -210,14 +210,14 @@ pub fn main() -> ExitCode {
Opt::value(Arg::Whitespace, &["--whitespace"], "\" \"").help_text("Emitted indentation (Default: \"\\t\")"), Opt::value(Arg::Whitespace, &["--whitespace"], "\" \"").help_text("Emitted indentation (Default: \"\\t\")"),
]).with_description("Convert one or more binary and text file(s) to a C header file,\n\ ]).with_description("Convert one or more binary and text file(s) to a C header file,\n\
as arrays and C strings respectively."); as arrays and C strings respectively.");
match OPTIONS.parse_easy(|program_name, id, _opt, _name, arg| { match OPTIONS.parse_easy(|ctx| {
match id { match ctx.id {
Arg::Out => { arguments.out = arg.into(); } Arg::Out => { arguments.out = ctx.arg.into(); }
Arg::Bin => { jobs.push(Job { job_type: JobType::Binary, path: 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: arg.into() }); } Arg::Txt => { jobs.push(Job { job_type: JobType::Text, path: ctx.arg.into() }); }
Arg::Whitespace => { arguments.whitespace = arg.into(); } Arg::Whitespace => { arguments.whitespace = ctx.arg.into(); }
Arg::Help => { Arg::Help => {
OPTIONS.print_full_help(program_name); OPTIONS.print_full_help(ctx.program_name);
return Ok(ParseControl::Quit); return Ok(ParseControl::Quit);
} }
} }
@@ -234,6 +234,6 @@ pub fn main() -> ExitCode {
} }
}, },
ParseResult::ExitSuccess => { ExitCode::SUCCESS } ParseResult::ExitSuccess => { ExitCode::SUCCESS }
ParseResult::ExitError => { ExitCode::FAILURE } ParseResult::ExitFailure => { ExitCode::FAILURE }
} }
} }

View File

@@ -17,18 +17,18 @@ impl Opts<&'static str> {
help: impl Fn(&str), error: impl FnOnce(&str, ParseError) help: impl Fn(&str), error: impl FnOnce(&str, ParseError)
) -> ParseMapResult { ) -> ParseMapResult {
let mut out: BTreeMap<&'static str, String> = BTreeMap::new(); let mut out: BTreeMap<&'static str, String> = BTreeMap::new();
match self.parse(program_name, args, |_program_name, id, opt, _name, arg| { match self.parse(program_name, args, |ctx| {
if opt.is_help() { if ctx.option.is_help() {
help(program_name); help(program_name);
Ok(ParseControl::Quit) Ok(ParseControl::Quit)
} else { } else {
out.insert(id, arg.into()); out.insert(ctx.id, ctx.arg.into());
Ok(ParseControl::Continue) Ok(ParseControl::Continue)
} }
}, error) { }, error) {
ParseResult::ContinueSuccess => ParseMapResult::Map(out), ParseResult::ContinueSuccess => ParseMapResult::Map(out),
ParseResult::ExitSuccess => ParseMapResult::ExitSuccess, ParseResult::ExitSuccess => ParseMapResult::ExitSuccess,
ParseResult::ExitError => ParseMapResult::ExitFailure, ParseResult::ExitFailure => ParseMapResult::ExitFailure,
} }
} }
} }

View File

@@ -15,7 +15,7 @@ pub enum ParseResult {
/// Parsing succeeded and program should exit with success (eg; `exit(0)`). /// Parsing succeeded and program should exit with success (eg; `exit(0)`).
ExitSuccess, ExitSuccess,
/// There was an error while parsing and program should exit with failure (eg; `exit(1)`). /// There was an error while parsing and program should exit with failure (eg; `exit(1)`).
ExitError, ExitFailure,
} }
/// Execution control for parser handlers. /// Execution control for parser handlers.
@@ -28,6 +28,21 @@ pub enum ParseControl {
Quit, Quit,
} }
#[derive(Debug)]
pub struct ParseHandlerContext<'a, ID: 'static> {
/// Name of the program, for printing statuses to the user.
pub program_name: &'a str,
/// The generic argument ID that was matched.
pub id: &'a ID,
/// The option that was matched by the parser.
pub option: &'a Opt<ID>,
/// 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,
}
/// Result type used by the handler passed to the parser. /// Result type used by the handler passed to the parser.
pub(crate) type HandlerResult<'a, T> = core::result::Result<T, ParseError<'a>>; pub(crate) type HandlerResult<'a, T> = core::result::Result<T, ParseError<'a>>;
@@ -118,7 +133,7 @@ impl<ID> Default for ParserState<ID> {
impl<ID: 'static> Opts<ID> { impl<ID: 'static> Opts<ID> {
/// Parses an iterator of strings as argument tokens. /// Parses an iterator of strings as argument tokens.
pub fn parse<'a, S: AsRef<str> + 'a, I: Iterator<Item = S>>(&self, program_name: &str, args: I, pub fn parse<'a, S: AsRef<str> + 'a, I: Iterator<Item = S>>(&self, program_name: &str, args: I,
mut handler: impl FnMut(&str, &ID, &Opt<ID>, &str, &str) -> HandlerResult<'a, ParseControl>, mut handler: impl FnMut(ParseHandlerContext<ID>) -> HandlerResult<'a, ParseControl>,
error: impl FnOnce(&str, ParseError), error: impl FnOnce(&str, ParseError),
) -> ParseResult { ) -> ParseResult {
let mut state = ParserState::default(); let mut state = ParserState::default();
@@ -131,7 +146,7 @@ impl<ID: 'static> Opts<ID> {
Err(err) => { Err(err) => {
// Call the error handler // Call the error handler
error(program_name, err); error(program_name, err);
return ParseResult::ExitError; return ParseResult::ExitFailure;
} }
} }
} }
@@ -139,7 +154,7 @@ impl<ID: 'static> Opts<ID> {
// Ensure that value options are provided a value // Ensure that value options are provided a value
if let Some((name, _)) = state.expects_arg.take() { if let Some((name, _)) = state.expects_arg.take() {
error(program_name, ParseError::ExpectArgument(name)); error(program_name, ParseError::ExpectArgument(name));
return ParseResult::ExitError; return ParseResult::ExitFailure;
} }
// Ensure that all required arguments have been provided // Ensure that all required arguments have been provided
@@ -148,12 +163,12 @@ impl<ID: 'static> Opts<ID> {
match option.r#type { match option.r#type {
OptType::Positional => if i >= state.positional_index && option.is_required() { OptType::Positional => if i >= state.positional_index && option.is_required() {
error(program_name, ParseError::RequiredPositional(option.first_name())); error(program_name, ParseError::RequiredPositional(option.first_name()));
return ParseResult::ExitError; return ParseResult::ExitFailure;
} }
OptType::Flag | OptType::Value => if option.is_required() { OptType::Flag | OptType::Value => if option.is_required() {
if !state.required_param_presences.get(required_flag_idx) { if !state.required_param_presences.get(required_flag_idx) {
error(program_name, ParseError::RequiredParameter(option.first_name())); error(program_name, ParseError::RequiredParameter(option.first_name()));
return ParseResult::ExitError; return ParseResult::ExitFailure;
} }
required_flag_idx += 1; required_flag_idx += 1;
} }
@@ -166,10 +181,10 @@ impl<ID: 'static> Opts<ID> {
/// Parse the next token in the argument stream /// Parse the next token in the argument stream
fn next<'a, 'b>(&self, state: &mut ParserState<ID>, token: &'b str, program_name: &str, fn next<'a, 'b>(&self, state: &mut ParserState<ID>, token: &'b str, program_name: &str,
handler: &mut impl FnMut(&str, &ID, &Opt<ID>, &str, &str) -> HandlerResult<'a, ParseControl> handler: &mut impl FnMut(ParseHandlerContext<ID>) -> HandlerResult<'a, ParseControl>
) -> HandlerResult<'b, ParseControl> where 'a: 'b { ) -> HandlerResult<'b, ParseControl> where 'a: 'b {
let mut call_handler = |option: &Opt<ID>, name, value| { let mut call_handler = |option: &Opt<ID>, name, value| {
match handler(program_name, &option.id, option, name, value) { match handler(ParseHandlerContext{ program_name, id: &option.id, option, name, arg: value }) {
// HACK: Ensure the string fields are set properly, because coerced // HACK: Ensure the string fields are set properly, because coerced
// ParseIntError/ParseFloatError will have the string fields blanked. // ParseIntError/ParseFloatError will have the string fields blanked.
Err(ParseError::ArgumentError("", "", kind)) Err(ParseError::ArgumentError("", "", kind))
@@ -245,13 +260,12 @@ impl<ID: 'static> Opts<ID> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
extern crate alloc;
use alloc::string::String;
use super::*; use super::*;
#[test] #[test]
fn test_parse() { fn test() {
extern crate alloc;
use alloc::string::String;
enum ArgID { One, Two, Three, Four, Five } enum ArgID { One, Two, Three, Four, Five }
const OPTIONS: Opts<ArgID> = Opts::new(&[ const OPTIONS: Opts<ArgID> = Opts::new(&[
Opt::positional(ArgID::One, "one"), Opt::positional(ArgID::One, "one"),
@@ -268,13 +282,13 @@ mod tests {
let mut three: Option<String> = None; let mut three: Option<String> = None;
let mut four: Option<String> = None; let mut four: Option<String> = None;
let mut five: Option<String> = None; let mut five: Option<String> = None;
assert!(matches!(OPTIONS.parse("", ARGUMENTS.iter(), |_program_name, id, _opt, _name, arg| { assert!(matches!(OPTIONS.parse("", ARGUMENTS.iter(), |ctx| {
match id { match ctx.id {
ArgID::One => { one = Some(arg.into()); } ArgID::One => { one = Some(ctx.arg.into()); }
ArgID::Two => { two = true; } ArgID::Two => { two = true; }
ArgID::Three => { three = Some(arg.into()); } ArgID::Three => { three = Some(ctx.arg.into()); }
ArgID::Four => { four = Some(arg.into()); } ArgID::Four => { four = Some(ctx.arg.into()); }
ArgID::Five => { five = Some(arg.into()); } ArgID::Five => { five = Some(ctx.arg.into()); }
} }
Ok(ParseControl::Continue) Ok(ParseControl::Continue)
}, |_, error| { }, |_, error| {

View File

@@ -6,8 +6,8 @@
extern crate std; extern crate std;
use crate::{ use crate::{
ErrorUsageWriter, ErrorUsageWriterContext, HandlerResult, HelpWriter, HelpWriterContext, Opt, Opts, alloc::ParseMapResult, ErrorUsageWriter, ErrorUsageWriterContext, HandlerResult, HelpWriter, HelpWriterContext,
ParseControl, ParseError, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter, alloc::ParseMapResult Opts, ParseControl, ParseError, ParseHandlerContext, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter
}; };
use std::path::Path; use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
@@ -18,7 +18,7 @@ impl<ID: 'static> Opts<ID> {
/// The errors are formatted in a standard user-friendly format. /// The errors are formatted in a standard user-friendly format.
/// ///
/// Requires `features = ["std"]`. /// Requires `features = ["std"]`.
pub fn parse_easy<'a>(&self, handler: impl FnMut(&str, &ID, &Opt<ID>, &str, &str) -> HandlerResult<'a, ParseControl> pub fn parse_easy<'a>(&self, handler: impl FnMut(ParseHandlerContext<ID>) -> HandlerResult<'a, ParseControl>
) -> ParseResult { ) -> ParseResult {
let (program_name, argv) = Self::easy_args(); let (program_name, argv) = Self::easy_args();
self.parse(&program_name, argv, handler, self.parse(&program_name, argv, handler,