vexy-vsvg-plugin-sdk 2.3.1

Plugin SDK for vexy-vsvg
Documentation
// this_file: crates/vexy-vsvg-plugin-sdk/src/lib.rs

//! Plugin SDK for vexy-vsvg — everything you need to build an SVG optimization plugin.
//!
//! This crate ships 52 ready-to-use plugins and the machinery to write your own.
//! Each plugin implements the [`Plugin`] trait and walks the SVG AST using
//! the [`Visitor`] pattern. The SDK handles registration, parameter parsing,
//! and CSS selector matching so you can focus on the optimization logic.
//!
//! ## What's inside
//!
//! - **52 built-in plugins** in the [`plugins`] module — ports of every SVGO plugin,
//!   same names, same behavior.
//! - **[`registry`]** — pre-populated registry of all plugins, ready to use.
//! - **[`selector`]** — CSS selector engine for plugins that target elements by selector.
//! - **[`css_matching`]** — matches CSS selectors against AST elements.
//! - **[`PluginWithParams`]** — trait for plugins that accept JSON configuration.
//!
//! ## Write a plugin in 4 steps
//!
//! 1. Define a struct for your plugin state.
//! 2. Implement [`Plugin`] — give it a name, description, and `apply()` method.
//! 3. Implement [`Visitor`] — override the hooks that matter (usually `visit_element_enter`).
//! 4. Call [`walk_document`] from `apply()` to kick off traversal.
//!
//! ## Example
//!
//! ```ignore
//! use vexy_vsvg_plugin_sdk::{Plugin, Visitor, Document, Element, walk_document};
//! use anyhow::Result;
//!
//! struct MyPlugin;
//!
//! impl Plugin for MyPlugin {
//!     fn name(&self) -> &'static str { "myPlugin" }
//!     fn description(&self) -> &'static str { "Does something cool" }
//!     fn apply(&self, doc: &mut Document) -> Result<()> {
//!         let mut visitor = MyVisitor;
//!         walk_document(&mut visitor, doc)?;
//!         Ok(())
//!     }
//! }
//!
//! struct MyVisitor;
//! impl Visitor<'_> for MyVisitor {
//!     fn visit_element_enter(&mut self, element: &mut Element) -> Result<(), vexy_vsvg::VexyError> {
//!         // Modify element here
//!         Ok(())
//!     }
//! }
//! ```

// Re-export core types needed for plugin development
pub use vexy_vsvg::{
    ast::{Document, Element, Node},
    visitor::{walk_document, walk_element, walk_node, Visitor},
    Config, Plugin, PluginConfig, VexyError,
};

pub mod css_matching;
pub mod registry;
// Note: enhanced_registry was just a wrapper around the registry, merged into registry
pub mod selector;

// Plugin implementations
pub mod plugins;

// Helper macros for tests
#[cfg(test)]
mod plugin_test_macros;

#[cfg(test)]
mod property_tests;

/// Extended trait for plugins that accept JSON parameters from `svgo.config.json`.
///
/// Many SVGO plugins have tunable behavior — `convertColors` has `shorthex`,
/// `cleanupNumericValues` has `floatPrecision`, etc. This trait standardizes
/// how those parameters flow from JSON config into a typed Rust struct.
///
/// The associated `Config` type is deserialized from the `params` field in
/// the plugin config entry. If omitted, `Config::default()` kicks in.
///
/// ```rust,ignore
/// #[derive(Deserialize, Default)]
/// struct MyConfig { precision: usize }
///
/// impl PluginWithParams for MyPlugin {
///     type Config = MyConfig;
///     fn with_config(config: MyConfig) -> Self { MyPlugin { precision: config.precision } }
/// }
/// ```
pub trait PluginWithParams: Plugin + Default {
    /// The config struct, deserialized from the JSON `params` object.
    type Config: serde::de::DeserializeOwned + Default;

    /// Deserialize JSON params into the typed config. Returns a clear error
    /// if the JSON doesn't match the expected shape.
    fn parse_params(params: &serde_json::Value) -> anyhow::Result<Self::Config> {
        serde_json::from_value(params.clone())
            .map_err(|e| anyhow::anyhow!("Invalid parameters: {}", e))
    }

    /// Construct the plugin with the given config. Called by the registry
    /// after parsing parameters.
    fn with_config(config: Self::Config) -> Self;

    /// Alias for `parse_params` — kept for backward compatibility.
    fn parse_config(params: &serde_json::Value) -> anyhow::Result<Self::Config> {
        Self::parse_params(params)
    }
}