tauri_named_invoke/lib.rs
1//! A small utility that generates a typescript declaration file for the [`invoke`] function
2//! from functions found in code by Tauri [commands].
3//! Thanks to this, there is no mistaking the name of the command.
4//!
5//! # Example
6//!
7//! **main.rs:**
8//!
9//! ```rust
10//! fn main() {
11//! tauri::Builder::default()
12//! .invoke_handler(generate_handler![get_weather, get_config])
13//! .run(tauri::generate_context!())
14//! .expect("error while running tauri application");
15//! }
16//!
17//! #[tauri::command]
18//! fn get_weather() -> String {
19//! "sunny".to_string()
20//! }
21//! // or
22//! use tauri::command;
23//! #[command]
24//! fn get_config() -> String {
25//! "config".to_string()
26//! }
27//! ```
28//!
29//! **build.rs:**
30//!
31//! ```rust
32//! fn main() {
33//! tauri_named_invoke::build("ui").unwrap();
34//! tauri_build::build();
35//! }
36//! ```
37//!
38//! The file will be generated at the following path:
39//!
40//! ```shell
41//! project root
42//! ├── ui
43//! │ └── invoke.d.ts
44//! ├── src
45//! │ └── main.rs
46//! └── Cargo.toml
47//! ```
48//!
49//! The generated file will contain:
50//!
51//! ```typescript
52//! import * as tauri from '@tauri-apps/api/tauri';
53//! declare module '@tauri-apps/api' {
54//! type Commands =
55//! 'get_weather'
56//! | 'get_config';
57//! function invoke<T>(cmd: Commands, args?: InvokeArgs): Promise<T>;
58//! }
59//! ```
60//!
61//! [`invoke`]: https://tauri.app/v1/api/js/tauri/#invoke
62//! [commands]: https://docs.rs/tauri/1.6.1/tauri/command/index.html
63
64use std::{env, path::Path};
65
66use glob::glob;
67use regex::Regex;
68
69/// Generates an `invoke.d.ts` file declaring [`invoke`] function values composed
70/// of function names labeled with the [`tauri::command`] attribute.
71///
72/// * path - The path to the directory where the `invoke.d.ts` file will be generated.
73///
74/// # Example
75///
76/// ```rust
77/// fn main() {
78/// tauri_named_invoke::build("ui").unwrap();
79/// }
80/// ```
81///
82/// The file will be generated at the following path:
83///
84/// ```shell
85/// project root
86/// ├── ui
87/// │ └── invoke.d.ts
88/// ├── src
89/// │ └── main.rs
90/// └── Cargo.toml
91/// ```
92///
93/// [`invoke`]: https://tauri.app/v1/api/js/tauri/#invoke
94/// [`tauri::command`]: https://docs.rs/tauri/1.6.1/tauri/command/index.html
95pub fn build(path: impl AsRef<std::path::Path>) -> Result<(), Box<dyn std::error::Error>> {
96 let typed_file = Path::new(env::var("CARGO_MANIFEST_DIR")?.as_str())
97 .join(path)
98 .join("invoke.d.ts");
99 let fn_names = parse_functions();
100 std::fs::write(typed_file, get_content(fn_names))?;
101 Ok(())
102}
103
104fn parse_functions() -> Vec<String> {
105 let mut names = Vec::new();
106
107 let rx = Regex::new(r"(?m)\#\[(?:tauri::)?command][\s\w]*fn\s+([\w\d_-]+)").unwrap();
108 for file in glob("**/*.rs").unwrap() {
109 let file = file.unwrap();
110 println!("cargo:rerun-if-changed={}", file.display());
111 let content = std::fs::read_to_string(file).unwrap();
112 for cap in rx.captures_iter(&content) {
113 names.push(cap[1].to_string());
114 }
115 }
116
117 names
118}
119
120fn get_content(names: Vec<String>) -> String {
121 let names = names
122 .iter()
123 .map(|f| format!("'{}'", f))
124 .collect::<Vec<_>>()
125 .join("\n\t\t| ");
126
127 format!(
128"import * as tauri from '@tauri-apps/api/tauri';
129declare module '@tauri-apps/api/tauri' {{
130 type Commands =
131\t\t {};
132 function invoke<T>(cmd: Commands, args?: InvokeArgs): Promise<T>;
133}}", names)
134}