use std::ffi::OsString;
use std::fmt::{self, Write as _};
#[derive(Debug, PartialEq)]
pub enum Error {
DuplicateOption(String),
MissingArgValue(String),
MissingRequirements(MissingRequirements),
OptionsAfterHelp,
ParseArgument {
arg: String,
value: OsString,
msg: String,
},
UnknownArgument(OsString),
Other(String),
}
impl Error {
#[inline]
pub fn other<S: ToString>(msg: S) -> Self {
Self::Other(msg.to_string())
}
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Error::*;
match &self {
DuplicateOption(arg) => write!(f, "Option '{}' can only be used once.", arg),
MissingArgValue(arg) => write!(f, "Option '{}' requires a value.", arg),
MissingRequirements(req) => req.fmt(f),
OptionsAfterHelp => {
write!(f, "Trailing options are not allowed after 'help' subcommand.")
}
ParseArgument { arg, value, msg } => {
let subj = if arg.starts_with('-') {
"option"
} else {
"argument"
};
write!(f, "Error parsing {} '{}' with value '{:?}': {}.", subj, arg, value, msg)
}
UnknownArgument(arg) => write!(f, "Unrecognized argument: {}", arg.to_string_lossy()),
Other(msg) => msg.fmt(f),
}
}
}
#[doc(hidden)]
#[derive(Debug, Default, PartialEq)]
pub struct MissingRequirements {
options: Vec<&'static str>,
subcommands: Option<Vec<&'static str>>,
positional_args: Vec<&'static str>,
}
impl MissingRequirements {
#[doc(hidden)]
pub fn missing_option(&mut self, name: &'static str) {
self.options.push(name)
}
#[doc(hidden)]
pub fn missing_subcommands(&mut self, commands: impl Iterator<Item = &'static str>) {
self.subcommands = Some(commands.collect());
}
#[doc(hidden)]
pub fn missing_positional_arg(&mut self, name: &'static str) {
self.positional_args.push(name)
}
#[doc(hidden)]
pub fn err_on_any(self) -> Result<(), Error> {
if self.options.is_empty() && self.subcommands.is_none() && self.positional_args.is_empty()
{
Ok(())
} else {
Err(Error::MissingRequirements(self))
}
}
}
const NEWLINE_INDENT: &str = "\n ";
impl fmt::Display for MissingRequirements {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.positional_args.is_empty() {
f.write_str("Required positional arguments not provided:")?;
for arg in &self.positional_args {
f.write_str(NEWLINE_INDENT)?;
f.write_str(arg)?;
}
}
if !self.options.is_empty() {
if !self.positional_args.is_empty() {
f.write_char('\n')?;
}
f.write_str("Required options not provided:")?;
for option in &self.options {
f.write_str(NEWLINE_INDENT)?;
f.write_str(option)?;
}
}
if let Some(missing_subcommands) = &self.subcommands {
if !self.options.is_empty() {
f.write_char('\n')?;
}
f.write_str("One of the following subcommands must be present:")?;
f.write_str(NEWLINE_INDENT)?;
f.write_str("help")?;
for subcommand in missing_subcommands {
f.write_str(NEWLINE_INDENT)?;
f.write_str(subcommand)?;
}
}
f.write_char('\n')
}
}