-
Notifications
You must be signed in to change notification settings - Fork 36
Create mdbook-tera-backend renderer #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
e3ab6b8
Create renderer
sakex ef30e5f
Load templates from directory
sakex 04629de
Fix
sakex 5061842
Fix
sakex f913253
Fix
sakex df15953
Remove custom components
sakex 0efb073
Add comments
sakex 4916c6a
Remove counter + only render html
sakex e89c2b5
README
sakex 9de875c
Update mdbook-tera-backend/src/tera_renderer/renderer.rs
sakex 24a27d6
fixes
sakex 2c57c1c
Add test
sakex 84181d1
Fix README.md formatting
sakex 5d86502
Make path relative
sakex ddbacea
Update mdbook-tera-backend/README.md
sakex 57f73e4
Fix README.md
sakex 23472a5
Fix lock
sakex File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| [workspace] | ||
| members = ["i18n-helpers"] | ||
| members = ["i18n-helpers", "mdbook-tera-backend"] | ||
| default-members = ["i18n-helpers"] | ||
| resolver = "2" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| [package] | ||
| name = "mdbook-tera-backend" | ||
| version = "0.0.1" | ||
| authors = ["Martin Geisler <[email protected]>", "Alexandre Senges <[email protected]>"] | ||
| categories = ["template-engine"] | ||
| edition = "2021" | ||
| keywords = ["mdbook", "tera", "renderer", "template"] | ||
| license = "Apache-2.0" | ||
| repository = "https://github.com/google/mdbook-i18n-helpers" | ||
| description = "Plugin to extend mdbook with Tera templates and custom HTML components." | ||
|
|
||
| [dependencies] | ||
| anyhow = "1.0.75" | ||
| mdbook = { version = "0.4.25", default-features = false } | ||
| serde = "1.0" | ||
| serde_json = "1.0.91" | ||
| tera = "1.19.1" | ||
|
|
||
| [dev-dependencies] | ||
| tempdir = "0.3.7" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Tera backend extension for `mdbook` | ||
|
|
||
| [](https://crates.io/crates/mdbook-tera-backend) | ||
| [](https://github.com/google/mdbook-i18n-helpers/actions/workflows/test.yml?query=branch%3Amain) | ||
| [](https://github.com/google/mdbook-i18n-helpers/graphs/contributors) | ||
| [](https://github.com/google/mdbook-i18n-helpers/stargazers) | ||
|
|
||
| This `mdbook` backend makes it possible to use | ||
| [tera](https://github.com/Keats/tera) templates and expand the capabilities of | ||
| your books. It works on top of the default HTML backend. | ||
|
|
||
| ## Installation | ||
|
|
||
| Run | ||
|
|
||
| ```shell | ||
| $ cargo install mdbook-tera-backend | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Configuring the backend | ||
|
|
||
| To enable the backend, simply add `[output.tera-backend]` to your `book.toml`, | ||
| and configure the place where youre templates will live. For instance | ||
| `theme/templates`: | ||
|
|
||
| ```toml | ||
| [output.html] # You must still enable the html backend. | ||
| [output.tera-backend] | ||
| template_dir = "theme/templates" | ||
| ``` | ||
|
|
||
| ### Creating templates | ||
|
|
||
| Create your template files in the same directory as your book. | ||
|
|
||
| ```html | ||
| <!-- ./theme/templates/hello_world.html --> | ||
| <div> | ||
| Hello world! | ||
| </div> | ||
| ``` | ||
|
|
||
| ### Using templates in `index.hbs` | ||
|
|
||
| Since the HTML renderer will first render Handlebars templates, we need to tell | ||
| it to ignore Tera templates using `{{{{raw}}}}` blocks: | ||
|
|
||
| ```html | ||
| {{{{raw}}}} | ||
| {% set current_language = ctx.config.book.language %} | ||
| <p>Current language: {{ current_language }}</p> | ||
| {% include "hello_world.html" %} | ||
| {{{{/raw}}}} | ||
| ``` | ||
|
|
||
| Includes names are based on the file name and not the whole file path. | ||
|
|
||
| ### Tera documentation | ||
|
|
||
| Find out all you can do with Tera templates | ||
| [here](https://keats.github.io/tera/docs/). | ||
|
|
||
| ## Changelog | ||
|
|
||
| Please see [CHANGELOG](../CHANGELOG.md) for details on the changes in each | ||
| release. | ||
|
|
||
| ## Contact | ||
|
|
||
| For questions or comments, please contact | ||
| [Martin Geisler](mailto:[email protected]) or | ||
| [Alexandre Senges](mailto:[email protected]) or start a | ||
| [discussion](https://github.com/google/mdbook-i18n-helpers/discussions). We | ||
| would love to hear from you. | ||
|
|
||
| --- | ||
|
|
||
| This is not an officially supported Google product. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| mod tera_renderer; | ||
|
|
||
| use anyhow::{anyhow, Context}; | ||
| use mdbook::renderer::RenderContext; | ||
| use std::io; | ||
|
|
||
| use crate::tera_renderer::custom_component::TeraRendererConfig; | ||
| use crate::tera_renderer::renderer::Renderer; | ||
|
|
||
| /// Re-renders HTML files outputed by the HTML backend with Tera templates. | ||
| /// Please make sure the HTML backend is enabled. | ||
| fn main() -> anyhow::Result<()> { | ||
| let mut stdin = io::stdin(); | ||
| let ctx = RenderContext::from_json(&mut stdin).unwrap(); | ||
| if ctx.config.get_renderer("html").is_none() { | ||
| return Err(anyhow!( | ||
| "Could not find the HTML backend. Please make sure the HTML backend is enabled." | ||
| )); | ||
| } | ||
| let config: TeraRendererConfig = ctx | ||
| .config | ||
| .get_deserialized_opt("output.tera-backend") | ||
| .context("Failed to get tera-backend config")? | ||
| .context("No tera-backend config found")?; | ||
|
|
||
| let tera_template = config | ||
| .create_template(&ctx.root) | ||
| .context("Failed to create components")?; | ||
|
|
||
| let mut renderer = Renderer::new(ctx, tera_template); | ||
|
|
||
| renderer.render_book().context("Failed to render book")?; | ||
|
|
||
| Ok(()) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| pub mod custom_component; | ||
| pub mod renderer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| use anyhow::Result; | ||
| use serde::Deserialize; | ||
| use std::path::{Path, PathBuf}; | ||
| use tera::Tera; | ||
|
|
||
| /// Configuration in `book.toml` `[output.tera-renderer]`. | ||
| #[derive(Deserialize)] | ||
| pub struct TeraRendererConfig { | ||
| /// Relative path to the templates directory from the `book.toml` directory. | ||
| pub template_dir: Option<PathBuf>, | ||
| } | ||
|
|
||
| /// Recursively add all templates in the `template_dir` to the `tera_template`. | ||
| fn add_templates_recursively(tera_template: &mut Tera, directory: &Path) -> Result<()> { | ||
| for entry in std::fs::read_dir(directory)? { | ||
| let entry = entry?; | ||
| let path = entry.path(); | ||
| if path.is_dir() { | ||
| add_templates_recursively(tera_template, &path)?; | ||
| } else { | ||
| tera_template.add_template_file(&path, path.file_name().unwrap().to_str())?; | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| impl TeraRendererConfig { | ||
| /// Create the `tera_template` and add all templates in the `template_dir` to it. | ||
| pub fn create_template(&self, current_dir: &Path) -> Result<Tera> { | ||
| let mut tera_template = Tera::default(); | ||
| if let Some(template_dir) = &self.template_dir { | ||
| add_templates_recursively(&mut tera_template, ¤t_dir.join(template_dir))?; | ||
| } | ||
|
|
||
| Ok(tera_template) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| use anyhow::{anyhow, Result}; | ||
| use mdbook::renderer::RenderContext; | ||
| use std::path::Path; | ||
| use tera::Tera; | ||
|
|
||
| /// Renderer for the tera backend. | ||
| /// | ||
| /// This will read all the files in the `RenderContext` and render them using the `Tera` template. | ||
| /// ``` | ||
| pub struct Renderer { | ||
| ctx: RenderContext, | ||
| tera_template: Tera, | ||
| } | ||
|
|
||
| impl Renderer { | ||
| /// Create a new `Renderer` from the `RenderContext` and `Tera` template. | ||
| pub fn new(ctx: RenderContext, tera_template: Tera) -> Self { | ||
| Renderer { ctx, tera_template } | ||
| } | ||
|
|
||
| /// Render the book. This goes through the output of the HTML renderer | ||
| /// by considering all the output HTML files as input to the Tera template. | ||
| /// It overwrites the preexisting files with their Tera-rendered version. | ||
| pub fn render_book(&mut self) -> Result<()> { | ||
| let dest_dir = self.ctx.destination.parent().unwrap().join("html"); | ||
| if !dest_dir.is_dir() { | ||
| return Err(anyhow!( | ||
| "{dest_dir:?} is not a directory. Please make sure the HTML renderer is enabled." | ||
| )); | ||
| } | ||
| self.render_book_directory(&dest_dir) | ||
| } | ||
|
|
||
| /// Render the book directory located at `path` recursively. | ||
| fn render_book_directory(&mut self, path: &Path) -> Result<()> { | ||
| for entry in path.read_dir()? { | ||
| let entry = entry?; | ||
| let path = entry.path(); | ||
| if path.is_dir() { | ||
| self.render_book_directory(&path)?; | ||
| } else { | ||
| self.process_file(&path)?; | ||
| } | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Reads the file at `path` and renders it. | ||
| fn process_file(&mut self, path: &Path) -> Result<()> { | ||
| if path.extension().unwrap_or_default() != "html" { | ||
| return Ok(()); | ||
| } | ||
| let file_content = std::fs::read_to_string(path)?; | ||
| let output = self.render_file_content(&file_content, path)?; | ||
| Ok(std::fs::write(path, output)?) | ||
| } | ||
|
|
||
| /// Creates the rendering context to be passed to the templates. | ||
| /// | ||
| /// # Arguments | ||
| /// | ||
| /// `path`: The path to the file that will be added as extra context to the renderer. | ||
| fn create_context(&mut self, path: &Path) -> Result<tera::Context> { | ||
| let mut context = tera::Context::new(); | ||
| let book_dir = self.ctx.destination.parent().unwrap(); | ||
| let relative_path = path.strip_prefix(book_dir).unwrap(); | ||
| context.insert("path", &relative_path); | ||
| context.insert("book_dir", &self.ctx.destination.parent().unwrap()); | ||
|
|
||
| Ok(context) | ||
| } | ||
|
|
||
| /// Rendering logic for an individual file. | ||
| fn render_file_content(&mut self, file_content: &str, path: &Path) -> Result<String> { | ||
| let tera_context = self.create_context(path)?; | ||
|
|
||
| let rendered_file = self | ||
| .tera_template | ||
| .render_str(file_content, &tera_context) | ||
| .map_err(|e| anyhow!("Error rendering file {path:?}: {e:?}"))?; | ||
| Ok(rendered_file) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod test { | ||
| use tempdir::TempDir; | ||
|
|
||
| use super::*; | ||
| use crate::tera_renderer::custom_component::TeraRendererConfig; | ||
| use anyhow::Result; | ||
|
|
||
| const RENDER_CONTEXT_STR: &str = r#" | ||
| { | ||
| "version":"0.4.32", | ||
| "root":"", | ||
| "book":{ | ||
| "sections": [], | ||
| "__non_exhaustive": null | ||
| }, | ||
| "destination": "", | ||
| "config":{ | ||
| "book":{ | ||
| "authors":[ | ||
| "Martin Geisler" | ||
| ], | ||
| "language":"en", | ||
| "multilingual":false, | ||
| "src":"src", | ||
| "title":"Comprehensive Rust 🦀" | ||
| }, | ||
| "build":{ | ||
| "build-dir":"book", | ||
| "use-default-preprocessors":true | ||
| }, | ||
| "output":{ | ||
| "tera-backend": { | ||
| "template_dir": "templates" | ||
| }, | ||
| "renderers":[ | ||
| "html", | ||
| "tera-backend" | ||
| ] | ||
| } | ||
| } | ||
| }"#; | ||
|
|
||
| const HTML_FILE: &str = r#" | ||
| <!DOCTYPE html> | ||
| {% include "test_template.html" %} | ||
| PATH: {{ path }} | ||
| </html> | ||
| "#; | ||
|
|
||
| const TEMPLATE_FILE: &str = "RENDERED"; | ||
|
|
||
| const RENDERED_HTML_FILE: &str = r#" | ||
| <!DOCTYPE html> | ||
| RENDERED | ||
| PATH: html/test.html | ||
| </html> | ||
| "#; | ||
|
|
||
| #[test] | ||
| fn test_renderer() -> Result<()> { | ||
| let mut ctx = RenderContext::from_json(RENDER_CONTEXT_STR.as_bytes()).unwrap(); | ||
|
|
||
| let tmp_dir = TempDir::new("output")?; | ||
| let html_path = tmp_dir.path().join("html"); | ||
| let templates_path = tmp_dir.path().join("templates"); | ||
|
|
||
| std::fs::create_dir(&html_path)?; | ||
| std::fs::create_dir(&templates_path)?; | ||
|
|
||
| let html_file_path = html_path.join("test.html"); | ||
| std::fs::write(&html_file_path, HTML_FILE)?; | ||
| std::fs::write(templates_path.join("test_template.html"), TEMPLATE_FILE)?; | ||
|
|
||
| ctx.destination = tmp_dir.path().join("tera-renderer"); | ||
| ctx.root = tmp_dir.path().to_owned(); | ||
|
|
||
| let config: TeraRendererConfig = ctx | ||
| .config | ||
| .get_deserialized_opt("output.tera-backend")? | ||
| .ok_or_else(|| anyhow!("No tera backend configuration."))?; | ||
|
|
||
| let tera_template = config.create_template(&ctx.root)?; | ||
| let mut renderer = Renderer::new(ctx, tera_template); | ||
| renderer.render_book().expect("Failed to render book"); | ||
|
|
||
| assert_eq!(std::fs::read_to_string(html_file_path)?, RENDERED_HTML_FILE); | ||
| Ok(()) | ||
| } | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be computed from
ctxso it looks unnecessary?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's annoying to compute from
ctxin tera. I think it's a good additionThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be computed with a small pipeline:
{{ ctx.destination | split(pat="/") | slice(end=-1) | join(sep="/") }}