standard-plugin-sdk 0.1.1

Write Standard Code plugins in Rust: wasm components against standard:plugin@2.0.0
Documentation
//! Write Standard Code plugins in Rust.
//!
//! Every plugin is a wasm component against the WIT package
//! `standard:plugin@2.0.0`, built for `wasm32-wasip2`. This crate wraps the
//! generated bindings in a safe API:
//!
//! - **UI plugins** ([`UiPlugin`], [`#[ui]`](macro@ui)) run in every viewer and paint
//!   [`Surface`]s in the cell model ([`Cells`]) or the graphics model
//!   ([`Pixels`]). They are side-effect free: effects that must happen once
//!   go through [`claims`](mod@claims) or a companion daemon ([`calls`](mod@calls)), and
//!   [`view::is_driving`] says whether the user is controlling this viewer.
//! - **Daemon plugins** ([`daemon::DaemonPlugin`], [`#[daemon]`](macro@daemon))
//!   run inside `standardd` with an async `run` loop on a small executor.
//!
//! The account interfaces ([`values`](mod@values), [`live`](mod@live), [`events`](mod@events), [`calls`](mod@calls),
//! [`config`](mod@config), [`secrets`](mod@secrets), [`account`](mod@account)) are shared by both. Every call the
//! plugin's manifest grants do not cover returns [`Error::GrantDenied`].
//!
//! A UI plugin is `#![no_std]` (the `std` library would import the WASI CLI
//! world, which a viewer does not offer); `alloc` is available and this
//! crate's default `runtime` feature supplies the allocator and panic
//! handler. A UI plugin, in full:
//!
//! ```rust,ignore
#![doc = include_str!("../examples/clock_card.rs")]
//! ```
//!
//! More: the [`guide`] pages (the same Markdown files are this crate's
//! `docs/` directory, and `standard-plugin guide` prints them), and the
//! compiled examples in this crate's `examples/` directory. `testing` (on
//! non-wasm targets) runs a plugin against an in-process host in ordinary
//! `cargo test`.
#![no_std]

extern crate alloc;
#[cfg(any(feature = "std", not(target_arch = "wasm32")))]
extern crate std;

#[cfg(target_endian = "big")]
compile_error!("the shared surface buffer is little-endian");

mod bindings;
mod colour;
mod error;
mod geometry;
mod host;
mod input;
mod local;
#[cfg(all(target_arch = "wasm32", not(feature = "std")))]
mod runtime;
mod ui_runtime;

pub mod api;
pub mod daemon;
pub mod draw;
pub mod surface;
#[cfg(not(target_arch = "wasm32"))]
pub mod testing;

pub use api::{
    Json, Target, account, calls, capabilities, claims, config, events, health, live, secrets, url,
    values, view,
};
pub use colour::{Attrs, Colour, Rgba, Style, ThemeSlot, recede};
pub use draw::{Area, Image, Shapes, font};
pub use error::{Error, Result};
pub use geometry::{Geometry, Rect};
pub use input::{Button, Key, KeyCode, KeyPhase, Modifiers, Pointer, PointerKind};
pub use surface::{AnySurface, AnySurfaceMut, Cell, Cells, Instances, Pixels, Surface};
pub use ui_runtime::{Context, Event, Frame, Power, UiPlugin};

/// Exports the annotated type, which implements [`UiPlugin`], as the
/// component's `ui-plugin` world. Put it on the plugin's struct or enum, or
/// on its `impl UiPlugin for ...` block; once per plugin crate.
pub use standard_plugin_macros::ui;

/// Exports the annotated type, which implements [`daemon::DaemonPlugin`],
/// as the component's `daemon-plugin` world.
pub use standard_plugin_macros::daemon;

/// Floating-point maths for `no_std` plugins (`libm::sinf`, `sqrtf`, ...):
/// `core` has no `f32::sin`.
pub use libm;

/// What most plugins import: `use standard_plugin::prelude::*;`.
pub mod prelude {
    pub use crate::daemon::DaemonPlugin;
    pub use crate::{
        AnySurface, Area, Attrs, Button, Cells, Colour, Context, Error, Event, Frame, Geometry,
        Instances, Json, Key, KeyCode, Modifiers, Pixels, Pointer, PointerKind, Rect, Rgba, Shapes,
        Style, Surface, Target, ThemeSlot, UiPlugin,
    };
    pub use alloc::borrow::ToOwned;
    pub use alloc::boxed::Box;
    pub use alloc::format;
    pub use alloc::string::{String, ToString};
    pub use alloc::vec;
    pub use alloc::vec::Vec;
}

