mini-docs 0.7.0

A minimal, secure build-time Markdown to HTML generator for the mini-* family.
Documentation
//! Adapter for integrating mini-cite as a MarkdownProcessor extension for mini-docs.

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

use serde_json::Value;

use crate::error::DocError;
use crate::extension::MarkdownProcessor;

/// Rewrites `[@key]` citations into numbered footnotes, resolved against a directory
/// of `.bib` files.
///
/// The bibliography is read once, when the processor is constructed — not per page —
/// so a `.bib` directory that is malformed or defines a key twice fails here, before
/// any page is built. Consequently a `.bib` file edited during a
/// [`crate::Watcher`] session is not picked up until the session restarts:
/// `Watcher` rebuilds on `.md` and template changes only, and knows nothing of this
/// directory.
#[cfg(feature = "cite")]
pub struct CiteProcessor {
    bibliography: mini_cite::Bibliography,
    bib_dir: PathBuf,
    style: mini_cite::CiteStyle,
}

#[cfg(feature = "cite")]
impl CiteProcessor {
    /// Loads every `.bib` file in `bib_dir` and returns a processor resolving
    /// citations against them.
    ///
    /// # Errors
    ///
    /// Returns [`mini_cite::CiteError`] — not a [`DocError`] — if `bib_dir` cannot be
    /// read, holds a malformed `.bib` file, or defines one citation key twice. This
    /// failure belongs to the caller constructing the processor, not to any page
    /// build; per-page citation failures surface later as `DocError::Extension`.
    pub fn new(bib_dir: impl AsRef<Path>) -> Result<Self, mini_cite::CiteError> {
        let bib_dir = bib_dir.as_ref().to_path_buf();

        Ok(Self {
            bibliography: mini_cite::Bibliography::load_dir(&bib_dir)?,
            bib_dir,
            style: mini_cite::CiteStyle::default(),
        })
    }

    /// Sets how the emitted footnote markup is classed and labelled.
    ///
    /// Defaults to [`mini_cite::CiteStyle::default()`], whose class names follow
    /// Pandoc's. The classes reach the page because this crate's sanitizer allowlists
    /// `class` as a generic attribute; on `mini-docs` before 0.5.0 they were stripped,
    /// leaving only the `fn-N`/`fnref-N` ids to style against.
    #[must_use]
    pub fn with_style(mut self, style: mini_cite::CiteStyle) -> Self {
        self.style = style;
        self
    }

    /// Returns the directory this processor's bibliography was loaded from.
    pub fn bib_dir(&self) -> &Path {
        &self.bib_dir
    }

    /// Returns the number of citation keys available to cite.
    pub fn len(&self) -> usize {
        self.bibliography.len()
    }

    /// Returns `true` when the bibliography defines no citation keys.
    pub fn is_empty(&self) -> bool {
        self.bibliography.is_empty()
    }
}

#[cfg(feature = "cite")]
impl MarkdownProcessor for CiteProcessor {
    fn process(&self, body: &str, _frontmatter: &Value) -> Result<String, DocError> {
        mini_cite::process(body, &self.bibliography, &self.style)
            .map_err(|e| DocError::Extension(format!("cite: {e}")))
    }

    fn name(&self) -> &str {
        "cite"
    }
}