mirror of
https://github.com/gay-pizza/jaarg.git
synced 2025-12-19 07:20:18 +00:00
Compare commits
7 Commits
v0.2.2
...
parse_borr
| Author | SHA1 | Date | |
|---|---|---|---|
| 40cdb24004 | |||
| afffd1ef91 | |||
| 75e2bde5fb | |||
| 14028ed2c8 | |||
| 23b6402db6 | |||
| 4f1a01f81c | |||
| a6abeff9f2 |
@@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
description = "It can parse your arguments you should use it it's called jaarg"
|
||||
repository = "https://github.com/gay-pizza/jaarg"
|
||||
homepage = "https://gay.pizza/"
|
||||
authors = ["a dinosaur", "Gay Pizza Specifications"]
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use jaarg::{
|
||||
ErrorUsageWriter, ErrorUsageWriterContext, HelpWriter, HelpWriterContext, Opt, Opts,
|
||||
ParseControl, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter
|
||||
@@ -18,8 +16,8 @@ use jaarg_nostd::{print, println, harness::ExitCode, simplepathbuf::SimplePathBu
|
||||
#[allow(improper_ctypes_definitions)]
|
||||
extern "C" fn safe_main(args: &[&str]) -> ExitCode {
|
||||
// Variables for arguments to fill
|
||||
let mut file = SimplePathBuf::default();
|
||||
let mut out: Option<SimplePathBuf> = None;
|
||||
let mut file: Option<&str> = None;
|
||||
let mut out: Option<&str> = None;
|
||||
let mut number = 0;
|
||||
|
||||
// Set up arguments table
|
||||
@@ -35,19 +33,18 @@ extern "C" fn safe_main(args: &[&str]) -> ExitCode {
|
||||
]).with_description("My simple utility.");
|
||||
|
||||
// Parse command-line arguments from argv
|
||||
match OPTIONS.parse(
|
||||
match OPTIONS.parse_slice(
|
||||
SimplePathBuf::from(*args.first().unwrap()).basename(),
|
||||
args.iter().skip(1),
|
||||
|program_name, id, _opt, _name, arg| {
|
||||
match id {
|
||||
&args[1..], |ctx| {
|
||||
match ctx.id {
|
||||
Arg::Help => {
|
||||
let ctx = HelpWriterContext { options: &OPTIONS, program_name };
|
||||
let ctx = HelpWriterContext { options: &OPTIONS, program_name: ctx.program_name };
|
||||
print!("{}", StandardFullHelpWriter::<'_, Arg>::new(ctx));
|
||||
return Ok(ParseControl::Quit);
|
||||
}
|
||||
Arg::Number => { number = str::parse(arg)?; }
|
||||
Arg::File => { file = arg.into(); }
|
||||
Arg::Out => { out = Some(arg.into()); }
|
||||
Arg::Number => { number = str::parse(ctx.arg.unwrap())?; }
|
||||
Arg::File => { file = ctx.arg; }
|
||||
Arg::Out => { out = ctx.arg; }
|
||||
}
|
||||
Ok(ParseControl::Continue)
|
||||
}, |program_name, error| {
|
||||
@@ -57,12 +54,13 @@ extern "C" fn safe_main(args: &[&str]) -> ExitCode {
|
||||
) {
|
||||
ParseResult::ContinueSuccess => (),
|
||||
ParseResult::ExitSuccess => { return ExitCode::SUCCESS; }
|
||||
ParseResult::ExitError => { return ExitCode::FAILURE; }
|
||||
ParseResult::ExitFailure => { return ExitCode::FAILURE; }
|
||||
}
|
||||
|
||||
// Print the result variables
|
||||
let file = SimplePathBuf::from(file.unwrap());
|
||||
println!("{file} -> {out} (number: {number})",
|
||||
out = out.unwrap_or(file.with_extension("out")));
|
||||
out = out.map_or(file.with_extension("out"), |out| SimplePathBuf::from(out)));
|
||||
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ version.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
|
||||
@@ -26,21 +26,21 @@ fn main() {
|
||||
]).with_description("My simple utility.");
|
||||
|
||||
// Parse command-line arguments from `std::env::args()`
|
||||
match OPTIONS.parse_easy(|program_name, id, _opt, _name, arg| {
|
||||
match id {
|
||||
match OPTIONS.parse_easy(|ctx| {
|
||||
match ctx.id {
|
||||
Arg::Help => {
|
||||
OPTIONS.print_full_help(program_name);
|
||||
OPTIONS.print_full_help(ctx.program_name);
|
||||
return Ok(ParseControl::Quit);
|
||||
}
|
||||
Arg::Number => { number = str::parse(arg)?; }
|
||||
Arg::File => { file = arg.into(); }
|
||||
Arg::Out => { out = Some(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::ExitError => std::process::exit(1),
|
||||
ParseResult::ExitSuccess => std::process::exit(0),
|
||||
ParseResult::ExitFailure => std::process::exit(1),
|
||||
}
|
||||
|
||||
// Print the result variables
|
||||
|
||||
@@ -210,14 +210,14 @@ pub fn main() -> ExitCode {
|
||||
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\
|
||||
as arrays and C strings respectively.");
|
||||
match OPTIONS.parse_easy(|program_name, id, _opt, _name, arg| {
|
||||
match id {
|
||||
Arg::Out => { arguments.out = arg.into(); }
|
||||
Arg::Bin => { jobs.push(Job { job_type: JobType::Binary, path: arg.into() }); }
|
||||
Arg::Txt => { jobs.push(Job { job_type: JobType::Text, path: arg.into() }); }
|
||||
Arg::Whitespace => { arguments.whitespace = arg.into(); }
|
||||
match OPTIONS.parse_easy(|ctx| {
|
||||
match ctx.id {
|
||||
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(program_name);
|
||||
OPTIONS.print_full_help(ctx.program_name);
|
||||
return Ok(ParseControl::Quit);
|
||||
}
|
||||
}
|
||||
@@ -234,6 +234,6 @@ pub fn main() -> ExitCode {
|
||||
}
|
||||
},
|
||||
ParseResult::ExitSuccess => { ExitCode::SUCCESS }
|
||||
ParseResult::ExitError => { ExitCode::FAILURE }
|
||||
ParseResult::ExitFailure => { ExitCode::FAILURE }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,30 +5,30 @@
|
||||
|
||||
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`].
|
||||
///
|
||||
/// Requires `features = ["alloc"]`.
|
||||
pub fn parse_map<'a, S: AsRef<str> + 'a, I: Iterator<Item = S>>(&self, program_name: &str, args: I,
|
||||
pub fn parse_map<'opt, 't, S: AsRef<str> + 't, I: Iterator<Item = S>>(&'opt self, program_name: &str, args: I,
|
||||
help: impl Fn(&str), error: impl FnOnce(&str, ParseError)
|
||||
) -> ParseMapResult {
|
||||
let mut out: BTreeMap<&'static str, String> = BTreeMap::new();
|
||||
match self.parse(program_name, args, |_program_name, id, opt, _name, arg| {
|
||||
if opt.is_help() {
|
||||
match self.parse(program_name, args, |ctx| {
|
||||
if ctx.option.is_help() {
|
||||
help(program_name);
|
||||
Ok(ParseControl::Quit)
|
||||
} else {
|
||||
out.insert(id, arg.into());
|
||||
out.insert(ctx.id, ctx.arg.map_or(String::new(), |o| o.to_string()));
|
||||
Ok(ParseControl::Continue)
|
||||
}
|
||||
}, error) {
|
||||
ParseResult::ContinueSuccess => ParseMapResult::Map(out),
|
||||
ParseResult::ExitSuccess => ParseMapResult::ExitSuccess,
|
||||
ParseResult::ExitError => ParseMapResult::ExitFailure,
|
||||
ParseResult::ExitFailure => ParseMapResult::ExitFailure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub enum ParseResult {
|
||||
/// Parsing succeeded and program should exit with success (eg; `exit(0)`).
|
||||
ExitSuccess,
|
||||
/// There was an error while parsing and program should exit with failure (eg; `exit(1)`).
|
||||
ExitError,
|
||||
ExitFailure,
|
||||
}
|
||||
|
||||
/// Execution control for parser handlers.
|
||||
@@ -28,16 +28,31 @@ pub enum ParseControl {
|
||||
Quit,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseHandlerContext<'a, 'name, ID: 'static> {
|
||||
/// Name of the program, for printing statuses to the user.
|
||||
pub program_name: &'name 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 (will always be Some), or None for flags.
|
||||
pub arg: Option<&'a str>,
|
||||
}
|
||||
|
||||
/// Result type used by the handler passed to the parser.
|
||||
pub(crate) type HandlerResult<'a, T> = core::result::Result<T, ParseError<'a>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError<'a> {
|
||||
UnknownOption(&'a str),
|
||||
UnexpectedToken(&'a str),
|
||||
ExpectArgument(&'a str),
|
||||
UnexpectedArgument(&'a str),
|
||||
ArgumentError(&'static str, &'a str, ParseErrorKind),
|
||||
pub enum ParseError<'t> {
|
||||
UnknownOption(&'t str),
|
||||
UnexpectedToken(&'t str),
|
||||
ExpectArgument(&'t str),
|
||||
UnexpectedArgument(&'t str),
|
||||
ArgumentError(&'static str, &'t str, ParseErrorKind),
|
||||
//TODO
|
||||
//Exclusive(&'static str, &'a str),
|
||||
RequiredPositional(&'static str),
|
||||
@@ -118,7 +133,7 @@ impl<ID> Default for ParserState<ID> {
|
||||
impl<ID: 'static> Opts<ID> {
|
||||
/// 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,
|
||||
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),
|
||||
) -> ParseResult {
|
||||
let mut state = ParserState::default();
|
||||
@@ -131,15 +146,44 @@ impl<ID: 'static> Opts<ID> {
|
||||
Err(err) => {
|
||||
// Call the error handler
|
||||
error(program_name, err);
|
||||
return ParseResult::ExitError;
|
||||
return ParseResult::ExitFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.validate_state(program_name, state, error)
|
||||
}
|
||||
|
||||
/// Parses a slice of strings as argument tokens.
|
||||
/// Like [Opts::parse] but allows borrowing argument tokens outside the handler.
|
||||
pub fn parse_slice<'opts, 't, S: AsRef<str>>(&'opts self, program_name: &str, args: &'t [S],
|
||||
mut handler: impl FnMut(ParseHandlerContext<'opts, '_, ID>) -> HandlerResult<'opts, ParseControl>,
|
||||
error: impl FnOnce(&str, ParseError),
|
||||
) -> ParseResult where 't: 'opts {
|
||||
let mut state = ParserState::default();
|
||||
for arg in args {
|
||||
// Fetch the next token
|
||||
match self.next_borrow(&mut state, arg.as_ref(), program_name, &mut handler) {
|
||||
Ok(ParseControl::Continue) => {}
|
||||
Ok(ParseControl::Stop) => { break; }
|
||||
Ok(ParseControl::Quit) => { return ParseResult::ExitSuccess; }
|
||||
Err(err) => {
|
||||
// Call the error handler
|
||||
error(program_name, err);
|
||||
return ParseResult::ExitFailure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.validate_state(program_name, state, error)
|
||||
}
|
||||
|
||||
fn validate_state(&self, program_name: &str, mut state: ParserState<ID>, 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));
|
||||
return ParseResult::ExitError;
|
||||
return ParseResult::ExitFailure;
|
||||
}
|
||||
|
||||
// Ensure that all required arguments have been provided
|
||||
@@ -148,12 +192,12 @@ impl<ID: 'static> Opts<ID> {
|
||||
match option.r#type {
|
||||
OptType::Positional => if i >= state.positional_index && option.is_required() {
|
||||
error(program_name, ParseError::RequiredPositional(option.first_name()));
|
||||
return ParseResult::ExitError;
|
||||
return ParseResult::ExitFailure;
|
||||
}
|
||||
OptType::Flag | OptType::Value => if option.is_required() {
|
||||
if !state.required_param_presences.get(required_flag_idx) {
|
||||
error(program_name, ParseError::RequiredParameter(option.first_name()));
|
||||
return ParseResult::ExitError;
|
||||
return ParseResult::ExitFailure;
|
||||
}
|
||||
required_flag_idx += 1;
|
||||
}
|
||||
@@ -164,16 +208,16 @@ impl<ID: 'static> Opts<ID> {
|
||||
ParseResult::ContinueSuccess
|
||||
}
|
||||
|
||||
/// Parse the next token in the argument stream
|
||||
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>
|
||||
) -> HandlerResult<'b, ParseControl> where 'a: 'b {
|
||||
/// Parse the next token in the argument stream.
|
||||
fn next<'r, 't>(&self, state: &mut ParserState<ID>, token: &'t str, program_name: &str,
|
||||
handler: &mut impl FnMut(ParseHandlerContext<ID>) -> HandlerResult<'r, ParseControl>
|
||||
) -> HandlerResult<'t, ParseControl> where 'r: 't {
|
||||
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
|
||||
// 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),
|
||||
}
|
||||
@@ -183,7 +227,7 @@ impl<ID: 'static> Opts<ID> {
|
||||
// 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)) {
|
||||
@@ -215,9 +259,9 @@ impl<ID: 'static> Opts<ID> {
|
||||
|
||||
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));
|
||||
@@ -232,7 +276,85 @@ impl<ID: 'static> Opts<ID> {
|
||||
// Find the next positional argument
|
||||
for (i, option) in self.options[state.positional_index..].iter().enumerate() {
|
||||
if matches!(option.r#type, OptType::Positional) {
|
||||
handler(program_name, &option.id, option, option.first_name(), token)?;
|
||||
call_handler(option, option.first_name(), Some(token))?;
|
||||
state.positional_index += i + 1;
|
||||
return Ok(ParseControl::Continue);
|
||||
}
|
||||
}
|
||||
Err(ParseError::UnexpectedToken(token))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// I absolutely hate that this needs to be DUPLICATED
|
||||
fn next_borrow<'opts, 't>(&'opts self, state: &mut ParserState<ID>, token: &'t str, program_name: &str,
|
||||
handler: &mut impl FnMut(ParseHandlerContext<'opts, '_, ID>) -> HandlerResult<'opts, ParseControl>
|
||||
) -> HandlerResult<'opts, ParseControl> where 't: 'opts {
|
||||
let mut call_handler = |option: &'opts Opt<ID>, name, value| {
|
||||
match handler(ParseHandlerContext{ program_name, id: &option.id, option, name, arg: value }) {
|
||||
// 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.unwrap(), kind)),
|
||||
Err(err) => Err(err),
|
||||
Ok(ctl) => Ok(ctl),
|
||||
}
|
||||
};
|
||||
|
||||
// If the previous token is expecting an argument, ie: value a value option
|
||||
// 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, 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)) {
|
||||
// Value options can have their value delineated by an equals sign or with whitespace.
|
||||
// In the latter case; the value will be in the next token.
|
||||
let (option_str, value_str) = token.split_once("=")
|
||||
.map_or((token, None), |(k, v)| (k, Some(v)));
|
||||
|
||||
// Keep track of how many required options we've seen
|
||||
let mut required_idx = 0;
|
||||
|
||||
// Match a suitable option by name (ignoring the first flag character & skipping positional arguments)
|
||||
let (name, option) = self.iter()
|
||||
.filter(|opt| matches!(opt.r#type, OptType::Flag | OptType::Value)).find_map(|opt| {
|
||||
if let Some(name) = opt.match_name(option_str, 1) {
|
||||
Some((name, opt))
|
||||
} else {
|
||||
if opt.is_required() {
|
||||
required_idx += 1
|
||||
}
|
||||
None
|
||||
}
|
||||
}).ok_or(ParseError::UnknownOption(option_str))?;
|
||||
|
||||
// Mark required option as visited
|
||||
if option.is_required() {
|
||||
state.required_param_presences.insert(required_idx, true);
|
||||
}
|
||||
|
||||
match (&option.r#type, value_str) {
|
||||
// Call handler for flag-only options
|
||||
(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, Some(value)),
|
||||
// No value available in this token, delay handling to next token
|
||||
(OptType::Value, None) => {
|
||||
state.expects_arg = Some((name, option));
|
||||
Ok(ParseControl::Continue)
|
||||
}
|
||||
// Flag-only options do not support arguments
|
||||
(OptType::Flag, Some(_)) => Err(ParseError::UnexpectedArgument(option_str)),
|
||||
// Positional arguments are filtered out so this is impossible
|
||||
(OptType::Positional, _) => unreachable!("Won't parse a positional argument as an option"),
|
||||
}
|
||||
} else {
|
||||
// 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(), Some(token))?;
|
||||
state.positional_index += i + 1;
|
||||
return Ok(ParseControl::Continue);
|
||||
}
|
||||
@@ -242,3 +364,75 @@ impl<ID: 'static> Opts<ID> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
enum ArgID { One, Two, Three, Four, Five }
|
||||
const OPTIONS: Opts<ArgID> = Opts::new(&[
|
||||
Opt::positional(ArgID::One, "one"),
|
||||
Opt::flag(ArgID::Two, &["--two"]),
|
||||
Opt::value(ArgID::Three, &["--three"], "value"),
|
||||
Opt::value(ArgID::Four, &["--four"], "value"),
|
||||
Opt::value(ArgID::Five, &["--five"], "value"),
|
||||
]);
|
||||
const ARGUMENTS: &[&str] = &["one", "--two", "--three=three", "--five=", "--four", "four"];
|
||||
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
extern crate alloc;
|
||||
use alloc::string::String;
|
||||
|
||||
let mut one: Option<String> = None;
|
||||
let mut two = false;
|
||||
let mut three: Option<String> = None;
|
||||
let mut four: Option<String> = None;
|
||||
let mut five: Option<String> = None;
|
||||
assert!(matches!(OPTIONS.parse("", ARGUMENTS.iter(), |ctx| {
|
||||
match ctx.id {
|
||||
ArgID::One => { one = Some(ctx.arg.unwrap().into()); }
|
||||
ArgID::Two => { two = true; }
|
||||
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| {
|
||||
panic!("unreachable: {error:?}");
|
||||
}), ParseResult::ContinueSuccess));
|
||||
|
||||
assert_eq!(one, Some("one".into()));
|
||||
assert!(two);
|
||||
assert_eq!(three, Some("three".into()));
|
||||
assert_eq!(four, Some("four".into()));
|
||||
assert_eq!(five, Some("".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_slice() {
|
||||
let mut one: Option<&str> = None;
|
||||
let mut two = false;
|
||||
let mut three: Option<&str> = None;
|
||||
let mut four: Option<&str> = None;
|
||||
let mut five: Option<&str> = None;
|
||||
assert!(matches!(OPTIONS.parse_slice("", &ARGUMENTS, |ctx| {
|
||||
match ctx.id {
|
||||
ArgID::One => { one = ctx.arg; }
|
||||
ArgID::Two => { two = true; }
|
||||
ArgID::Three => { three = ctx.arg; }
|
||||
ArgID::Four => { four = ctx.arg; }
|
||||
ArgID::Five => { five = ctx.arg; }
|
||||
}
|
||||
Ok(ParseControl::Continue)
|
||||
}, |_, error| {
|
||||
panic!("unreachable: {error:?}");
|
||||
}), ParseResult::ContinueSuccess));
|
||||
|
||||
assert_eq!(one, Some("one"));
|
||||
assert!(two);
|
||||
assert_eq!(three, Some("three"));
|
||||
assert_eq!(four, Some("four"));
|
||||
assert_eq!(five, Some(""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ impl core::ops::BitOr for OptFlag {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod opt_tests {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -79,7 +79,7 @@ impl<ID: 'static> Opts<ID> {
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod opts_tests {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
extern crate std;
|
||||
|
||||
use crate::{
|
||||
ErrorUsageWriter, ErrorUsageWriterContext, HandlerResult, HelpWriter, HelpWriterContext, Opt, Opts,
|
||||
ParseControl, ParseError, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter, alloc::ParseMapResult
|
||||
alloc::ParseMapResult, ErrorUsageWriter, ErrorUsageWriterContext, HandlerResult, HelpWriter, HelpWriterContext,
|
||||
Opts, ParseControl, ParseError, ParseHandlerContext, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter
|
||||
};
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
@@ -18,7 +18,7 @@ impl<ID: 'static> Opts<ID> {
|
||||
/// The errors are formatted in a standard user-friendly format.
|
||||
///
|
||||
/// 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>(&'static self, handler: impl FnMut(ParseHandlerContext<ID>) -> HandlerResult<'a, ParseControl>
|
||||
) -> ParseResult {
|
||||
let (program_name, argv) = Self::easy_args();
|
||||
self.parse(&program_name, argv, handler,
|
||||
@@ -69,7 +69,7 @@ impl Opts<&'static str> {
|
||||
/// Help and errors are formatted in a standard user-friendly format.
|
||||
///
|
||||
/// Requires `features = ["std"]`.
|
||||
pub fn parse_map_easy(&self) -> ParseMapResult {
|
||||
pub fn parse_map_easy(&'static self) -> ParseMapResult {
|
||||
let (program_name, argv) = Self::easy_args();
|
||||
self.parse_map(&program_name, argv,
|
||||
|name| self.print_full_help(name),
|
||||
|
||||
Reference in New Issue
Block a user