Merge branch 'main' into feat/async

This commit is contained in:
Jonas Kruckenberg
2023-09-14 06:57:28 -06:00
35 changed files with 1650 additions and 218 deletions

View File

@@ -44,7 +44,7 @@ jobs:
RUSTFLAGS: ${{matrix.rustflags}} ${{env.RUSTFLAGS}}
msrv:
name: Rust 1.70.0
name: Rust MSRV
needs: pre_ci
if: needs.pre_ci.outputs.continue
runs-on: ubuntu-latest
@@ -55,7 +55,9 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libgtk-3-dev libsoup-3.0-dev libjavascriptcoregtk-4.1-dev
- uses: dtolnay/rust-toolchain@1.70.0
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: 1.70.0 # MSRV
- uses: Swatinem/rust-cache@v2
- run: cargo check --workspace --tests

21
.github/workflows/spelling.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
name: Spelling
permissions:
contents: read
on: [pull_request]
env:
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
CLICOLOR: 1
jobs:
spelling:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v3
- name: Spell Check Repo
uses: crate-ci/typos@master

View File

@@ -3,7 +3,7 @@ name = "tauri-bindgen"
authors.workspace = true
version.workspace = true
edition.workspace = true
rust-version.workspace = true
rust-version.workspace = true # MSRV
[workspace]
members = ["crates/*"]

View File

@@ -34,7 +34,7 @@ Here are a few reasons why that is cool:
- **Compile-time Checks**
When using strongly typed languages, such as Rust, TypeScript or ReScript the generated code will automatically ensure that you are calling the API correctly, as long as it passes the type checking youre golden. This is especially neat **when working in a team**, so your colleagues can't just change command signatures and pull the rug out from under you.
When using strongly typed languages, such as Rust, TypeScript or ReScript the generated code will automatically ensure that you are calling the API correctly, as long as it passes the type checking your golden. This is especially neat **when working in a team**, so your colleagues can't just change command signatures and pull the rug out from under you.
- **Easily auditable**

View File

