#![feature(iter_intersperse)]
#![feature(doc_cfg)]
#![warn(missing_docs)]
use proc_macro::TokenStream;
use std::collections::HashSet;
use std::sync::Mutex;
use proc_macro_error::{emit_call_site_error, emit_call_site_warning, proc_macro_error};
use quote::{format_ident, quote, ToTokens};
use syn::{
parse::Parser, parse_macro_input, punctuated::Punctuated, ExprPath, ItemFn, ItemMod, Token,
};
use crate::command::collect::commands_to_punctuated;
mod command;
#[cfg(feature = "event")]
mod event;
#[cfg(feature = "event")]
#[doc(cfg(feature = "event"))]
#[proc_macro_derive(Event, attributes(auto_naming, mod_name))]
pub fn derive_event(stream: TokenStream) -> TokenStream {
if cfg!(feature = "_wasm") {
event::listen::derive(stream)
} else {
event::emit::derive(stream)
}
}
#[cfg(all(feature = "event", feature = "initial_value"))]
#[doc(cfg(all(feature = "event", feature = "initial_value")))]
#[proc_macro_derive(ManagedEmit)]
pub fn derive_managed_emit(stream: TokenStream) -> TokenStream {
(!cfg!(feature = "_wasm"))
.then(|| event::emit::derive_managed_emit(stream))
.unwrap_or_default()
}
#[cfg(feature = "event")]
#[doc(cfg(feature = "event"))]
#[proc_macro_derive(Emit, attributes(auto_naming, mod_name))]
pub fn derive_emit(stream: TokenStream) -> TokenStream {
event::emit::derive(stream)
}
#[cfg(feature = "event")]
#[doc(cfg(feature = "event"))]
#[proc_macro_derive(EmitField, attributes(parent, parent_field_name, parent_field_ty))]
pub fn derive_emit_field(stream: TokenStream) -> TokenStream {
event::emit::derive_field(stream)
}
#[cfg(feature = "event")]
#[doc(cfg(feature = "event"))]
#[proc_macro_derive(Listen, attributes(auto_naming, mod_name))]
pub fn derive_listen(stream: TokenStream) -> TokenStream {
event::listen::derive(stream)
}
#[cfg(feature = "event")]
#[doc(cfg(feature = "event"))]
#[proc_macro_derive(ListenField, attributes(parent, parent_field_ty))]
pub fn derive_listen_field(stream: TokenStream) -> TokenStream {
event::listen::derive_field(stream)
}
#[proc_macro_attribute]
pub fn binding(_attributes: TokenStream, stream: TokenStream) -> TokenStream {
command::convert_to_binding(stream)
}
lazy_static::lazy_static! {
static ref COMMAND_LIST_ALL: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
}
lazy_static::lazy_static! {
static ref COMMAND_LIST: Mutex<HashSet<String>> = Mutex::new(HashSet::new());
}
static COMMAND_MOD_NAME: Mutex<Option<String>> = Mutex::new(None);
#[proc_macro_attribute]
pub fn command(_attributes: TokenStream, stream: TokenStream) -> TokenStream {
let fn_item = parse_macro_input!(stream as ItemFn);
COMMAND_LIST
.lock()
.unwrap()
.insert(fn_item.sig.ident.to_string());
let command_macro = quote! {
#[cfg_attr(target_family = "wasm", ::tauri_interop::binding)]
#[cfg_attr(not(target_family = "wasm"), ::tauri_interop::export::tauri::command(root = "tauri_interop", rename_all = "snake_case"))]
#fn_item
};
TokenStream::from(command_macro.to_token_stream())
}
#[proc_macro_attribute]
pub fn commands(_attributes: TokenStream, stream: TokenStream) -> TokenStream {
let item_mod = parse_macro_input!(stream as ItemMod);
let _ = COMMAND_MOD_NAME
.lock()
.unwrap()
.insert(item_mod.ident.to_string());
TokenStream::from(item_mod.to_token_stream())
}
#[proc_macro]
pub fn collect_commands(_: TokenStream) -> TokenStream {
let mut commands = COMMAND_LIST.lock().unwrap();
let stream = command::collect::get_handler_function(
format_ident!("get_handlers"),
&commands,
commands_to_punctuated(&commands),
Vec::new(),
);
if let Some(mod_name) = COMMAND_MOD_NAME.lock().unwrap().as_ref() {
COMMAND_LIST_ALL
.lock()
.unwrap()
.extend(command::collect::commands_with_mod_name(
mod_name, &commands,
));
} else {
COMMAND_LIST_ALL
.lock()
.unwrap()
.extend(commands.iter().cloned());
}
commands.clear();
let _ = COMMAND_MOD_NAME.lock().unwrap().take();
TokenStream::from(stream.to_token_stream())
}
#[proc_macro_error]
#[proc_macro]
pub fn combine_handlers(stream: TokenStream) -> TokenStream {
if cfg!(feature = "_wasm") {
return Default::default();
}
let command_mods = Punctuated::<ExprPath, Token![,]>::parse_terminated
.parse2(stream.into())
.unwrap()
.into_iter()
.collect::<Vec<_>>();
let org_commands = COMMAND_LIST_ALL.lock().unwrap();
let commands = command::collect::get_filtered_commands(&org_commands, &command_mods);
if commands.is_empty() {
emit_call_site_error!("No commands will be registered")
}
let remaining_commands = COMMAND_LIST.lock().unwrap();
if !remaining_commands.is_empty() {
emit_call_site_error!(
"Their are dangling commands that won't be registered. See {:?}",
remaining_commands
)
}
if org_commands.len() > commands.len() {
let diff = org_commands
.difference(&commands)
.cloned()
.intersperse(String::from(","))
.collect::<String>();
emit_call_site_warning!(
"Not all commands will be registered. Missing commands: {:?}",
diff
);
}
TokenStream::from(command::collect::get_handler_function(
format_ident!("get_all_handlers"),
&commands,
commands_to_punctuated(&commands),
command_mods,
))
}
#[proc_macro]
pub fn host_usage(stream: TokenStream) -> TokenStream {
let uses = command::collect::uses(stream);
TokenStream::from(quote! {
#(
#[cfg(not(target_family = "wasm"))]
#uses
)*
})
}
#[proc_macro]
pub fn wasm_usage(stream: TokenStream) -> TokenStream {
let uses = command::collect::uses(stream);
TokenStream::from(quote! {
#(
#[cfg(target_family = "wasm")]
#uses
)*
})
}