Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unify js/ts logic #48

Merged
merged 1 commit into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 16 additions & 13 deletions example/src/bindings.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable */
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.

export const commands = {
Expand All @@ -8,26 +7,24 @@ export const commands = {
* !!!!
*/
async helloWorld(myName: string): Promise<string> {
return await TAURI_INVOKE<string>("plugin:tauri-specta|hello_world", {
myName,
});
return await TAURI_INVOKE("plugin:tauri-specta|hello_world", { myName });
},
async goodbyeWorld(): Promise<string> {
return await TAURI_INVOKE<string>("plugin:tauri-specta|goodbye_world");
return await TAURI_INVOKE("plugin:tauri-specta|goodbye_world");
},
async hasError(): Promise<__Result__<string, number>> {
try {
return [
await TAURI_INVOKE<string>("plugin:tauri-specta|has_error"),
undefined,
];
} catch (e: any) {
return {
status: "ok",
data: await TAURI_INVOKE("plugin:tauri-specta|has_error"),
};
} catch (e) {
if (e instanceof Error) throw e;
else return [undefined, e];
else return { status: "error", error: e as any };
}
},
async someStruct(): Promise<MyStruct> {
return await TAURI_INVOKE<MyStruct>("plugin:tauri-specta|some_struct");
return await TAURI_INVOKE("plugin:tauri-specta|some_struct");
},
};

Expand All @@ -39,10 +36,14 @@ export const events = __makeEvents__<{
emptyEvent: "plugin:tauri-specta:empty-event",
});

/** user-defined types **/

export type EmptyEvent = null;
export type MyStruct = { some_field: string };
export type DemoEvent = string;

/** tauri-specta globals **/

import { invoke as TAURI_INVOKE } from "@tauri-apps/api";
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
import { type WebviewWindowHandle as __WebviewWindowHandle__ } from "@tauri-apps/api/window";
Expand All @@ -59,7 +60,9 @@ type __EventObj__<T> = {
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
};

type __Result__<T, E> = [T, undefined] | [undefined, E];
type __Result__<T, E> =
| { status: "ok"; data: T }
| { status: "error"; error: E };

