diff --git a/Cargo.lock b/Cargo.lock index 31dc0db..0350e4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const_format" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -3166,9 +3186,9 @@ dependencies = [ [[package]] name = "specta" version = "2.0.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065150caa210db01913bb6f3c6d2540030937f3b0a0d9fad3dc453011f350c52" +source = "git+https://github.com/oscartbeaumont/specta?rev=efdd024fd1ab2a569ffe9d7fc484c419eb6ef9c5#efdd024fd1ab2a569ffe9d7fc484c419eb6ef9c5" dependencies = [ + "const_format", "document-features", "indoc", "once_cell", @@ -3182,8 +3202,7 @@ dependencies = [ [[package]] name = "specta-macros" version = "2.0.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a53b00ba374af8b6bd7c9061019df8b5ee0d7ab0d10d1c7c98c527d2fdb0fd" +source = "git+https://github.com/oscartbeaumont/specta?rev=efdd024fd1ab2a569ffe9d7fc484c419eb6ef9c5#efdd024fd1ab2a569ffe9d7fc484c419eb6ef9c5" dependencies = [ "Inflector", "itertools", @@ -3954,6 +3973,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "url" version = "2.4.1" diff --git a/Cargo.toml b/Cargo.toml index 94ebe38..9c86552 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = [] -javascript = ["specta/typescript"] +javascript = ["specta/javascript"] typescript = ["specta/typescript"] [dependencies] @@ -46,3 +46,6 @@ members = [ specta = { version = "=2.0.0-rc.2" } serde = "1.0.188" tauri = "1.4.1" + +[patch.crates-io] +specta = { git = "https://github.com/oscartbeaumont/specta", rev = "efdd024fd1ab2a569ffe9d7fc484c419eb6ef9c5" } diff --git a/examples/custom-plugin/plugin/src/lib.rs b/examples/custom-plugin/plugin/src/lib.rs index 1845f94..418c6e5 100644 --- a/examples/custom-plugin/plugin/src/lib.rs +++ b/examples/custom-plugin/plugin/src/lib.rs @@ -31,7 +31,7 @@ pub fn init() -> TauriPlugin { .invoke_handler(plugin_utils.invoke_handler) .setup(move |app| { let app = app.clone(); - (plugin_utils.setup)(&app.clone()); + (plugin_utils.setup)(&app); std::thread::spawn(move || loop { RandomNumber(rand::random()).emit_all(&app).unwrap(); diff --git a/src/event.rs b/src/event.rs index 7e1d156..7db207a 100644 --- a/src/event.rs +++ b/src/event.rs @@ -51,7 +51,7 @@ impl EventRegistry { ); } - pub fn get_or_manage<'a, R: Runtime>(handle: &'a impl Manager) -> tauri::State<'a, Self> { + pub fn get_or_manage(handle: &impl Manager) -> tauri::State<'_, Self> { if handle.try_state::().is_none() { handle.manage(Self::default()); } @@ -146,7 +146,7 @@ pub trait Event: Type + NamedType { { let meta = get_meta!(handle); - handle.listen_global(&meta.wrap_with_plugin(Self::NAME), make_handler!(handler)) + handle.listen_global(meta.wrap_with_plugin(Self::NAME), make_handler!(handler)) } fn once_global(handle: &impl Manager, handler: F) -> EventHandler @@ -156,7 +156,7 @@ pub trait Event: Type + NamedType { { let meta = get_meta!(handle); - handle.once_global(&meta.wrap_with_plugin(Self::NAME), make_handler!(handler)) + handle.once_global(meta.wrap_with_plugin(Self::NAME), make_handler!(handler)) } // Window functions @@ -198,7 +198,7 @@ pub trait Event: Type + NamedType { { let meta = get_meta!(window); - window.listen(&meta.wrap_with_plugin(Self::NAME), make_handler!(handler)) + window.listen(meta.wrap_with_plugin(Self::NAME), make_handler!(handler)) } fn once(window: &Window, handler: F) -> EventHandler @@ -208,7 +208,7 @@ pub trait Event: Type + NamedType { { let meta = get_meta!(window); - window.once(&meta.wrap_with_plugin(Self::NAME), make_handler!(handler)) + window.once(meta.wrap_with_plugin(Self::NAME), make_handler!(handler)) } } @@ -217,11 +217,7 @@ pub struct EventDataType { pub typ: DataType, } -pub(crate) type CollectEventsTuple = ( - EventCollection, - Result, specta::ExportError>, - specta::TypeMap, -); +pub(crate) type CollectEventsTuple = (EventCollection, Vec, specta::TypeMap); #[macro_export] macro_rules! collect_events { @@ -233,19 +229,19 @@ macro_rules! collect_events { let mut type_map = Default::default(); let event_data_types = [$( - <$event as ::specta::Type>::reference( - ::specta::DefOpts { - type_map: &mut type_map, - parent_inline: false - }, - &[] - ).map(|typ| $crate::EventDataType { - name: <$event as $crate::Event>::NAME, - typ - }) + $crate::EventDataType { + name: <$event as $crate::Event>::NAME, + typ: <$event as ::specta::Type>::reference( + ::specta::DefOpts { + type_map: &mut type_map, + parent_inline: false + }, + &[] + ).inner + } ),+] .into_iter() - .collect::, _>>(); + .collect::>(); (collection, event_data_types, type_map) }}; diff --git a/src/js.rs b/src/js.rs index 05cf476..07a6a99 100644 --- a/src/js.rs +++ b/src/js.rs @@ -1,12 +1,9 @@ -use crate::{ - js_ts::{self, ExportConfig}, EventDataType, ExportLanguage, NoCommands, NoEvents, PluginBuilder, -}; +use crate::*; use heck::ToLowerCamelCase; use indoc::formatdoc; use specta::{ functions::FunctionDataType, - ts::{self, js_doc, TsExportError}, - TypeMap, + js::{self, ExportError}, }; use tauri::Runtime; @@ -19,13 +16,21 @@ pub fn builder() -> PluginBuilder; + impl ExportLanguage for Language { + type Config = specta::js_ts::ExportConfig; + + fn run_format(path: PathBuf, cfg: &ExportConfig) { + cfg.inner.run_format(path).ok(); + } + /// Renders a collection of [`FunctionDataType`] into a JavaScript string. fn render_commands( commands: &[FunctionDataType], type_map: &TypeMap, cfg: &ExportConfig, - ) -> Result { + ) -> Result { let commands = commands .iter() .map(|function| { @@ -36,7 +41,7 @@ impl ExportLanguage for Language { .into_iter() .chain(function.docs.iter().map(|s| s.to_string())) .chain(function.args.iter().flat_map(|(name, typ)| { - ts::datatype(&cfg.inner, typ, type_map).map(|typ| { + js::datatype(&cfg.inner, typ, type_map).map(|typ| { let name = name.to_lower_camel_case(); format!("@param {{ {typ} }} {name}") @@ -46,7 +51,7 @@ impl ExportLanguage for Language { .map(Into::into) .collect::>(); - js_doc(&vec) + js::js_doc(&vec) }; Ok(js_ts::function( @@ -57,7 +62,7 @@ impl ExportLanguage for Language { &js_ts::command_body(cfg, function, false), )) }) - .collect::, TsExportError>>()? + .collect::, ExportError>>()? .join(",\n"); Ok(formatdoc! { @@ -72,14 +77,14 @@ impl ExportLanguage for Language { events: &[EventDataType], type_map: &TypeMap, cfg: &ExportConfig, - ) -> Result { + ) -> Result { if events.is_empty() { return Ok(Default::default()); } let (events_types, events_map) = js_ts::events_data(events, cfg, type_map)?; - let events = js_doc( + let events = js::js_doc( &[].into_iter() .chain(["@type {typeof __makeEvents__<{".to_string()]) .chain(events_types) @@ -104,17 +109,11 @@ impl ExportLanguage for Language { events: &[EventDataType], type_map: &TypeMap, cfg: &ExportConfig, - ) -> Result { + ) -> Result { let dependant_types = type_map .values() .filter_map(|v| v.as_ref()) - .map(|v| { - ts::named_datatype(&cfg.inner, v, type_map).map(|typ| { - let name = v.name(); - - js_doc(&[format!("@typedef {{ {typ} }} {name}").into()]) - }) - }) + .map(|v| js::typedef_named_datatype(&cfg.inner, v, type_map)) .collect::, _>>() .map(|v| v.join("\n"))?; diff --git a/src/js_ts.rs b/src/js_ts.rs index 4cd5b2d..d7188ce 100644 --- a/src/js_ts.rs +++ b/src/js_ts.rs @@ -1,28 +1,25 @@ -use std::{borrow::Cow, path::PathBuf}; +use std::borrow::Cow; use heck::ToLowerCamelCase; use indoc::formatdoc; -use specta::{ - functions::FunctionDataType, - ts::{self, TsExportError}, - DataType, TypeMap, -}; +use specta::{functions::FunctionDataType, js_ts::ExportError, ts, DataType, TypeMap}; -use crate::{PluginName, EventDataType, ExportLanguage, ItemType}; +use crate::{EventDataType, ExportLanguage, ItemType}; pub const DO_NOT_EDIT: &str = "// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually."; const CRINGE_ESLINT_DISABLE: &str = "/* eslint-disable */ "; +pub type ExportConfig = crate::ExportConfig; -pub fn render_all_parts( +pub fn render_all_parts>( commands: &[FunctionDataType], events: &[EventDataType], type_map: &TypeMap, cfg: &ExportConfig, dependant_types: &str, globals: &str, -) -> Result { +) -> Result { let commands = T::render_commands(commands, type_map, cfg)?; let events = T::render_events(events, type_map, cfg)?; @@ -106,7 +103,7 @@ pub fn handle_result( function: &FunctionDataType, type_map: &TypeMap, cfg: &ExportConfig, -) -> Result { +) -> Result { Ok(match &function.result { DataType::Result(t) => { let (t, e) = t.as_ref(); @@ -150,7 +147,7 @@ pub fn events_types( events: &[EventDataType], cfg: &ExportConfig, type_map: &TypeMap, -) -> Result, TsExportError> { +) -> Result, ExportError> { events .iter() .map(|event| { @@ -167,39 +164,18 @@ pub fn events_data( events: &[EventDataType], cfg: &ExportConfig, type_map: &TypeMap, -) -> Result<(Vec, String), TsExportError> { +) -> Result<(Vec, String), ExportError> { Ok(( events_types(events, cfg, type_map)?, events_map(events, cfg), )) } -/// The configuration for the generator -#[derive(Default, Clone)] -pub struct ExportConfig { - /// The name of the plugin to invoke. - /// - /// If there is no plugin name (i.e. this is an app), this should be `None`. - pub(crate) plugin_name: PluginName, - /// The specta export configuration - pub(crate) inner: specta::ts::ExportConfig, - pub(crate) path: Option, - pub(crate) header: Cow<'static, str>, -} - -impl ExportConfig { - /// Creates a new [`ExportConfiguration`] from a [`specta::ts::ExportConfiguration`] - pub fn new(config: specta::ts::ExportConfig) -> Self { +impl From for ExportConfig { + fn from(config: specta::js_ts::ExportConfig) -> Self { Self { - inner: config, header: CRINGE_ESLINT_DISABLE.into(), - ..Default::default() + ..Self::new(config) } } } - -impl From for ExportConfig { - fn from(config: specta::ts::ExportConfig) -> Self { - Self::new(config) - } -} diff --git a/src/lib.rs b/src/lib.rs index 65368a3..370165e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,36 +53,56 @@ //! ## Export your bindings //! //! ```rust -//! # #[specta::specta] -//! # fn greet() {} -//! # #[specta::specta] -//! # fn greet2() {} -//! # #[specta::specta] -//! # fn greet3() {} -//! use specta::collect_types; -//! use tauri_specta::{ts, js}; +//! #[tauri::command] +//! #[specta::specta] +//! fn greet() {} +//! #[tauri::command] +//! #[specta::specta] +//! fn greet2() {} +//! #[tauri::command] +//! #[specta::specta] +//! fn greet3() {} + +//! use tauri_specta::*; //! //! // this example exports your types on startup when in debug mode or in a unit test. You can do whatever. //! fn main() { //! #[cfg(debug_assertions)] -//! ts::export(collect_types![greet, greet2, greet3], "../src/bindings.ts").unwrap(); +//! ts::builder() +//! .commands(collect_commands![greet, greet2, greet3]) +//! .path("../src/bindings.ts") +//! .export() +//! .unwrap(); //! //! // or export to JS with JSDoc //! #[cfg(debug_assertions)] -//! js::export(collect_types![greet, greet2, greet3], "../src/bindings.js").unwrap(); +//! js::builder() +//! .commands(collect_commands![greet, greet2, greet3]) +//! .path("../src/bindings.js") +//! .export() +//! .unwrap(); //! } //! //! #[test] //! fn export_bindings() { -//! ts::export(collect_types![greet, greet2, greet3], "../src/bindings.ts").unwrap(); -//! js::export(collect_types![greet, greet2, greet3], "../src/bindings.js").unwrap(); +//! ts::builder() +//! .commands(collect_commands![greet, greet2, greet3]) +//! .path("../src/bindings.ts") +//! .export() +//! .unwrap(); +//! +//! js::builder() +//! .commands(collect_commands![greet, greet2, greet3]) +//! .path("../src/bindings.js") +//! .export() +//! .unwrap(); //! } //! ``` //! //! ## Usage on frontend //! //! ```ts -//! import * as commands from "./bindings"; // This should point to the file we export from Rust +//! import { commands } from "./bindings"; // This should point to the file we export from Rust //! //! await commands.greet("Brendan"); //! ``` @@ -94,14 +114,18 @@ #![cfg_attr(docsrs, feature(doc_cfg))] use std::{ + borrow::Cow, fs::{self, File}, io::Write, marker::PhantomData, - path::Path, + path::{Path, PathBuf}, }; -use specta::{functions::FunctionDataType, ts::TsExportError, ExportError, TypeMap}; -use js_ts::ExportConfig; +use specta::{ + functions::{CollectFunctionsResult, FunctionDataType}, + ts::ExportError, + TypeMap, +}; use tauri::{Invoke, Manager, Runtime}; pub use tauri_specta_macros::Event; @@ -116,14 +140,15 @@ pub mod js; #[cfg_attr(docsrs, doc(cfg(feature = "typescript")))] pub mod ts; -mod event; +#[cfg(any(feature = "javascript", feature = "typescript"))] mod js_ts; -pub use event::*; +mod event; -pub type CollectFunctionsResult = Result<(Vec, TypeMap), ExportError>; +pub use event::*; -pub type CollectCommandsTuple = (CollectFunctionsResult, TInvokeHandler); +pub type CollectCommandsTuple = + (specta::functions::CollectFunctionsResult, TInvokeHandler); #[macro_export] macro_rules! collect_commands { @@ -145,26 +170,30 @@ macro_rules! collect_commands { /// A set of functions that produce language-specific code pub trait ExportLanguage: 'static { + type Config: Default + Clone; + + fn run_format(path: PathBuf, cfg: &ExportConfig); + fn render_events( events: &[EventDataType], type_map: &TypeMap, - cfg: &ExportConfig, - ) -> Result; + cfg: &ExportConfig, + ) -> Result; /// Renders a collection of [`FunctionDataType`] into a string. fn render_commands( commands: &[FunctionDataType], type_map: &TypeMap, - cfg: &ExportConfig, - ) -> Result; + cfg: &ExportConfig, + ) -> Result; /// Renders the output of [`globals`], [`render_functions`] and all dependant types into a TypeScript string. fn render( commands: &[FunctionDataType], events: &[EventDataType], type_map: &TypeMap, - cfg: &ExportConfig, - ) -> Result; + cfg: &ExportConfig, + ) -> Result; } pub trait CommandsTypeState: 'static { @@ -188,7 +217,7 @@ where type InvokeHandler = fn(Invoke); fn split(self) -> CollectCommandsTuple { - (Ok(Default::default()), dummy_invoke_handler) + (Default::default(), dummy_invoke_handler) } fn macro_data(&self) -> &CollectFunctionsResult { @@ -226,7 +255,7 @@ pub struct NoEvents; impl EventsTypeState for NoEvents { fn get(self) -> CollectEventsTuple { - (Default::default(), Ok(vec![]), Default::default()) + Default::default() } } @@ -239,26 +268,30 @@ impl EventsTypeState for Events { } /// General exporter, takes a generic for the specific language that is being exported to. -pub struct PluginBuilder { +pub struct PluginBuilder { lang: PhantomData, commands: TCommands, events: TEvents, - config: ExportConfig, + config: ExportConfig, } -impl Default for PluginBuilder, NoEvents> { +impl Default for PluginBuilder, NoEvents> +where + TLang: ExportLanguage, +{ fn default() -> Self { Self { lang: PhantomData, - commands: NoCommands(Ok((vec![], Default::default())), Default::default()), + commands: NoCommands(Default::default(), Default::default()), events: NoEvents, - config: ExportConfig::default(), + config: Default::default(), } } } impl PluginBuilder, TEvents> where + TLang: ExportLanguage, TRuntime: tauri::Runtime, { pub fn commands) + Send + Sync + 'static>( @@ -274,7 +307,10 @@ where } } -impl PluginBuilder { +impl PluginBuilder +where + TLang: ExportLanguage, +{ pub fn events(self, events: CollectEventsTuple) -> PluginBuilder { PluginBuilder { lang: self.lang, @@ -285,9 +321,12 @@ impl PluginBuilder { } } -impl PluginBuilder { +impl PluginBuilder +where + TLang: ExportLanguage, +{ /// Allows for specifying a custom [`ExportConfiguration`](specta::ts::ExportConfiguration). - pub fn config(mut self, config: specta::ts::ExportConfig) -> Self { + pub fn config(mut self, config: TLang::Config) -> Self { self.config.inner = config; self } @@ -363,12 +402,12 @@ where } } - fn export_inner(self) -> Result<(TCommands::InvokeHandler, EventCollection), TsExportError> { + fn export_inner(self) -> Result<(TCommands::InvokeHandler, EventCollection), ExportError> { let cfg = self.config.clone(); let (rendered, ret) = self.render()?; - if let Some(path) = cfg.path { + if let Some(path) = cfg.path.clone() { if let Some(export_dir) = path.parent() { fs::create_dir_all(export_dir)?; } @@ -377,15 +416,13 @@ where write!(file, "{}", rendered)?; - cfg.inner.run_format(path)?; + TLang::run_format(path, &cfg); } Ok(ret) } - fn render( - self, - ) -> Result<(String, (TCommands::InvokeHandler, EventCollection)), TsExportError> { + fn render(self) -> Result<(String, (TCommands::InvokeHandler, EventCollection)), ExportError> { let Self { commands, config, @@ -393,14 +430,13 @@ where .. } = self; - let (macro_data, invoke_handler) = commands.split(); - let (commands, commands_type_map) = macro_data?; + let ((commands, commands_type_map), invoke_handler) = commands.split(); let (events_registry, events, events_type_map) = events.get(); let rendered = TLang::render( &commands, - &events?, + &events, &commands_type_map .into_iter() .chain(events_type_map) @@ -424,11 +460,14 @@ where TEvents: EventsTypeState, { /// Exports the output of [`internal::render`] for a collection of [`FunctionDataType`] into a TypeScript file. - pub fn export(self) -> Result<(), TsExportError> { + pub fn export(self) -> Result<(), specta::js_ts::ExportError> { self.export_for_plugin(PLUGIN_NAME) } - pub fn export_for_plugin(mut self, plugin_name: &'static str) -> Result<(), TsExportError> { + pub fn export_for_plugin( + mut self, + plugin_name: &'static str, + ) -> Result<(), specta::js_ts::ExportError> { self.config.plugin_name = PluginName::new(plugin_name); self.export_inner().map(|_| ()) @@ -466,3 +505,26 @@ impl PluginName { ) } } + +/// The configuration for the generator +#[derive(Default, Clone)] +pub struct ExportConfig { + /// The name of the plugin to invoke. + /// + /// If there is no plugin name (i.e. this is an app), this should be `None`. + pub(crate) plugin_name: PluginName, + /// The specta export configuration + pub(crate) inner: TConfig, + pub(crate) path: Option, + pub(crate) header: Cow<'static, str>, +} + +impl ExportConfig { + /// Creates a new [`ExportConfiguration`] from a [`specta::ts::ExportConfiguration`] + pub fn new(config: TConfig) -> Self { + Self { + inner: config, + ..Default::default() + } + } +} diff --git a/src/ts.rs b/src/ts.rs index 41e5dc6..a69b0df 100644 --- a/src/ts.rs +++ b/src/ts.rs @@ -1,12 +1,9 @@ -use crate::{ - js_ts::{self, ExportConfig}, - EventDataType, ExportLanguage, NoCommands, NoEvents, PluginBuilder, -}; +use crate::{js_ts, *}; use heck::ToLowerCamelCase; use indoc::formatdoc; use specta::{ functions::FunctionDataType, - ts::{self, TsExportError}, + ts::{self, ExportError}, TypeMap, }; use tauri::Runtime; @@ -20,13 +17,21 @@ pub fn builder() -> PluginBuilder; + impl ExportLanguage for Language { + type Config = specta::js_ts::ExportConfig; + + fn run_format(path: PathBuf, cfg: &ExportConfig) { + cfg.inner.run_format(path).ok(); + } + /// Renders a collection of [`FunctionDataType`] into a TypeScript string. fn render_commands( commands: &[FunctionDataType], type_map: &TypeMap, cfg: &ExportConfig, - ) -> Result { + ) -> Result { let commands = commands .iter() .map(|function| { @@ -49,7 +54,7 @@ impl ExportLanguage for Language { &js_ts::command_body(cfg, function, true), )) }) - .collect::, TsExportError>>()? + .collect::, ExportError>>()? .join(",\n"); Ok(formatdoc! { @@ -64,7 +69,7 @@ impl ExportLanguage for Language { events: &[EventDataType], type_map: &TypeMap, cfg: &ExportConfig, - ) -> Result { + ) -> Result { if events.is_empty() { return Ok(Default::default()); } @@ -88,7 +93,7 @@ impl ExportLanguage for Language { events: &[EventDataType], type_map: &TypeMap, cfg: &ExportConfig, - ) -> Result { + ) -> Result { let dependant_types = type_map .values() .filter_map(|v| v.as_ref())