tauri_bindgen_ts_macro/
lib.rs1use proc_macro::TokenStream;
2use quote::{quote, format_ident};
3use syn::{ItemFn, FnArg, Type, Pat, PatType, Path, Ident};
4
5#[proc_macro_attribute]
11pub fn entity(attr: TokenStream, item: TokenStream) -> TokenStream {
12 let item: proc_macro2::TokenStream = item.into();
13 let dir = format!("{}/", parse_dir_arg(&attr));
14
15 quote! {
16 #[derive(ts_rs::TS, serde::Serialize, serde::Deserialize)]
17 #[ts(export)]
18 #[ts(export_to=#dir)]
19 #item
20 }.into()
21}
22
23#[proc_macro_attribute]
29pub fn command(attr: TokenStream, item: TokenStream) -> TokenStream {
30 let func = syn::parse::<ItemFn>(item.clone()).expect("This attribute should be used on a function!");
31 let item: proc_macro2::TokenStream = item.into();
32
33 let dir = parse_dir_arg(&attr);
34 let func = func_metadata(func);
35 let test = generate_test(func, dir);
36
37 quote! {
38 #[tauri::command]
39 #item
40 #test
41 }.into()
42}
43
44
45fn parse_dir_arg(attr: &TokenStream) -> String {
47 let dir = attr.to_string();
49 let dir = dir.trim_matches(|c| c == '"' || c == '\'' );
50 if dir.is_empty() { "../src-gen".to_owned() } else { dir.to_owned() }
51}
52
53struct Func {
54 name: String,
55 args: Vec<(Ident, Path)>,
56}
57
58fn func_metadata(func: ItemFn) -> Func {
59 let name = func.sig.ident.to_string();
60 let args = func.sig.inputs.into_iter()
62 .filter_map(|arg| if let FnArg::Typed(t) = arg { Some(t) } else { panic!("Only top-level functions are allowed as commands!") })
63 .collect::<Vec<_>>();
64 let args = types(&args);
66
67 Func { name, args }
68}
69
70fn types(args: &[PatType]) -> Vec<(Ident, Path)> {
71 args.iter()
72 .map(|arg| (arg.pat.clone(), arg.ty.clone()))
73 .filter_map(|(pat, ty)| match *pat {
74 Pat::Ident(p) => Some((p, ty)),
75 _ => panic!("Only simple owned types are allowed as arguments at the moment!"),
76 })
77 .filter_map(|(pat, ty)| match *ty {
78 Type::Path(t) => Some((pat, t)),
79 _ => { panic!("Only simple owned types are allowed as arguments at the moment!") }
80 })
81 .map(|(pat, ty)| (pat.ident, ty.path))
82 .collect()
83}
84
85fn generate_test(func: Func, dir: String) -> proc_macro2::TokenStream {
88 let Func { name, args } = func;
89 let arg_names = args.iter().map(|(ident, _)| ident.to_string()).collect::<Vec<_>>();
90 let arg_types = args.iter().map(|(_, path)| path).collect::<Vec<_>>();
91
92 let test_fn = format_ident!("export_function_bindings_{}", name);
93
94 let header = "// This file was generated by [tauri-bindgen-ts](https://github.com/antoniusnaumann/tauri-bindgen-ts). Do not edit this file manually.";
95 let import = "import { invoke } from \"@tauri-apps/api/tauri\"";
97 let binding = format!("export async function {name}(%0) {{ return await invoke('{name}', {{ %1 }}) }}");
98
99 let file_name = format!("{dir}/{name}.ts");
100 let content = format!("{header}\n{import}\n\n{binding}");
101
102 quote! {
103 #[cfg(test)]
104 #[test]
105 fn #test_fn() {
106 use std::fs;
107 use tauri_bindgen_ts::ts_rs::TS;
108
109 let types = vec![#(#arg_types::name()),*];
110 let names = vec![#(#arg_names),*];
111 let args = types.iter().enumerate().map(|(index, elem)| [names[index].to_owned(), elem.to_owned()].join(": ")).collect::<Vec<String>>().join(", ");
112
113 fs::create_dir_all(#dir).expect("Could not create directory");
114 fs::write(#file_name, #content.replace("%0", args.as_str()).replace("%1", names.join(", ").as_str())).expect("Could not write generated function binding to file");
115 }
116 }
117}