Skip to content

Commit ee0cc82

Browse files
committed
Load templates from directory
1 parent 3538b57 commit ee0cc82

File tree

7 files changed

+97
-131
lines changed

7 files changed

+97
-131
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mdbook-tera-backend/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
[package]
22
name = "mdbook-tera-backend"
33
version = "0.0.1"
4-
authors = ["Martin Geisler <[email protected]>"]
4+
authors = ["Martin Geisler <[email protected]>", "Alexandre Senges <[email protected]>"]
55
categories = ["template-engine"]
66
edition = "2021"
77
keywords = ["mdbook", "tera", "renderer", "template"]
88
license = "Apache-2.0"
99
repository = "https://github.com/google/mdbook-i18n-helpers"
10-
description = "Plugins for a mdbook translation workflow based on Gettext."
10+
description = "Plugin to extend mdbook with Tera templates and custom HTML components."
1111

1212
[dependencies]
13+
anyhow = "1.0.75"
1314
chrono = { version = "0.4.31", default-features = false, features = ["alloc"] }
1415
lol_html = "1.2.0"
1516
mdbook = { version = "0.4.25", default-features = false }
1617
serde = "1.0"
1718
serde_json = "1.0.91"
1819
tera = "1.19.1"
19-
thiserror = "1.0.48"
2020

2121
[dev-dependencies]
2222
tempfile = "3.5.0"