function __makeEvents__<T extends Record<string, any>>(
mappings: Record<keyof T, string>
Expand Down
4 changes: 2 additions & 2 deletions src/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import * as TAURI_API_EVENT from "@tauri-apps/api/event";
*/

/**
* #template T,E
* @typedef {[T, undefined] | [undefined, E]} __Result__
* @template T,E
* @typedef { { status: "ok", data: T } | { status: "error", error: E } } __Result__
*/

/**
Expand Down
4 changes: 3 additions & 1 deletion src/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ type __EventObj__<T> = {
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
};

type __Result__<T, E> = [T, undefined] | [undefined, E];
type __Result__<T, E> =
| { status: "ok"; data: T }
| { status: "error"; error: E };

function __makeEvents__<T extends Record<string, any>>(
mappings: Record<keyof T, string>
Expand Down
128 changes: 19 additions & 109 deletions src/js.rs
Original file line number Diff line number Diff line change
@@ -1,58 +1,42 @@
use crate::{
ts::ExportConfig, EventDataType, ExportLanguage, ItemType, NoCommands, NoEvents, DO_NOT_EDIT,
js_ts, ts::ExportConfig, EventDataType, ExportLanguage, NoCommands, NoEvents, PluginBuilder,
};
use heck::ToLowerCamelCase;
use indoc::formatdoc;
use specta::{
functions::FunctionDataType,
ts::{self, js_doc, TsExportError},
DataType, TypeMap,
TypeMap,
};
use tauri::Runtime;

/// Implements [`ExportLanguage`] for JS exporting
pub struct Language;

/// [`Exporter`](crate::Exporter) for JavaScript
pub type PluginBuilder<TCommands, TEvents> = crate::PluginBuilder<Language, TCommands, TEvents>;

pub fn builder<TRuntime: Runtime>() -> PluginBuilder<NoCommands<TRuntime>, NoEvents> {
pub fn builder<TRuntime: Runtime>() -> PluginBuilder<Language, NoCommands<TRuntime>, NoEvents> {
PluginBuilder::default()
}

impl ExportLanguage for Language {
fn globals() -> String {
include_str!("./globals.js").to_string()
}
pub const GLOBALS: &str = include_str!("./globals.js");

impl ExportLanguage for Language {
/// Renders a collection of [`FunctionDataType`] into a JavaScript string.
fn render_commands(
commands: &[FunctionDataType],
type_map: &TypeMap,
cfg: &ExportConfig,
) -> Result<String, TsExportError> {
let commands = commands
.into_iter()
.iter()
.map(|function| {
let jsdoc = {
let ret_type = match &function.result {
DataType::Result(t) => {
let (t, e) = t.as_ref();

format!(
"[{}, undefined] | [undefined, {}]",
ts::datatype(&cfg.inner, t, &type_map)?,
ts::datatype(&cfg.inner, e, &type_map)?
)
}
t => ts::datatype(&cfg.inner, t, &type_map)?,
};
let ret_type = js_ts::handle_result(function, type_map, cfg)?;

let vec = []
.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| {
ts::datatype(&cfg.inner, typ, type_map).map(|typ| {
let name = name.to_lower_camel_case();

format!("@param {{ {typ} }} {name}")
Expand All @@ -65,48 +49,13 @@ impl ExportLanguage for Language {
js_doc(&vec)
};

let name_camel = function.name.to_lower_camel_case();

let arg_list = function
.args
.iter()
.map(|(name, _)| name.to_lower_camel_case())
.collect::<Vec<_>>();

let arg_defs = arg_list.join(", ");

let body = {
let name = cfg
.plugin_name
.apply_as_prefix(&function.name, ItemType::Command);

let arg_usages = arg_list
.is_empty()
.then(Default::default)
.unwrap_or_else(|| format!(", {{ {} }}", arg_list.join(", ")));

let invoke = format!("await invoke()(\"{name}\"{arg_usages})");

match &function.result {
DataType::Result(_) => formatdoc!(
r#"
try {{
return [{invoke}, undefined];
}} catch (e) {{
if(e instanceof Error) throw e;
else return [undefined, e];
}}"#
),
_ => format!("return {invoke};"),
}
};

Ok(formatdoc! {
r#"
{jsdoc}async {name_camel}({arg_defs}) {{
{body}
}}"#
})
Ok(js_ts::function(
&jsdoc,
&function.name.to_lower_camel_case(),
&js_ts::arg_names(&function.args),
None,
&js_ts::command_body(cfg, function, false),
))
})
.collect::<Result<Vec<_>, TsExportError>>()?
.join(",\n");
Expand All @@ -128,34 +77,12 @@ impl ExportLanguage for Language {
return Ok(Default::default());
}

let events_map = events
.iter()
.map(|event| {
let name_str = cfg
.plugin_name
.apply_as_prefix(&event.name, ItemType::Event);
let name_camel = event.name.to_lower_camel_case();

format!(r#" {name_camel}: "{name_str}""#)
})
.collect::<Vec<_>>()
.join(",\n");

let events = events
.iter()
.map(|event| {
let typ = ts::datatype(&cfg.inner, &event.typ, type_map)?;

let name_camel = event.name.to_lower_camel_case();

Ok(format!(r#"{name_camel}: {typ},"#))
})
.collect::<Result<Vec<_>, TsExportError>>()?;
let (events_types, events_map) = js_ts::events_data(events, cfg, type_map)?;

let events = js_doc(
&[].into_iter()
.chain(["@type {typeof __makeEvents__<{".to_string()])
.chain(events)
.chain(events_types)
.chain(["}>}".to_string()])
.map(Into::into)
.collect::<Vec<_>>(),
Expand All @@ -178,16 +105,11 @@ impl ExportLanguage for Language {
type_map: &TypeMap,
cfg: &ExportConfig,
) -> Result<String, TsExportError> {
let globals = Self::globals();

let commands = Self::render_commands(commands, &type_map, cfg)?;
let events = Self::render_events(events, &type_map, cfg)?;

let dependant_types = type_map
.values()
.filter_map(|v| v.as_ref())
.map(|v| {
ts::named_datatype(&cfg.inner, v, &type_map).map(|typ| {
ts::named_datatype(&cfg.inner, v, type_map).map(|typ| {
let name = v.name();

js_doc(&[format!("@typedef {{ {typ} }} {name}").into()])
Expand All @@ -196,18 +118,6 @@ impl ExportLanguage for Language {
.collect::<Result<Vec<_>, _>>()
.map(|v| v.join("\n"))?;

Ok(formatdoc! {
r#"
{DO_NOT_EDIT}

{commands}

{events}

{dependant_types}

{globals}
"#
})
js_ts::render_all_parts::<Self>(commands, events, type_map, cfg, &dependant_types, GLOBALS)
}
}
Loading