mdxbook 0.4.25

Fork of mdBook, with more customizations and flexibility for programmers
Documentation
//! Custom Postprocessing tools.
//!
//! Those processors must be provided when creating a `MDBook` instance, or provided later through mutation,
//! also, if you are using renderers directly, like the [`HtmlHandlebars`][`crate::renderer::html_handlebars::HtmlHandlebars`],
//! you could provide custom processors directly to the construction of its instance.
//!
//! Post Processors are currently designed for the use of [mdxbook][crate] as a library,
//! even though a lot of the mechanism is copied from [`Preprocessors`][`crate::preprocess`],
//! allowing external commands to be provided as [`Postprocessor`][`Postprocessor`] like
//! it is possible with [`Preprocessors`][`crate::preprocess::Preprocessor`],
//! its not the main focus of this API, which is providing a way to developers customize
//! the rendering process.
//!
//! If you want your custom [`Postprocessor`] to be accessible by custom [`Renderers`][`crate::renderer`],
//! you must implement [`Postprocessor::to_cmd_postprocessor`] and return a valid command to be executed
//! by them. However, it's up to [`Renderers`][`crate::renderer`] if they will ever execute it, and how
//! they will do.
pub use self::cmd::CmdPostprocessor;

use crate::book::{Book, Chapter};
use crate::renderer::RenderContext;
use crate::Config;
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::PathBuf;

mod cmd;

/// Extra information for a `Postprocessor` to give them more context when
/// processing a book.
///
/// **Note**
/// This is likely the same as `PreprocessorContext`, with the main difference of having the [`RenderContext`]
/// available, since it's being called from a `Renderer`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PostprocessorContext {
    /// The location of the book directory on disk.
    pub root: PathBuf,
    /// The book configuration (`book.toml`).
    pub config: Config,
    /// The `Renderer` this preprocessor is being used with.
    pub renderer: String,
    /// The calling `mdbook` version.
    pub mdbook_version: String,
    /// The render context passed to the Renderer
    pub render_context: RenderContext,
    #[serde(skip)]
    pub(crate) chapter_titles: RefCell<HashMap<PathBuf, String>>,
    #[serde(skip)]
    __non_exhaustive: (),
}

/// The Postprocessor context.
impl PostprocessorContext {
    /// Creates a new Postprocessor context.
    pub fn new(
        root: PathBuf,
        config: Config,
        renderer: String,
        render_context: RenderContext,
    ) -> Self {
        Self {
            root,
            config,
            renderer,
            render_context,
            mdbook_version: crate::MDBOOK_VERSION.to_string(),
            chapter_titles: RefCell::new(HashMap::new()),
            __non_exhaustive: (),
        }
    }
}

/// Struct representing a rendered element.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Rendered {
    /// Rendered book
    Book(RenderedBook),
    /// Rendered document of a book
    Document(RenderedDocumentOfBook),
}

/// Struct representing a rendered document of the book
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderedDocumentOfBook {
    /// The original book representation.
    pub book: Book,
    /// The rendered chapter.
    pub page: RenderedDocument,
}

/// Struct representing a rendered document
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderedDocument {
    /// The original chapter.
    pub document: Document,
    /// The renderer of this chapter.
    pub renderer: String,
    /// The rendered context.
    pub rendered_content: String,
}

/// Struct representing a rendered document of the book
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Document {
    /// A book chapter
    Chapter(Chapter),
    /// The book index page
    Index,
    /// The book separator
    ///
    /// **Not emitted by `HtmlHandlebars`**
    Separator,
    /// The book document title.
    ///
    /// **Not emitted by `HtmlHandlebars`**
    DocTitle(String),
    /// A page of a not-known type.
    ///
    /// Like 404 pages and additional pages.
    Page(String),
}

/// Struct representing the rendered version of the book
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RenderedBook {
    /// The original book representation.
    pub book: Book,
    /// The rendered sections.
    pub rendered_documents: Vec<RenderedDocument>,
}

/// Custom Postprocessor to handle modifications to documents after they are rendered.
pub trait Postprocessor
where
    Self: Sync + Send,
{
    /// The name of this Postprocessor
    fn name(&self) -> &str;

    /// Process a single document. This is called for every rendered document,
    /// including 404 pages, book chapters and index page.
    fn postprocess_document(
        &self,
        context: &PostprocessorContext,
        rendered_document: RenderedDocumentOfBook,
    ) -> std::result::Result<RenderedDocumentOfBook, anyhow::Error>;

    /// Process the entire book. This is called after all documents processing finishes.
    fn postprocess_book(
        &self,
        context: &PostprocessorContext,
        rendered_book: RenderedBook,
    ) -> std::result::Result<RenderedBook, anyhow::Error>;

    /// Whether this processor supports a specific renderer.
    fn supports_renderer(&self, renderer: &str) -> bool;

    /// Clones this `Postprocessor` and returns a heap allocated [`Postprocessor`] Trait object.
    fn clone_dyn(&self) -> Box<dyn Postprocessor>;

    /// Converts this [`PostProcessor`]  into a [`CmdPostprocessor`].
    ///
    /// This is required for programmatically included [`Postprocessors`][`Postprocessor`] be accessible
    /// from custom `Renderers`.
    ///
    /// The way **mdBook** works with custom `Renderers`, `Preprocessors` (and `mdxBook`'s `Postprocessors`)
    /// is by launching a separated process which runs those binaries. They don't have access to other
    /// `Renderers` and `Processors` unless they depend on **mdBook**, but for cases where the **mdBook**
    /// is included as dependency of another project, which runs the **mdBook**, they would not have
    /// access to `processors` configured by those projects.
    ///
    /// The actual solution is to allow conversion of any [`Postprocessor`] to a [`CmdPostprocessor`],
    /// then custom `Renderers` are able to receive this information serialized, and run the binaries
    /// themselves.
    ///
    /// **mdBook** cannot run this automatically, like it does with `Preprocessors`, because `Preprocessors`
    /// are executed against the `markdown`, before the final, preprocessed markdown, is sent to
    /// `Renderer`, which does not have any clue about the `Preprocessors` that took in place.  
    ///
    /// Also, **mdBook** does not handle file saving or receive the processed contents in any way,
    /// which prevents it from manipulating the rendered files before saving them to disk. It is up to
    /// `Renderers` to save the file to disk in proper location.
    ///
    /// **mdxBook** does not change this behavior because it would involve changing the structure of
    /// `Renderer`, and **mdxBook** still backward compatible with custom `Renderers` and `Preprocessors`
    /// written for **mdBook**.
    ///
    /// **Note**
    /// It's important to know that this function does not need to return an actual [`CmdPostprocessor`]
    /// when running the default `HtmlHandlebars` (and `Postprocessors` are not implemented for `MarkdownRenderer` yet).
    fn to_cmd_postprocessor(&self) -> Option<CmdPostprocessor>;
}

impl Clone for Box<dyn Postprocessor> {
    fn clone(&self) -> Self {
        self.clone_dyn()
    }
}