pochoir 0.15.1

Main crate of the pochoir template engine used to compile and render pochoir files with components
Documentation
//! Transformers are used to extend the behavior of `pochoir` by manipulating the HTML tree of
//! templates.
//!
//! They can be used to do various, repeated tasks like selecting nodes, inserting nodes, setting
//! attributes, setting text content or collecting data. For example, they can be used to
//! **enhance the CSS** `<style>` elements used in components by providing scoped CSS,
//! minification, autoprefixing and bundling, like what is done in the `EnhancedCss` structure
//! of the `pochoir-extra` crate. Another example is the **accessibility checker** using the
//! awesome [`axe-core`](https://github.com/dequelabs/axe-core) engine, which can have its
//! JavaScript inserted in the page *only in development*.
//!
//! ```ignore
//! use pochoir::{Context, StaticMapProvider, Transformers};
//! use pochoir_extra::{EnhancedCss, AccessibilityChecker};
//!
//! let provider = StaticMapProvider::new().with_template("index", "<main><h1>Index page</h1></main>
//!     <style enhanced>
//!     main {
//!       padding: 4rem;
//!
//!       & h1 {
//!         font-size: 1.2rem;
//!       }
//!     }
//!     </style>", None);
//! let mut context = Context::new();
//!
//! let transformers = Transformers::new()
//!     .with_transformer(EnhancedCss::new())
//!     .with_transformer(AccessibilityChecker::new());
//!
//! let _html = provider.transform_and_compile("index", &mut context, &mut transformers)?;
//! ```
//!
//! For more advanced uses, you can, of course, develop your own transformers. The
//! `Transformer` API is built around the main `Transformer` trait containing
//! methods taking at least a mutable reference to a `Tree`. Each method can be seen
//! as an "event" and starts with `on_`: `on_before_element`, `on_after_element` and
//! `on_tree_parsed`. For example, if you want to modify some attributes of an element, you would
//! use `on_before_element`. If you want to remove an element, you would use
//! `on_after_element` (called after all children of an element are parsed). If you
//! want to modify the full tree, you would use `on_tree_parsed`.
//!
//! For instance, if you want to set the `<title>` of a page based on the first `<h1>`
//! element of the page:
//!
//! ```
//! # fn main() -> pochoir::Result<()> {
//! use pochoir::{
//!     Context, Transformer, Transformers, TransformerTreeContext, TransformerResult, StaticMapProvider,
//!     parser::Tree, template_engine::Escaping,
//! };
//! use std::path::Path;
//!
//! struct TitleExtractor;
//!
//! impl Transformer for TitleExtractor {
//!     fn on_tree_parsed(&mut self, ctx: &mut TransformerTreeContext) -> TransformerResult{
//!         // You can use `Tree::select` or `TreeRef::select` to select elements of the tree
//!         // from a CSS selector (its variant, `select_all` can also be used to select *all the
//!         // elements* matching a CSS selector, otherwise just the first matching
//!         // element will be returned).
//!         // Regarding errors, they are just ignored here
//!         let Some(title_id) = ctx.tree.select("head title").expect("failed to parse the CSS selector") else { return Ok(()) };
//!         let Some(first_h1_id) = ctx.tree.select("body h1").expect("failed to parse the CSS selector") else { return Ok(()) };
//!
//!         let title = format!(
//!             "{} | {}",
//!             ctx.tree.get(first_h1_id).text(),
//!             ctx.tree.get(title_id).text(),
//!         );
//!         ctx.tree.get_mut(title_id).set_text(title, Escaping::default());
//!
//!         Ok(())
//!     }
//! }
//!
//! let mut transformers = Transformers::new()
//!     .with_transformer(TitleExtractor);
//!
//! let provider = StaticMapProvider::new().with_template("index", r#"
//! <!DOCTYPE HTML>
//! <html lang="en">
//!   <head>
//!     <title>My Website</title>
//!   </head>
//!   <body>
//!     <h1>Index page</h1>
//!     <main>
//!       <p>Some content</p>
//!     </main>
//!   </body>
//! </html>
//! "#, None);
//! let mut context = Context::new();
//!
//! let html = provider.transform_and_compile("index", &mut context, &mut transformers)?;
//!
//! assert_eq!(html, r#"
//! <!DOCTYPE HTML>
//! <html lang="en">
//!   <head>
//!     <title>Index page | My Website</title>
//!   </head>
//!   <body>
//!     <h1>Index page</h1>
//!     <main>
//!       <p>Some content</p>
//!     </main>
//!   </body>
//! </html>
//! "#);
//! # Ok(())
//! # }
//! ```
//!
//! You can also use transformers to collect some information about the tree. For
//! example you can get a list of all the components used:
//!
//! ```
//! # fn main() -> pochoir::Result<()> {
//! use pochoir::{
//!     Context, Transformer, Transformers, TransformerElementContext, TransformerResult, StaticMapProvider,
//!     parser::{Tree, TreeRefId},
//! };
//!
//! struct ComponentCollector<'a> {
//!     list: &'a mut Vec<String>,
//! }
//!
//! impl Transformer for ComponentCollector<'_> {
//!     fn on_before_element(&mut self, ctx: &mut TransformerElementContext) -> TransformerResult {
//!         let Ok(element_name) = ctx.tree.get(ctx.element_id).name() else {
//!             // Not an element, ignore it
//!             return Ok(())
//!         };
//!
//!         if pochoir::is_custom_element(&element_name) {
//!             self.list.push(element_name.to_string());
//!         }
//!
//!         Ok(())
//!     }
//! }
//!
//! let mut components = vec![];
//! let mut transformers = Transformers::new()
//!     .with_transformer(ComponentCollector {
//!         list: &mut components,
//!     });
//!
//! let provider = StaticMapProvider::new()
//!     .with_template("index", "<my-heading>Hello!</my-heading><my-button />", None)
//!     .with_template("my-heading", r#"<h1 class="heading"><slot></slot></h1>"#, None)
//!     .with_template("my-button", "<button>Click me!</button>", None);
//! let mut context = Context::new();
//!
//! let _html = provider.transform_and_compile("index", &mut context, &mut transformers)?;
//!
//! // Transformers need to be dropped manually to avoid referencing the components
//! // while mutably borrowing them. You can also make a scope just for compilation
//! drop(transformers);
//! assert_eq!(components, vec!["my-heading".to_string(), "my-button".to_string()]);
//! # Ok(())
//! # }
//! ```
//!
//! As you can see, using buffers is the right way to define mutable state that
//! could be used after compiling.
//!
//! Note that all transformations happen before templating, so expressions and
//! statements are not yet replaced with content.
//!
//! ### Where to go next?
//!
//! - Check out [`pochoir_parser`]'s documentation to learn how to manipulate trees in
//!   transformers, especially the [`Manipulating the tree`](`pochoir_parser#manipulating-the-tree`) section.