@@ -73,7 +73,10 @@ pub struct TypeInfos {
impl TypeInfos {
#[must_use]
pub fn collect_from_functions(typedefs: &TypeDefArena, functions: &[Function]) -> Self {
pub fn collect_from_functions<'a>(
typedefs: &TypeDefArena,
functions: impl Iterator<Item = &'a Function>,
) -> Self {
let mut this = Self::default();
for func in functions {

View File

@@ -23,7 +23,22 @@ pub struct Builder {
impl GeneratorBuilder for Builder {
fn build(self, interface: Interface) -> Box<dyn Generate> {
let infos = TypeInfos::collect_from_functions(&interface.typedefs, &interface.functions);
let methods = interface
.typedefs
.iter()
.filter_map(|(_, typedef)| {
if let TypeDefKind::Resource(methods) = &typedef.kind {
Some(methods.iter())
} else {
None
}
})
.flatten();
let infos = TypeInfos::collect_from_functions(
&interface.typedefs,
interface.functions.iter().chain(methods),
);
let serde_utils =
SerdeUtils::collect_from_functions(&interface.typedefs, &interface.functions);
@@ -80,6 +95,7 @@ export async function {ident} ({params}) {{
fn print_resource(
&self,
mod_ident: &str,
docs: &str,
ident: &str,
functions: &[Function],
@@ -91,13 +107,32 @@ export async function {ident} ({params}) {{
.iter()
.map(|func| {
let docs = self.print_docs(func);
let mod_ident = mod_ident.to_snake_case();
let resource_ident = ident.to_snake_case();
let ident = func.ident.to_lower_camel_case();
let params = print_function_params(&func.params);
let deserialize_result = func
.result
.as_ref()
.map(|res| self.print_deserialize_function_result(res))
.unwrap_or_default();
let serialize_params = func
.params
.iter()
.map(|(ident, ty)| self.print_serialize_ty(&ident.to_lower_camel_case(), ty))
.collect::<Vec<_>>()
.join(";\n");
format!(
r#"
{docs}
r#"{docs}
async {ident} ({params}) {{
const out = []
serializeU32(out, this.#id);
{serialize_params}
await fetch('ipc://localhost/{mod_ident}::resource::{resource_ident}/{ident}', {{ method: "POST", body: Uint8Array.from(out), headers: {{ 'Content-Type': 'application/octet-stream' }} }}){deserialize_result}
}}
"#
)
@@ -106,9 +141,9 @@ async {ident} ({params}) {{
let deserialize = if info.contains(TypeInfo::RESULT) {
format!(
"deserialize(de) {{
"static deserialize(de) {{
const self = new {ident}();
self.#id = deserializeU64(de);
self.#id = deserializeU32(de);
return self
}}"
)
@@ -117,7 +152,7 @@ async {ident} ({params}) {{
};
format!(
"{docs}\nclass {ident} {{
"{docs}\nexport class {ident} {{
#id;
{functions}
{deserialize}
@@ -292,7 +327,13 @@ impl Generate for JavaScript {
.filter_map(|(id, typedef)| {
let info = self.infos[id];
if let TypeDefKind::Resource(functions) = &typedef.kind {
Some(self.print_resource(&typedef.docs, &typedef.ident, functions, info))
Some(self.print_resource(
&self.interface.ident,
&typedef.docs,
&typedef.ident,
functions,
info,
))
} else {
None
}

View File

@@ -335,15 +335,18 @@ serializeS64(out, val.c4)
}function serializeOtherVariant(out, val) {
if (val.A) {
serializeU32(out, 0);
return
return
}
if (val.B) {
serializeU32(out, 1);
return serializeU32(out, val.B)
serializeU32(out, val.B)
return
}
if (val.C) {
serializeU32(out, 2);
return serializeString(out, val.C)
serializeString(out, val.C)
return
}
@@ -351,19 +354,23 @@ if (val.C) {
}function serializeSomeVariant(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeString(out, val.A)
serializeString(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return
return
}
if (val.C) {
serializeU32(out, 2);
return serializeU32(out, val.C)
serializeU32(out, val.C)
return
}
if (val.D) {
serializeU32(out, 3);
return serializeList(out, (out, v) => serializeOtherVariant(out, v), val.D)
serializeList(out, (out, v) => serializeOtherVariant(out, v), val.D)
return
}

View File

@@ -17,6 +17,68 @@ class Deserializer {
return out
}
}
// function varint_max(bits) {
// const BITS_PER_BYTE = 8;
// const BITS_PER_VARINT_BYTE = 7;
// const roundup_bits = bits + (BITS_PER_BYTE - 1);
// return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE);
// }
const varint_max = {
16: 3,
32: 5,
64: 10,
128: 19
}
function max_of_last_byte(type) {
let extra_bits = type % 7;
return (1 << extra_bits) - 1;
}
function de_varint(de, bits) {
let out = 0;
for (let i = 0; i < varint_max[bits]; i++) {
const val = de.pop();
const carry = val & 0x7F;
out |= carry << (7 * i);
if ((val & 0x80) === 0) {
if (i === varint_max[bits] - 1 && val > max_of_last_byte(bits)) {
throw new Error('deserialize bad variant')
} else {
return out
}
}
}
throw new Error('deserialize bad variant')
}
function de_varint_big(de, bits) {
let out = 0n;
for (let i = 0; i < varint_max[bits]; i++) {
const val = de.pop();
const carry = BigInt(val) & 0x7Fn;
out |= carry << (7n * BigInt(i));
if ((val & 0x80) === 0) {
if (i === varint_max[bits] - 1 && val > max_of_last_byte(bits)) {
throw new Error('deserialize bad variant')
} else {
return out
}
}
}
throw new Error('deserialize bad variant')
}
function deserializeU32(de) {
return de_varint(de, 32)
}
/**
@@ -52,59 +114,102 @@ export async function constructorB () {
}
class A {
export class A {
#id;
/**
/**
*/
async f1 () {
}
const out = []
serializeU32(out, this.#id);
await fetch('ipc://localhost/resources::resource::a/f1', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
}
/**
* @param {number} a
*/
async f2 (a) {
}
const out = []
serializeU32(out, this.#id);
serializeU32(out, a)
await fetch('ipc://localhost/resources::resource::a/f2', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
}
/**
* @param {number} a
* @param {number} b
*/
async f3 (a, b) {
const out = []
serializeU32(out, this.#id);
serializeU32(out, a);
serializeU32(out, b)
await fetch('ipc://localhost/resources::resource::a/f3', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
}
deserialize(de) {
static deserialize(de) {
const self = new A();
self.#id = deserializeU64(de);
self.#id = deserializeU32(de);
return self
}
}
class B {
export class B {
#id;
/**
/**
* @returns {Promise<A>}
*/
async f1 () {
}
const out = []
serializeU32(out, this.#id);
await fetch('ipc://localhost/resources::resource::b/f1', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
.then(r => r.arrayBuffer())
.then(bytes => {
const de = new Deserializer(new Uint8Array(bytes))
return A.deserialize(de)
})
}
/**
* @param {A} x
* @returns {Promise<Result<number, _>>}
*/
async f2 (x) {
}
const out = []
serializeU32(out, this.#id);
x.serialize(out)
await fetch('ipc://localhost/resources::resource::b/f2', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
.then(r => r.arrayBuffer())
.then(bytes => {
const de = new Deserializer(new Uint8Array(bytes))
return deserializeResult(de, (de) => deserializeU32(de), () => {})
})
}
/**
* @param {A[] | null} x
* @returns {Promise<Result<A, _>>}
*/
async f3 (x) {
const out = []
serializeU32(out, this.#id);
serializeOption(out, (out, v) => serializeList(out, (out, v) => v.serialize(out), v), x)
await fetch('ipc://localhost/resources::resource::b/f3', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
.then(r => r.arrayBuffer())
.then(bytes => {
const de = new Deserializer(new Uint8Array(bytes))
return deserializeResult(de, (de) => A.deserialize(de), () => {})
})
}
deserialize(de) {
static deserialize(de) {
const self = new B();
self.#id = deserializeU64(de);
self.#id = deserializeU32(de);
return self
}
}

View File

@@ -431,31 +431,38 @@ case 1:
}function serializeV1(out, val) {
if (val.A) {
serializeU32(out, 0);
return
return
}
if (val.B) {
serializeU32(out, 1);
return serializeU1(out, val.B)
serializeU1(out, val.B)
return
}
if (val.C) {
serializeU32(out, 2);
return serializeE1(out, val.C)
serializeE1(out, val.C)
return
}
if (val.D) {
serializeU32(out, 3);
return serializeString(out, val.D)
serializeString(out, val.D)
return
}
if (val.E) {
serializeU32(out, 4);
return serializeEmpty(out, val.E)
serializeEmpty(out, val.E)
return
}
if (val.F) {
serializeU32(out, 5);
return
return
}
if (val.G) {
serializeU32(out, 6);
return serializeU32(out, val.G)
serializeU32(out, val.G)
return
}
@@ -463,11 +470,13 @@ if (val.G) {
}function serializeCasts1(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeS32(out, val.A)
serializeS32(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeF32(out, val.B)
serializeF32(out, val.B)
return
}
@@ -475,11 +484,13 @@ if (val.B) {
}function serializeCasts2(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeF64(out, val.A)
serializeF64(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeF32(out, val.B)
serializeF32(out, val.B)
return
}
@@ -487,11 +498,13 @@ if (val.B) {
}function serializeCasts3(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeF64(out, val.A)
serializeF64(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeU64(out, val.B)
serializeU64(out, val.B)
return
}
@@ -499,11 +512,13 @@ if (val.B) {
}function serializeCasts4(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeU32(out, val.A)
serializeU32(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeS64(out, val.B)
serializeS64(out, val.B)
return
}
@@ -511,11 +526,13 @@ if (val.B) {
}function serializeCasts5(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeF32(out, val.A)
serializeF32(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeS64(out, val.B)
serializeS64(out, val.B)
return
}
@@ -523,11 +540,13 @@ if (val.B) {
}function serializeCasts6(out, val) {
if (val.A) {
serializeU32(out, 0);
return {serializeF32(out, val.A[0]);serializeU32(out, val.A[1])}
{serializeF32(out, val.A[0]);serializeU32(out, val.A[1])}
return
}
if (val.B) {
serializeU32(out, 1);
return {serializeU32(out, val.B[0]);serializeU32(out, val.B[1])}
{serializeU32(out, val.B[0]);serializeU32(out, val.B[1])}
return
}

View File

@@ -14,6 +14,7 @@ use tauri_bindgen_core::TypeInfo;
use tauri_bindgen_core::TypeInfos;
use tauri_bindgen_gen_rust::FnSig;
use tauri_bindgen_gen_rust::{BorrowMode, RustGenerator};
use wit_parser::TypeDefKind;
use wit_parser::{Function, Interface};
#[derive(Default, Debug, Clone)]
@@ -35,7 +36,22 @@ pub struct Builder {
impl GeneratorBuilder for Builder {
fn build(self, interface: Interface) -> Box<dyn Generate> {
let infos = TypeInfos::collect_from_functions(&interface.typedefs, &interface.functions);
let methods = interface
.typedefs
.iter()
.filter_map(|(_, typedef)| {
if let TypeDefKind::Resource(methods) = &typedef.kind {
Some(methods.iter())
} else {
None
}
})
.flatten();
let infos = TypeInfos::collect_from_functions(
&interface.typedefs,
interface.functions.iter().chain(methods),
);
Box::new(RustWasm {
opts: self,
@@ -117,6 +133,7 @@ impl RustGenerator for RustWasm {
fn print_resource(
&self,
mod_ident: &str,
docs: &str,
ident: &proc_macro2::Ident,
functions: &[Function],
@@ -139,9 +156,17 @@ impl RustGenerator for RustWasm {
&BorrowMode::Owned,
);
let mod_ident = format!("{mod_ident}::resource::{}", ident.to_string().to_snake_case());
let ident = func.ident.to_snake_case();
let param_idents = func
.params
.iter()
.map(|(ident, _)| format_ident!("{}", ident));
quote! {
#sig {
todo!()
::tauri_bindgen_guest_rust::invoke(#mod_ident, #ident, &(self.0, #(#param_idents),*)).await.unwrap()
}
}
});
@@ -149,9 +174,7 @@ impl RustGenerator for RustWasm {
quote! {
#docs
#additional_attrs
pub struct #ident {
id: u64
}
pub struct #ident(u32);
impl #ident {
#(#functions)*

View File

@@ -3,34 +3,66 @@
pub mod resources {
use ::tauri_bindgen_guest_rust::serde;
use ::tauri_bindgen_guest_rust::bitflags;
#[derive(serde::Deserialize)]
pub struct A {
id: u64,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct A(u32);
impl A {
pub async fn f1(&self) {
todo!()
::tauri_bindgen_guest_rust::invoke(
"resources::resource::a",
"f1",
&(self.0,),
)
.await
.unwrap()
}
pub async fn f2(&self, a: u32) {
todo!()
::tauri_bindgen_guest_rust::invoke(
"resources::resource::a",
"f2",
&(self.0, a),
)
.await
.unwrap()
}
pub async fn f3(&self, a: u32, b: u32) {
todo!()
::tauri_bindgen_guest_rust::invoke(
"resources::resource::a",
"f3",
&(self.0, a, b),
)
.await
.unwrap()
}
}
#[derive(serde::Deserialize)]
pub struct B {
id: u64,
}
pub struct B(u32);
impl B {
pub async fn f1(&self) -> A {
todo!()
::tauri_bindgen_guest_rust::invoke(
"resources::resource::b",
"f1",
&(self.0,),
)
.await
.unwrap()
}
pub async fn f2(&self, x: A) -> Result<u32, ()> {
todo!()
::tauri_bindgen_guest_rust::invoke(
"resources::resource::b",
"f2",
&(self.0, x),
)
.await
.unwrap()
}
pub async fn f3(&self, x: Option<&'_ [A]>) -> Result<A, ()> {
todo!()
::tauri_bindgen_guest_rust::invoke(
"resources::resource::b",
"f3",
&(self.0, x),
)
.await
.unwrap()
}
}
pub async fn constructor_a() -> A {

View File

@@ -31,7 +31,22 @@ pub struct Builder {
impl GeneratorBuilder for Builder {
fn build(self, interface: Interface) -> Box<dyn Generate> {
let infos = TypeInfos::collect_from_functions(&interface.typedefs, &interface.functions);
let methods = interface
.typedefs
.iter()
.filter_map(|(_, typedef)| {
if let TypeDefKind::Resource(methods) = &typedef.kind {
Some(methods.iter())
} else {
None
}
})
.flatten();
let infos = TypeInfos::collect_from_functions(
&interface.typedefs,
interface.functions.iter().chain(methods),
);
let serde_utils =
SerdeUtils::collect_from_functions(&interface.typedefs, &interface.functions);
@@ -189,7 +204,9 @@ export async function {ident} ({params}) : {result} {{
TypeDefKind::Variant(cases) => self.print_variant(&docs, ident, cases),
TypeDefKind::Enum(cases) => self.print_enum(&docs, ident, cases),
TypeDefKind::Union(cases) => self.print_union(&docs, ident, cases),
TypeDefKind::Resource(functions) => self.print_resource(&docs, ident, functions),
TypeDefKind::Resource(functions) => {
self.print_resource(&self.interface.ident, &docs, ident, functions)
}
}
}
@@ -293,25 +310,48 @@ export async function {ident} ({params}) : {result} {{
format!("{docs}\nexport type {ident} = {cases};\n")
}
fn print_resource(&self, docs: &str, ident: &str, functions: &[Function]) -> String {
fn print_resource(
&self,
mod_ident: &str,
docs: &str,
ident: &str,
functions: &[Function],
) -> String {
let functions: String = functions
.iter()
.map(|func| {
let docs = print_docs(&func.docs);
let mod_ident = mod_ident.to_snake_case();
let resource_ident = ident.to_snake_case();
let ident = func.ident.to_lower_camel_case();
let params = self.print_function_params(&func.params);
let result = func
.result
.as_ref()
.map(|result| self.print_function_result(result))
.map_or("void".to_string(), |result| self.print_function_result(result));
let deserialize_result = func
.result
.as_ref()
.map(|res| self.print_deserialize_function_result(res))
.unwrap_or_default();
let serialize_params = func
.params
.iter()
.map(|(ident, ty)| self.print_serialize_ty(&ident.to_lower_camel_case(), ty))
.collect::<Vec<_>>()
.join(";\n");
format!(
r#"
{docs}
async {ident} ({params}) {result} {{
r#"{docs}
async {ident} ({params}) : {result} {{
const out = []
serializeU32(out, this.#id);
{serialize_params}
await fetch('ipc://localhost/{mod_ident}::resource::{resource_ident}/{ident}', {{ method: "POST", body: Uint8Array.from(out), headers: {{ 'Content-Type': 'application/octet-stream' }} }}){deserialize_result}
}}
"#
)
@@ -319,7 +359,7 @@ async {ident} ({params}) {result} {{
.collect();
format!(
"{docs}\nclass {ident} {{
"{docs}\nexport class {ident} {{
#id: number;
{functions}

View File

@@ -336,15 +336,18 @@ serializeS64(out, val.c4)
}function serializeOtherVariant(out, val) {
if (val.A) {
serializeU32(out, 0);
return
return
}
if (val.B) {
serializeU32(out, 1);
return serializeU32(out, val.B)
serializeU32(out, val.B)
return
}
if (val.C) {
serializeU32(out, 2);
return serializeString(out, val.C)
serializeString(out, val.C)
return
}
@@ -352,19 +355,23 @@ if (val.C) {
}function serializeSomeVariant(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeString(out, val.A)
serializeString(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return
return
}
if (val.C) {
serializeU32(out, 2);
return serializeU32(out, val.C)
serializeU32(out, val.C)
return
}
if (val.D) {
serializeU32(out, 3);
return serializeList(out, (out, v) => serializeOtherVariant(out, v), val.D)
serializeList(out, (out, v) => serializeOtherVariant(out, v), val.D)
return
}

View File

@@ -18,39 +18,144 @@ class Deserializer {
return out
}
}
// function varint_max(bits) {
// const BITS_PER_BYTE = 8;
// const BITS_PER_VARINT_BYTE = 7;
// const roundup_bits = bits + (BITS_PER_BYTE - 1);
// return Math.floor(roundup_bits / BITS_PER_VARINT_BYTE);
// }
const varint_max = {
16: 3,
32: 5,
64: 10,
128: 19
}
function max_of_last_byte(type) {
let extra_bits = type % 7;
return (1 << extra_bits) - 1;
}
function de_varint(de, bits) {
let out = 0;
for (let i = 0; i < varint_max[bits]; i++) {
const val = de.pop();
const carry = val & 0x7F;
out |= carry << (7 * i);
if ((val & 0x80) === 0) {
if (i === varint_max[bits] - 1 && val > max_of_last_byte(bits)) {
throw new Error('deserialize bad variant')
} else {
return out
}
}
}
throw new Error('deserialize bad variant')
}
function de_varint_big(de, bits) {
let out = 0n;
for (let i = 0; i < varint_max[bits]; i++) {
const val = de.pop();
const carry = BigInt(val) & 0x7Fn;
out |= carry << (7n * BigInt(i));
if ((val & 0x80) === 0) {
if (i === varint_max[bits] - 1 && val > max_of_last_byte(bits)) {
throw new Error('deserialize bad variant')
} else {
return out
}
}
}
throw new Error('deserialize bad variant')
}
function deserializeU32(de) {
return de_varint(de, 32)
}
class A {
export class A {
#id: number;
async f1 () : void {
const out = []
serializeU32(out, this.#id);
async f1 () {
await fetch('ipc://localhost/resources::resource::a/f1', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
}
async f2 (a: number) : void {
const out = []
serializeU32(out, this.#id);
serializeU32(out, a)
async f2 (a: number) {
await fetch('ipc://localhost/resources::resource::a/f2', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
}
async f3 (a: number, b: number) : void {
const out = []
serializeU32(out, this.#id);
serializeU32(out, a);
serializeU32(out, b)
async f3 (a: number, b: number) {
await fetch('ipc://localhost/resources::resource::a/f3', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
}
}
class B {
export class B {
#id: number;
async f1 () : Promise<A> {
const out = []
serializeU32(out, this.#id);
async f1 () Promise<A> {
await fetch('ipc://localhost/resources::resource::b/f1', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
.then(r => r.arrayBuffer())
.then(bytes => {
const de = new Deserializer(new Uint8Array(bytes))
return A.deserialize(de)
})
}
async f2 (x: A) : Promise<Result<number, null>> {
const out = []
serializeU32(out, this.#id);
x.serialize(out)
async f2 (x: A) Promise<Result<number, null>> {
await fetch('ipc://localhost/resources::resource::b/f2', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
.then(r => r.arrayBuffer())
.then(bytes => {
const de = new Deserializer(new Uint8Array(bytes))
return deserializeResult(de, (de) => deserializeU32(de), () => {})
})
}
async f3 (x: A[] | null) : Promise<Result<A, null>> {
const out = []
serializeU32(out, this.#id);
serializeOption(out, (out, v) => serializeList(out, (out, v) => v.serialize(out), v), x)
async f3 (x: A[] | null) Promise<Result<A, null>> {
await fetch('ipc://localhost/resources::resource::b/f3', { method: "POST", body: Uint8Array.from(out), headers: { 'Content-Type': 'application/octet-stream' } })
.then(r => r.arrayBuffer())
.then(bytes => {
const de = new Deserializer(new Uint8Array(bytes))
return deserializeResult(de, (de) => A.deserialize(de), () => {})
})
}
}

View File

@@ -433,31 +433,38 @@ case 1:
}function serializeV1(out, val) {
if (val.A) {
serializeU32(out, 0);
return
return
}
if (val.B) {
serializeU32(out, 1);
return serializeU1(out, val.B)
serializeU1(out, val.B)
return
}
if (val.C) {
serializeU32(out, 2);
return serializeE1(out, val.C)
serializeE1(out, val.C)
return
}
if (val.D) {
serializeU32(out, 3);
return serializeString(out, val.D)
serializeString(out, val.D)
return
}
if (val.E) {
serializeU32(out, 4);
return serializeEmpty(out, val.E)
serializeEmpty(out, val.E)
return
}
if (val.F) {
serializeU32(out, 5);
return
return
}
if (val.G) {
serializeU32(out, 6);
return serializeU32(out, val.G)
serializeU32(out, val.G)
return
}
@@ -465,11 +472,13 @@ if (val.G) {
}function serializeCasts1(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeS32(out, val.A)
serializeS32(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeF32(out, val.B)
serializeF32(out, val.B)
return
}
@@ -477,11 +486,13 @@ if (val.B) {
}function serializeCasts2(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeF64(out, val.A)
serializeF64(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeF32(out, val.B)
serializeF32(out, val.B)
return
}
@@ -489,11 +500,13 @@ if (val.B) {
}function serializeCasts3(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeF64(out, val.A)
serializeF64(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeU64(out, val.B)
serializeU64(out, val.B)
return
}
@@ -501,11 +514,13 @@ if (val.B) {
}function serializeCasts4(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeU32(out, val.A)
serializeU32(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeS64(out, val.B)
serializeS64(out, val.B)
return
}
@@ -513,11 +528,13 @@ if (val.B) {
}function serializeCasts5(out, val) {
if (val.A) {
serializeU32(out, 0);
return serializeF32(out, val.A)
serializeF32(out, val.A)
return
}
if (val.B) {
serializeU32(out, 1);
return serializeS64(out, val.B)
serializeS64(out, val.B)
return
}
@@ -525,11 +542,13 @@ if (val.B) {
}function serializeCasts6(out, val) {
if (val.A) {
serializeU32(out, 0);
return {serializeF32(out, val.A[0]);serializeU32(out, val.A[1])}
{serializeF32(out, val.A[0]);serializeU32(out, val.A[1])}
return
}
if (val.B) {
serializeU32(out, 1);
return {serializeU32(out, val.B[0]);serializeU32(out, val.B[1])}
{serializeU32(out, val.B[0]);serializeU32(out, val.B[1])}
return
}

View File

@@ -5,14 +5,13 @@
clippy::unused_self
)]
use std::collections::HashSet;
use std::path::PathBuf;
use heck::ToKebabCase;
use heck::{ToSnakeCase, ToUpperCamelCase};
use proc_macro2::{Literal, TokenStream};
use quote::format_ident;
use quote::quote;
use std::collections::HashSet;
use std::path::PathBuf;
use tauri_bindgen_core::{Generate, GeneratorBuilder, TypeInfo, TypeInfos};
use tauri_bindgen_gen_rust::{print_generics, BorrowMode, FnSig, RustGenerator};
use wit_parser::{Function, Interface, Type, TypeDefKind};
@@ -35,7 +34,22 @@ pub struct Builder {
impl GeneratorBuilder for Builder {
fn build(self, interface: Interface) -> Box<dyn Generate> {
let infos = TypeInfos::collect_from_functions(&interface.typedefs, &interface.functions);
let methods = interface
.typedefs
.iter()
.filter_map(|(_, typedef)| {
if let TypeDefKind::Resource(methods) = &typedef.kind {
Some(methods.iter())
} else {
None
}
})
.flatten();
let infos = TypeInfos::collect_from_functions(
&interface.typedefs,
interface.functions.iter().chain(methods),
);
Box::new(Host {
opts: self,
@@ -86,6 +100,7 @@ impl RustGenerator for Host {
fn print_resource(
&self,
_mod_ident: &str,
docs: &str,
ident: &proc_macro2::Ident,
functions: &[Function],
@@ -299,6 +314,7 @@ impl Host {
&self,
mod_ident: &str,
functions: impl Iterator<Item = &'a Function>,
methods: impl Iterator<Item = (&'a str, &'a Function)>,
) -> TokenStream {
let trait_ident = format_ident!("{}", mod_ident.to_upper_camel_case());
@@ -370,6 +386,61 @@ impl Host {
}
});
let methods = methods.map(|(resource_name, method)| {
let func_name = method.ident.to_snake_case();
let func_ident = format_ident!("{}", func_name);
let params = self.print_function_params(&method.params, &BorrowMode::Owned);
let param_idents = method
.params
.iter()
.map(|(ident, _)| format_ident!("{}", ident));
let result = match method.result.as_ref() {
Some(FunctionResult::Anon(ty)) => {
let ty = self.print_ty(ty, &BorrowMode::Owned);
quote! { #ty }
}
Some(FunctionResult::Named(types)) if types.len() == 1 => {
let (_, ty) = &types[0];
let ty = self.print_ty(ty, &BorrowMode::Owned);
quote! { #ty }
}
Some(FunctionResult::Named(types)) => {
let types = types
.iter()
.map(|(_, ty)| self.print_ty(ty, &BorrowMode::Owned));
quote! { (#(#types),*) }
}
_ => quote! { () },
};
let mod_name = format!("{mod_name}::resource::{resource_name}");
let get_r_ident = format_ident!("get_{}", resource_name.to_snake_case());
quote! {
let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx);
router.func_wrap(
#mod_name,
#func_name,
move |
ctx: ::tauri_bindgen_host::ipc_router_wip::Caller<T>,
this_rid: ::tauri_bindgen_host::ResourceId,
#params
| -> ::tauri_bindgen_host::anyhow::Result<#result> {
let ctx = get_cx(ctx.data());
let r = ctx.#get_r_ident(this_rid)?;
Ok(r.#func_ident(#(#param_idents),*))
},
)?;
}
});
quote! {
pub fn add_to_router<T, U>(
router: &mut ::tauri_bindgen_host::ipc_router_wip::Router<T>,
@@ -382,6 +453,7 @@ impl Host {
let wrapped_get_cx = ::std::sync::Arc::new(get_cx);
#( #functions )*
#( #methods )*
Ok(())
}
@@ -393,22 +465,36 @@ impl Generate for Host {
fn to_tokens(&mut self) -> TokenStream {
let docs = self.print_docs(&self.interface.docs);
let ident = format_ident!("{}", self.interface.ident.to_snake_case());
let iface_name = self.interface.ident.to_snake_case();
let ident = format_ident!("{}", iface_name);
let typedefs = self.print_typedefs(
self.interface.typedefs.iter().map(|(id, _)| id),
&BorrowMode::Owned,
);
let methods = self
.interface()
.typedefs
.iter()
.filter_map(|(_, typedef)| {
if let TypeDefKind::Resource(methods) = &typedef.kind {
Some(std::iter::repeat(typedef.ident.as_str()).zip(methods.iter()))
} else {
None
}
})
.flatten();
let resources = self.interface.typedefs.iter().filter_map(|(_, typedef)| {
if let TypeDefKind::Resource(_) = &typedef.kind {
let ident = format_ident!("{}", typedef.ident.to_upper_camel_case());
let func_ident = format_ident!("get_{}_mut", typedef.ident.to_snake_case());
let func_ident = format_ident!("get_{}", typedef.ident.to_snake_case());
Some(quote! {
type #ident: #ident;
type #ident: #ident + Send + Sync;
fn #func_ident(&mut self, id: ::tauri_bindgen_host::ResourceId) -> &mut Self::#ident;
fn #func_ident(&self, id: ::tauri_bindgen_host::ResourceId) -> ::tauri_bindgen_host::Result<::std::sync::Arc<Self::#ident>>;
})
} else {
None
@@ -422,8 +508,11 @@ impl Generate for Host {
true,
);
let add_to_router =
self.print_add_to_router(&self.interface.ident, self.interface.functions.iter());
let add_to_router = self.print_add_to_router(
&self.interface.ident,
self.interface.functions.iter(),
methods,
);
quote! {
#docs

View File

@@ -18,10 +18,16 @@ pub mod resources {
) -> Result<::tauri_bindgen_host::ResourceId, ()>;
}
pub trait Resources: Sized {
type A: A;
fn get_a_mut(&mut self, id: ::tauri_bindgen_host::ResourceId) -> &mut Self::A;
type B: B;
fn get_b_mut(&mut self, id: ::tauri_bindgen_host::ResourceId) -> &mut Self::B;
type A: A + Send + Sync;
fn get_a(
&self,
id: ::tauri_bindgen_host::ResourceId,
) -> ::tauri_bindgen_host::Result<::std::sync::Arc<Self::A>>;
type B: B + Send + Sync;
fn get_b(
&self,
id: ::tauri_bindgen_host::ResourceId,
) -> ::tauri_bindgen_host::Result<::std::sync::Arc<Self::B>>;
fn constructor_a(&self) -> ::tauri_bindgen_host::ResourceId;
fn constructor_b(&self) -> ::tauri_bindgen_host::ResourceId;
}
@@ -54,6 +60,99 @@ pub mod resources {
Ok(ctx.constructor_b())
},
)?;
let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx);
router
.func_wrap(
"resources::resource::a",
"f1",
move |
ctx: ::tauri_bindgen_host::ipc_router_wip::Caller<T>,
this_rid: ::tauri_bindgen_host::ResourceId,
| -> ::tauri_bindgen_host::anyhow::Result<()> {
let ctx = get_cx(ctx.data());
let r = ctx.get_a(this_rid)?;
Ok(r.f1())
},
)?;
let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx);
router
.func_wrap(
"resources::resource::a",
"f2",
move |
ctx: ::tauri_bindgen_host::ipc_router_wip::Caller<T>,
this_rid: ::tauri_bindgen_host::ResourceId,
a: u32,
| -> ::tauri_bindgen_host::anyhow::Result<()> {
let ctx = get_cx(ctx.data());
let r = ctx.get_a(this_rid)?;
Ok(r.f2(a))
},
)?;
let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx);
router
.func_wrap(
"resources::resource::a",
"f3",
move |
ctx: ::tauri_bindgen_host::ipc_router_wip::Caller<T>,
this_rid: ::tauri_bindgen_host::ResourceId,
a: u32,
b: u32,
| -> ::tauri_bindgen_host::anyhow::Result<()> {
let ctx = get_cx(ctx.data());
let r = ctx.get_a(this_rid)?;
Ok(r.f3(a, b))
},
)?;
let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx);
router
.func_wrap(
"resources::resource::b",
"f1",
move |
ctx: ::tauri_bindgen_host::ipc_router_wip::Caller<T>,
this_rid: ::tauri_bindgen_host::ResourceId,
| -> ::tauri_bindgen_host::anyhow::Result<
::tauri_bindgen_host::ResourceId,
> {
let ctx = get_cx(ctx.data());
let r = ctx.get_b(this_rid)?;
Ok(r.f1())
},
)?;
let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx);
router
.func_wrap(
"resources::resource::b",
"f2",
move |
ctx: ::tauri_bindgen_host::ipc_router_wip::Caller<T>,
this_rid: ::tauri_bindgen_host::ResourceId,
x: ::tauri_bindgen_host::ResourceId,
| -> ::tauri_bindgen_host::anyhow::Result<Result<u32, ()>> {
let ctx = get_cx(ctx.data());
let r = ctx.get_b(this_rid)?;
Ok(r.f2(x))
},
)?;
let get_cx = ::std::sync::Arc::clone(&wrapped_get_cx);
router
.func_wrap(
"resources::resource::b",
"f3",
move |
ctx: ::tauri_bindgen_host::ipc_router_wip::Caller<T>,
this_rid: ::tauri_bindgen_host::ResourceId,
x: Option<Vec<::tauri_bindgen_host::ResourceId>>,
| -> ::tauri_bindgen_host::anyhow::Result<
Result<::tauri_bindgen_host::ResourceId, ()>,
> {
let ctx = get_cx(ctx.data());
let r = ctx.get_b(this_rid)?;
Ok(r.f3(x))
},
)?;
Ok(())
}
}

View File

@@ -387,7 +387,8 @@ pub trait JavaScriptGenerator {
format!(
"if ({prop_access}) {{
serializeU32(out, {tag});
return {inner}
{inner}
return
}}
"
)
@@ -740,7 +741,7 @@ impl SerdeUtils {
info |= Self::collect_type_info(typedefs, &case.ty);
}
}
TypeDefKind::Enum(_) => {
TypeDefKind::Enum(_) | TypeDefKind::Resource(_) => {
info |= SerdeUtils::U32;
}
TypeDefKind::Flags(fields) => {
@@ -752,7 +753,6 @@ impl SerdeUtils {
wit_parser::Int::U128 => SerdeUtils::U128,
};
}
TypeDefKind::Resource(_) => {}
}
log::debug!("collected info for {:?}: {:?}", typedefs[id].ident, info,);

View File

@@ -15,6 +15,7 @@ heck.workspace = true
pulldown-cmark = { version = "0.9", default-features = false }
clap = { workspace = true, optional = true }
wit-parser.workspace = true
log.workspace = true
[features]
cli = ["clap"]
cli = ["clap"]

View File

@@ -183,7 +183,7 @@ impl Markdown {
fn print_function(&self, func: &Function) -> String {
format!(
"### Function {ident}\n\n`func {ident} ({params}){result}`\n\n{docs}",
"### Function {ident}\n\n` func {ident} ({params}){result}`\n\n{docs}",
ident = func.ident,
params = self.print_named_types(&func.params),
result = func

View File

@@ -19,6 +19,7 @@ pub trait RustGenerator {
fn default_param_mode(&self) -> BorrowMode;
fn print_resource(
&self,
print_resource: &str,
docs: &str,
ident: &Ident,
functions: &[Function],
@@ -63,7 +64,7 @@ pub trait RustGenerator {
self.print_union(docs, &ident, cases, info, &borrow_mode)
}
TypeDefKind::Resource(functions) => {
self.print_resource(docs, &ident, functions, info)
self.print_resource(&self.interface().ident, docs, &ident, functions, info)
}
};

View File

@@ -1,34 +1,139 @@
pub use tauri_bindgen_host_macro::*;
use std::sync::Arc;
use std::sync::RwLock;
use std::{any::Any, collections::HashMap};
pub use generational_arena::Arena as ResourceTable;
pub use tauri_bindgen_host_macro::*;
#[doc(hidden)]
pub use {anyhow, async_trait::async_trait, bitflags, ipc_router_wip, serde, tauri, tracing};
pub type ResourceId = u64;
pub type Result<T> = anyhow::Result<T>;
// #[derive(Debug)]
// pub struct ResourceId<T> {
// id: generational_arena::Index,
// _m: PhantomData<T>,
// }
pub type ResourceId = u32;
// impl<T> Clone for ResourceId<T> {
// fn clone(&self) -> Self {
// Self {
// id: self.id.clone(),
// _m: PhantomData,
// }
// }
// }
#[derive(Default)]
pub struct ResourceTable(RwLock<ResourceTableInner>);
// impl<T> Copy for ResourceId<T> {}
#[derive(Default)]
pub struct ResourceTableInner {
map: HashMap<ResourceId, Arc<dyn Any + Send + Sync>>,
next_rid: ResourceId,
}
// impl<T> PartialEq for ResourceId<T> {
// fn eq(&self, other: &Self) -> bool {
// self.id == other.id
// }
// }
impl ResourceTable {
/// Create an empty table.
#[must_use]
pub fn new() -> Self {
Self(RwLock::new(ResourceTableInner {
map: HashMap::new(),
next_rid: 0,
}))
}
// impl<T> Eq for ResourceId<T> {}
/// Insert a resource at the next available index.
///
/// # Errors
///
/// Returns an error if the table is full.
///
/// # Panics
///
/// Panics if the resource is already borrowed.
pub fn push<T: Any + Send + Sync>(&self, a: Arc<T>) -> Result<ResourceId> {
let mut inner = self.0.write().unwrap();
// NOTE: The performance of this new key calculation could be very bad once keys wrap
// around.
if inner.map.len() == u32::MAX as usize {
return Err(anyhow::anyhow!("table has no free keys"));
}
loop {
let key = inner.next_rid;
inner.next_rid += 1;
if inner.map.contains_key(&key) {
continue;
}
inner.map.insert(key, a);
return Ok(key);
}
}
/// Check if the table has a resource at the given index.
///
/// # Panics
///
/// Panics if the resource is already borrowed.
pub fn contains_key(&self, key: ResourceId) -> bool {
self.0.read().unwrap().map.contains_key(&key)
}
/// Check if the resource at a given index can be downcast to a given type.
/// Note: this will always fail if the resource is already borrowed.
///
/// # Panics
///
/// Panics if the resource is already borrowed.
pub fn is<T: Any + Sized>(&self, key: ResourceId) -> bool {
if let Some(r) = self.0.read().unwrap().map.get(&key) {
r.is::<T>()
} else {
false
}
}
/// Get an Arc reference to a resource of a given type at a given index. Multiple
/// immutable references can be borrowed at any given time.
///
/// # Errors
///
/// Returns an error if the resource is not of the given type.
///
/// # Panics
///
/// Panics if the resource is already borrowed.
pub fn get<T: Any + Send + Sync + Sized>(&self, key: ResourceId) -> Result<Arc<T>> {
if let Some(r) = self.0.read().unwrap().map.get(&key).cloned() {
r.downcast::<T>()
.map_err(|_| anyhow::anyhow!("element is a different type"))
} else {
Err(anyhow::anyhow!("key not in table"))
}
}
/// Get a mutable reference to a resource of a given type at a given index.
///
/// # Errors
///
/// Returns an error if the resource is not of the given type or if the resource is already borrowed.
///
/// # Panics
///
/// Panics if the resource is already borrowed.
pub fn get_mut<T: Any>(&mut self, key: ResourceId) -> Result<&mut T> {
let entry = self
.0
.get_mut()
.unwrap()
.map
.get_mut(&key)
.ok_or(anyhow::anyhow!("key not in table"))?;
let entry =
Arc::get_mut(entry).ok_or(anyhow::anyhow!("cannot mutably borrow shared file"))?;
entry
.downcast_mut::<T>()
.ok_or_else(|| anyhow::anyhow!("element is a different type"))
}
/// Remove a resource at a given index from the table and returns it.
///
/// # Panics
///
/// Panics if the resource is already borrowed.
pub fn take<T: Any + Send + Sync>(&self, key: ResourceId) -> Option<Arc<T>> {
self.0
.write()
.unwrap()
.map
.remove(&key)
.map(|r| r.downcast::<T>().unwrap())
}
}

View File

@@ -76,7 +76,7 @@ pub enum Error {
#[label("cannot be defined twice")]
location: Span,
},
/// Types can't recursively refer to themselves as that would make then possibly infinitly-sized.
/// Types can't recursively refer to themselves as that would make then possibly infinitely-sized.
/// In Rust the compiler would force you to use heap indirection, however such a thing doesn't exist in out type system.
///
/// This wouldn't be a problem with the current JSON format, but a custom binary one would have this limitation so for future proofing we deny recursive types.

View File

@@ -69,7 +69,7 @@ impl<'a> Resolver<'a> {
.map(|span| {
let str = self.read_span(span);
let str = str.strip_prefix("///").unwrap_or(str);
let str = str.strip_prefix("//*").unwrap_or(str);
let str = str.strip_prefix("/**").unwrap_or(str);
let str = str.trim();
str

View File

@@ -111,7 +111,7 @@ func greet(name: string) -> string
At the top-level of each `wit` document lives the `interface` definition, file must contain exactly one such definition.
The name you give to an interface will dictate the name of the generated module and printed debug output.
An interface may contain function declarations and type defintions. The order of declaration doesn't matter so you are free to define types after you have used them for example.
An interface may contain function declarations and type definitions. The order of declaration doesn't matter so you are free to define types after you have used them for example.
```wit
interface empty {}

View File

@@ -19,4 +19,5 @@ tauri-bindgen-gen-markdown = { path = "../crates/gen-markdown" }
tauri-bindgen-gen-guest-rust = { path = "../crates/gen-guest-rust" }
tauri-bindgen-gen-guest-ts = { path = "../crates/gen-guest-ts" }
tauri-bindgen-gen-guest-js = { path = "../crates/gen-guest-js" }
wit-parser = { path = "../crates/wit-parser" }
wit-parser = { path = "../crates/wit-parser" }
pulldown-cmark = { version = "0.9", default-features = false }

View File

@@ -1,4 +1,8 @@
lockfileVersion: '6.0'
lockfileVersion: '6.1'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
'@codemirror/lang-javascript':

View File

@@ -9,55 +9,59 @@ const default_content = `interface greet {
}`
const outputs = {
'errors': new EditorView({
parent: document.getElementById('errors')!,
extensions: [basicSetup, javascript(), EditorState.readOnly.of(true)],
}),
'host': new EditorView({
parent: document.getElementById('host')!,
extensions: [basicSetup, rust(), EditorState.readOnly.of(true)],
}),
'guest-rust': new EditorView({
parent: document.getElementById('guest_rust')!,
extensions: [basicSetup, rust(), EditorState.readOnly.of(true)],
}),
'guest-js': new EditorView({
parent: document.getElementById('guest_js')!,
extensions: [basicSetup, javascript(), EditorState.readOnly.of(true)],
}),
'guest-ts': new EditorView({
parent: document.getElementById('guest_ts')!,
extensions: [basicSetup, javascript(), EditorState.readOnly.of(true)],
}),
'errors': new EditorView({
parent: document.getElementById('errors')!,
extensions: [basicSetup, javascript(), EditorState.readOnly.of(true)],
}),
'host': new EditorView({
parent: document.getElementById('host')!,
extensions: [basicSetup, rust(), EditorState.readOnly.of(true)],
}),
'guest-rust': new EditorView({
parent: document.getElementById('guest_rust')!,
extensions: [basicSetup, rust(), EditorState.readOnly.of(true)],
}),
'guest-js': new EditorView({
parent: document.getElementById('guest_js')!,
extensions: [basicSetup, javascript(), EditorState.readOnly.of(true)],
}),
'guest-ts': new EditorView({
parent: document.getElementById('guest_ts')!,
extensions: [basicSetup, javascript(), EditorState.readOnly.of(true)],
}),
}
export function updateOutput(id: 'errors' | 'host' | 'guest-rust' | 'guest-js' | 'guest-ts', data: string) {
export function updateOutput(id: 'errors' | 'host' | 'guest-rust' | 'guest-js' | 'guest-ts' | 'markdown', data: string) {
if (id === "markdown") {
document.getElementById('markdown')!.innerHTML = data
} else {
outputs[id].dispatch({
changes: {
from: 0,
to: outputs[id].state.doc.length,
insert: data,
},
changes: {
from: 0,
to: outputs[id].state.doc.length,
insert: data,
},
})
}
}
export function setup(onChange: (data: string) => void) {
const input = new EditorView({
parent: document.getElementById('input')!,
extensions: [basicSetup, wit(), EditorView.updateListener.of((v) => {
if (v.docChanged) {
console.log(v.state.doc.toString())
const input = new EditorView({
parent: document.getElementById('input')!,
extensions: [basicSetup, wit(), EditorView.updateListener.of((v) => {
if (v.docChanged) {
console.log(v.state.doc.toString())
onChange(v.state.doc.toString())
}
})],
})
onChange(v.state.doc.toString())
}
})],
})
input.dispatch({
changes: {
from: 0,
to: input.state.doc.length,
insert: default_content,
},
})
}
input.dispatch({
changes: {
from: 0,
to: input.state.doc.length,
insert: default_content,
},
})
}

View File

@@ -19,6 +19,7 @@
<input id="iguest-rust" type="radio" name="tabs" />
<input id="iguest-js" type="radio" name="tabs" />
<input id="iguest-ts" type="radio" name="tabs" />
<input id="imarkdown" type="radio" name="tabs" />
<nav>
<label for="iinfo">Info</label>
@@ -27,16 +28,19 @@
<label for="iguest-rust">Guest Rust</label>
<label for="iguest-js">Guest JavaScript</label>
<label for="iguest-ts">Guest TypeScript</label>
<label for="imarkdown">Markdown</label>
</nav>
<figure>
<section id="info">
<h1>Welcome</h1>
<p>This is the tauri-bindgen playground. You can enter any .wit interface definition file and inspect the generated code.</p>
<p>This is the tauri-bindgen playground. You can enter any .wit interface definition file and inspect
the generated code.</p>
<p>For more info see <a href="https://github.com/tauri-apps/tauri-bindgen">the repo</a>. </p>
</section>
<section id="errors"></section>
<section id="markdown"></section>
<section id="host"></section>
<section id="guest_rust"></section>
<section id="guest_js"></section>
@@ -55,6 +59,7 @@
grid-template-rows: 50vh 50vh;
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif;
}
main {
width: 100%;
height: 100%;
@@ -62,6 +67,7 @@
padding: 5px;
box-sizing: border-box;
}
aside figure {
display: block;
margin-left: 0;
@@ -86,6 +92,7 @@
#iinfo:checked~figure #info,
#ierrors:checked~figure #errors,
#imarkdown:checked~figure #markdown,
#ihost:checked~figure #host,
#iguest-rust:checked~figure #guest_rust,
#iguest-js:checked~figure #guest_js,
@@ -116,6 +123,7 @@
#iinfo:checked~nav label[for="iinfo"],
#ierrors:checked~nav label[for="ierrors"],
#imarkdown:checked~nav label[for="imarkdown"],
#ihost:checked~nav label[for="ihost"],
#iguest-rust:checked~nav label[for="iguest-rust"],
#iguest-js:checked~nav label[for="iguest-js"],
@@ -128,6 +136,7 @@
#iinfo:checked~nav label[for="iinfo"]::after,
#ierrors:checked~nav label[for="ierrors"]::after,
#imarkdown:checked~nav label[for="imarkdown"]::after,
#ihost:checked~nav label[for="ihost"]::after,
#iguest-rust:checked~nav label[for="iguest-rust"]::after,
#iguest-js:checked~nav label[for="iguest-js"]::after,
@@ -141,6 +150,588 @@
left: 0;
bottom: -1px;
}
#markdown {
color-scheme: dark;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
margin: 0;
line-height: 1.5;
word-wrap: break-word;
}
#markdown details,
#markdown figcaption,
#markdown figure {
display: block;
}
#markdown summary {
display: list-item;
}
#markdown [hidden] {
display: none !important;
}
#markdown abbr[title] {
border-bottom: none;
text-decoration: underline dotted;
}
#markdown b,
#markdown strong {
font-weight: 600;
}
#markdown dfn {
font-style: italic;
}
#markdown h1 {
margin: .67em 0;
font-weight: 600;
padding-bottom: .3em;
font-size: 2em;
/* border-bottom: 1px solid $white; */
}
#markdown mark {
/* background-color: rgba($bright-yellow, 0.5); */
color: #c9d1d9;
}
#markdown small {
font-size: 90%;
}
#markdown sub,
#markdown sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
#markdown sub {
bottom: -0.25em;
}
#markdown sup {
top: -0.5em;
}
#markdown img {
border-style: none;
max-width: 35%;
box-sizing: content-box;
/* background-color: $black; */
}
#markdown code,
#markdown kbd,
#markdown pre,
#markdown samp {
font-family: monospace;
font-size: 1em;
}
#markdown figure {
margin: 1em 40px;
}
#markdown hr {
box-sizing: content-box;
overflow: hidden;
background: transparent;
/* border-bottom: 1px solid darken(grayscale($white), 10%); */
height: .25em;
padding: 0;
margin: 24px 0;
/* background-color: darken(grayscale($white), 10%); */
border: 0;
}
#markdown input {
font: inherit;
margin: 0;
overflow: visible;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
#markdown [type=button],
#markdown [type=reset],
#markdown [type=submit] {
appearance: button;
-webkit-appearance: button;
}
#markdown [type=checkbox],
#markdown [type=radio] {
box-sizing: border-box;
padding: 0;
}
#markdown [type=number]::-webkit-inner-spin-button,
#markdown [type=number]::-webkit-outer-spin-button {
height: auto;
}
#markdown [type=search]::-webkit-search-cancel-button,
#markdown [type=search]::-webkit-search-decoration {
-webkit-appearance: none;
}
#markdown ::-webkit-input-placeholder {
color: inherit;
opacity: .54;
}
#markdown ::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
#markdown ::placeholder {
color: #6e7681;
opacity: 1;
}
#markdown hr::before {
display: table;
content: "";
}
#markdown hr::after {
display: table;
clear: both;
content: "";
}
#markdown table {
border-spacing: 0;
border-collapse: collapse;
display: block;
width: max-content;
max-width: 100%;
overflow: auto;
}
#markdown td,
#markdown th {
padding: 0;
}
#markdown details summary {
cursor: pointer;
}
#markdown details:not([open])>*:not(summary) {
display: none !important;
}
#markdown a:focus,
#markdown [role=button]:focus,
#markdown input[type=radio]:focus,
#markdown input[type=checkbox]:focus {
outline: 2px solid #58a6ff;
outline-offset: -2px;
box-shadow: none;
}
#markdown a:focus:not(:focus-visible),
#markdown [role=button]:focus:not(:focus-visible),
#markdown input[type=radio]:focus:not(:focus-visible),
#markdown input[type=checkbox]:focus:not(:focus-visible) {
outline: solid 1px transparent;
}
#markdown a:focus-visible,
#markdown [role=button]:focus-visible,
#markdown input[type=radio]:focus-visible,
#markdown input[type=checkbox]:focus-visible {
outline: 2px solid #58a6ff;
outline-offset: -2px;
box-shadow: none;
}
#markdown a:not([class]):focus,
#markdown a:not([class]):focus-visible,
#markdown input[type=radio]:focus,
#markdown input[type=radio]:focus-visible,
#markdown input[type=checkbox]:focus,
#markdown input[type=checkbox]:focus-visible {
outline-offset: 0;
}
#markdown kbd {
display: inline-block;
padding: 3px 5px;
/* font: 80% $font-mono; */
line-height: 10px;
color: #c9d1d9;
vertical-align: middle;
/* background-color: $code-bg; */
/* border: solid 1px grayscale(darken($white, 60%)); */
/* border-bottom-color: grayscale(darken($white, 60%)); */
border-radius: 6px;
/* box-shadow: inset 0 -1px 0 grayscale(darken($white, 60%)); */
}
#markdown h1,
#markdown h2,
#markdown h3,
#markdown h4,
#markdown h5,
#markdown h6 {
margin-top: 24px;
margin-bottom: 16px;
font-weight: 600;
line-height: 1.25;
}
#markdown h2 {
font-weight: 600;
padding-bottom: .3em;
font-size: 1.5em;
/* border-bottom: 1px solid grayscale(darken($white, 25%)); */
}
#markdown h3 {
font-weight: 600;
font-size: 1.25em;
}
#markdown h4 {
font-weight: 600;
font-size: 1em;
}
#markdown h5 {
font-weight: 600;
font-size: .875em;
}
#markdown h6 {
font-weight: 600;
font-size: .85em;
/* color: grayscale(darken($white, 30%)); */
}
#markdown p {
margin-top: 0;
margin-bottom: 10px;
}
#markdown blockquote {
margin: 0;
padding: 0 1em;
/* color: grayscale(darken($white, 30%)); */
/* border-left: .25em solid grayscale(lighten($code-bg, 5%)); */
}
#markdown ul,
#markdown ol {
margin-top: 0;
margin-bottom: 0;
padding-left: 2em;
}
#markdown ol ol,
#markdown ul ol {
list-style-type: lower-roman;
}
#markdown ul ul ol,
#markdown ul ol ol,
#markdown ol ul ol,
#markdown ol ol ol {
list-style-type: lower-alpha;
}
#markdown dd {
margin-left: 0;
}
#markdown pre {
margin-top: 0;
margin-bottom: 0;
word-wrap: normal;
padding: 0.75rem;
}
#markdown input::-webkit-outer-spin-button,
#markdown input::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
appearance: none;
}
#markdown a:not([href]) {
color: inherit;
text-decoration: none;
}
#markdown p,
#markdown blockquote,
#markdown ul,
#markdown ol,
#markdown dl,
#markdown table,
#markdown pre,
#markdown details {
margin-top: 0;
margin-bottom: 16px;
}
#markdown blockquote>:first-child {
margin-top: 0;
}
#markdown blockquote>:last-child {
margin-bottom: 0;
}
#markdown h1 tt,
#markdown h1 code,
#markdown h2 tt,
#markdown h2 code,
#markdown h3 tt,
#markdown h3 code,
#markdown h4 tt,
#markdown h4 code,
#markdown h5 tt,
#markdown h5 code,
#markdown h6 tt,
#markdown h6 code {
padding: 0 .2em;
font-size: inherit;
}
#markdown summary h1,
#markdown summary h2,
#markdown summary h3,
#markdown summary h4,
#markdown summary h5,
#markdown summary h6 {
display: inline-block;
}
#markdown summary h1,
#markdown summary h2 {
padding-bottom: 0;
border-bottom: 0;
}
#markdown ol[type=a] {
list-style-type: lower-alpha;
}
#markdown ol[type=A] {
list-style-type: upper-alpha;
}
#markdown ol[type=i] {
list-style-type: lower-roman;
}
#markdown ol[type=I] {
list-style-type: upper-roman;
}
#markdown ol[type="1"] {
list-style-type: decimal;
}
#markdown div>ol:not([type]) {
list-style-type: decimal;
}
#markdown ul ul,
#markdown ul ol,
#markdown ol ol,
#markdown ol ul {
margin-top: 0;
margin-bottom: 0;
}
#markdown li>p {
margin-top: 16px;
}
#markdown li+li {
margin-top: .25em;
}
#markdown dl {
padding: 0;
}
#markdown dl dt {
padding: 0;
margin-top: 16px;
font-size: 1em;
font-style: italic;
font-weight: 600;
}
#markdown dl dd {
padding: 0 16px;
margin-bottom: 16px;
}
#markdown table th {
font-weight: 600;
}
#markdown table th,
#markdown table td {
padding: 6px 13px;
/* border: 1px solid grayscale(darken($white, 60%)); */
}
/* #markdown table tr {
background-color: $code-bg;
} */
/* #markdown table tr:nth-child(2n) {
background-color: lighten($code-bg, 5%);
} */
#markdown table img {
background-color: transparent;
}
#markdown code br,
#markdown tt br {
display: none;
}
#markdown del code {
text-decoration: inherit;
}
#markdown samp {
font-size: 85%;
}
#markdown pre code {
font-size: 100%;
}
#markdown pre>code {
padding: 0;
margin: 0;
word-break: normal;
white-space: pre;
background: transparent;
border: 0;
}
#markdown pre code,
#markdown pre tt {
display: inline;
max-width: auto;
padding: 0;
margin: 0;
overflow: visible;
line-height: inherit;
word-wrap: normal;
background-color: transparent;
border: 0;
}
#markdown [data-footnote-ref]::before {
content: "[";
}
#markdown [data-footnote-ref]::after {
content: "]";
}
#markdown .footnotes {
font-size: 12px;
/* color: grayscale(darken($white, 15%)); */
/* border-top: 1px solid $white; */
}
#markdown .footnotes ol {
padding-left: 16px;
}
#markdown .footnotes ol ul {
display: inline-block;
padding-left: 16px;
margin-top: 16px;
}
#markdown .footnotes li {
position: relative;
}
#markdown .footnotes li:target::before {
position: absolute;
top: -8px;
right: -8px;
bottom: -8px;
left: -24px;
pointer-events: none;
content: "";
/* border: 2px solid $bright-yellow; */
border-radius: 6px;
}
/* #markdown .footnotes li:target {
color: $white;
}
#markdown .footnotes .data-footnote-backref g-emoji {
font-family: $font-mono;
} */
#markdown .task-list-item {
list-style-type: none;
}
#markdown .task-list-item label {
font-weight: 400;
}
#markdown .task-list-item.enabled label {
cursor: pointer;
}
#markdown .task-list-item+.task-list-item {
margin-top: 4px;
}
#markdown .task-list-item .handle {
display: none;
}
#markdown .task-list-item-checkbox {
margin: 0 .2em .25em -1.4em;
vertical-align: middle;
}
#markdown .contains-task-list:dir(rtl) .task-list-item-checkbox {
margin: 0 -1.6em .25em .2em;
}
#markdown .contains-task-list {
position: relative;
}
#markdown .contains-task-list:hover .task-list-item-convert-container,
#markdown .contains-task-list:focus-within .task-list-item-convert-container {
display: block;
width: auto;
height: 24px;
overflow: visible;
clip: auto;
}
</style>
</html>
</html>

View File

@@ -2,10 +2,10 @@
NPM_FLAGS = "--version" # prevent Netlify npm install
[build]
publish = 'dist'
command = """
command = """
cd editor
npx pnpm install --store=node_modules/.pnpm-store
cd ..
wget -qO- https://github.com/thedodd/trunk/releases/download/v0.16.0/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
./trunk build
"""
"""

View File

@@ -1,4 +1,5 @@
use miette::NamedSource;
use pulldown_cmark::{html, Options, Parser};
use tauri_bindgen_core::GeneratorBuilder;
use wasm_bindgen::prelude::*;
use wit_parser::Interface;
@@ -64,9 +65,20 @@ fn main() {
prettier: false,
romefmt: false,
},
iface,
iface.clone(),
),
);
let markdown = gen_interface(tauri_bindgen_gen_markdown::Builder {}, iface);
let parser = Parser::new_ext(
&markdown,
Options::ENABLE_STRIKETHROUGH
| Options::ENABLE_FOOTNOTES
| Options::ENABLE_TABLES
| Options::ENABLE_TASKLISTS,
);
let mut html_output = String::new();
html::push_html(&mut html_output, parser);
update_output("markdown", &html_output);
}
Err(err) => {
update_output("errors", &format!("{:?}", err));

View File

@@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
]
}

103
renovate.json5 Normal file
View File

@@ -0,0 +1,103 @@
{
// schedule: [
// 'before 5am on the first day of the month',
//],
semanticCommits: 'enabled',
configMigration: true,
dependencyDashboard: true,
regexManagers: [
{
customType: 'regex',
fileMatch: [
'^rust-toolchain\\.toml$',
'Cargo.toml$',
'clippy.toml$',
'\\.clippy.toml$',
'^\\.github/workflows/ci.yml$',
'^\\.github/workflows/rust-next.yml$',
],
matchStrings: [
'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)',
'(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV',
],
depNameTemplate: 'rust',
packageNameTemplate: 'rust-lang/rust',
datasourceTemplate: 'github-releases',
},
],
packageRules: [
{
commitMessageTopic: 'MSRV',
matchManagers: [
'regex',
],
matchPackageNames: [
'rust',
],
minimumReleaseAge: '252 days', // 6 releases * 6 weeks per release * 7 days per week
internalChecksFilter: 'strict',
},
// Goals:
// - Keep version reqs low, ignoring compatible normal/build dependencies
// - Take advantage of latest dev-dependencies
// - Rollup safe upgrades to reduce CI runner load
// - Help keep number of versions down by always using latest breaking change
// - Have lockfile and manifest in-sync
{
matchManagers: [
'cargo',
],
matchDepTypes: [
'build-dependencies',
'dependencies',
],
matchCurrentVersion: '>=0.1.0',
matchUpdateTypes: [
'patch',
],
enabled: false,
},
{
matchManagers: [
'cargo',
],
matchDepTypes: [
'build-dependencies',
'dependencies',
],
matchCurrentVersion: '>=1.0.0',
matchUpdateTypes: [
'minor',
],
enabled: false,
},
{
matchManagers: [
'cargo',
],
matchDepTypes: [
'dev-dependencies',
],
matchCurrentVersion: '>=0.1.0',
matchUpdateTypes: [
'patch',
],
automerge: true,
groupName: 'compatible (dev)',
},
{
matchManagers: [
'cargo',
],
matchDepTypes: [
'dev-dependencies',
],
matchCurrentVersion: '>=1.0.0',
matchUpdateTypes: [
'minor',
],
automerge: true,
groupName: 'compatible (dev)',
},
],
}

View File

@@ -29,7 +29,7 @@ struct Cli {
#[derive(Debug, Parser)]
enum Command {
/// Check a defintion file for errors.
/// Check a definition file for errors.
Check {
#[clap(flatten)]
world: WorldOpt,

4
typos.toml Normal file
View File

@@ -0,0 +1,4 @@
[default.extend-words]
# Abbreviations
inout = "inout"
ser = "ser"