Skip to main content

satteri_plugin_api/
plugin.rs

1use crate::context::PluginContext;
2use crate::typed_nodes::*;
3use satteri_arena::{Arena, Mdast};
4use satteri_ast::mdast::MdastNodeType;
5
6/// Metadata about a plugin.
7#[derive(Debug, Clone)]
8pub struct PluginMeta {
9    pub name: &'static str,
10    pub version: Option<&'static str>,
11    pub description: Option<&'static str>,
12}
13
14impl PluginMeta {
15    pub fn new(name: &'static str) -> Self {
16        Self {
17            name,
18            version: None,
19            description: None,
20        }
21    }
22}
23
24/// The result of a visitor method: either no change, or a replacement.
25pub enum VisitResult {
26    /// No structural change (plugin may have written to data_map via ctx)
27    NoChange,
28    /// Replace this node with a new one
29    Replace(crate::commands::NewNode),
30    /// Remove this node
31    Remove,
32}
33
34impl VisitResult {
35    pub fn no_change() -> Self {
36        Self::NoChange
37    }
38    pub fn replace(node: crate::commands::NewNode) -> Self {
39        Self::Replace(node)
40    }
41    pub fn remove() -> Self {
42        Self::Remove
43    }
44}
45
46/// The Rust plugin trait.
47///
48/// Implement only the visitor methods you need. Default implementations
49/// return NoChange (no-op) so unimplemented visitors have zero overhead.
50pub trait Plugin: Send + Sync {
51    fn meta(&self) -> PluginMeta;
52
53    /// Called once before any files are processed.
54    fn init(&mut self) {}
55
56    /// Called before each file.
57    fn before(&mut self, _arena: &Arena<Mdast>, _ctx: &mut PluginContext) {}
58
59    /// Called after each file.
60    fn after(&mut self, _arena: &Arena<Mdast>, _ctx: &mut PluginContext) {}
61
62    fn visit_heading(&mut self, _node: &Heading, _ctx: &mut PluginContext) -> VisitResult {
63        VisitResult::NoChange
64    }
65    fn visit_paragraph(&mut self, _node: &Paragraph, _ctx: &mut PluginContext) -> VisitResult {
66        VisitResult::NoChange
67    }
68    fn visit_text(&mut self, _node: &Text, _ctx: &mut PluginContext) -> VisitResult {
69        VisitResult::NoChange
70    }
71    fn visit_link(&mut self, _node: &Link, _ctx: &mut PluginContext) -> VisitResult {
72        VisitResult::NoChange
73    }
74    fn visit_image(&mut self, _node: &Image, _ctx: &mut PluginContext) -> VisitResult {
75        VisitResult::NoChange
76    }
77    fn visit_code(&mut self, _node: &Code, _ctx: &mut PluginContext) -> VisitResult {
78        VisitResult::NoChange
79    }
80    fn visit_list(&mut self, _node: &NodeView, _ctx: &mut PluginContext) -> VisitResult {
81        VisitResult::NoChange
82    }
83    fn visit_list_item(&mut self, _node: &NodeView, _ctx: &mut PluginContext) -> VisitResult {
84        VisitResult::NoChange
85    }
86    fn visit_blockquote(&mut self, _node: &NodeView, _ctx: &mut PluginContext) -> VisitResult {
87        VisitResult::NoChange
88    }
89    fn visit_emphasis(&mut self, _node: &NodeView, _ctx: &mut PluginContext) -> VisitResult {
90        VisitResult::NoChange
91    }
92    fn visit_strong(&mut self, _node: &NodeView, _ctx: &mut PluginContext) -> VisitResult {
93        VisitResult::NoChange
94    }
95    fn visit_inline_code(&mut self, _node: &Text, _ctx: &mut PluginContext) -> VisitResult {
96        VisitResult::NoChange
97    }
98    fn visit_html(&mut self, _node: &Text, _ctx: &mut PluginContext) -> VisitResult {
99        VisitResult::NoChange
100    }
101    fn visit_table(&mut self, _node: &NodeView, _ctx: &mut PluginContext) -> VisitResult {
102        VisitResult::NoChange
103    }
104
105    /// Optional: full arena access for wholesale rewrites. Return None to leave unchanged.
106    fn transform_root(
107        &mut self,
108        _arena: &Arena<Mdast>,
109        _ctx: &mut PluginContext,
110    ) -> Option<Arena<Mdast>> {
111        None
112    }
113}
114
115/// A generic node view for nodes that don't have type-specific fields.
116pub struct NodeView<'a> {
117    pub(crate) node_id: u32,
118    pub(crate) arena: &'a Arena<Mdast>,
119}
120
121impl<'a> NodeView<'a> {
122    pub fn id(&self) -> u32 {
123        self.node_id
124    }
125    pub fn children(&self) -> &[u32] {
126        self.arena.get_children(self.node_id)
127    }
128    pub fn position(&self) -> NodePosition {
129        NodePosition::from_node(self.arena.get_node(self.node_id))
130    }
131    pub fn node_type(&self) -> MdastNodeType {
132        let raw = self.arena.get_node(self.node_id).node_type;
133        MdastNodeType::from_u8(raw).unwrap_or(MdastNodeType::Root)
134    }
135}