7 Commits

Author SHA1 Message Date
44b2addecb Merge branch '0.2.x'
# Conflicts:
#	jaarg/src/argparse.rs
2025-11-26 15:40:14 +11:00
75fc27bc58 Release 0.2.2 2025-11-26 15:30:43 +11:00
8d9e8aea89 Cargo stinky aaaa 2025-11-26 15:26:07 +11:00
20f5a0bf10 Backport positional argument handler fix. 2025-11-26 15:12:49 +11:00
7165bb9841 Rename test modules for previously included files.
(cherry picked from commit 75e2bde5fb)
2025-11-26 15:03:55 +11:00
8f6f1827ce Add simple argparse test for values
(cherry picked from commit 14028ed2c8)
2025-11-26 15:03:28 +11:00
33af658e93 Add homepage
(cherry picked from commit 23b6402db6)
2025-11-26 15:00:54 +11:00
11 changed files with 68 additions and 196 deletions

View File

@@ -4,7 +4,7 @@ members = ["jaarg-nostd"]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
version = "0.2.1" version = "0.2.2"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
description = "It can parse your arguments you should use it it's called jaarg" description = "It can parse your arguments you should use it it's called jaarg"

View File

@@ -57,8 +57,10 @@ println!("{file:?} -> {out:?} (number: {number:?})",
### Changelog ### ### Changelog ###
main: v0.2.2:
* Fixed coerced `ArgumentError` not being rewritten for positional arguments.
* Moved top level includes to `pub use`. * Moved top level includes to `pub use`.
* Hopefully work around licence & read me texts not being included in crate.
v0.2.1: v0.2.1:
* Fixed licence field in `Cargo.toml`. * Fixed licence field in `Cargo.toml`.

View File

@@ -6,6 +6,8 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
extern crate alloc;
use jaarg::{ use jaarg::{
ErrorUsageWriter, ErrorUsageWriterContext, HelpWriter, HelpWriterContext, Opt, Opts, ErrorUsageWriter, ErrorUsageWriterContext, HelpWriter, HelpWriterContext, Opt, Opts,
ParseControl, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter ParseControl, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter
@@ -16,8 +18,8 @@ use jaarg_nostd::{print, println, harness::ExitCode, simplepathbuf::SimplePathBu
#[allow(improper_ctypes_definitions)] #[allow(improper_ctypes_definitions)]
extern "C" fn safe_main(args: &[&str]) -> ExitCode { extern "C" fn safe_main(args: &[&str]) -> ExitCode {
// Variables for arguments to fill // Variables for arguments to fill
let mut file: Option<&str> = None; let mut file = SimplePathBuf::default();
let mut out: Option<&str> = None; let mut out: Option<SimplePathBuf> = None;
let mut number = 0; let mut number = 0;
// Set up arguments table // Set up arguments table
@@ -33,18 +35,18 @@ extern "C" fn safe_main(args: &[&str]) -> ExitCode {
]).with_description("My simple utility."); ]).with_description("My simple utility.");
// Parse command-line arguments from argv // Parse command-line arguments from argv
match OPTIONS.parse_slice( match OPTIONS.parse(
SimplePathBuf::from(*args.first().unwrap()).basename(), SimplePathBuf::from(*args.first().unwrap()).basename(),
&args[1..], |ctx| { args.iter().skip(1), |ctx| {
match ctx.id { match ctx.id {
Arg::Help => { Arg::Help => {
let ctx = HelpWriterContext { options: &OPTIONS, program_name: ctx.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(ctx.arg.unwrap())?; } Arg::Number => { number = str::parse(ctx.arg)?; }
Arg::File => { file = ctx.arg; } Arg::File => { file = ctx.arg.into(); }
Arg::Out => { out = ctx.arg; } Arg::Out => { out = Some(ctx.arg.into()); }
} }
Ok(ParseControl::Continue) Ok(ParseControl::Continue)
}, |program_name, error| { }, |program_name, error| {
@@ -58,9 +60,8 @@ extern "C" fn safe_main(args: &[&str]) -> ExitCode {
} }
// Print the result variables // Print the result variables
let file = SimplePathBuf::from(file.unwrap());
println!("{file} -> {out} (number: {number})", println!("{file} -> {out} (number: {number})",
out = out.map_or(file.with_extension("out"), |out| SimplePathBuf::from(out))); out = out.unwrap_or(file.with_extension("out")));
ExitCode::SUCCESS ExitCode::SUCCESS
} }

1
jaarg/LICENSE.Apache-2.0 Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE.Apache-2.0

1
jaarg/LICENSE.MIT Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE.MIT

1
jaarg/README.md Symbolic link
View File

@@ -0,0 +1 @@
../README.md

View File

@@ -32,15 +32,15 @@ fn main() {
OPTIONS.print_full_help(ctx.program_name); OPTIONS.print_full_help(ctx.program_name);
return Ok(ParseControl::Quit); return Ok(ParseControl::Quit);
} }
Arg::Number => { number = str::parse(ctx.arg.unwrap().as_ref())?; } Arg::Number => { number = str::parse(ctx.arg)?; }
Arg::File => { file = ctx.arg.unwrap().into(); } Arg::File => { file = ctx.arg.into(); }
Arg::Out => { out = Some(ctx.arg.unwrap().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::ExitFailure => std::process::exit(1), ParseResult::ExitFailure => std::process::exit(1),
} }
// Print the result variables // Print the result variables

View File

@@ -212,10 +212,10 @@ pub fn main() -> ExitCode {
as arrays and C strings respectively."); as arrays and C strings respectively.");
match OPTIONS.parse_easy(|ctx| { match OPTIONS.parse_easy(|ctx| {
match ctx.id { match ctx.id {
Arg::Out => { arguments.out = ctx.arg.unwrap().into(); } Arg::Out => { arguments.out = ctx.arg.into(); }
Arg::Bin => { jobs.push(Job { job_type: JobType::Binary, path: ctx.arg.unwrap().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.unwrap().into() }); } Arg::Txt => { jobs.push(Job { job_type: JobType::Text, path: ctx.arg.into() }); }
Arg::Whitespace => { arguments.whitespace = ctx.arg.unwrap().into(); } Arg::Whitespace => { arguments.whitespace = ctx.arg.into(); }
Arg::Help => { Arg::Help => {
OPTIONS.print_full_help(ctx.program_name); OPTIONS.print_full_help(ctx.program_name);
return Ok(ParseControl::Quit); return Ok(ParseControl::Quit);

View File

@@ -5,15 +5,15 @@
extern crate alloc; extern crate alloc;
use crate::{Opts, ParseControl, ParseError, ParseResult};
use alloc::collections::BTreeMap; use alloc::collections::BTreeMap;
use alloc::string::{String, ToString}; use alloc::string::String;
use crate::{Opts, ParseControl, ParseError, ParseResult};
impl Opts<&'static str> { impl Opts<&'static str> {
/// Parse an iterator of strings as arguments and return the results in a [`BTreeMap`]. /// Parse an iterator of strings as arguments and return the results in a [`BTreeMap`].
/// ///
/// Requires `features = ["alloc"]`. /// Requires `features = ["alloc"]`.
pub fn parse_map<'opt, 't, S: AsRef<str> + 't, I: Iterator<Item = S>>(&'opt self, program_name: &str, args: I, pub fn parse_map<'a, S: AsRef<str> + 'a, I: Iterator<Item = S>>(&self, program_name: &str, args: I,
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();
@@ -22,7 +22,7 @@ impl Opts<&'static str> {
help(program_name); help(program_name);
Ok(ParseControl::Quit) Ok(ParseControl::Quit)
} else { } else {
out.insert(ctx.id, ctx.arg.map_or(String::new(), |o| o.to_string())); out.insert(ctx.id, ctx.arg.into());
Ok(ParseControl::Continue) Ok(ParseControl::Continue)
} }
}, error) { }, error) {

View File

@@ -29,9 +29,9 @@ pub enum ParseControl {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct ParseHandlerContext<'a, 'name, ID: 'static> { pub struct ParseHandlerContext<'a, ID: 'static> {
/// Name of the program, for printing statuses to the user. /// Name of the program, for printing statuses to the user.
pub program_name: &'name str, pub program_name: &'a str,
/// The generic argument ID that was matched. /// The generic argument ID that was matched.
pub id: &'a ID, pub id: &'a ID,
/// The option that was matched by the parser. /// The option that was matched by the parser.
@@ -39,20 +39,20 @@ pub struct ParseHandlerContext<'a, 'name, ID: 'static> {
/// The name of the argument parameter that was matched, /// The name of the argument parameter that was matched,
/// for option parameters this is the token supplied by the user. /// for option parameters this is the token supplied by the user.
pub name: &'a str, pub name: &'a str,
/// The argument provided to positional arguments and value options (will always be Some), or None for flags. /// The argument provided to positional arguments and value options, else "".
pub arg: Option<&'a str>, 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>>;
#[derive(Debug)] #[derive(Debug)]
pub enum ParseError<'t> { pub enum ParseError<'a> {
UnknownOption(&'t str), UnknownOption(&'a str),
UnexpectedToken(&'t str), UnexpectedToken(&'a str),
ExpectArgument(&'t str), ExpectArgument(&'a str),
UnexpectedArgument(&'t str), UnexpectedArgument(&'a str),
ArgumentError(&'static str, &'t str, ParseErrorKind), ArgumentError(&'static str, &'a str, ParseErrorKind),
//TODO //TODO
//Exclusive(&'static str, &'a str), //Exclusive(&'static str, &'a str),
RequiredPositional(&'static str), RequiredPositional(&'static str),
@@ -151,35 +151,6 @@ impl<ID: 'static> Opts<ID> {
} }
} }
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 // 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));
@@ -208,16 +179,16 @@ impl<ID: 'static> Opts<ID> {
ParseResult::ContinueSuccess ParseResult::ContinueSuccess
} }
/// Parse the next token in the argument stream. /// Parse the next token in the argument stream
fn next<'r, 't>(&self, state: &mut ParserState<ID>, token: &'t str, program_name: &str, fn next<'a, 'b>(&self, state: &mut ParserState<ID>, token: &'b str, program_name: &str,
handler: &mut impl FnMut(ParseHandlerContext<ID>) -> HandlerResult<'r, ParseControl> handler: &mut impl FnMut(ParseHandlerContext<ID>) -> HandlerResult<'a, ParseControl>
) -> HandlerResult<'t, ParseControl> where 'r: 't { ) -> 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(ParseHandlerContext{ program_name, id: &option.id, option, name, arg: 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))
=> Err(ParseError::ArgumentError(name, value.unwrap(), kind)), => Err(ParseError::ArgumentError(name, value, kind)),
Err(err) => Err(err), Err(err) => Err(err),
Ok(ctl) => Ok(ctl), Ok(ctl) => Ok(ctl),
} }
@@ -227,7 +198,7 @@ impl<ID: 'static> Opts<ID> {
// was matched and didn't have an equals sign separating a value, // was matched and didn't have an equals sign separating a value,
// then call the handler here. // then call the handler here.
if let Some((name, option)) = state.expects_arg.take() { if let Some((name, option)) = state.expects_arg.take() {
call_handler(option, name, Some(token)) call_handler(option, name, token)
} else { } else {
// Check if the next argument token starts with an option flag // Check if the next argument token starts with an option flag
if self.flag_chars.chars().any(|c| token.starts_with(c)) { if self.flag_chars.chars().any(|c| token.starts_with(c)) {
@@ -259,9 +230,9 @@ impl<ID: 'static> Opts<ID> {
match (&option.r#type, value_str) { match (&option.r#type, value_str) {
// Call handler for flag-only options // Call handler for flag-only options
(OptType::Flag, None) => call_handler(option, name, None), (OptType::Flag, None) => call_handler(option, name, ""),
// Value was provided this token, so call the handler right now // Value was provided this token, so call the handler right now
(OptType::Value, Some(value)) => call_handler(option, name, Some(value)), (OptType::Value, Some(value)) => call_handler(option, name, value),
// No value available in this token, delay handling to next token // No value available in this token, delay handling to next token
(OptType::Value, None) => { (OptType::Value, None) => {
state.expects_arg = Some((name, option)); state.expects_arg = Some((name, option));
@@ -276,85 +247,7 @@ impl<ID: 'static> Opts<ID> {
// Find the next positional argument // Find the next positional argument
for (i, option) in self.options[state.positional_index..].iter().enumerate() { for (i, option) in self.options[state.positional_index..].iter().enumerate() {
if matches!(option.r#type, OptType::Positional) { if matches!(option.r#type, OptType::Positional) {
call_handler(option, option.first_name(), Some(token))?; call_handler(option, option.first_name(), 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; state.positional_index += i + 1;
return Ok(ParseControl::Continue); return Ok(ParseControl::Continue);
} }
@@ -367,23 +260,23 @@ impl<ID: 'static> Opts<ID> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
extern crate alloc;
use alloc::string::String;
use super::*; 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] #[test]
fn test_parse() { fn test() {
extern crate alloc; enum ArgID { One, Two, Three, Four, Five }
use alloc::string::String; 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"];
//TODO: currently needs alloc to deal with arguments not being able to escape handler
let mut one: Option<String> = None; let mut one: Option<String> = None;
let mut two = false; let mut two = false;
let mut three: Option<String> = None; let mut three: Option<String> = None;
@@ -391,11 +284,11 @@ mod tests {
let mut five: Option<String> = None; let mut five: Option<String> = None;
assert!(matches!(OPTIONS.parse("", ARGUMENTS.iter(), |ctx| { assert!(matches!(OPTIONS.parse("", ARGUMENTS.iter(), |ctx| {
match ctx.id { match ctx.id {
ArgID::One => { one = Some(ctx.arg.unwrap().into()); } ArgID::One => { one = Some(ctx.arg.into()); }
ArgID::Two => { two = true; } ArgID::Two => { two = true; }
ArgID::Three => { three = Some(ctx.arg.unwrap().into()); } ArgID::Three => { three = Some(ctx.arg.into()); }
ArgID::Four => { four = Some(ctx.arg.unwrap().into()); } ArgID::Four => { four = Some(ctx.arg.into()); }
ArgID::Five => { five = Some(ctx.arg.unwrap().into()); } ArgID::Five => { five = Some(ctx.arg.into()); }
} }
Ok(ParseControl::Continue) Ok(ParseControl::Continue)
}, |_, error| { }, |_, error| {
@@ -408,31 +301,4 @@ mod tests {
assert_eq!(four, Some("four".into())); assert_eq!(four, Some("four".into()));
assert_eq!(five, Some("".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(""));
}
} }

View File

@@ -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>(&'static self, handler: impl FnMut(ParseHandlerContext<ID>) -> 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,
@@ -69,7 +69,7 @@ impl Opts<&'static str> {
/// Help and errors are formatted in a standard user-friendly format. /// Help and errors are formatted in a standard user-friendly format.
/// ///
/// Requires `features = ["std"]`. /// Requires `features = ["std"]`.
pub fn parse_map_easy(&'static self) -> ParseMapResult { pub fn parse_map_easy(&self) -> ParseMapResult {
let (program_name, argv) = Self::easy_args(); let (program_name, argv) = Self::easy_args();
self.parse_map(&program_name, argv, self.parse_map(&program_name, argv,
|name| self.print_full_help(name), |name| self.print_full_help(name),