use std::fmt::Display;
use twilight_mention::ParseMention;
use twilight_model::id::Id;
use twilight_model::id::marker::{ChannelMarker, RoleMarker, UserMarker};
use crate::commands::errors::ArgumentError;
use crate::commands::prefixed::context::PrefixedContext;
use crate::state::StateBound;
use crate::utils::DynFuture;
use crate::wrappers::types::channels::{Channel, ChannelMention};
use crate::wrappers::types::roles::{Role, RoleMention};
use crate::wrappers::types::users::{User, UserMention};
pub trait IntoArgument<State = ()>: Sized + Send + Sync
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>>;
}
impl<State> IntoArgument<State> for String
where
State: StateBound,
{
fn into_argument(
_ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let trimmed = args.trim_start();
if let Some(argument) = parse_token(trimmed) {
let remaining = trimmed[(argument.value().last + 1)..].to_string();
Ok((argument.to_string(), remaining))
} else {
Err(ArgumentError::Missing)
}
})
}
}
#[derive(Debug)]
enum Token {
String(TokenValue),
InSingleQuote(TokenValue),
InDoubleQuote(TokenValue),
Spaces(TokenValue),
}
impl Token {
fn value(&self) -> &TokenValue {
match self {
Self::InDoubleQuote(value) => value,
Self::InSingleQuote(value) => value,
Self::Spaces(value) => value,
Self::String(value) => value,
}
}
}
impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(inner) => f.write_str(&inner.value),
Self::Spaces(inner) => f.write_str(&inner.value),
Self::InDoubleQuote(inner) => f.write_str(&inner.value),
Self::InSingleQuote(inner) => f.write_str(&inner.value),
}
}
}
#[derive(Debug)]
#[allow(dead_code)]
struct TokenValue {
value: String,
first: usize,
last: usize,
}
fn parse_token(args: &str) -> Option<Token> {
if args.is_empty() {
return None;
}
let chars: Vec<_> = args.chars().collect();
match chars[0] {
' ' => Some(parse_space(&chars, &mut 0)),
'\'' => Some(parse_single_quote(&chars, &mut 0)),
'"' => Some(parse_double_quote(&chars, &mut 0)),
_ => Some(parse_string(&chars, &mut 0)),
}
}
fn parse_string(chars: &[char], i: &mut usize) -> Token {
let mut current = String::new();
let first_i = *i;
while *i < chars.len() {
let current_char = chars[*i];
match current_char {
' ' => {
break;
}
'\\' => {
*i += 1;
if ['\'', '"', '\\'].contains(&chars[*i]) {
current.push(chars[*i]);
} else {
current.push('\\');
current.push(chars[*i]);
}
}
ch => {
current.push(ch);
}
}
*i += 1;
}
Token::String(TokenValue {
value: current,
first: first_i,
last: *i - 1,
})
}
fn parse_space(chars: &[char], i: &mut usize) -> Token {
let mut current = String::new();
let first_i = *i;
while *i < chars.len() && chars[*i] == ' ' {
current.push(chars[*i]);
*i += 1;
}
Token::Spaces(TokenValue {
value: current,
first: first_i,
last: *i - 1,
})
}
fn parse_single_quote(chars: &[char], i: &mut usize) -> Token {
let first_i = *i;
let mut current = String::new();
if chars[*i] != '\'' {
unreachable!("The first character of a single-quote string must be a single quote (').");
}
*i += 1;
while *i < chars.len() {
match chars[*i] {
'\\' => {
*i += 1;
if let Some(next) = chars.get(*i) {
if ['\\', '\''].contains(next) {
current.push(*next);
} else {
current.push('\\');
current.push(*next);
}
} else {
break; }
}
'\'' => {
*i += 1;
return Token::InSingleQuote(TokenValue {
value: current,
first: first_i,
last: *i - 1,
});
}
ch => {
current.push(ch);
}
}
*i += 1;
}
*i = first_i;
parse_string(chars, i)
}
fn parse_double_quote(chars: &[char], i: &mut usize) -> Token {
let first_i = *i;
let mut current = String::new();
if chars[*i] != '"' {
unreachable!("The first character of a double-quote string must be a double quote (\").");
}
*i += 1;
while *i < chars.len() {
match chars[*i] {
'\\' => {
*i += 1;
if let Some(next) = chars.get(*i) {
if ['\\', '"'].contains(next) {
current.push(*next);
} else {
current.push('\\');
current.push(*next);
}
} else {
break; }
}
'"' => {
*i += 1;
return Token::InDoubleQuote(TokenValue {
value: current,
first: first_i,
last: *i - 1,
});
}
ch => {
current.push(ch);
}
}
*i += 1;
}
*i = first_i;
parse_string(chars, i)
}
pub struct GreedyString(pub String);
impl<State> IntoArgument<State> for GreedyString
where
State: StateBound,
{
fn into_argument(
_ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move { Ok((GreedyString(args.trim_start().to_string()), "".to_string())) })
}
}
impl<State> IntoArgument<State> for char
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
let mut chars = arg.chars();
match (chars.next(), chars.next()) {
(Some(c), None) => Ok((c, remaining)),
_ => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for i8
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for i16
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for i32
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for i64
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for i128
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for isize
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for u8
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for u16
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for u32
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for u64
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for u128
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for usize
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for f32
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for f64
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.parse::<Self>() {
Ok(num) => Ok((num, remaining)),
Err(_) => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for bool
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx, args).await?;
match arg.to_lowercase().as_str() {
"true" | "y" | "yes" | "1" | "on" => Ok((true, remaining)),
"false" | "n" | "no" | "0" | "off" => Ok((false, remaining)),
_ => Err(ArgumentError::Misformatted),
}
})
}
}
impl<State> IntoArgument<State> for User
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx.clone(), args).await?;
let user_id = Id::<UserMarker>::parse(&arg).map_err(|_| ArgumentError::Misformatted)?;
ctx.event
.mentions
.iter()
.find(|mention| mention.id == user_id)
.ok_or(ArgumentError::MissingResolved)?;
let user = ctx
.handle
.get_or_fetch_user(user_id.get())
.await
.map_err(ArgumentError::new)?;
Ok((user, remaining))
})
}
}
impl<State> IntoArgument<State> for UserMention
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx.clone(), args).await?;
let user_id = Id::<UserMarker>::parse(&arg).map_err(|_| ArgumentError::Misformatted)?;
let mention = ctx
.event
.mentions
.iter()
.find(|mention| mention.id == user_id)
.ok_or(ArgumentError::MissingResolved)?;
Ok((mention.clone().into(), remaining))
})
}
}
impl<State> IntoArgument<State> for Channel
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx.clone(), args).await?;
let channel_id =
Id::<ChannelMarker>::parse(&arg).map_err(|_| ArgumentError::Misformatted)?;
ctx.event
.mention_channels
.iter()
.find(|mention| mention.id == channel_id)
.ok_or(ArgumentError::MissingResolved)?;
let channel = ctx
.handle
.client
.channel(channel_id)
.await
.map_err(ArgumentError::new)?
.model()
.await
.map_err(ArgumentError::new)?;
Ok((channel.into(), remaining))
})
}
}
impl<State> IntoArgument<State> for ChannelMention
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx.clone(), args).await?;
let channel_id =
Id::<ChannelMarker>::parse(&arg).map_err(|_| ArgumentError::Misformatted)?;
let mention = ctx
.event
.mention_channels
.iter()
.find(|mention| mention.id == channel_id)
.ok_or(ArgumentError::MissingResolved)?;
Ok((mention.clone().into(), remaining))
})
}
}
impl<State> IntoArgument<State> for Role
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
if let Some(guild_id) = ctx.event.guild_id {
let (arg, remaining) = String::into_argument(ctx.clone(), args).await?;
let role_id =
Id::<RoleMarker>::parse(&arg).map_err(|_| ArgumentError::Misformatted)?;
ctx.event
.mention_roles
.iter()
.find(|mention| **mention == role_id)
.ok_or(ArgumentError::MissingResolved)?;
let role = ctx
.handle
.client
.role(guild_id, role_id)
.await
.map_err(ArgumentError::new)?
.model()
.await
.map_err(ArgumentError::new)?;
Ok((role.into(), remaining))
} else {
Err(ArgumentError::WrongContext)
}
})
}
}
impl<State> IntoArgument<State> for RoleMention
where
State: StateBound,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
let (arg, remaining) = String::into_argument(ctx.clone(), args).await?;
let role_id = Id::<RoleMarker>::parse(&arg).map_err(|_| ArgumentError::Misformatted)?;
let mention = ctx
.event
.mention_roles
.iter()
.find(|mention| **mention == role_id)
.ok_or(ArgumentError::MissingResolved)?;
Ok(((*mention).into(), remaining))
})
}
}
impl<State, T> IntoArgument<State> for Option<T>
where
State: StateBound,
T: IntoArgument<State>,
{
fn into_argument(
ctx: PrefixedContext<State>,
args: String,
) -> DynFuture<'static, Result<(Self, String), ArgumentError>> {
Box::pin(async move {
match T::into_argument(ctx, args.clone()).await {
Ok((arg, remaining)) => Ok((Some(arg), remaining)),
Err(_) => Ok((None, args)),
}
})
}
}