diff --git a/jaarg/src/help.rs b/jaarg/src/help.rs index 57b9275..84fad67 100644 --- a/jaarg/src/help.rs +++ b/jaarg/src/help.rs @@ -3,6 +3,7 @@ * SPDX-License-Identifier: MIT */ +/// Enough context to show full help text. pub struct HelpWriterContext<'a, ID: 'static> { pub options: &'a Opts, pub program_name: &'a str, @@ -184,3 +185,43 @@ impl core::fmt::Display for StandardFullHelpWriter<'_, ID> { Ok(()) } } + + +// Enough context to show usage and error information. +pub struct ErrorUsageWriterContext<'a, ID: 'static> { + pub options: &'a Opts, + 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 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(()) + } +} diff --git a/jaarg/src/std.rs b/jaarg/src/std.rs index e710a54..2b5319b 100644 --- a/jaarg/src/std.rs +++ b/jaarg/src/std.rs @@ -6,10 +6,10 @@ extern crate std; 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::rc::Rc; -use std::{env, eprint, eprintln, print}; +use std::{env, eprint, print}; impl Opts { /// Wrapper around [Opts::parse] that gathers arguments from the command line and prints errors to stderr. @@ -19,7 +19,8 @@ impl Opts { pub fn parse_easy<'a>(&self, handler: impl FnMut(&str, &ID, &Opt, &str, &str) -> HandlerResult<'a, ParseControl> ) -> ParseResult { 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::>(name, e)) } /// Prints full help text for the options using the standard full. @@ -45,21 +46,20 @@ impl Opts { eprint!("{}", W::new(ctx)); } - fn easy_args<'a>() -> (Rc, 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, env::Args) { let mut argv = env::args(); let argv0 = argv.next().unwrap(); let program_name = Path::new(&argv0).file_name().unwrap().to_string_lossy(); (program_name.into(), argv) } - - fn easy_error(&self, program_name: &str, err: ParseError) { - eprintln!("{program_name}: {err}"); - self.eprint_help::>(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> { @@ -71,6 +71,6 @@ impl Opts<&'static str> { let (program_name, argv) = Self::easy_args(); self.parse_map(&program_name, argv, |name| self.print_full_help(name), - |name, e| self.easy_error(name, e)) + |name, e| self.eprint_usage::>(name, e)) } }