mdbook-tera-backend/src/main.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ fn main() {
1515
.expect("Failed to get Gaia config")
1616
.unwrap();
1717

18-
let components = config
19-
.create_components(&ctx.root)
18+
let (tera_template, components) = config
19+
.create_template_and_components(&ctx.root)
2020
.expect("Failed to create components");
2121

22-
let mut renderer = Renderer::new(ctx).expect("Failed to create renderer");
22+
let mut renderer = Renderer::new(ctx, tera_template).expect("Failed to create renderer");
2323

2424
for component in components {
2525
renderer.add_component(component);
Lines changed: 66 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,10 @@
1+
use anyhow::Result;
12
use serde::Deserialize;
23
use std::cell::RefCell;
3-
use std::collections::{BTreeMap, HashMap};
4+
use std::collections::BTreeMap;
45
use std::path::{Path, PathBuf};
5-
66
use tera::Tera;
77

8-
use crate::{RendererError, Result};
9-
10-
use super::RenderingContext;
11-
12-
fn make_strip_prefix_function() -> impl tera::Function {
13-
move |args: &HashMap<String, serde_json::value::Value>| -> tera::Result<tera::Value> {
14-
let string = args
15-
.get("s")
16-
.ok_or_else(|| tera::Error::from(format!("No s argument provided")))?
17-
.as_str()
18-
.ok_or_else(|| tera::Error::from(format!("S has invalid type, expected string")))?;
19-
let prefix = args
20-
.get("prefix")
21-
.ok_or_else(|| tera::Error::from(format!("No prefix argument provided")))?
22-
.as_str()
23-
.ok_or_else(|| {
24-
tera::Error::from(format!("Prefix has invalid type, expected string"))
25-
})?;
26-
string
27-
.strip_prefix(prefix)
28-
.map(|s| tera::Value::String(s.to_owned()))
29-
.ok_or_else(|| tera::Error::from(format!("Could not strip prefix")))
30-
}
31-
}
32-
338
pub struct CustomComponent {
349
template: Tera,
3510
name: String,
@@ -38,14 +13,7 @@ pub struct CustomComponent {
3813
}
3914

4015
impl CustomComponent {
41-
pub fn new(name: &str, template_str: &str, dependencies: &[&Self]) -> Result<CustomComponent> {
42-
let mut template = Tera::default();
43-
for dep in dependencies {
44-
template.extend(&dep.template)?;
45-
}
46-
template.add_raw_template(name, template_str)?;
47-
template.register_function("strip_prefix", make_strip_prefix_function());
48-
16+
pub fn new(name: &str, template: Tera) -> Result<CustomComponent> {
4917
Ok(CustomComponent {
5018
name: String::from(name),
5119
counter: RefCell::new(0),
@@ -57,33 +25,17 @@ impl CustomComponent {
5725
self.template.register_function(name, function);
5826
}
5927

60-
fn create_context(
61-
&self,
62-
rendering_context: &RenderingContext,
63-
attributes: BTreeMap<String, String>,
64-
) -> tera::Context {
65-
let counter = self.counter.replace_with(|&mut counter| counter + 1);
66-
let mut context = tera::Context::new();
67-
context.insert("counter", &counter);
68-
context.insert("language", &rendering_context.language);
69-
context.insert("path", &rendering_context.path);
70-
context.insert("ctx", &rendering_context.serialized_ctx);
71-
context.insert(
72-
"book_dir",
73-
&rendering_context.ctx.destination.parent().unwrap(),
74-
);
75-
context.insert("attributes", &attributes);
76-
77-
context
78-
}
79-
8028
pub fn render(
8129
&self,
82-
rendering_context: &RenderingContext,
30+
tera_context: &tera::Context,
8331
attributes: BTreeMap<String, String>,
8432
) -> Result<String> {
85-
let context = self.create_context(rendering_context, attributes);
86-
let output = self.template.render(&self.name, &context)?;
33+
let counter = self.counter.replace_with(|&mut counter| counter + 1);
34+
let mut tera_context = tera_context.clone();
35+
tera_context.insert("count", &counter);
36+
tera_context.insert("attributes", &attributes);
37+
38+
let output = self.template.render(&self.name, &tera_context)?;
8739
Ok(output)
8840
}
8941

@@ -93,41 +45,69 @@ impl CustomComponent {
9345
}
9446

9547
#[derive(Deserialize)]
96-
pub struct TeraComponentConfig {
97-
pub name: String,
98-
pub path: PathBuf,
99-
100-
#[serde(default)]
101-
pub dependencies: Vec<String>,
48+
pub enum Component {
49+
Named { name: String, path: PathBuf },
50+
Anonymous(PathBuf),
10251
}
10352

53+
/// Configuration in `book.toml` `[output.tera-renderer]`.
10454
#[derive(Deserialize)]
10555
pub struct TeraRendererConfig {
106-
pub components: Vec<TeraComponentConfig>,
56+
/// Relative path to the templates directory.
57+
pub templates_dir: PathBuf,
58+
/// Custom HTML components to register.
59+
#[serde(default)]
60+
pub html_components: Vec<Component>,
10761
}
10862

10963
impl TeraRendererConfig {
110-
pub fn create_components(&self, current_dir: &Path) -> Result<Vec<CustomComponent>> {
111-
let mut name_to_component = HashMap::new();
112-
for component in &self.components {
113-
let component_path = current_dir.join(&component.path);
114-
let template_str = std::fs::read_to_string(&component_path)?;
115-
let dependencies = component
116-
.dependencies
117-
.iter()
118-
.map(|name| {
119-
name_to_component.get(name).ok_or_else(|| {
120-
RendererError::DependencyNotFound(format!(
121-
"Could not find depdendency {}",
122-
name
123-
))
124-
})
125-
})
126-
.collect::<Result<Vec<_>>>()?;
127-
let new_component =
128-
CustomComponent::new(&component.name, &template_str, &dependencies)?;
129-
name_to_component.insert(component.name.clone(), new_component);
64+
fn add_templates_recursively(tera_template: &mut Tera, directory: &Path) -> Result<()> {
65+
for entry in std::fs::read_dir(directory)? {
66+
let entry = entry?;
67+
let path = entry.path();
68+
if path.is_dir() {
69+
Self::add_templates_recursively(tera_template, &path)?;
70+
} else {
71+
tera_template.add_template_file(&path, path.file_name().unwrap().to_str())?;
72+
}
13073
}
131-
Ok(name_to_component.into_values().collect())
74+
Ok(())
75+
}
76+
77+
fn create_custom_components(&self, tera_template: &Tera) -> Result<Vec<CustomComponent>> {
78+
self.html_components
79+
.iter()
80+
.map(|component| {
81+
Ok(match component {
82+
Component::Named { name, path } => {
83+
let mut template = tera_template.clone();
84+
template.add_template_file(path, Some(name))?;
85+
CustomComponent::new(name, template)?
86+
}
87+
Component::Anonymous(path) => {
88+
let name = path
89+
.file_name()
90+
.unwrap_or_default()
91+
.to_str()
92+
.unwrap_or_default();
93+
CustomComponent::new(name, tera_template.clone())?
94+
}
95+
})
96+
})
97+
.collect()
98+
}
99+
100+
pub fn create_template_and_components(
101+
&self,
102+
current_dir: &Path,
103+
) -> Result<(Tera, Vec<CustomComponent>)> {
104+
let mut tera_template = Tera::default();
105+
Self::add_templates_recursively(
106+
&mut tera_template,
107+
&current_dir.join(&self.templates_dir),
108+
)?;
109+
let components = self.create_custom_components(&tera_template)?;
110+
111+
Ok((tera_template, components))
132112
}
133113
}

mdbook-tera-backend/src/tera_renderer/error.rs

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
mod custom_component;
2-
mod error;
32
mod renderer;
43

54
pub(crate) use custom_component::*;
6-
pub(crate) use error::*;
75
pub(crate) use renderer::*;

mdbook-tera-backend/src/tera_renderer/renderer.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use super::error::RendererError;
21
use super::CustomComponent;
3-
use crate::tera_renderer::error::Result;
2+
use anyhow::{anyhow, Result};
43
use lol_html::html_content::ContentType;
54
use lol_html::{element, RewriteStrSettings};
65
use mdbook::renderer::RenderContext;
@@ -10,6 +9,7 @@ use std::fs;
109
use std::io::{Read, Write};
1110
use std::path::{Path, PathBuf};
1211
use std::sync::Arc;
12+
use tera::Tera;
1313

1414
pub struct RenderingContext<'a> {
1515
pub path: PathBuf,
@@ -38,14 +38,16 @@ pub(crate) struct Renderer {
3838
ctx: Arc<RenderContext>,
3939
serialized_ctx: serde_json::Value,
4040
components: Vec<CustomComponent>,
41+
tera_template: Tera,
4142
}
4243

4344
impl Renderer {
44-
pub(crate) fn new(ctx: RenderContext) -> Result<Renderer> {
45+
pub(crate) fn new(ctx: RenderContext, tera_template: Tera) -> Result<Renderer> {
4546
Ok(Renderer {
4647
serialized_ctx: serde_json::to_value(&ctx)?,
4748
ctx: Arc::new(ctx),
4849
components: Vec::new(),
50+
tera_template,
4951
})
5052
}
5153

@@ -79,17 +81,14 @@ impl Renderer {
7981
.destination
8082
.parent()
8183
.ok_or_else(|| {
82-
RendererError::InvalidPath(format!(
84+
anyhow!(
8385
"Destination directory {:?} has no parent",
8486
self.ctx.destination
85-
))
87+
)
8688
})?
8789
.to_owned();
8890
if !dest_dir.is_dir() {
89-
return Err(RendererError::InvalidPath(format!(
90-
"{:?} is not a directory",
91-
dest_dir
92-
)));
91+
return Err(anyhow!("{:?} is not a directory", dest_dir));
9392
}
9493
self.render_book_directory(&dest_dir)
9594
}
@@ -123,13 +122,27 @@ impl Renderer {
123122
Ok(())
124123
}
125124

125+
fn create_context(&self, rendering_context: &RenderingContext) -> tera::Context {
126+
let mut context = tera::Context::new();
127+
context.insert("path", &rendering_context.path);
128+
context.insert("ctx", &rendering_context.serialized_ctx);
129+
context.insert(
130+
"book_dir",
131+
&rendering_context.ctx.destination.parent().unwrap(),
132+
);
133+
134+
context
135+
}
136+
126137
fn render_components(&mut self, file_content: &str, path: &Path) -> Result<String> {
127138
let rendering_context = RenderingContext::new(
128139
path.to_owned(),
129140
self.ctx.config.book.language.clone(),
130141
&self.serialized_ctx,
131142
&self.ctx,
132143
)?;
144+
let tera_context = self.create_context(&rendering_context);
145+
let rendered_file = self.tera_template.render_str(file_content, &tera_context)?;
133146
let custom_components_handlers = self
134147
.components
135148
.iter()
@@ -140,14 +153,14 @@ impl Renderer {
140153
.iter()
141154
.map(|attribute| (attribute.name(), attribute.value()))
142155
.collect();
143-
let rendered = component.render(&rendering_context, attributes)?;
156+
let rendered = component.render(&tera_context, attributes)?;
144157
el.replace(&rendered, ContentType::Html);
145158
Ok(())
146159
})
147160
})
148161
.collect();
149162
let output = lol_html::rewrite_str(
150-
file_content,
163+
&rendered_file,
151164
RewriteStrSettings {
152165
element_content_handlers: custom_components_handlers,
153166
..RewriteStrSettings::default()

0 commit comments

Comments
 (0)