Skip to main content

diffr_plugin_sdk/
lib.rs

1//! The diffr plugin contract in Rust. `wit/plugin.wit` in the diffr
2//! repository is the contract; this crate is its one Rust form, shared by
3//! every plugin whether diffr compiles it in or runs it as a WASM component.
4//!
5//! - [`types`] holds the contract's records: the file entry, each side's
6//!   flat preorder region list with parent ids and text, and the moves. They
7//!   are generated from `wit/plugin.wit` itself, so there is one definition
8//!   of each, and a plugin hands diffr the same records natively and as a
9//!   component.
10//! - [`Plugin`] is the one trait every plugin implements: `new`, which makes
11//!   it from its options, `queries`, then `classify` and `mutate`, taking
12//!   and returning exactly those records.
13//! - [`host`] holds what diffr gives every plugin: `git`.
14//! - [`export!`] makes a plugin the `plugin` resource a component exports
15//!   when the crate is built for `wasm32-wasip2`, and
16//!   otherwise exposes its native registration for the host to collect.
17//!   The same source builds both ways.
18//!
19//! Plugins reason about regions with the same code:
20//!
21//! - [`tree`] rebuilds a side's list as a tree ([`tree::sides`], which the
22//!   SDK calls on the way in so a plugin is handed [`Pairing`] of [`Source`]
23//!   rather than the flat records) and holds the helpers for reading trees ([`walk`], [`OtherSide`], [`one_sided`],
24//!   [`docstring_of`], and the rest).
25//! - [`apply`] carries moves out. diffr carries every plugin's moves out with
26//!   it, so [`Draft`], which carries a plugin's moves out on a copy as it
27//!   makes them, and [`apply::Fresh`], which predicts fresh ids, give a
28//!   plugin exactly the ids diffr will.
29pub mod apply;
30pub mod draft;
31pub mod host;
32#[cfg(not(target_arch = "wasm32"))]
33pub mod native;
34pub mod tree;
35pub mod types;
36
37pub use anyhow;
38pub use draft::Draft;
39use serde::de::DeserializeOwned;
40pub use tree::{
41    before_and_after_ids, docstring_of, has_tag, is_fold, line_count, one_sided, path_to,
42    siblings_of, sides_with_other_ids, walk, walk_mut, Node, OtherSide, Pairing, Region, Source,
43};
44pub use types::{
45    Annotation, FileEntry, FileRef, FileSides, FileStatus, Move, Position, QuerySource, Range,
46    Side, Span, Visibility, ROOT,
47};
48
49/// A diffr plugin: the `plugin` resource of `wit/plugin.wit`. diffr makes one
50/// with [`Plugin::new`] when it builds its pipeline, before any file, and
51/// calls that one instance for every file of the run.
52pub trait Plugin: Sized {
53    /// The plugin's options, deserialized from its bundled or external config
54    /// entry: a JSON object, validated against the options schema in
55    /// `plugin.toml` and filled with its defaults.
56    type Options: DeserializeOwned;
57
58    /// Make the plugin from its options. An error, like options that do not
59    /// deserialize, is a setup error naming the plugin.
60    fn new(options: Self::Options) -> anyhow::Result<Self>;
61
62    /// Named query text, collected once during setup and compiled by diffr.
63    fn queries(&self) -> anyhow::Result<Vec<QuerySource>> {
64        Ok(Vec::new())
65    }
66
67    /// Tags to add to the file's manifest entry before it is diffed. A
68    /// plugin that does not classify returns none.
69    fn classify(&self, file: &FileEntry) -> anyhow::Result<Vec<String>>;
70
71    /// The moves that shape how the diffed file starts out. `sides` are the
72    /// sides the file has, already rebuilt as trees.
73    fn mutate(&self, file: &FileEntry, sides: &Pairing<Source>) -> anyhow::Result<Vec<Move>>;
74
75    /// Deferred labels for stable region IDs, after all initial mutations.
76    fn enrich(
77        &self,
78        _file: &FileEntry,
79        _sides: &Pairing<Source>,
80    ) -> anyhow::Result<Vec<Annotation>> {
81        Ok(Vec::new())
82    }
83}
84
85/// The contract generated from `wit/plugin.wit`. Its records are plain Rust
86/// and compile for every target, so [`types`] re-exports them and a plugin
87/// works with the generated records wherever it runs; only the `export!`
88/// macro this generates is wasm-specific, and [`export!`] calls it there.
89#[doc(hidden)]
90pub mod bindings {
91    wit_bindgen::generate!({
92        path: "wit",
93        world: "plugin",
94        pub_export_macro: true,
95        default_bindings_module: "diffr_plugin_sdk::bindings",
96        additional_derives: [PartialEq, Eq],
97    });
98}
99
100#[cfg(target_arch = "wasm32")]
101#[doc(hidden)]
102pub mod guest;
103
104/// Export a [`Plugin`] as the component's `plugin` resource when the crate
105/// is built for `wasm32`: the resource's `new` deserializes the options
106/// string into [`Plugin::Options`] and calls [`Plugin::new`], and its
107/// `classify` and `mutate` call the instance. Built for anything else it
108/// exposes its name and constructor as `DIFFR_PLUGIN` for the host registry.
109#[macro_export]
110macro_rules! export {
111    ($name:literal, $plugin:ty) => {
112        #[cfg(not(target_arch = "wasm32"))]
113        #[doc(hidden)]
114        pub static DIFFR_PLUGIN: $crate::native::Registration = $crate::native::Registration {
115            name: $name,
116            create: $crate::native::create::<$plugin>,
117        };
118        #[cfg(target_arch = "wasm32")]
119        const _: () = {
120            struct DiffrPluginExport;
121
122            impl $crate::bindings::exports::diffr::plugin::guest::Guest for DiffrPluginExport {
123                type Plugin = $crate::guest::Instance<$plugin>;
124            }
125
126    $crate::bindings::export!(DiffrPluginExport with_types_in $crate::bindings);
127        };
128    };
129}