Skip to content

Commit

Permalink
Merge pull request #8 from rust-diplomat/demo_gen
Browse files Browse the repository at this point in the history
demo_gen Documentation
  • Loading branch information
Manishearth authored Aug 20, 2024
2 parents 901b6da + 26da6d6 commit 051e367
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 3 deletions.
Binary file added images/demo_output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/demo_output_renamed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@
- [Stringifiers](attrs/stringifiers.md)
- [Notes on Diplomat and safety](safety.md)
- [Backend developer guide](developer.md)
- [demo_gen](demo_gen/intro.md)
- [Quickstart](demo_gen/quickstart.md)
- [Attributes](demo_gen/attributes.md)
- [Configuring Markup](demo_gen/markup.md)
- [Configuring the Default Renderer](demo_gen/renderer.md)
- [Making Your Own Renderer](demo_gen/custom_renderer.md)
86 changes: 86 additions & 0 deletions src/demo_gen/attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Attributes

demo_gen does a lot in its attempt to automagically generate demonstrations for you. Sometimes however, hands on configuration is required.

Find below a list of the attributes that demo_gen supports.

## \#\[diplomat::attr\]

demo_gen supports all attributes listed in the [attrs chapter](../attrs.md). You mostly will want to use the `disable` attribute, to disable any functions that you may not want to include in output.

Because demo_gen is so heavily based on the JS backend, any `#[diplomat::attr]`s that apply to the JS backend will also apply to the demo_gen backend. So for example, any methods disabled in the JS backend will also be disabled in demo_gen's output.

## \#\[diplomat::demo\]

This is the core attribute that demo_gen looks for in configuring its output. There are a few supported attributes currently:

### \#\[diplomat::demo(generate)\]

Used in explicit generation of output. See [markup](./markup.md) for more.

### \#\[diplomat::demo(default_constructor)\]

demo_gen will throw errors for any Opaque types that do not have a method labelled with this attribute. demo_gen also looks for any Opaque methods labelled with `#[diplomat::attr(auto, constructor)]` as an alternative.

You should label each Opaque in your FFI definition with a `default_constructor` attribute, where the method is one you expect most users to call regularly when trying to create the Opaque in question. If your Opaque does not have an associated constructor method in its `impl` block, you should consider disabling functions (as this sort of behavior is too advanced for demo_gen to parse correctly).

