#![deny(
warnings,
rustdoc::all,
clippy::all,
clippy::cargo,
clippy::nursery,
clippy::pedantic
)]
#![allow(clippy::module_name_repetitions)]
#![cfg_attr(doc, deny(rustdoc::all))]
#![cfg_attr(doc, allow(rustdoc::missing_doc_code_examples))]
mod context;
use std::path::Path;
use anyhow::anyhow;
use globwalk::GlobWalkerBuilder;
use mdbook::book::{Book, BookItem};
use mdbook::errors::Error;
use mdbook::preprocess::{Preprocessor, PreprocessorContext};
use tera::{Context, Tera};
pub use self::context::{ContextSource, StaticContextSource};
#[derive(Clone)]
pub struct TeraPreprocessor<C = StaticContextSource> {
tera: Tera,
context: C,
}
impl<C> TeraPreprocessor<C> {
pub fn new(context: C) -> Self {
Self {
context,
tera: Tera::default(),
}
}
#[allow(clippy::missing_panics_doc)]
pub fn include_templates<P>(&mut self, root: P, glob_str: &str) -> Result<(), Error>
where
P: AsRef<Path>,
{
let root = &root.as_ref().canonicalize()?;
let paths = GlobWalkerBuilder::from_patterns(root, &[glob_str])
.build()?
.filter_map(Result::ok)
.map(|p| {
let path = p.into_path();
let name = path
.strip_prefix(root)
.unwrap()
.to_string_lossy()
.into_owned();
(path, Some(name))
});
self.tera.add_template_files(paths)?;
Ok(())
}
pub fn tera_mut(&mut self) -> &mut Tera {
&mut self.tera
}
}
impl<C: Default> Default for TeraPreprocessor<C> {
fn default() -> Self {
Self::new(Default::default())
}
}
impl<C> Preprocessor for TeraPreprocessor<C>
where
C: ContextSource,
{
fn name(&self) -> &str {
"tera"
}
fn run(&self, book_ctx: &PreprocessorContext, mut book: Book) -> Result<Book, Error> {
let mut tera = Tera::default();
tera.extend(&self.tera).unwrap();
let mut ctx = Context::new();
ctx.insert("ctx", &book_ctx);
ctx.extend(self.context.context());
render_book_items(&mut book, &mut tera, &ctx)?;
Ok(book)
}
}
fn render_book_items(book: &mut Book, tera: &mut Tera, context: &Context) -> Result<(), Error> {
let mut templates = Vec::new();
collect_item_chapters(&mut templates, book.sections.as_slice())?;
tera.add_raw_templates(templates)?;
render_item_chapters(tera, context, book.sections.as_mut_slice())
}
fn collect_item_chapters<'a>(
templates: &mut Vec<(&'a str, &'a str)>,
items: &'a [BookItem],
) -> Result<(), Error> {
for item in items {
match item {
BookItem::Chapter(chapter) => {
if let Some(ref path) = chapter.path {
let path = path
.to_str()
.ok_or_else(|| anyhow!("invalid chapter path"))?;
templates.push((path, chapter.content.as_str()));
collect_item_chapters(templates, chapter.sub_items.as_slice())?;
}
}
BookItem::PartTitle(_) | BookItem::Separator => (),
}
}
Ok(())
}
fn render_item_chapters(
tera: &mut Tera,
context: &Context,
items: &mut [BookItem],
) -> Result<(), Error> {
for item in items {
match item {
BookItem::Chapter(chapter) => {
if let Some(ref path) = chapter.path {
let path = path
.to_str()
.ok_or_else(|| anyhow!("invalid chapter path"))?;
chapter.content = tera.render(path, context)?;
render_item_chapters(tera, context, chapter.sub_items.as_mut_slice())?;
}
}
BookItem::PartTitle(_) | BookItem::Separator => (),
}
}
Ok(())
}