use quote::{format_ident, quote, ToTokens};
use syn::{
token::{Brace, Bracket, Comma, Paren},
Attribute, ExprLit, Field, FieldMutability, Fields, FnArg, Generics, Ident, ImplItem,
ImplItemFn, Item, Lit, LitStr, Meta, MetaList, MetaNameValue, Pat, PatIdent, PatType, Path,
PathArguments, PathSegment, Receiver, Token, TypePath, Visibility,
__private::Span,
punctuated::Punctuated,
AttrStyle, Expr, FieldsNamed, ItemFn, ItemStruct, MacroDelimiter, Type,
};
fn type_hint(mut type_str: String) -> String {
let reference = match type_str.clone().chars().next().unwrap() {
'&' => "ref | ",
_ => "",
};
type_str = type_str.replace("&", "").replace(" ", "");
let hint: String = match type_str.as_str() {
"String" => "string".to_string(),
"str" => "str".to_string(),
"bool" => "boolean".to_string(),
s if s.starts_with("i") => format!("signed int, {} bits", s[1..].to_string()),
s if s.starts_with("u") => format!("unsigned int, {} bits", s[1..].to_string()),
s if s.starts_with("f") => format!("float, {} bits", s[1..].to_string()),
s if s.starts_with("Vec<") => {
let inner = type_hint(s[4..s.len() - 1].to_string());
format!("vector of {}", inner[1..inner.len() - 1].to_string())
}
s if s.starts_with("[") && s.ends_with("]") => {
let type_number = s.split("; ").collect::<Vec<&str>>();
let inner = type_hint(type_number[0].to_string());
format!(
"{}-array of {}",
type_number[1].to_string(),
inner[1..inner.len() - 1].to_string()
)
}
s => s.to_string(), }
.into();
format!("[{}{}]", reference, hint)
}
fn attrs_with_type_comment(attrs: &Vec<Attribute>, type_str: &String) -> Vec<Attribute> {
let mut attrs = attrs.clone();
let mut path_segments = Punctuated::new();
path_segments.push(PathSegment {
ident: format_ident!("doc"),
arguments: PathArguments::None,
});
attrs.insert(
0,
Attribute {
pound_token: Token),
style: AttrStyle::Outer,
bracket_token: Bracket(Span::call_site()),
meta: Meta::NameValue(MetaNameValue {
path: Path {
leading_colon: None,
segments: path_segments,
},
eq_token: Token),
value: Expr::Lit(ExprLit {
attrs: vec![],
lit: Lit::Str(LitStr::new(
&type_hint(type_str.clone()).as_str(),
Span::call_site(),
)),
}),
}),
},
);
attrs
}
pub fn fields(function: ItemFn) -> Fields {
let mut fields: Punctuated<Field, Comma> = Punctuated::new();
function.sig.inputs.iter().for_each(|x| match x {
FnArg::Typed(PatType {
attrs,
pat,
colon_token,
ty,
}) => {
let type_str = ty.to_token_stream().to_string();
fields.push(Field {
attrs: attrs_with_type_comment(&attrs, &type_str),
vis: Visibility::Inherited,
mutability: FieldMutability::None,
ident: match pat.as_ref() {
Pat::Ident(PatIdent { ident, .. }) => Some(ident.clone()),
_ => None,
},
colon_token: Some(*colon_token),
ty: syn::parse_str(&type_str).unwrap(),
});
}
FnArg::Receiver(_) => {
panic!("'self' as argument not allowed in function signature.");
}
});
Fields::Named(FieldsNamed {
brace_token: Brace(Span::call_site()),
named: fields,
})
}
pub fn to_struct(name: Ident, fields: Fields) -> Item {
let mut inner_path = Punctuated::new();
inner_path.push(PathSegment {
ident: format_ident!("derive"),
arguments: PathArguments::None,
});
Item::Struct(ItemStruct {
attrs: vec),
style: AttrStyle::Outer,
bracket_token: Bracket(Span::call_site()),
meta: Meta::List(MetaList {
path: Path {
leading_colon: None,
segments: inner_path,
},
delimiter: MacroDelimiter::Paren(Paren(Span::call_site())),
tokens: quote! { Debug, Clone, Args },
}),
}],
vis: Visibility::Public(Token)),
struct_token: Token),
ident: name,
generics: Generics {
lt_token: None,
params: Default::default(),
gt_token: None,
where_clause: None,
},
fields: fields.clone(),
semi_token: None,
})
}
pub fn to_impl_item(new_function: ItemFn) -> ImplItem {
let function = modify_function(new_function);
ImplItem::Fn(ImplItemFn {
attrs: function.attrs,
vis: function.vis,
defaultness: None,
sig: function.sig,
block: syn::parse_str(&function.block.to_token_stream().to_string()).unwrap(),
})
}
pub fn modify_function(mut new_function: ItemFn) -> ItemFn {
new_function.sig.ident = format_ident!("delegate");
let mut new_function_block = new_function.block.to_token_stream().to_string();
new_function.sig.inputs.iter().for_each(|x| match x {
FnArg::Typed(PatType {
attrs: _,
pat,
colon_token: _,
ty: _,
}) => {
let pat = pat.to_token_stream().to_string();
new_function_block = new_function_block.replace(&pat, &format!("self.{}", pat));
}
FnArg::Receiver(_) => {
panic!("'self' as argument not allowed in function signature.");
}
});
new_function.sig.inputs = Punctuated::new();
new_function.sig.inputs.push(self_argument());
new_function.block = syn::parse_str(&new_function_block).unwrap();
new_function
}
fn self_argument() -> FnArg {
FnArg::Receiver(Receiver {
attrs: vec![],
reference: None,
mutability: None, self_token: Token),
colon_token: Some(Token)),
ty: Box::new(Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: None,
segments: Punctuated::from_iter(vec![PathSegment {
ident: format_ident!("Self"),
arguments: PathArguments::None,
}]),
},
})),
})
}