#![warn(missing_docs, clippy::all, clippy::pedantic, clippy::cargo)]
#![deny(rustdoc::broken_intra_doc_links, missing_debug_implementations)]
#![allow(clippy::module_name_repetitions)]
use std::{
convert::Infallible,
fmt,
fmt::{Display, Formatter},
str::FromStr,
};
use thiserror::Error;
#[cfg(feature = "strsim")]
mod did_you_mean;
mod fold_error;
mod fold_help;
mod tokenize;
#[cfg(feature = "strsim")]
pub use did_you_mean::did_you_mean;
pub use fold_error::{Downcast, FoldError, SimpleFoldError};
pub use fold_help::{FoldHelp, SimpleFoldHelp};
pub use tokenize::tokenize_str_simple;
#[derive(Error, Debug)]
pub enum IdParseError {
#[error("no ID match for {0:?}")]
NoMatch(String, &'static [&'static str]),
#[error("ambiguous ID {0:?}, could be any of {}", .0.join(", "))]
Ambiguous(&'static [&'static str], String),
}
#[derive(Error, Debug)]
pub enum PathParseError {
#[error("no values given for command path, expected one of {}", .0.join(", "))]
Incomplete(&'static [&'static str]),
#[error("failed to parse command ID")]
BadId(#[from] IdParseError),
#[error("trailing argument {0:?}")]
Trailing(String),
}
#[derive(Clone, Copy, Debug)]
pub struct ArgumentName {
pub cmd: &'static str,
pub arg: &'static str,
}
impl Display for ArgumentName {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_fmt(format_args!("{:?} of {:?}", self.arg, self.cmd))
}
}
#[derive(Error, Debug)]
pub enum CommandParseError {
#[error("no values in command parse input")]
NoInput,
#[error("failed to parse command ID")]
BadId(#[from] IdParseError),
#[error("missing required argument {0}")]
MissingRequired(ArgumentName),
#[error("failed to convert argument {0} from a string")]
BadConvert(ArgumentName, anyhow::Error),
#[error("trailing argument {1:?} of {0:?}")]
Trailing(&'static str, String),
#[error("failed to parse subcommand {0:?}")]
Subcommand(&'static str, Box<CommandParseError>),
}
impl From<Infallible> for CommandParseError {
fn from(i: Infallible) -> CommandParseError { match i {} }
}
#[doc(no_inline)]
pub use anyhow::Error as Anyhow;
#[doc(inline)]
pub use docbot_derive::*;
pub trait Command: Sized {
type Id: CommandId;
type Path: CommandPath<Id = Self::Id>;
fn parse<I: IntoIterator<Item = S>, S: AsRef<str>>(iter: I) -> Result<Self, CommandParseError>;
fn id(&self) -> Self::Id;
}
pub trait CommandId: Copy + FromStr<Err = IdParseError> + Display {
fn names() -> &'static [&'static str];
fn to_str(&self) -> &'static str;
}
pub trait CommandPath: From<Self::Id> {
type Id: CommandId;
fn parse<I: IntoIterator<Item = S>, S: AsRef<str>>(iter: I) -> Result<Self, PathParseError>;
fn parse_opt<I: IntoIterator<Item = S>, S: AsRef<str>>(
iter: I,
) -> Result<Option<Self>, PathParseError> {
Self::parse(iter).map_or_else(
|e| match e {
PathParseError::Incomplete(_) => Ok(None),
e => Err(e),
},
|p| Ok(Some(p)),
)
}
fn head(&self) -> Self::Id;
}
impl<T: CommandId> CommandPath for T {
type Id = Self;
fn parse<I: IntoIterator<Item = S>, S: AsRef<str>>(iter: I) -> Result<Self, PathParseError> {
let mut iter = iter.into_iter();
let head = iter
.next()
.ok_or_else(|| PathParseError::Incomplete(Self::Id::names()))?;
if let Some(s) = iter.next() {
return Err(PathParseError::Trailing(s.as_ref().into()));
}
head.as_ref().parse().map_err(PathParseError::BadId)
}
fn head(&self) -> Self::Id { *self }
}
#[derive(Debug, Clone)]
pub struct ArgumentUsage {
pub name: &'static str,
pub is_required: bool,
pub is_rest: bool,
}
#[derive(Debug, Clone)]
pub struct CommandUsage {
pub ids: &'static [&'static str],
pub args: &'static [ArgumentUsage],
pub desc: &'static str,
}
#[derive(Debug, Clone)]
pub struct ArgumentDesc {
pub name: &'static str,
pub is_required: bool,
pub desc: &'static str,
}
#[derive(Debug, Clone)]
pub struct CommandDesc {
pub summary: Option<&'static str>,
pub args: &'static [ArgumentDesc],
pub examples: Option<&'static str>,
}
#[derive(Debug, Clone)]
pub enum HelpTopic {
Command(CommandUsage, CommandDesc),
CommandSet(Option<&'static str>, &'static [CommandUsage]),
Custom(&'static str),
}
pub trait Help: Command {
fn help<U: Into<Self::Path>>(topic: Option<U>) -> &'static HelpTopic;
}
pub mod prelude {
pub use super::{Command, CommandId, Docbot, FoldError, FoldHelp, Help};
}