use convert_case::{Case, Casing};
use std::collections::HashMap;
use proc_macro::TokenStream;
use quote::{ToTokens, format_ident, quote, quote_spanned};
use syn::{
Attribute, DeriveInput, Expr, FnArg, Ident, ItemFn, ItemMod, Lit, MetaNameValue, Type,
parse_macro_input, punctuated::Punctuated, token::Comma,
};
#[derive(PartialEq)]
enum FuncType {
Env,
Node,
Network,
}
impl std::fmt::Display for FuncType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
FuncType::Env => "Env",
FuncType::Node => "Node",
FuncType::Network => "Network",
}
)
}
}
fn nadi_trait_idents(relaxed: bool) -> (proc_macro2::TokenStream, Ident, Ident) {
let (tr, func1, func2) = if relaxed {
(
format_ident!("FromAttributeRelaxed"),
format_ident!("from_attr_relaxed"),
format_ident!("try_from_attr_relaxed"),
)
} else {
(
format_ident!("FromAttribute"),
format_ident!("from_attr"),
format_ident!("try_from_attr"),
)
};
(quote! {::nadi_core::attrs::#tr}, func1, func2)
}
fn nadi_struct_name(name: &Ident, suff: &str) -> Ident {
format_ident!("{}{suff}", name.to_string().to_case(Case::UpperCamel))
}
fn nadi_func_impl(ft: &FuncType) -> proc_macro2::TokenStream {
match ft {
FuncType::Env => quote! {::nadi_core::functions::EnvFunction},
FuncType::Node => quote! {::nadi_core::functions::NodeFunction},
FuncType::Network => quote! {::nadi_core::functions::NetworkFunction},
}
}
#[proc_macro_derive(FromAttribute)]
pub fn from_attribute_derive(input: TokenStream) -> TokenStream {
from_attr_derive(input, false)
}
#[proc_macro_derive(FromAttributeRelaxed)]
pub fn from_attribute_relaxed_derive(input: TokenStream) -> TokenStream {
from_attr_derive(input, true)
}
fn from_attr_derive(input: TokenStream, relaxed: bool) -> TokenStream {
let (trt, func, try_func) = nadi_trait_idents(relaxed);
let input = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let name_s = name.to_string();
let data = input.data.clone();
let conversion = match data {
syn::Data::Struct(st) => {
match st.fields {
syn::Fields::Named(flds) => {
let conv: Vec<_> = flds.named.iter().map(|f| {
let i = &f.ident;
let is = i.as_ref().map(|l| l.to_string()).expect("should be named");
quote!{
let val = match attrmap.get(#is) {
Some(v) => v,
None => return Err(format!("FieldError: Field {} not found in the value for {}", #is, #name_s)),
};
let #i = #trt :: #try_func (val)?;
}
}).collect();
let names = flds.named.iter().map(|f| &f.ident);
quote! {
let attrmap: ::nadi_core::attrs::AttrMap = ::nadi_core::attrs::FromAttribute::try_from_attr (value)?;
#(#conv)*
Ok(Self {
#(#names),*
})
}
}
syn::Fields::Unnamed(_) => {
quote! {
Ok(Self(#trt :: #func (value)?))
}
}
_ => panic!("Not supported"),
}
}
syn::Data::Enum(en) => {
let vars: Vec<_> = en
.variants
.iter()
.map(|v| {
let i = &v.ident;
quote! {
if let Some(val) = #trt :: #func (value) {
return Ok(Self::#i(val));
}
}
})
.collect();
let names: Vec<String> = en
.variants
.iter()
.map(|v| {
v.fields
.to_token_stream()
.to_string()
.trim_matches(['(', ')'])
.to_string()
})
.collect();
let names = names.join(" | ");
quote! {
#(#vars)*
Err(format!("Incorrect Type: got {} instead of one of: [ {} ]", value.type_name(), #names))
}
}
syn::Data::Union(_) => {
panic!("Union derive not supported!")
}
};
let expanded = quote! {
impl #trt for #name {
fn #func (value: &Attribute) -> Option<Self>{
#trt :: #try_func(value).ok()
}
fn #try_func (value: &Attribute) -> Result<Self, String> {
#conversion
}
}
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn env_func(args: TokenStream, item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as ItemFn);
let args = parse_macro_input!(args with Punctuated<MetaNameValue, Comma>::parse_terminated);
nadi_func_inner(args, item, FuncType::Env).into()
}
#[proc_macro_attribute]
pub fn node_func(args: TokenStream, item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as ItemFn);
let args = parse_macro_input!(args with Punctuated<MetaNameValue, Comma>::parse_terminated);
nadi_func_inner(args, item, FuncType::Node).into()
}
#[proc_macro_attribute]
pub fn network_func(args: TokenStream, item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as ItemFn);
let args = parse_macro_input!(args with Punctuated<MetaNameValue, Comma>::parse_terminated);
nadi_func_inner(args, item, FuncType::Network).into()
}
#[derive(Clone, Copy, Default, PartialEq)]
enum FuncArgType {
#[default]
Arg,
Relaxed,
Args,
KwArgs,
}
const FUNC_ARG_ATTRS: [(&str, FuncArgType); 3] = [
("args", FuncArgType::Args),
("kwargs", FuncArgType::KwArgs),
("relaxed", FuncArgType::Relaxed),
];
fn nadi_func_inner(
args: Punctuated<MetaNameValue, Comma>,
item: ItemFn,
ft: FuncType,
) -> proc_macro2::TokenStream {
let default_args: HashMap<Ident, Expr> = args
.into_iter()
.map(|p| (p.path.segments.first().unwrap().ident.clone(), p.value))
.collect();
let arg0 = if FuncType::Env == ft {
None
} else {
item.sig.inputs.first()
};
let func_args: Vec<(&Ident, &Type, FuncArgType)> = item
.sig
.inputs
.iter()
.skip((FuncType::Env != ft) as usize)
.map(get_fn_arg)
.collect();
let warnings = check_args_kwargs_order(&func_args, &default_args);
let func_struct_name = nadi_struct_name(&item.sig.ident, &ft.to_string());
let name_func = get_name_func(&item);
let code_func = get_code_func(&item);
let (call_func, default_exprs) = get_call_func(
&item,
arg0,
&ft,
&func_args,
&default_args,
&func_struct_name,
);
let help_func = get_help_func(&item);
let signature_func = get_signature_func(&item, &ft, &default_args, default_exprs);
let impl_trait = nadi_func_impl(&ft);
let clean_func = clean_function(&item);
quote! {
#warnings
#[derive(Debug, Clone)]
pub struct #func_struct_name;
impl #impl_trait for #func_struct_name {
#name_func
#code_func
#help_func
#signature_func
#call_func
}
impl #func_struct_name {
#clean_func
}
}
}
const INVALID_ARG_NAME: [&str; 63] = [
"clear", "import", "exec", "from", "node", "network",
"net", "networks", "networksmap",
"netm", "env", "exit", "end", "help", "input", "inputs", "inputsmap",
"im", "output", "outputs", "outputsmap",
"om", "edge", "edges", "edgesmap",
"em", "nodes", "nodesmap",
"nm", "root", "roots", "rootsmap",
"rm", "leaf", "leaves", "leavesmap",
"lm", "if", "else", "while", "try", "catch", "in", "match", "hook", "local",
"loc", "struct", "function",
"func", "return", "break", "continue", "error", "for", "map", "attrs", "loop", "progress",
"prog", "do", "par", "dopar", ];
fn check_args_kwargs_order(
args: &[(&Ident, &Type, FuncArgType)],
default_args: &HashMap<Ident, Expr>,
) -> proc_macro2::TokenStream {
let mut warnings: Vec<proc_macro2::TokenStream> = default_args
.keys()
.filter_map(|id| {
if INVALID_ARG_NAME.contains(&id.to_string().as_str()) {
Some(quote_spanned! {
id.span() =>
compile_error!("Invalid name for the argument (keyword)");
})
} else if !args.iter().any(|a| a.0 == id) {
Some(quote_spanned! {
id.span() =>
compile_error!("Argument not in the inner function");
})
} else {
None
}
})
.collect();
let mut flag = false;
for (a, t, at) in args {
if type_is_opt(t) || default_args.contains_key(a) || matches!(at, FuncArgType::KwArgs) {
flag = true;
} else if flag {
warnings.push(quote_spanned! {
a.span()=> compile_error!("Positional argument after default argument(s)");
});
}
}
quote! { #(#warnings)* }
}
fn clean_function(func: &ItemFn) -> ItemFn {
let mut func = func.clone();
for arg in &mut func.sig.inputs {
match arg {
syn::FnArg::Typed(a) => {
let attrs = std::mem::take(&mut a.attrs);
let (_, remain): (Vec<_>, Vec<_>) = attrs.into_iter().partition(|at| {
FUNC_ARG_ATTRS
.iter()
.map(|a| a.0)
.chain(["doc"])
.any(|a| at.path().is_ident(a))
});
a.attrs = remain;
}
_ => panic!("unsuported args"),
}
}
func
}
fn get_fn_arg(arg: &FnArg) -> (&Ident, &Type, FuncArgType) {
match arg {
syn::FnArg::Typed(arg) => {
let t: FuncArgType = FUNC_ARG_ATTRS
.iter()
.filter(|a| arg.attrs.iter().any(|at| at.path().is_ident(a.0)))
.map(|a| a.1)
.next()
.unwrap_or_default();
match arg.pat.as_ref() {
syn::Pat::Ident(i) => (&i.ident, arg.ty.as_ref(), t),
_ => panic!("Invalid Argument Type for Nadi function"),
}
}
_ => panic!("Invalid Argument Type for Nadi function"),
}
}
#[proc_macro_attribute]
pub fn nadi_plugin(args: TokenStream, item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as ItemMod);
let args = parse_macro_input!(args with Punctuated<MetaNameValue, Comma>::parse_terminated);
nadi_export_plugin(args, item, true).into()
}
#[proc_macro_attribute]
pub fn nadi_internal_plugin(args: TokenStream, item: TokenStream) -> TokenStream {
let item = parse_macro_input!(item as ItemMod);
let args = parse_macro_input!(args with Punctuated<MetaNameValue, Comma>::parse_terminated);
nadi_export_plugin(args, item, false).into()
}
fn nadi_export_plugin(
_args: Punctuated<MetaNameValue, Comma>,
item: ItemMod,
external: bool,
) -> proc_macro2::TokenStream {
let name = &item.ident;
let name_s = if external {
name.to_string().to_lowercase()
} else {
name.to_string().to_uppercase()
};
let name_mod = nadi_struct_name(name, "Mod");
let env_funcs = get_nadi_functions(&item, "env_func");
let node_funcs = get_nadi_functions(&item, "node_func");
let network_funcs = get_nadi_functions(&item, "network_func");
let regis_env_funcs = env_funcs
.iter()
.map(|n| (n.to_string(), nadi_struct_name(n, "Env")))
.map(|(alias, n)| {
let alias = (!external).then(|| {
quote! {
let name = format!("{}.{}", #name_s, #alias);
nf.register_alias(name, #alias .to_string(), ::nadi_core::tasks::FunctionType::Env);
}
});
quote! {
#alias
nf.register_env_function(
#name_s,
::nadi_core::functions::EnvFunction_TO::from_value(
#n,
::nadi_core::abi_stable::sabi_trait::TD_CanDowncast
)
);
}
});
let regis_node_funcs = node_funcs
.iter()
.map(|n| (n.to_string(), nadi_struct_name(n, "Node")))
.map(|(alias, n)| {
let alias = (!external).then(|| {
quote! {
let name = format!("{}.{}", #name_s, #alias);
nf.register_alias(name, #alias .to_string(), ::nadi_core::tasks::FunctionType::Node);
}
});
quote! {
#alias
nf.register_node_function(
#name_s,
::nadi_core::functions::NodeFunction_TO::from_value(
#n,
::nadi_core::abi_stable::sabi_trait::TD_CanDowncast
)
);
}
});
let regis_network_funcs = network_funcs
.iter()
.map(|n| (n.to_string(), nadi_struct_name(n, "Network")))
.map(|(alias, n)| {
let alias = (!external).then(|| {
quote! {
let name = format!("{}.{}", #name_s, #alias);
nf.register_alias(name, #alias .to_string(),::nadi_core::tasks::FunctionType::Network);
}
});
quote! {
#alias
nf.register_network_function(
#name_s,
::nadi_core::functions::NetworkFunction_TO::from_value(
#n,
::nadi_core::abi_stable::sabi_trait::TD_CanDowncast
)
);
}
});
if external {
quote! {
#[::nadi_core::abi_stable::export_root_module]
pub fn get_library() -> ::nadi_core::plugins::NadiExternalPlugin_Ref {
::nadi_core::abi_stable::prefix_type::PrefixTypeTrait::leak_into_prefix(
::nadi_core::plugins::NadiExternalPlugin {
register_functions,
plugin_name,
})
}
#[::nadi_core::abi_stable::sabi_extern_fn]
fn plugin_name() -> ::nadi_core::abi_stable::std_types::RString {
#name_s .into()
}
#[::nadi_core::abi_stable::sabi_extern_fn]
fn register_functions(nf: &mut ::nadi_core::functions::NadiFunctions) {
#(#regis_env_funcs)*
#(#regis_node_funcs)*
#(#regis_network_funcs)*
}
use #name::*;
#item
}
} else {
quote! {
pub struct #name_mod;
impl ::nadi_core::plugins::NadiPlugin for #name_mod {
fn name(&self) -> ::nadi_core::abi_stable::std_types::RString {
#name_s .into()
}
fn register(&self, nf: &mut ::nadi_core::functions::NadiFunctions) {
#(#regis_env_funcs)*
#(#regis_node_funcs)*
#(#regis_network_funcs)*
}
}
use #name::*;
#item
}
}
}
fn get_nadi_functions<'a>(item: &'a ItemMod, funct: &'_ str) -> Vec<&'a Ident> {
if let Some((_, cont)) = &item.content {
cont.iter()
.filter_map(|c| {
if let syn::Item::Fn(f) = c {
Some(f)
} else {
None
}
})
.filter_map(|f| {
if f.attrs.iter().any(|a| match &a.meta {
syn::Meta::Path(p) => p.is_ident(funct),
syn::Meta::List(l) => l.path.is_ident(funct),
_ => false,
}) {
Some(&f.sig.ident)
} else {
None
}
})
.collect()
} else {
vec![]
}
}
fn get_name_func(item: &ItemFn) -> proc_macro2::TokenStream {
let func_name = item.sig.ident.to_string();
quote! {
fn name(&self) -> ::nadi_core::abi_stable::std_types::RString {
#func_name .into()
}
}
}
fn get_code_func(item: &ItemFn) -> proc_macro2::TokenStream {
let func_code = prettyplease::unparse(
&syn::parse2(item.to_token_stream()).expect("code should be valid for prettyplease"),
);
quote! {
fn code(&self) -> ::nadi_core::abi_stable::std_types::RString {
#func_code .into()
}}
}
fn get_call_func(
item: &ItemFn,
arg0: Option<&FnArg>,
ft: &FuncType,
args: &[(&Ident, &Type, FuncArgType)],
defaults: &HashMap<Ident, Expr>,
func_struct_name: &Ident,
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
let mut defaults_expr: Vec<proc_macro2::TokenStream> = Vec::new();
let ret_err = quote! { ::nadi_core::functions::FunctionRet::Error };
let mut argc: usize = 0;
let mut kwonly = false;
let extract_args: Vec<proc_macro2::TokenStream> = args
.iter()
.map(|(arg, ty, at)| {
argc += 1;
let arg_name = arg.to_string();
let ty_name = ty.to_token_stream().to_string();
let arg_func_call = match at {
FuncArgType::Arg if kwonly => quote! { ctx.just_kwarg (#arg_name) },
FuncArgType::Arg => quote! { ctx.arg_kwarg (#argc - 1, #arg_name)},
FuncArgType::Relaxed if kwonly => quote! { ctx.just_kwarg_relaxed (#arg_name)},
FuncArgType::Relaxed => quote! { ctx.arg_kwarg_relaxed (#argc - 1, #arg_name)},
FuncArgType::Args => {
kwonly = true;
return if let Type::Path(p) = ty
&& p.path.is_ident("FunctionArgs")
{
quote! {
let #arg: #ty = ctx.args();
}
} else {
quote! {
let #arg: #ty = match ctx.try_args() {
Ok(v)=>v,
Err(e) => return #ret_err(e),
};
}
};
}
FuncArgType::KwArgs => {
kwonly = true;
return if let Type::Path(p) = ty
&& p.path.is_ident("FunctionKwArgs")
{
quote! {
let #arg: #ty = ctx.kwargs();
}
} else {
quote! {
let #arg: #ty = match ctx.try_kwargs(){
Ok(v)=>v,
Err(e) => return #ret_err(e),
};
}
};
}
};
let def = if let Some(val) = defaults.get(arg) {
let warn = match ty {
Type::Reference(r) => {
r.mutability.map(|m| {
quote_spanned! {
m.span=> compile_error!(
"Mutable Reference not supported for nadi function args"
);
}
})
}
_ => None,
};
defaults_expr.push(quote! {
let #arg : #ty = #val .into();
});
quote! {
#warn
::std::convert::Into::<#ty>::into(#val)
}
} else if type_is_opt(ty) {
quote!(None)
} else {
quote! {
return #ret_err (
format!("Argument {} ({} [{}]) is required", #argc, #arg_name, #ty_name).into()
);
}
};
let patterns = quote! {
Some(Ok(v)) => v,
Some(Err(e)) => return #ret_err (e.into()),
None => {#def},
};
quote! {
let #arg : #ty = match #arg_func_call {
#patterns
};
}
})
.collect();
let args_n: Vec<proc_macro2::TokenStream> = args
.iter()
.map(|(arg, _, _)| arg.into_token_stream())
.collect();
let func_name = &item.sig.ident;
let (fname, arg0, fcall) = match ft {
FuncType::Env => (quote! {call}, quote! {}, quote! {#func_name(#(#args_n),*)}),
_ => {
let a0 = get_fn_arg(arg0.expect("Should have at least one argument"));
let arg0_name = a0.0;
let arg0_ty = a0.1;
let fname = match &arg0_ty {
syn::Type::Reference(r) => {
if r.mutability.is_some() {
quote! {call_mut}
} else {
quote! {call}
}
}
_ => panic!("First argument should be a reference"),
};
(
fname,
quote! {#arg0_name : #arg0_ty,},
quote! {#func_name(#arg0_name, #(#args_n),*)},
)
}
};
let call_func = quote! {
fn #fname(&self,
#arg0
ctx: &::nadi_core::functions::FunctionCtx)
-> ::nadi_core::functions::FunctionRet {
#(#extract_args)*
::nadi_core::functions::FunctionRet::from(
#func_struct_name :: #fcall
)
}
};
let default_exprs = quote! {
#(#defaults_expr)*
};
(call_func, default_exprs)
}
fn get_help_func(item: &ItemFn) -> proc_macro2::TokenStream {
let docs = get_doc(&item.attrs);
quote! {
fn help(&self) -> ::nadi_core::abi_stable::std_types::RString {
#docs .into()
}
}
}
fn type_is_opt(ty: &Type) -> bool {
let ty = ty.to_token_stream().to_string();
let tt = ty
.split('<')
.next()
.unwrap_or_default()
.split("::")
.last()
.unwrap_or_default()
.trim();
tt == "Option" || tt == "ROption"
}
fn get_signature_func(
item: &ItemFn,
ft: &FuncType,
default_args: &HashMap<Ident, Expr>,
default_exprs: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
let args: Vec<proc_macro2::TokenStream> = item
.sig
.inputs
.iter()
.skip((FuncType::Env != *ft) as usize)
.map(|a| {
match a {
syn::FnArg::Typed(a) => {
match a.pat.as_ref() {
syn::Pat::Ident(i) => {
let doc = get_doc(&a.attrs);
let (n, t) = (
i.ident.to_string(),
a.ty.as_ref().into_token_stream().to_string(),
);
let ft = if a.attrs.iter().any(|at| at.path().is_ident("args")) {
quote! { Args }
} else if a.attrs.iter().any(|at| at.path().is_ident("kwargs")) {
quote! { KwArgs }
} else if default_args.contains_key(&i.ident) {
let v = format!("{{{}:?}}", i.ident);
quote! { DefArg(format!(#v) .into()) }
} else if type_is_opt(&a.ty) {
quote! { OptArg }
} else {
quote! { Arg }
};
quote! {
::nadi_core::functions::FuncArg {
name: #n .into(),
ty: #t .into(),
help: #doc .into(),
category: ::nadi_core::functions::FuncArgType:: #ft
}
}
}
_ => panic!("Not supported"),
}
}
_ => panic!("Not supported"),
}
})
.collect();
quote! {
fn args(&self) -> ::nadi_core::abi_stable::std_types::RVec<
::nadi_core::functions::FuncArg
> {
#default_exprs
vec![
#(#args),*
] .into()
}
}
}
fn get_doc(attrs: &[Attribute]) -> String {
let docs: Vec<String> = attrs
.iter()
.filter(|a| a.path().is_ident("doc"))
.filter_map(|a| match &a.meta {
syn::Meta::NameValue(val) => match &val.value {
Expr::Lit(lit) => match &lit.lit {
Lit::Str(c) => {
let c = c.value();
Some(c.trim_matches(' ').to_string())
}
_ => None,
},
_ => None,
},
_ => None,
})
.collect();
format_docstrings(docs.join("\n"))
}
fn format_docstrings(string: String) -> String {
match string.lines().count() {
0 => {
String::new()
}
1 => string.trim().to_string(),
_ => {
let num_leading = string
.lines()
.skip(1)
.filter(|s| !s.is_empty())
.filter_map(|l| l.chars().position(|c| !c.is_whitespace()))
.min()
.unwrap_or(0);
let lines = string
.lines()
.skip(1)
.map(|line| {
if line.len() > num_leading {
&line[num_leading..]
} else {
line
}
})
.map(|l| l.trim_end())
.collect::<Vec<_>>()
.join("\n");
format!(
"{}\n{}",
string
.lines()
.next()
.expect("There should be at least one line of documentation"),
lines
)
}
}
}
#[cfg(test)]
mod test {
use super::*;
use rstest::*;
use syn::parse_quote;
#[rstest]
#[case(parse_quote!(Option<i32>), true)]
#[case(parse_quote!(i32), false)]
#[case(parse_quote!(OptionT), false)]
#[case(parse_quote!(std::core::Option<i32>), true)]
#[case(parse_quote!(some_crate::Option), true)]
#[case(parse_quote!(Option<some_crate::CustomVec<i32>>), true)]
#[case(parse_quote!(std::core::Option<some_crate::CustomVec<i32>>), true)]
fn test_type_is_opt(#[case] ty: Type, #[case] is_opt: bool) {
assert_eq!(type_is_opt(&ty), is_opt);
}
#[test]
fn test_clean_func() {
let item: syn::ItemFn = parse_quote! {
fn func_name(
node: &NodeInner,
val: i64,
#[relaxed]
val2: String,
) {
todo!()
}
};
let clean = clean_function(&item);
let clean = quote!(#clean).to_string();
assert!(!clean.contains("Document for val"));
assert!(!clean.contains("relaxed"));
assert!(clean.contains("Document for Function"));
}
#[test]
fn count_node_functions() {
let item: syn::ItemMod = parse_quote! {
mod plugin {
#[node_func]
fn func_one(node: &NodeInner) {
todo!()
}
#[node_func]
fn func_two(node: &NodeInner, val: i64) {
todo!()
}
#[node_func(val=12)]
fn func_three(node: &NodeInner, val: i64) {
todo!()
}
}
};
let env_funcs = get_nadi_functions(&item, "env_func");
let node_funcs = get_nadi_functions(&item, "node_func");
let network_funcs = get_nadi_functions(&item, "network_func");
assert!(env_funcs.is_empty());
assert!(network_funcs.is_empty());
let node_funcs: Vec<String> = node_funcs.iter().map(|i| i.to_string()).collect();
assert_eq!(node_funcs, vec!["func_one", "func_two", "func_three"])
}
#[test]
fn count_network_functions() {
let item: syn::ItemMod = parse_quote! {
mod plugin {
#[network_func]
fn func_one(net: &Network) {
todo!()
}
#[network_func]
fn func_two(net: &Network, val: i64) {
todo!()
}
#[network_func(val=12)]
fn func_three(net: &Network, val: i64) {
todo!()
}
}
};
let env_funcs = get_nadi_functions(&item, "env_func");
let node_funcs = get_nadi_functions(&item, "node_func");
let network_funcs = get_nadi_functions(&item, "network_func");
assert!(env_funcs.is_empty());
assert!(node_funcs.is_empty());
let network_funcs: Vec<String> = network_funcs.iter().map(|i| i.to_string()).collect();
assert_eq!(network_funcs, vec!["func_one", "func_two", "func_three"])
}
#[test]
fn count_env_functions() {
let item: syn::ItemMod = parse_quote! {
mod plugin {
#[env_func]
fn func_one(net: &Network) {
todo!()
}
#[env_func]
fn func_two(net: &Network, val: i64) {
todo!()
}
#[env_func(val=12)]
fn func_three(net: &Network, val: i64) {
todo!()
}
}
};
let network_funcs = get_nadi_functions(&item, "network_func");
let node_funcs = get_nadi_functions(&item, "node_func");
let env_funcs = get_nadi_functions(&item, "env_func");
assert!(network_funcs.is_empty());
assert!(node_funcs.is_empty());
let env_funcs: Vec<String> = env_funcs.iter().map(|i| i.to_string()).collect();
assert_eq!(env_funcs, vec!["func_one", "func_two", "func_three"])
}
#[test]
fn test_docs() {
let item: syn::ItemMod = parse_quote! {
mod plugin {
#[env_func]
fn func_one(net: &Network) {
todo!()
}
}
};
let mod_doc = get_doc(&item.attrs);
assert_eq!(mod_doc, "Document for Plugin");
let item: syn::ItemFn = parse_quote! {
fn func_two(
net: &Network,
val: i64
) {
todo!()
}
};
let func_doc = get_doc(&item.attrs);
assert_eq!(func_doc, "Document for Function");
}
#[test]
fn test_valid_env_func() {
let item: syn::ItemFn = parse_quote! {
fn func_name(
val: i64
) {
todo!()
}
};
let args = Punctuated::<MetaNameValue, Comma>::new();
let output = nadi_func_inner(args, item, FuncType::Env).to_string();
assert!(!output.contains("compile_error!"));
assert!(output.contains("Document for val"));
assert!(output.contains("Document for Function"));
}
#[test]
fn test_invalid_env_func() {
let item: syn::ItemFn = parse_quote! {
fn func_two(
val: i64
) {
todo!()
}
};
let args: Punctuated<MetaNameValue, Comma> = parse_quote! {
value = 12
};
let output = nadi_func_inner(args, item, FuncType::Env).to_string();
assert!(output.contains("compile_error !"));
}
#[test]
fn test_valid_node_func() {
let item: syn::ItemFn = parse_quote! {
fn func_name(
node: &NodeInner,
val: i64
) {
todo!()
}
};
let args = Punctuated::<MetaNameValue, Comma>::new();
let output = nadi_func_inner(args, item, FuncType::Node).to_string();
assert!(!output.contains("compile_error!"));
assert!(output.contains("Document for val"));
assert!(output.contains("Document for Function"));
}
#[test]
#[should_panic]
fn test_invalid_node_func() {
let item: syn::ItemFn = parse_quote! {
fn func_two(
val: i64
) {
todo!()
}
};
let args: Punctuated<MetaNameValue, Comma> = parse_quote! {
val = 12
};
let _ = nadi_func_inner(args, item, FuncType::Node);
}
#[test]
fn test_valid_network_func() {
let item: syn::ItemFn = parse_quote! {
fn func_name(
network: &Network,
val: i64
) {
todo!()
}
};
let args = Punctuated::<MetaNameValue, Comma>::new();
let output = nadi_func_inner(args, item, FuncType::Network).to_string();
assert!(!output.contains("compile_error!"));
assert!(output.contains("Document for val"));
assert!(output.contains("Document for Function"));
}
#[test]
#[should_panic]
fn test_invalid_network_func() {
let item: syn::ItemFn = parse_quote! {
fn func_two(
val: i64
) {
todo!()
}
};
let args: Punctuated<MetaNameValue, Comma> = parse_quote! {
val = 12
};
let _ = nadi_func_inner(args, item, FuncType::Network);
}
#[test]
fn test_valid_plugin() {
let item: syn::ItemMod = parse_quote! {
mod plug_name{
#[env_func]
fn func_name(
network: &Network,
val: i64
) {
todo!()
}
}
};
let args = Punctuated::<MetaNameValue, Comma>::new();
let output = nadi_export_plugin(args.clone(), item.clone(), true).to_string();
assert!(!output.contains("compile_error!"));
assert!(output.contains("NadiExternalPlugin"));
assert!(!output.contains("NadiPlugin"));
let output = nadi_export_plugin(args, item, false).to_string();
assert!(!output.contains("compile_error!"));
assert!(!output.contains("NadiExternalPlugin"));
assert!(output.contains("NadiPlugin"));
}
}