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}