/// The sources this SDK version is built from, for tools: the
/// `standard-plugin` CLI checks components against [`sources::WIT`] and
/// scaffolds new plugins from the examples, so both always match the SDK
/// the new plugin depends on.
pub mod sources {
    /// The WIT package `standard:plugin@2.0.0` (`wit/plugin.wit`): the one
    /// copy the SDK bindings, the viewer and daemon hosts and the CLI use.
    pub const WIT: &str = include_str!("../wit/plugin.wit");

    /// The compiled examples `standard-plugin new` starts from.
    pub mod examples {
        /// A cell-model sidebar card (`examples/clock_card.rs`).
        pub const CLOCK_CARD: &str = include_str!("../examples/clock_card.rs");
        /// A daemon plugin (`examples/heartbeat_daemon.rs`).
        pub const HEARTBEAT_DAEMON: &str = include_str!("../examples/heartbeat_daemon.rs");
        /// The UI half of a companion pair (`examples/together_ui.rs`).
        pub const TOGETHER_UI: &str = include_str!("../examples/together_ui.rs");
        /// The companion daemon (`examples/together_companion.rs`).
        pub const TOGETHER_COMPANION: &str = include_str!("../examples/together_companion.rs");
    }

    /// One guide of this SDK version: its name, the file stem in `docs/`
    /// (`standard-plugin guide <name>`), and its Markdown text.
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub struct Guide {
        pub name: &'static str,
        pub text: &'static str,
    }

    impl Guide {
        /// The text of its first heading, else its name.
        #[must_use]
        pub fn title(&self) -> &'static str {
            self.text
                .lines()
                .next()
                .and_then(|line| line.strip_prefix("# "))
                .unwrap_or(self.name)
        }
    }

    /// Every guide in this crate's `docs/` directory, in reading order,
    /// the index first ([`crate::guide`] shows the same pages).
    pub const GUIDES: &[Guide] = &[
        Guide {
            name: "README",
            text: include_str!("../docs/README.md"),
        },
        Guide {
            name: "getting-started",
            text: include_str!("../docs/getting-started.md"),
        },
        Guide {
            name: "agent-guide",
            text: include_str!("../docs/agent-guide.md"),
        },
        Guide {
            name: "manifest",
            text: include_str!("../docs/manifest.md"),
        },
        Guide {
            name: "grants",
            text: include_str!("../docs/grants.md"),
        },
        Guide {
            name: "rendering",
            text: include_str!("../docs/rendering.md"),
        },
        Guide {
            name: "input",
            text: include_str!("../docs/input.md"),
        },
        Guide {
            name: "working-together",
            text: include_str!("../docs/working-together.md"),
        },
        Guide {
            name: "daemon-plugins",
            text: include_str!("../docs/daemon-plugins.md"),
        },
        Guide {
            name: "testing",
            text: include_str!("../docs/testing.md"),
        },
        Guide {
            name: "publishing",
            text: include_str!("../docs/publishing.md"),
        },
        Guide {
            name: "abi",
            text: include_str!("../docs/abi.md"),
        },
        Guide {
            name: "other-languages",
            text: include_str!("../docs/other-languages.md"),
        },
    ];
}

/// The guides, as pages of this documentation. The same Markdown files are
/// this crate's `docs/` directory, where links between guides resolve, and
/// `standard-plugin guide` prints them for the SDK version a plugin's
/// `Cargo.lock` resolves.
pub mod guide {
    #[doc = include_str!("../docs/README.md")]
    pub mod index {}
    #[doc = include_str!("../docs/getting-started.md")]
    pub mod getting_started {}
    #[doc = include_str!("../docs/agent-guide.md")]
    pub mod agent_guide {}
    #[doc = include_str!("../docs/manifest.md")]
    pub mod manifest {}
    #[doc = include_str!("../docs/grants.md")]
    pub mod grants {}
    #[doc = include_str!("../docs/rendering.md")]
    pub mod rendering {}
    #[doc = include_str!("../docs/input.md")]
    pub mod input {}
    #[doc = include_str!("../docs/working-together.md")]
    pub mod working_together {}
    #[doc = include_str!("../docs/daemon-plugins.md")]
    pub mod daemon_plugins {}
    #[doc = include_str!("../docs/testing.md")]
    pub mod testing {}
    #[doc = include_str!("../docs/publishing.md")]
    pub mod publishing {}
    #[doc = include_str!("../docs/abi.md")]
    pub mod abi {}
    #[doc = include_str!("../docs/other-languages.md")]
    pub mod other_languages {}
}

