use crate::args::{on_parse_miss, ArgError, FromArg};
use crate::ctx::Ctx;
use crate::extract::{Extracted, FromContext, Reject};
use crate::matcher::{Matcher, ParsedCommand};
use async_trait::async_trait;
use nagisa_types::segment::Segment;
use std::borrow::Cow;
use std::collections::HashMap;
pub type NamedCaptures = HashMap<Cow<'static, str>, Option<String>>;
pub trait SlotValue: Sized {
const TYPE_NAME: &'static str;
fn from_capture(s: &str) -> Result<Self, ArgError>;
}
impl<T: FromArg> SlotValue for T {
const TYPE_NAME: &'static str = <T as FromArg>::TYPE_NAME;
fn from_capture(s: &str) -> Result<Self, ArgError> {
T::from_arg(s).ok_or_else(|| ArgError::Parse {
field: "<slot>",
value: s.to_string(),
expected: <T as FromArg>::TYPE_NAME,
})
}
}
pub trait FromSlots: Sized {
fn matcher() -> Matcher;
fn from_slots(caps: &NamedCaptures) -> Result<Self, ArgError>;
fn usage() -> Option<&'static str> {
None
}
}
pub struct Slots<T>(pub T);
#[async_trait]
impl<T: FromSlots + Send> FromContext for Slots<T> {
async fn from_context(ctx: &Ctx) -> Extracted<Self> {
let p = ctx.get_ext::<ParsedCommand>().ok_or(Reject::Skip)?; match T::from_slots(&p.named_captures) {
Ok(v) => Ok(Slots(v)),
Err(e) => {
on_parse_miss(ctx, &p.command, &e, T::usage()).await;
Err(Reject::Skip)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Tail<T>(pub T);
pub trait FromTailText: Sized {
fn from_tail_text(s: &str) -> Self;
}
impl FromTailText for String {
fn from_tail_text(s: &str) -> Self {
s.to_string()
}
}
impl<T: FromTailText> FromTailText for Tail<T> {
fn from_tail_text(s: &str) -> Self {
Tail(T::from_tail_text(s))
}
}
impl FromTailText for Vec<Segment> {
fn from_tail_text(s: &str) -> Self {
if s.is_empty() {
Vec::new()
} else {
vec![Segment::text(s)]
}
}
}