vexy_vsvg_plugin_sdk/lib.rs
1// this_file: crates/vexy-vsvg-plugin-sdk/src/lib.rs
2
3//! Plugin SDK for vexy-vsvg — everything you need to build an SVG optimization plugin.
4//!
5//! This crate ships 52 ready-to-use plugins and the machinery to write your own.
6//! Each plugin implements the [`Plugin`] trait and walks the SVG AST using
7//! the [`Visitor`] pattern. The SDK handles registration, parameter parsing,
8//! and CSS selector matching so you can focus on the optimization logic.
9//!
10//! ## What's inside
11//!
12//! - **52 built-in plugins** in the [`plugins`] module — ports of every SVGO plugin,
13//! same names, same behavior.
14//! - **[`registry`]** — pre-populated registry of all plugins, ready to use.
15//! - **[`selector`]** — CSS selector engine for plugins that target elements by selector.
16//! - **[`css_matching`]** — matches CSS selectors against AST elements.
17//! - **[`PluginWithParams`]** — trait for plugins that accept JSON configuration.
18//!
19//! ## Write a plugin in 4 steps
20//!
21//! 1. Define a struct for your plugin state.
22//! 2. Implement [`Plugin`] — give it a name, description, and `apply()` method.
23//! 3. Implement [`Visitor`] — override the hooks that matter (usually `visit_element_enter`).
24//! 4. Call [`walk_document`] from `apply()` to kick off traversal.
25//!
26//! ## Example
27//!
28//! ```ignore
29//! use vexy_vsvg_plugin_sdk::{Plugin, Visitor, Document, Element, walk_document};
30//! use anyhow::Result;
31//!
32//! struct MyPlugin;
33//!
34//! impl Plugin for MyPlugin {
35//! fn name(&self) -> &'static str { "myPlugin" }
36//! fn description(&self) -> &'static str { "Does something cool" }
37//! fn apply(&self, doc: &mut Document) -> Result<()> {
38//! let mut visitor = MyVisitor;
39//! walk_document(&mut visitor, doc)?;
40//! Ok(())
41//! }
42//! }
43//!
44//! struct MyVisitor;
45//! impl Visitor<'_> for MyVisitor {
46//! fn visit_element_enter(&mut self, element: &mut Element) -> Result<(), vexy_vsvg::VexyError> {
47//! // Modify element here
48//! Ok(())
49//! }
50//! }
51//! ```
52
53// Re-export core types needed for plugin development
54pub use vexy_vsvg::{
55 ast::{Document, Element, Node},
56 visitor::{walk_document, walk_element, walk_node, Visitor},
57 Config, Plugin, PluginConfig, VexyError,
58};
59
60pub mod css_matching;
61pub mod registry;
62// Note: enhanced_registry was just a wrapper around the registry, merged into registry
63pub mod selector;
64
65// Plugin implementations
66pub mod plugins;
67
68// Helper macros for tests
69#[cfg(test)]
70mod plugin_test_macros;
71
72#[cfg(test)]
73mod property_tests;
74
75/// Extended trait for plugins that accept JSON parameters from `svgo.config.json`.
76///
77/// Many SVGO plugins have tunable behavior — `convertColors` has `shorthex`,
78/// `cleanupNumericValues` has `floatPrecision`, etc. This trait standardizes
79/// how those parameters flow from JSON config into a typed Rust struct.
80///
81/// The associated `Config` type is deserialized from the `params` field in
82/// the plugin config entry. If omitted, `Config::default()` kicks in.
83///
84/// ```rust,ignore
85/// #[derive(Deserialize, Default)]
86/// struct MyConfig { precision: usize }
87///
88/// impl PluginWithParams for MyPlugin {
89/// type Config = MyConfig;
90/// fn with_config(config: MyConfig) -> Self { MyPlugin { precision: config.precision } }
91/// }
92/// ```
93pub trait PluginWithParams: Plugin + Default {
94 /// The config struct, deserialized from the JSON `params` object.
95 type Config: serde::de::DeserializeOwned + Default;
96
97 /// Deserialize JSON params into the typed config. Returns a clear error
98 /// if the JSON doesn't match the expected shape.
99 fn parse_params(params: &serde_json::Value) -> anyhow::Result<Self::Config> {
100 serde_json::from_value(params.clone())
101 .map_err(|e| anyhow::anyhow!("Invalid parameters: {}", e))
102 }
103
104 /// Construct the plugin with the given config. Called by the registry
105 /// after parsing parameters.
106 fn with_config(config: Self::Config) -> Self;
107
108 /// Alias for `parse_params` — kept for backward compatibility.
109 fn parse_config(params: &serde_json::Value) -> anyhow::Result<Self::Config> {
110 Self::parse_params(params)
111 }
112}