Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/add-macros-allow-rename-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-macros": minor:feat
"tauri": minor:feat
---

Add support for the `rename` attribute in the `tauri::command` macro to allow renaming the command to something other than the function name.
28 changes: 22 additions & 6 deletions crates/tauri-macros/src/command/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use quote::format_ident;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
Attribute, Ident, Path, Token,
Expand Down Expand Up @@ -151,14 +151,30 @@ impl From<Handler> for proc_macro::TokenStream {
) -> Self {
let cmd = format_ident!("__tauri_cmd__");
let invoke = format_ident!("__tauri_invoke__");
let (paths, attrs): (Vec<Path>, Vec<Vec<Attribute>>) = command_defs
.into_iter()
.map(|def| (def.path, def.attrs))
.unzip();
let mut paths: Vec<Path> = Vec::new();
let mut attrs: Vec<Vec<Attribute>> = Vec::new();
let mut command_name_macros: Vec<proc_macro2::TokenStream> = Vec::new();
for (def, command) in command_defs.into_iter().zip(commands) {
let path = def.path;
let attrs_vec = def.attrs;

let mut command_name_macro_path = path.clone();
let last = command_name_macro_path
.segments
.last_mut()
.expect("path has at least one segment");
last.ident = format_ident!("__tauri_command_name_{command}");

paths.push(path);
attrs.push(attrs_vec);
// Call the macro to get the command name string literal
command_name_macros.push(quote!(#command_name_macro_path!()));
}

quote::quote!(move |#invoke| {
let #cmd = #invoke.message.command();
match #cmd {
#(#(#attrs)* stringify!(#commands) => #wrappers!(#paths, #invoke),)*
#(#(#attrs)* #command_name_macros => #wrappers!(#paths, #invoke),)*
_ => {
return false;
},
Expand Down
60 changes: 55 additions & 5 deletions crates/tauri-macros/src/command/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct WrapperAttributes {
root: TokenStream2,
execution_context: ExecutionContext,
argument_case: ArgumentCase,
rename: RenamePolicy,
}

impl Parse for WrapperAttributes {
Expand All @@ -48,6 +49,7 @@ impl Parse for WrapperAttributes {
root: quote!(::tauri),
execution_context: ExecutionContext::Blocking,
argument_case: ArgumentCase::Camel,
rename: RenamePolicy::Keep,
};

let attrs = Punctuated::<WrapperAttributeKind, Token![,]>::parse_terminated(input)?;
Expand All @@ -74,6 +76,19 @@ impl Parse for WrapperAttributes {
}
};
}
} else if v.path.is_ident("rename") {
if let Expr::Lit(ExprLit {
lit: Lit::Str(s), ..
}) = v.value
{
let lit = s.value();
wrapper_attributes.rename = RenamePolicy::Rename(quote!(#lit));
} else {
return Err(syn::Error::new(
v.span(),
"expected string literal for rename",
));
}
} else if v.path.is_ident("root") {
if let Expr::Lit(ExprLit {
lit: Lit::Str(s),
Expand All @@ -94,7 +109,7 @@ impl Parse for WrapperAttributes {
WrapperAttributeKind::Meta(Meta::Path(_)) => {
return Err(syn::Error::new(
input.span(),
"unexpected input, expected one of `rename_all`, `root`, `async`",
"unexpected input, expected one of `rename_all`, `rename`, `root`, `async`",
));
}
WrapperAttributeKind::Async => {
Expand All @@ -120,6 +135,12 @@ enum ArgumentCase {
Camel,
}

/// The rename policy for the command.
enum RenamePolicy {
Keep,
Rename(TokenStream2),
}

/// The bindings we attach to `tauri::Invoke`.
struct Invoke {
message: Ident,
Expand All @@ -138,9 +159,11 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
attrs.execution_context = ExecutionContext::Async;
}

// macros used with `pub use my_macro;` need to be exported with `#[macro_export]`
// macros used with `pub use my_macro;` need to be exported with `#[macro_export]`.
let maybe_macro_export = match &function.vis {
Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]),
Visibility::Public(_) | Visibility::Restricted(_) => {
quote!(#[macro_export])
}
_ => TokenStream2::default(),
};

Expand Down Expand Up @@ -270,13 +293,35 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
TokenStream2::default()
};

// Always define a hidden macro that returns the externally invoked command name.
// This lets the handler match on the renamed string while the original function
// identifier remains usable in `generate_handler![original_fn_name]`.
let command_name_macro_ident = format_ident!("__tauri_command_name_{}", function.sig.ident);
let command_name_value = if let RenamePolicy::Rename(ref rename) = attrs.rename {
quote!(#rename)
} else {
let ident = &function.sig.ident;
quote!(stringify!(#ident))
};

// Rely on rust 2018 edition to allow importing a macro from a path.
quote!(
#async_command_check

#maybe_allow_unused
#function

// Command name macro used by the handler for pattern matching.
// This macro returns the command name string literal (renamed or original).
#maybe_allow_unused
#maybe_macro_export
#[doc(hidden)]
macro_rules! #command_name_macro_ident {
() => {
#command_name_value
};
}

#maybe_allow_unused
#maybe_macro_export
#[doc(hidden)]
Expand All @@ -303,7 +348,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {

// allow the macro to be resolved with the same path as the command function
#[allow(unused_imports)]
#visibility use #wrapper;
#visibility use {#wrapper, #command_name_macro_ident};
)
.into()
}
Expand Down Expand Up @@ -467,11 +512,16 @@ fn parse_arg(
}

let root = &attributes.root;
let command_name = if let RenamePolicy::Rename(r) = &attributes.rename {
quote!(stringify!(#r))
} else {
quote!(stringify!(#command))
};

Ok(quote!(#root::ipc::CommandArg::from_command(
#root::ipc::CommandItem {
plugin: #plugin_name,
name: stringify!(#command),
name: #command_name,
key: #key,
message: &#message,
acl: &#acl,
Expand Down
5 changes: 5 additions & 0 deletions examples/commands/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ pub fn simple_command(the_argument: String) {
pub fn stateful_command(the_argument: Option<String>, state: State<'_, super::MyState>) {
println!("{:?} {:?}", the_argument, state.inner());
}

#[command(rename = "renamed_command_in_mod_new")]
pub fn renamed_command_in_mod() {
println!("renamed command in mod called");
}
6 changes: 5 additions & 1 deletion examples/commands/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ <h1>Tauri Commands</h1>
{
name: 'command_arguments_tuple_struct',
args: { inlinePerson: ['ferris', 6] }
}
},
{ name: 'renamed_command' },
{ name: 'renamed_command_new' },
{ name: 'renamed_command_in_mod' },
{ name: 'renamed_command_in_mod_new' }
]

for (const command of commands) {
Expand Down
9 changes: 8 additions & 1 deletion examples/commands/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

// we move some basic commands to a separate module just to show it works
mod commands;
use commands::{cmd, invoke, message, resolver};
use commands::{cmd, invoke, message, renamed_command_in_mod, resolver};

use serde::Deserialize;
use tauri::{
Expand Down Expand Up @@ -188,6 +188,11 @@ fn command_arguments_wild(_: Window) {
println!("we saw the wildcard!")
}

#[command(rename = "renamed_command_new")]
fn renamed_command() {
println!("renamed command called")
}

#[derive(Deserialize)]
struct Person<'a> {
name: &'a str,
Expand Down Expand Up @@ -246,6 +251,8 @@ fn main() {
future_simple_command,
async_stateful_command,
command_arguments_wild,
renamed_command,
renamed_command_in_mod,
Comment on lines 254 to +255

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How abort export same function name from different module?

A::renamed_command,
B::renamed_command,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's no way to support all use cases :/ not exporting macros breaks some other scenarios (like importing the function and then using it directly IIRC)

command_arguments_struct,
simple_command_with_result,
async_simple_command_snake,
Expand Down
Loading