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}