/// Used by the export macros; not part of the public API.
#[doc(hidden)]
pub mod __private {
    pub use crate::bindings::{daemon as daemon_bindings, ui as ui_bindings};
    pub use crate::daemon::runtime::DaemonRuntime;
    pub use crate::ui_runtime::UiRuntime;
    pub use alloc::string::String;
}

/// Exports a [`UiPlugin`] type; `#[standard_plugin::ui]` expands to this.
#[doc(hidden)]
#[macro_export]
macro_rules! __export_ui {
    ($plugin:ty) => {
        const _: () = {
            static RUNTIME: $crate::__private::UiRuntime<$plugin> =
                $crate::__private::UiRuntime::new();

            struct Export;

            impl $crate::__private::ui_bindings::Guest for Export {
                fn activate(config: $crate::__private::String) {
                    RUNTIME.activate(config);
                }

                fn frame(
                    now_ms: u64,
                    interval_ms: u32,
                    power: $crate::__private::ui_bindings::Power,
                ) -> Option<u64> {
                    RUNTIME.frame(now_ms, interval_ms, power.into())
                }

                fn event(event: $crate::__private::ui_bindings::Event) {
                    RUNTIME.event(event.into());
                }

                fn deactivate() {
                    RUNTIME.deactivate();
                }
            }

    $crate::__export_ui_plugin_impl!(Export with_types_in $crate::__private::ui_bindings);
        };
    };
}

/// Exports a [`daemon::DaemonPlugin`] type; `#[standard_plugin::daemon]`
/// expands to this.
#[doc(hidden)]
#[macro_export]
macro_rules! __export_daemon {
    ($plugin:ty) => {
        const _: () = {
            static RUNTIME: $crate::__private::DaemonRuntime<$plugin> =
                $crate::__private::DaemonRuntime::new();

            struct Export;

            impl $crate::__private::daemon_bindings::Guest for Export {
                fn activate(config: $crate::__private::String) {
                    RUNTIME.activate(config);
                }

                fn event(event: $crate::__private::daemon_bindings::Event) {
                    RUNTIME.event(event.into());
                }

                fn drive(now_ms: u64) -> Option<u64> {
                    RUNTIME.drive(now_ms)
                }

                fn deactivate() {
                    RUNTIME.deactivate();
                }
            }

            impl $crate::__private::daemon_bindings::exports::standard::plugin::daemon_events::Guest
                for Export
            {
                fn handle_event(
                    event: $crate::__private::daemon_bindings::exports::standard::plugin::daemon_events::DaemonEvent,
                ) {
                    RUNTIME.event(event.into());
                }
            }

            impl $crate::__private::daemon_bindings::exports::standard::plugin::companion::Guest
                for Export
            {
                fn handle_call(
                    method: $crate::__private::String,
                    payload: $crate::__private::String,
                    from: $crate::__private::daemon_bindings::exports::standard::plugin::companion::Caller,
                ) -> Result<
                    $crate::__private::String,
                    $crate::__private::ui_bindings::standard::plugin::types::CallError,
                > {
                    RUNTIME
                        .handle_call(&method, payload, from.into())
                        .map_err(Into::into)
                }
            }

            // Only a component exports. Natively, interface exports are
            // `<interface>#<function>` symbols, and `#` starts a comment in
            // the macOS linker's exported-symbols list.
            #[cfg(target_family = "wasm")]
            $crate::__export_daemon_plugin_impl!(
                Export with_types_in $crate::__private::daemon_bindings
            );

            // A native build (tests, `cargo check`) still uses the plugin
            // through the same entry points.
            #[cfg(not(target_family = "wasm"))]
            #[allow(dead_code)]
            fn native_entry_points() {
                use $crate::__private::daemon_bindings::Guest as _;
                use $crate::__private::daemon_bindings::exports::standard::plugin::companion::Guest as _;
                use $crate::__private::daemon_bindings::exports::standard::plugin::daemon_events::Guest as _;
                let _ = (
                    Export::activate,
                    Export::event,
                    Export::handle_event,
                    Export::drive,
                    Export::deactivate,
                    Export::handle_call,
                );
            }
        };
    };
}