use std::{fmt, path::Path};

use pochoir_lang::Context;
use pochoir_parser::{EventHandlerResult, Tree, TreeRefId};

/// An alias to an [`EventHandlerResult`].
pub type TransformerResult = EventHandlerResult;

/// A wrapper structure containing all accessible elements usable in `on_*_element` transformer methods.
#[derive(Debug)]
pub struct TransformerElementContext<'a, 'b, 'c> {
    /// The tree of the current component.
    pub tree: &'a mut Tree<'b>,

    /// The context of the current component.
    pub context: &'c mut Context,

    /// The ID of the current element.
    pub element_id: TreeRefId,
}

/// A wrapper structure containing all accessible elements usable in `on_*_element `transformer methods.
#[derive(Debug)]
pub struct TransformerTreeContext<'a, 'b, 'c, 'd, 'e> {
    /// The tree of the current component.
    pub tree: &'a mut Tree<'b>,

    /// The context of the current component.
    pub context: &'c mut Context,

    /// The name of the current component.
    pub component_name: &'d str,

    /// A path to the file containing the current component.
    pub file_path: &'e Path,
}

/// Transformers are used to extend the behavior of `pochoir` by manipulating the HTML tree of
/// templates.
///
/// See [the module documentation](self).
pub trait Transformer {
    /// A method called when an element is discovered, after its attributes are parsed but before
    /// the children of this element are parsed.
    ///
    /// If you remove the current element in this method, the program will probably panic because
    /// it will try to add children to an element that does not exist.
    ///
    /// # Errors
    ///
    /// Errors are function-dependent and are returned as boxed [`std::error::Error`].
    fn on_before_element(
        &mut self,
        _context: &mut TransformerElementContext,
    ) -> Result<(), Box<dyn std::error::Error>> {
        Ok(())
    }

    /// A method called when an element is fully parsed, including its children.
    ///
    /// # Errors
    ///
    /// Errors are function-dependent and are returned as boxed [`std::error::Error`].
    fn on_after_element(
        &mut self,
        _context: &mut TransformerElementContext,
    ) -> Result<(), Box<dyn std::error::Error>> {
        Ok(())
    }

    /// A method called when all nodes of a [`Tree`] are parsed.
    ///
    /// # Errors
    ///
    /// Errors are function-dependent and are returned as boxed [`std::error::Error`].
    fn on_tree_parsed(
        &mut self,
        _context: &mut TransformerTreeContext,
    ) -> Result<(), Box<dyn std::error::Error>> {
        Ok(())
    }
}

/// A list of transformers.
///
/// It is a helper structure used to easily insert/get/remove structures implementing the
/// [`Transformer`] trait using the builder pattern or not.
pub struct Transformers<'a> {
    pub(crate) inner: Vec<Box<dyn Transformer + Send + Sync + 'a>>,
}

impl<'a> Transformers<'a> {
    /// Create a new, empty [`Transformers`].
    pub fn new() -> Self {
        Self { inner: vec![] }
    }

    /// Insert a structure implementing the [`Transformer`] trait using the builder pattern.
    #[must_use]
    pub fn with_transformer<T: Transformer + Send + Sync + 'a>(mut self, transformer: T) -> Self {
        self.inner.push(Box::new(transformer));
        self
    }

    /// Insert a structure implementing the [`Transformer`] trait.
    pub fn insert_transformer<T: Transformer + Send + Sync + 'a>(&mut self, transformer: T) {
        self.inner.push(Box::new(transformer));
    }

    /// Get a transformer from the order it was inserted in the list.
    ///
    /// 0 is the first transformer inserted, 1 the second, and so on
    pub fn get(&self, index: usize) -> Option<&(dyn Transformer + Send + Sync)> {
        self.inner.get(index).map(|t| &**t)
    }

    /// Remove a transformer from the order it was inserted in the list.
    ///
    /// 0 is the first transformer inserted, 1 the second, and so on
    ///
    /// Keep in mind that this method will change the order of elements, so if you remove the first
    /// element, the old second element will be the first.
    pub fn remove(&mut self, index: usize) -> Option<Box<dyn Transformer + 'a>> {
        if index < self.inner.len() {
            Some(self.inner.remove(index))
        } else {
            None
        }
    }
}

impl Default for Transformers<'_> {
    fn default() -> Self {
        Self::new()
    }
}

impl fmt::Debug for Transformers<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Transformers")
            .field("len", &self.inner.len())
            .finish()
    }
}