use crate::builder::{unwrap_type, Wrapper};
use proc_macro::Span;
use proc_macro::TokenStream;
use proc_macro_error::abort;
use quote::ToTokens;
use crate::constants::*;
use lazy_static::lazy_static;
use regex::Regex;
use std::str::FromStr;
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Expr, FnArg, Item, Lit, Token, Type};
#[derive(Debug)]
struct ParamType {
ty: String,
wrapper: Option<Wrapper>,
}
#[derive(Debug)]
struct Param {
name: String,
ty: Option<ParamType>,
}
pub fn try_command(attr: TokenStream, item: TokenStream) -> Result<TokenStream, ()> {
let item = if let Item::Fn(f) = syn::parse::<Item>(item).map_err(|_| abort!(Span::call_site(), "Could not parse"))? {
f
} else {
abort!(Span::call_site(), "Can only be applied to functions")
};
let attr = Punctuated::<Expr, Token![,]>::parse_terminated.parse(attr).map_err(|_| abort!(Span::call_site(), "Could not parse attributes"))?;
let syntax = attr
.iter()
.filter(|e| matches!(e, Expr::Lit(_)))
.map(|e| match e {
Expr::Lit(l) => &l.lit,
_ => panic!(),
})
.filter(|l| matches!(l, Lit::Str(_)))
.map(|l| match l {
Lit::Str(s) => s.value(),
_ => panic!(),
})
.collect::<Vec<String>>()
.first()
.ok_or_else(|| abort!(attr.span(), "Missing command syntax attribute"))?
.clone();
lazy_static! {
static ref SYNTAX_DELIMITER: Regex = Regex::new(r"\s+").unwrap();
}
let mut syntax_vec: Vec<String> = SYNTAX_DELIMITER.split(syntax.as_str()).map(str::to_string).collect();
let name = syntax_vec.first().unwrap_or_else(|| abort!(Span::call_site(), "Invalid command syntax")).clone();
syntax_vec.remove(0);
if &name[0..1] != "/" {
abort!(Span::call_site(), "Invalid command syntax")
}
let name = name.as_str()[1..].to_string();
let params = syntax_vec
.iter()
.map(|s| {
lazy_static! {
static ref DYNAMIC_PARAM: Regex = Regex::new(r"^<(.*)>$").unwrap();
}
if let Some(mat) = DYNAMIC_PARAM.captures(s.as_str()) {
let s = mat.get(1).unwrap().as_str();
let fun_par: (String, String) = item
.sig
.inputs
.iter()
.find_map(|a| match a {
FnArg::Receiver(_) => None,
FnArg::Typed(pat) => (pat.pat.to_token_stream().to_string() == *s).then(|| (pat.pat.to_token_stream().to_string(), pat.ty.to_token_stream().to_string())),
})
.unwrap_or_else(|| abort!(Span::call_site(), "Missing parameter with name {}", s));
let (ty, wrapper) = unwrap_type(fun_par.1.replace(" ", ""));
Param {
name: s.to_string(),
ty: Some(ParamType { ty, wrapper }),
}
} else {
Param { name: s.clone(), ty: None }
}
})
.collect::<Vec<Param>>();
if !params
.iter()
.fold((None, true), |(acc, b), v| {
if let Some(ty) = &v.ty {
match (acc, ty.wrapper.as_ref(), b) {
(_, _, false) => (None, false),
(None, None, _) => (None, true),
(Some(Wrapper::Option), Some(Wrapper::Option), _) => (Some(Wrapper::Option), true),
(Some(Wrapper::Vec), Some(Wrapper::Vec), _) => (Some(Wrapper::Vec), true),
(None, Some(Wrapper::Option), _) => (Some(Wrapper::Option), true),
(Some(Wrapper::Option), Some(Wrapper::Vec), _) => (Some(Wrapper::Vec), true),
_ => (None, false),
}
} else {
(acc, b)
}
})
.1
{
abort!(Span::call_site(), "Parameters order is not respected")
}
lazy_static! {
static ref STATE_REGEX: Regex = Regex::new("(?:(?:std::)?sync::)?Arc<(?:(?:std::)?opt::)?Option<(.*)>>").unwrap();
}
let type_bounds = item
.sig
.inputs
.iter()
.map(|a| match a {
FnArg::Receiver(_) => abort!(a, "Handlers are not allowed self parameters"),
FnArg::Typed(t) => {
let name = t.pat.to_token_stream().to_string();
if let Some(p) = params.iter().find(|p| p.name == name) {
if let Some(ty) = &p.ty {
format!("{}: {FROM_STR}", ty.ty)
} else {
String::new()
}
} else {
match t.ty.as_ref() {
Type::Reference(r) => {
format!("for<'a> &'a {} {}: {FROM_UPDATE}<'a, T>", r.mutability.map(|_| "mut").unwrap_or(""), r.elem.to_token_stream())
}
_ => format!("for<'a> {}: {FROM_UPDATE}<'a, T>", t.ty.to_token_stream()),
}
}
}
})
.collect::<Vec<_>>()
.join(", ");
let rank = attr
.iter()
.find_map(|e| match e {
Expr::Assign(a) => match (a.left.as_ref(), a.right.as_ref()) {
(Expr::Path(path), Expr::Lit(lit)) => (path.path.to_token_stream().to_string() == "rank").then(|| match &lit.lit {
Lit::Str(s) => u8::from_str(s.value().as_str()).ok(),
_ => None,
}),
_ => None,
},
_ => None,
})
.flatten()
.unwrap_or(0);
let vis = item.vis.to_token_stream().to_string();
Ok(format!(
r#"
{vis} fn {name}_command<T: Sync>() -> {COMMAND_HANDLER}<T>
where {type_bounds}
{{
struct H {{}}
#[async_trait::async_trait]
impl<T: {SYNC}> {COMMAND_HANDLER_TRAIT}<T> for H
where {type_bounds}
{{
async fn handle(&self, args: &[String], u: &{UPDATE}, b: &{BOT}, s: Option<&{BOT_STATE}<T>>) -> Result<(), {HANDLER_ERROR}> {{
let mut __i = 0;
{}
{name}({}).await.map_err(|e| {{
{LOG}::error!("Command {name} exited with {{:?}}", e);
{HANDLER_ERROR}::Runtime
}})
}}
}}
{COMMAND_HANDLER} {{
name: "{name}".to_string(),
handler: Box::new(H {{}}),
rank: {rank},
syntax: "{syntax}",
}}
}}
{}
"#,
params
.iter()
.map(|p| {
match &p.ty {
None => format!("if args.get(__i).is_none() || args.get(__i).unwrap() != \"{}\" {{ return Err({HANDLER_ERROR}::Parse) }}", p.name),
Some(ty) => {
let from_str = format!("<{} as std::str::FromStr>::from_str(args.get(__i).ok_or({HANDLER_ERROR}::Parse)?.as_str()).map_err(|_| {HANDLER_ERROR}::Parse)?", ty.ty);
match ty.wrapper {
None => format!(r#"let {} = {};"#, p.name, from_str),
Some(Wrapper::Option) => format!(r#"let {} = if let Some(s) = args.get(__i) {{ Some({}) }} else {{ None }};"#, p.name, from_str),
Some(Wrapper::Vec) => format!(r#"let mut {name} = vec[]; while let Some(s) = args.get(__i) {{ {name}.push({}}}"#, from_str, name = p.name),
}
}
}
})
.collect::<Vec<String>>()
.join("\n__i += 1;\n"),
item.sig
.inputs
.iter()
.map(|a| {
if let FnArg::Typed(a) = a {
let name = a.pat.to_token_stream().to_string();
if params.iter().any(|p| p.name == name) {
name
} else {
format!("<{} as {FROM_UPDATE}<T>>::from_update(b, u, s).ok_or({HANDLER_ERROR}::Parse)?", a.ty.to_token_stream())
}
} else {
abort!(a, "Invalid parameter")
}
})
.collect::<Vec<String>>()
.join(","),
item.to_token_stream()
)
.parse()
.unwrap())
}
pub fn try_commands(item: TokenStream) -> Result<TokenStream, ()> {
let commands = Punctuated::<Expr, Token![,]>::parse_terminated.parse(item).map_err(|_| abort!(Span::call_site(), "Could not parse commands"))?;
let commands = commands.iter().map(|c| c.to_token_stream().to_string().replace(' ', "") + "_command()").collect::<Vec<String>>().join(",");
Ok(format!(r#"{GROUP_COMMANDS}(vec![{}])"#, commands).parse().unwrap())
}