Generalise error & usage writer

This commit is contained in:
2025-11-05 07:48:15 +11:00
parent 9d8960e772
commit 67dc191443
2 changed files with 55 additions and 14 deletions

View File

@@ -3,6 +3,7 @@
* SPDX-License-Identifier: MIT * SPDX-License-Identifier: MIT
*/ */
/// Enough context to show full help text.
pub struct HelpWriterContext<'a, ID: 'static> { pub struct HelpWriterContext<'a, ID: 'static> {
pub options: &'a Opts<ID>, pub options: &'a Opts<ID>,
pub program_name: &'a str, pub program_name: &'a str,
@@ -184,3 +185,43 @@ impl<ID> core::fmt::Display for StandardFullHelpWriter<'_, ID> {
Ok(()) Ok(())
} }
} }
// Enough context to show usage and error information.
pub struct ErrorUsageWriterContext<'a, ID: 'static> {
pub options: &'a Opts<ID>,
pub program_name: &'a str,
pub error: ParseError<'a>
}
pub trait ErrorUsageWriter<'a, ID: 'static>: core::fmt::Display {
fn new(ctx: ErrorUsageWriterContext<'a, ID>) -> Self;
}
pub struct StandardErrorUsageWriter<'a, ID: 'static>(ErrorUsageWriterContext<'a, ID>);
impl<'a, ID: 'static> ErrorUsageWriter<'a, ID> for StandardErrorUsageWriter<'a, ID> {
fn new(ctx: ErrorUsageWriterContext<'a, ID>) -> Self { Self(ctx) }
}
impl<ID> core::fmt::Display for StandardErrorUsageWriter<'_, ID> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Write error
writeln!(f, "{name}: {error}", name=self.0.program_name, error = self.0.error)?;
// Write usage
writeln!(f, "{}", StandardShortUsageWriter::new(HelpWriterContext {
options: self.0.options,
program_name: self.0.program_name
}))?;
// Write full help instruction if a
if let Some(help_option) = self.0.options.help_option() {
writeln!(f, "Run '{name} {help}' to view all available options.",
name = self.0.program_name,
// Prefer long name, but otherwise any name is fine
help = help_option.first_long_name().unwrap_or(help_option.first_name()))?;
}
Ok(())
}
}

View File

@@ -6,10 +6,10 @@
extern crate std; extern crate std;
use crate::alloc::ParseMapResult; use crate::alloc::ParseMapResult;
use crate::{HandlerResult, HelpWriter, HelpWriterContext, Opt, Opts, ParseControl, ParseError, ParseResult, StandardFullHelpWriter, StandardShortUsageWriter}; use crate::{ErrorUsageWriter, ErrorUsageWriterContext, HandlerResult, HelpWriter, HelpWriterContext, Opt, Opts, ParseControl, ParseError, ParseResult, StandardErrorUsageWriter, StandardFullHelpWriter};
use std::path::Path; use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use std::{env, eprint, eprintln, print}; use std::{env, eprint, print};
impl<ID: 'static> Opts<ID> { impl<ID: 'static> Opts<ID> {
/// Wrapper around [Opts::parse] that gathers arguments from the command line and prints errors to stderr. /// Wrapper around [Opts::parse] that gathers arguments from the command line and prints errors to stderr.
@@ -19,7 +19,8 @@ impl<ID: 'static> Opts<ID> {
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(&str, &ID, &Opt<ID>, &str, &str) -> 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, |name, e| self.easy_error(name, e)) self.parse(&program_name, argv, handler,
|name, e| self.eprint_usage::<StandardErrorUsageWriter<'_, ID>>(name, e))
} }
/// Prints full help text for the options using the standard full. /// Prints full help text for the options using the standard full.
@@ -45,21 +46,20 @@ impl<ID: 'static> Opts<ID> {
eprint!("{}", W::new(ctx)); eprint!("{}", W::new(ctx));
} }
fn easy_args<'a>() -> (Rc<str>, env::Args) { /// Print error & usage text to stderr using the provided error & usage writer.
///
/// Requires `features = ["std"]`.
pub fn eprint_usage<'a, W: ErrorUsageWriter<'a, ID>>(&'a self, program_name: &'a str, error: ParseError<'a>) {
let ctx = ErrorUsageWriterContext { options: self, program_name, error };
eprint!("{}", W::new(ctx));
}
fn easy_args() -> (Rc<str>, env::Args) {
let mut argv = env::args(); let mut argv = env::args();
let argv0 = argv.next().unwrap(); let argv0 = argv.next().unwrap();
let program_name = Path::new(&argv0).file_name().unwrap().to_string_lossy(); let program_name = Path::new(&argv0).file_name().unwrap().to_string_lossy();
(program_name.into(), argv) (program_name.into(), argv)
} }
fn easy_error(&self, program_name: &str, err: ParseError) {
eprintln!("{program_name}: {err}");
self.eprint_help::<StandardShortUsageWriter<'_, ID>>(program_name);
if let Some(help_option) = self.help_option() {
eprintln!("Run '{program_name} {help}' to view all available options.",
help = help_option.first_long_name().unwrap_or(help_option.first_name()));
}
}
} }
impl Opts<&'static str> { impl Opts<&'static str> {
@@ -71,6 +71,6 @@ impl Opts<&'static str> {
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),
|name, e| self.easy_error(name, e)) |name, e| self.eprint_usage::<StandardErrorUsageWriter<'_, &'static str>>(name, e))
} }
} }