use std::{fmt, fmt::Write};
use super::{Anyhow, ArgumentName, CommandParseError, IdParseError, PathParseError};
#[derive(Debug)]
pub enum Downcast {
CommandParse(CommandParseError),
IdParse(IdParseError),
PathParse(PathParseError),
Other(Anyhow),
}
macro_rules! try_downcast {
($id:ident => $($var:expr),+ $(,)?) => { try_downcast!(@map $id => $($var),+) };
(@map $id:ident => $then:expr, $($else:expr),+) => {
$id.downcast().map_or_else(try_downcast!(@cls $($else),+), $then)
};
(@cls $then:expr, $($else:expr),+) => { |e| try_downcast!(@map e => $then, $($else),+) };
(@cls $then:expr) => { $then };
}
impl From<Anyhow> for Downcast {
fn from(anyhow: Anyhow) -> Self {
try_downcast!(anyhow =>
Self::CommandParse,
Self::IdParse,
Self::PathParse,
Self::Other,
)
}
}
pub trait FoldError {
type Output;
fn fold_anyhow(&self, err: Anyhow) -> Self::Output {
match err.into() {
Downcast::CommandParse(c) => self.fold_command_parse(c),
Downcast::IdParse(i) => self.fold_id_parse(i),
Downcast::PathParse(p) => self.fold_path_parse(p),
Downcast::Other(o) => self.other(o),
}
}
fn fold_id_parse(&self, err: IdParseError) -> Self::Output {
match err {
IdParseError::NoMatch(given, available) => self.no_id_match(given, available),
IdParseError::Ambiguous(possible, given) => self.ambiguous_id(possible, given),
}
}
fn fold_path_parse(&self, err: PathParseError) -> Self::Output {
match err {
PathParseError::Incomplete(available) => self.incomplete_path(available),
PathParseError::BadId(err) => self.bad_path_id(err),
PathParseError::Trailing(extra) => self.trailing_path(extra),
}
}
fn fold_command_parse(&self, err: CommandParseError) -> Self::Output {
match err {
CommandParseError::NoInput => self.no_input(),
CommandParseError::BadId(err) => self.bad_id(err),
CommandParseError::MissingRequired(ArgumentName { cmd, arg }) => {
self.missing_required(cmd, arg)
},
CommandParseError::BadConvert(ArgumentName { cmd, arg }, err) => {
self.bad_convert(cmd, arg, self.fold_anyhow(err))
},
CommandParseError::Trailing(cmd, extra) => self.trailing(cmd, extra),
CommandParseError::Subcommand(subcmd, err) => {
self.subcommand(subcmd, self.fold_command_parse(*err))
},
}
}
fn no_id_match(&self, given: String, available: &'static [&'static str]) -> Self::Output;
fn ambiguous_id(&self, possible: &'static [&'static str], given: String) -> Self::Output;
fn incomplete_path(&self, possible: &'static [&'static str]) -> Self::Output;
fn bad_path_id(&self, err: IdParseError) -> Self::Output { self.fold_id_parse(err) }
fn trailing_path(&self, extra: String) -> Self::Output;
fn no_input(&self) -> Self::Output;
fn bad_id(&self, err: IdParseError) -> Self::Output { self.fold_id_parse(err) }
fn missing_required(&self, cmd: &'static str, arg: &'static str) -> Self::Output;
fn bad_convert(
&self,
cmd: &'static str,
arg: &'static str,
inner: Self::Output,
) -> Self::Output;
fn trailing(&self, cmd: &'static str, extra: String) -> Self::Output;
fn subcommand(&self, subcmd: &'static str, inner: Self::Output) -> Self::Output;
fn other(&self, error: Anyhow) -> Self::Output;
}
#[derive(Debug, Clone, Copy)]
pub struct SimpleFoldError;
impl SimpleFoldError {
pub fn write_options<S: fmt::Display>(
mut w: impl Write,
opts: impl IntoIterator<Item = S>,
) -> fmt::Result {
opts.into_iter().enumerate().try_for_each(|(i, opt)| {
if i != 0 {
write!(w, ", ")?;
}
write!(w, "'{}'", opt)
})
}
}
#[cfg(feature = "strsim")]
use crate::did_you_mean;
#[cfg(not(feature = "strsim"))]
fn did_you_mean(_: impl std::any::Any, _: impl std::any::Any) -> std::iter::Empty<String> {
std::iter::empty()
}
impl FoldError for SimpleFoldError {
type Output = Result<String, fmt::Error>;
fn no_id_match(&self, given: String, available: &'static [&'static str]) -> Self::Output {
let mut s = String::new();
write!(s, "Not sure what you mean by {:?}.", given)?;
let mut dym = did_you_mean(given, available).peekable();
if dym.peek().is_some() {
s.push_str(" Did you mean: ");
Self::write_options(&mut s, dym)?;
} else if !available.is_empty() {
s.push_str(" Available options are: ");
Self::write_options(&mut s, available)?;
}
Ok(s)
}
fn ambiguous_id(&self, possible: &'static [&'static str], given: String) -> Self::Output {
let mut s = String::new();
write!(s, "Not sure what you mean by {:?}. Could be: ", given)?;
Self::write_options(&mut s, possible)?;
Ok(s)
}
fn incomplete_path(&self, possible: &'static [&'static str]) -> Self::Output {
let mut s = String::new();
write!(s, "Incomplete command path, expected one of: ")?;
Self::write_options(&mut s, possible)?;
Ok(s)
}
fn trailing_path(&self, extra: String) -> Self::Output {
Ok(format!("Unexpected extra path argument {:?}", extra))
}
fn no_input(&self) -> Self::Output { Ok(String::new()) }
fn missing_required(&self, cmd: &'static str, arg: &'static str) -> Self::Output {
Ok(format!(
"Missing required argument '{}' to command '{}'",
arg, cmd
))
}
fn bad_convert(
&self,
cmd: &'static str,
arg: &'static str,
inner: Self::Output,
) -> Self::Output {
Ok(format!(
"Couldn't parse argument '{}' of command '{}': {}",
arg, cmd, inner?
))
}
fn trailing(&self, cmd: &'static str, extra: String) -> Self::Output {
Ok(format!(
"Unexpected extra argument {:?} to '{}'",
extra, cmd
))
}
fn subcommand(&self, subcmd: &'static str, inner: Self::Output) -> Self::Output {
Ok(format!("Subcommand '{}' failed: {}", subcmd, inner?))
}
fn other(&self, error: anyhow::Error) -> Self::Output { Ok(format!("{:?}", error)) }
}