For reasons on why demo_gen requires explicit labelling of Opaque constructors, see [the demo_gen design doc](https://github.com/rust-diplomat/diplomat/blob/main/docs/demo_gen.md).

### \#\[diplomat::demo(external)\]

Can be used above a parameter, struct field, or Opaque type.

It represents any input that you want to specify custom behavior for in the [rendering](./renderer.md) Javascript.

For example: In ICU4X, we have a `DataProvider` Opaque type that must be compiled ahead of time, and so we flag it as an external type:

```rs
#[diplomat::bridge]
mod ffi {
#[diplomat::opaque]
#[diplomat::demo(external)]
pub struct DataProvider;
}
```

We then override the [default renderer's runtime.mjs](renderer.md#runtimemjs) file to provide the compiled `DataProvider` when it is requested.

### \#\[diplomat::demo(input(...))\]

For configuring user input to your demos. `input(...)` takes in a comma separated list of values.

May be used on parameters or struct fields to configure specific properties passed to the [renderer](renderer.md).

Here are some valid `input` values:

- `input(label = "Label Here")`. Changes the label a given function parameter will have in the output.

#### Input Example

If we modify our [quickstart](quickstart.md) example, we can add `#[diplomat::demo(input(...))]` labels to the function parameters:

```rs
#[diplomat::bridge]
mod ffi {
use std::fmt::Write;

#[diplomat::opaque]
#[diplomat::rust_link(basic_adder, Mod)]
pub struct AddResult;

impl AddResult {
pub fn get_add_str(
#[diplomat::demo(input(label = "x"))]
left : u32,
#[diplomat::demo(input(label = "y"))]
right : u32, write: &mut DiplomatWrite) {
write.write_str(&format!("{}", basic_adder::add(left, right))).unwrap();
write.flush();
}
}
}
```

Which creates the following HTML output:

!["AddResult.getAddStr" in large text. Below are two inputs: one labelled "x" that has a value of 10, and one labelled "y" that has a value of 2. Below is a submit button. There is output below the button, with the label "Output" and a value of 12.](../../images/demo_output_renamed.png)
9 changes: 9 additions & 0 deletions src/demo_gen/custom_renderer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Making Your Own Renderer

Inside of `index.mjs`, demo_gen outputs an object called `RenderInfo` that points to all the functions demo_gen has created for the purposes of demonstration.

`RenderInfo` gives you the function to call directly, as well as the required parameters needed for each function in order.

This is meant to slot in to almost any Javascript solution with ease, but if there's an issue with `RenderInfo`s setup that is not quite compatible with your solution, please [open an issue](https://github.com/rust-diplomat/diplomat/issues/new?labels=B-demo_gen).

The exact structure of `RenderInfo` is available in the demo_gen [design docs](https://github.com/rust-diplomat/diplomat/blob/main/docs/design_doc.md#step-two-constructing-renderinfo).
18 changes: 18 additions & 0 deletions src/demo_gen/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# demo_gen

## What is demo_gen?

demo_gen is a backend for creating demonstration webpages automatically from Diplomat FFI definitions. These webpages are meant to showcase the capabilities of your library.


You can view the tracking issue for the demo_gen backend [here](https://github.com/rust-diplomat/diplomat/issues/604).

If you're interested in the design behind demo_gen, we have a [design document](https://github.com/rust-diplomat/diplomat/blob/main/docs/demo_gen.md) viewable on the Diplomat repository.

## Why do I want to use demo_gen?

The current big-name use case for demo_gen is the ICU4X internationalization library. ICU4X has a wide breadth of functions that can be somewhat hard to grasp if you're not already using the library heavily. demo_gen is a quick way

You can view the ICU4X demo_gen results [here](https://ambiguous.name/icu4x/) for a full demonstration of what demo_gen is currently capable.

If you're interested in trying demo_gen yourself, hop on to the [Quickstart](./quickstart.md) page to get started!
25 changes: 25 additions & 0 deletions src/demo_gen/markup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Configuring Markup
Diplomat takes the `-l` or `--library-config` option (in `diplomat_tool::gen` this is the `library_config` parameter). This represents a path to a `.toml` file that demo_gen will then read and convert into `DemoConfig`.

Here's a sample .toml file for configuration (with comments for clarity):

```toml
# If false, demo_gen will automatically search all methods for functions it can generate demonstration JS for.
# If true, demo_gen will look for any methods explicitly flagged with #[diplomat::demo(generate)] to perform generation.
explicit-generation=true # default = false (bool)

# This removes the rendering/ folder.
hide-default-renderer=true # default = false (bool)

# Adjusts all imports that demo_gen creates to a specific module. Setting this will not generate the js/ folder.
#
# So for instance, this setting will adjust imports to: `import { type } from "icu4x";
module-name="icu4x" # (string)

# Adjusts all imports that demo_gen creates to a relative path where Diplomat JS output should be. Setting this will not generate the js/ folder.
#
# Setting this will adjust imports to: `import {type} from "../js/folder/here/index.mjs";
#
# Intended to be a mutually exclusive setting with module-name, although you can set both simultaneously to import modules from a relative path.
relative-js-path="../js/folder/here" # (string)
```
103 changes: 103 additions & 0 deletions src/demo_gen/quickstart.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Quickstart

Demo Gen takes a bit of configuration if you don't already have your own Diplomat library set up.

For that reason, we have a [quickstart repository](https://github.com/rust-diplomat/demo-gen-quickstart). You can also follow along in your own library if you'd like.

## Requirements

You'll need to clone [the repository](https://github.com/rust-diplomat/demo-gen-quickstart).

You'll need to have Rust, Cargo, and the `wasm32-unknown-unknown` target installed:

```sh
rustup target add wasm32-unknown-unknown
```

You'll also need `Node` and `npm` [installed](https://nodejs.org/en/download/package-manager), as Diplomat generates JS code in modules that is easier for Node to parse as a package.

## Getting Started

You just need to run (in the repository folder):

```sh
cargo build -p adder_bindings --target wasm32-unknown-unknown
cargo run -p generator
cp target/wasm32-unknown-unknown/debug/adder_bindings.wasm adder_bindings/demo
```

You'll notice the `demo` folder now has a `demo_gen` folder, which is full of JS and rendering files. We can view our results in an HTTP server:

```sh
npm -C adder_bindings/demo install
npm -C adder_bindings/demo run start
```

If you open the server, you should see a webpage listing `AddResult.getAddStr` with a link. If you click the link, you should see something like:

![A search bar for the web server, with the added URL /demo_gen/rendering/template.html?func=AddResult.getAddStr. Displayed on the webpage is "AddResult.getAddStr" in large text. Below are two inputs: one labelled "left" that has a value of 1, and one labelled "right" that has a value of 2. Below is a submit button. There is output below the button, with the label "Output" and a value of 3.](../../images/demo_output.png)

And that's it! Let's talk about what each step means.

# What We Just Accomplished

When you clone the repository, you'll notice three packages.

## The Library

`basic_adder` is our Rust library that we want to make examples of. It only has one function:

```rs
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
```

Everything else we build will be based on this.

## The Bindings

`adder_bindings` is our [Diplomat bridge](../basics.md). When we want to make our library accessible in other languages, we need to run `diplomat-tool` on these bindings.

We build these bundings with

```sh
cargo build -p adder_bindings --target wasm32-unknown-unknown
```

`--target wasm32-unknown-unknown` tells `cargo` to build a `.wasm` file.

For more on how you can explicitly configure your bindings to work better in demos, see [the chapter on attributes](attributes.md).

## The Generator

`generator` is our wrapper for calling `diplomat-tool`. demo_gen is still in progress, and so we need a wayt to use the latest version of `diplomat-tool` to use this experimental backend. `generator` may be removed in future versions of this tutorial.

We run the generator with

```sh
cargo run -p generator
```

And it performs the equivalent of:

```sh
diplomat-tool demo_gen adder_bindings/demo/demo_gen --entry adder_bindings/src/lib.rs
```

demo_gen will automatically generate JS bindings by default, along with a bunch of other files to help you get started as easily as possible. If you have JS bindings elsewhere, or a different file structure from the Quickstart repository, you can configure how demo_gen works with a library config file. See [the section on markup generation for more](markup.md).

## The Web Demo

demo_gen is designed to work with most Diplomat libraries with minimal configuration. However, we currently don't provide *everything* that you'll need to instantly see a demo

The minimum requirement is at least a web server to load JS modules.

This is why we have you run

```sh
npm -C adder_bindings/demo install
npm -C adder_bindings/demo run start
```

See the chapter on [the renderer](./renderer.md) for more.
94 changes: 94 additions & 0 deletions src/demo_gen/renderer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Configuring the Default Renderer

demo_gen comes bundled with an HTML renderer to make getting started with creating demo_gen output to be as fast as possible. The default renderer is also designed to be highly customizable for your own preferences or front ends.

The front end renderer uses [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components), which are natively supported by most browsers. For this reason, it should be very portable into other front end systems like Svelte or React. However, if you're dead set on a solution that works even *better* for your front end of choice, you should read [making your own renderer](./custom_renderer.md).

For more on how the default renderer works, you can read our [design doc](https://github.com/rust-diplomat/diplomat/blob/main/docs/design_doc.md).

Regardless, let's discuss some ways you can customize the default renderer to your liking.

## template.html

`rendering/template.html` represents a list of templates that demo_gen's default renderer will use

demo_gen will automatically generate `template.html` in the rendering folder. There is nothing that ties `template.html` to this folder specifically however; you can copy, modify, and link to a changed `template.html` file for custom HTML, JS, and CSS.

For instance, this is one template we've overridden in the ICU4X repo to take advantage of Bootstrap:

```html
<template id="terminus">
<link rel="stylesheet" href="dist/index.css"/>
<div class="vstack gap-2">
<h1><slot name="func-name"></slot></h1>
<slot name="parameters"></slot>
<button type="submit" class="btn btn-primary" data-submit>Submit</button>
<div class="card">
<div class="card-header">Output</div>
<div class="card-body">
<p><slot name="output">Output Shown Here</slot></p>
</div>
</div>
</div>
</template>
```

For `<template>` tags, we hook into events by looking for `data-*` attributes, which have some of the following properties:

- `data-submit` tells the attached element to await a press before attempting to run demo_gen code (only works for the `#terminus` tag).
- `data-oninput` tells the attached element to listen for the `oninput` event and save the user's input on this element for submission.

If you're on the [quickstart](quickstart.md) repository, you might try copying `template.html` out of the rendering folder and modifying it yourself to include your own stylesheets.

> [!NOTE]
> Because the renderer uses the Web Components API, stylesheets need to be linked inside of each `<template>` tag.
## runtime.mjs

This is simply a wrapper for the underlying `rendering/rendering.mjs`, which contains most of the logic for taking `<template>` tags and transforming them into

The expected end result of `runtime.mjs` is to create a `TerminusRender` object from `rendering.mjs`, and append it to the HTML.

If you are interested in overriding the underlying Javascript more thoroughly, reading the documentation [on writing your own custom renderer](custom_renderer.md) is recommended. Otherwise, you will mostly be interested in overwriting the `evaluateExternal` parameter, which looks something like this:

```js
(param, updateParamEvent) => {
console.error(`Unrecognized parameter type ${param}`);
}
```

If you've flagged anything with the [external](attributes.md#diplomatdemoexternal) attribute, you can check for parameters that Diplomat cannot evaluate on its own and provide these yourself with the `updateParamEvent(updatedParamValue)` callback, containing the value of the parameter that is required.

> [!TIP]
> `evaluateExternal` is only called once on creation, so if you're planning on updating a param more than once, you should save a dictionary of `updateParamEvent` callbacks somewhere for future reference.
For example, in the ICU4X demo, we look for the DataProvider parameter and provide it from a compiled set of data:

```js
let dataProvider = DataProvider.compiled();
let evaluateExternal = (param, updateParamEvent) => {
if (parameter.type === "DataProvider") {
updateParamEvent(dataProvider);
} else {
console.error(`Unrecognized parameter type ${param}`);
}
};
```

## index.html

demo_gen currently doesn't provide an `index.html` file for you, as even with the default renderer your file structure can vary wildly. It is up to the user to provide their own additional `.html` files.

If you're looking to get into output right away: you can access any function from the default renderer by opening `template.html` from your webserver with the URL `/renderer/template.html?func=TypeName.functionName`.

Here's the current script that [the quickstart](quickstart.md) has to list all possible function names:

```js
import { RenderInfo } from "./demo_gen/index.mjs";

Object.values(RenderInfo.termini).forEach((t) => {
let a = document.createElement("li");
a.innerHTML = `<a href="demo_gen/rendering/template.html?func=${t.funcName}">${t.funcName}</a>`;
document.getElementById("links").appendChild(a);
});
```
6 changes: 3 additions & 3 deletions src/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ Diplomat currently supports the following backends:
- C
- C++
- JavaScript/TypeScript (using WASM)
- [demo_gen](./demo_gen/intro.md)
- Dart
- Kotlin (using JNA)

There is work in progress for a [Java backend] (using Panama), as well as a [demo-autogenerating backend]. We used to have a .NET backend but it was removed in a refactor, it may get added again.
There is work in progress for a [Java backend] (using Panama). We used to have a .NET backend but it was removed in a refactor, it may get added again.

We're happy to fix bugs or add configurability in the current backends if their produced output does not match what you need in your language. Details on how to write new backends is documented [later in this book](developer.html): you can do so as a third party library depending on `diplomat_core`, but we are also happy to accept these into Diplomat with the understanding that we'll only do minimal work to keep them working over time.

Expand All @@ -51,5 +52,4 @@ It is recommended to create a separate crate for the FFI interface. Diplomat wil


[Diplomat]: https://github.com/rust-diplomat/diplomat
[Java backend]: https://github.com/rust-diplomat/diplomat/issues/144
[demo-autogenerating backend]: https://github.com/rust-diplomat/diplomat/issues/604
[Java backend]: https://github.com/rust-diplomat/diplomat/issues/144

0 comments on commit 051e367

Please sign in to comment.