Rewrite theme tera paths and merge theme.toml data

This commit is contained in:
Vincent Prouillet 2017-08-25 08:38:03 +09:00
parent 521b9755f8
commit 262ff5ec00
18 changed files with 206 additions and 39 deletions

39
Cargo.lock generated
View file

@ -4,7 +4,7 @@ version = "0.1.0"
dependencies = [
"errors 0.1.0",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -61,14 +61,6 @@ dependencies = [
"libc 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "base64"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "base64"
version = "0.6.0"
@ -245,7 +237,7 @@ dependencies = [
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
]
@ -312,7 +304,7 @@ name = "errors"
version = "0.1.0"
dependencies = [
"error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -348,7 +340,7 @@ dependencies = [
"regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -423,10 +415,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "hyper"
version = "0.10.12"
version = "0.10.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@ -473,7 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
"modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -766,7 +758,7 @@ dependencies = [
"front_matter 0.1.0",
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
]
@ -910,7 +902,7 @@ dependencies = [
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"syntect 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
"templates 0.1.0",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
]
@ -1025,7 +1017,7 @@ dependencies = [
"taxonomies 0.1.0",
"tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"templates 0.1.0",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
"walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -1155,7 +1147,7 @@ dependencies = [
"serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
"slug 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
]
@ -1177,13 +1169,13 @@ dependencies = [
"errors 0.1.0",
"lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"pulldown-cmark 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)",
"tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)",
"utils 0.1.0",
]
[[package]]
name = "tera"
version = "0.10.9"
version = "0.10.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1442,7 +1434,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
"checksum backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76"
"checksum backtrace-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "afccc5772ba333abccdf60d55200fa3406f8c59dcf54d5f7998c9107d3799c7c"
"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
"checksum bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e103c8b299b28a9c6990458b7013dc4a8356a9b854c51b9883241f5866fac36e"
"checksum bindgen 0.26.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c57d6c0f6e31f8dcf4d12720a3c2a9ffb70638772a5784976cf4fce52145f22a"
@ -1480,7 +1471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
"checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07"
"checksum humansize 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "92d211e6e70b05749dce515b47684f29a3c8c38bbbb21c50b30aff9eca1b0bd3"
"checksum hyper 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0f01e4a20f5dfa5278d7762b7bdb7cab96e24378b9eca3889fbd4b5e94dc7063"
"checksum hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)" = "368cb56b2740ebf4230520e2b90ebb0461e69034d85d1945febd9b3971426db2"
"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d"
"checksum inotify 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887fcc180136e77a85e6a6128579a719027b1bab9b1c38ea4444244fe262c20c"
"checksum iovec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29d062ee61fccdf25be172e70f34c9f6efc597e1fb8f6526e8437b2046ab26be"
@ -1558,7 +1549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13ad4762fe52abc9f4008e85c4fb1b1fe3aa91ccb99ff4826a439c7c598e1047"
"checksum syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e0e4dbae163dd98989464c23dd503161b338790640e11537686f2ef0f25c791"
"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
"checksum tera 0.10.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f489baf141c77dbcd2734a339fff2878ca1f4253481dfc4adf9e063b5c3f5a96"
"checksum tera 0.10.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d706c3bec8103f346fc7b8a3887a2ff4195cf704bdbc6307069f32ea8a2b3af5"
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
"checksum term-painter 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "dcaa948f0e3e38470cd8dc8dcfe561a75c9e43f28075bb183845be2b9b3c08cf"
"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"

View file

@ -9,7 +9,7 @@ extern crate chrono;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::path::{Path, PathBuf};
use toml::{Value as Toml};
use chrono::Utc;
@ -18,6 +18,10 @@ use errors::{Result, ResultExt};
use rendering::highlighting::THEME_SET;
mod theme;
use theme::Theme;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Config {
/// Base URL of the site, the only required config argument
@ -82,6 +86,7 @@ impl Config {
set_default!(config.generate_categories_pages, false);
set_default!(config.insert_anchor_links, false);
set_default!(config.compile_sass, false);
set_default!(config.extra, HashMap::new());
match config.highlight_theme {
Some(ref t) => {
@ -124,6 +129,33 @@ impl Config {
format!("{}/{}{}", self.base_url, path, trailing_bit)
}
}
/// Merges the extra data from the theme with the config extra data
fn add_theme_extra(&mut self, theme: &Theme) -> Result<()> {
if let Some(ref mut config_extra) = self.extra {
// 3 pass merging
// 1. save config to preserve user
let original = config_extra.clone();
// 2. inject theme extra values
for (key, val) in &theme.extra {
config_extra.entry(key.to_string()).or_insert(val.clone());
}
// 3. overwrite with original config
for (key, val) in &original {
config_extra.entry(key.to_string()).or_insert(val.clone());
}
}
Ok(())
}
/// Parse the theme.toml file and merges the extra data from the theme
/// with the config extra data
pub fn merge_with_theme(&mut self, path: &PathBuf) -> Result<()> {
let theme = Theme::from_file(path)?;
self.add_theme_extra(&theme)
}
}
/// Exists only for testing purposes
@ -167,7 +199,7 @@ pub fn get_config(path: &Path, filename: &str) -> Config {
#[cfg(test)]
mod tests {
use super::{Config};
use super::{Config, Theme};
#[test]
fn can_import_valid_config() {
@ -230,4 +262,26 @@ hello = "world"
config.base_url = "http://vincent.is/".to_string();
assert_eq!(config.make_permalink("/hello"), "http://vincent.is/hello/");
}
#[test]
fn can_merge_with_theme_data_and_preserve_config_value() {
let config_str = r#"
title = "My site"
base_url = "https://replace-this-with-your-url.com"
[extra]
hello = "world"
"#;
let mut config = Config::parse(config_str).unwrap();
let theme_str = r#"
[extra]
hello = "foo"
a_value = 10
"#;
let theme = Theme::parse(theme_str).unwrap();
assert!(config.add_theme_extra(&theme).is_ok());
let extra = config.extra.unwrap();
assert_eq!(extra["hello"].as_str().unwrap(), "world".to_string());
assert_eq!(extra["a_value"].as_integer().unwrap(), 10);
}
}

View file

@ -0,0 +1,52 @@
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::PathBuf;
use toml::{Value as Toml};
use errors::{Result, ResultExt};
/// Holds the data from a `theme.toml` file.
/// There are other fields than `extra` in it but Gutenberg
/// itself doesn't care about them.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Theme {
/// All user params set in [extra] in the theme.toml
pub extra: HashMap<String, Toml>,
}
impl Theme {
/// Parses a TOML string to our Theme struct
pub fn parse(content: &str) -> Result<Theme> {
let theme = match content.parse::<Toml>() {
Ok(t) => t,
Err(e) => bail!(e),
};
let mut extra = HashMap::new();
if let Some(ref theme_table) = theme.as_table() {
if let Some(ex) = theme_table.get("extra") {
if ex.is_table() {
extra = ex.clone().try_into().unwrap();
}
}
} else {
bail!("Expected the `theme.toml` to be a TOML table")
}
Ok(Theme {extra})
}
/// Parses a theme file from the given path
pub fn from_file(path: &PathBuf) -> Result<Theme> {
let mut content = String::new();
File::open(path)
.chain_err(|| "No `theme.toml` file found. Are you in the right directory?")?
.read_to_string(&mut content)?;
Theme::parse(&content)
}
}

View file

@ -33,7 +33,7 @@ use sass_rs::{Options, compile_file};
use errors::{Result, ResultExt};
use config::{Config, get_config};
use utils::fs::{create_file, create_directory, ensure_directory_exists};
use utils::templates::render_template;
use utils::templates::{render_template, rewrite_theme_paths};
use content::{Page, Section, populate_previous_and_next_pages, sort_pages};
use templates::{GUTENBERG_TERA, global_fns, render_redirect_template};
use front_matter::{SortBy, InsertAnchor};
@ -81,15 +81,18 @@ impl Site {
/// Passing in a path is only used in tests
pub fn new<P: AsRef<Path>>(path: P, config_file: &str) -> Result<Site> {
let path = path.as_ref();
let config = get_config(path, config_file);
let mut config = get_config(path, config_file);
let tpl_glob = format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "templates/**/*.*ml");
let mut tera = Tera::new(&tpl_glob).chain_err(|| "Error parsing templates")?;
tera.extend(&GUTENBERG_TERA)?;
if let Some(ref theme) = config.theme {
if let Some(theme) = config.theme.clone() {
// Grab data from the extra section of the theme
config.merge_with_theme(&path.join("themes").join(&theme).join("theme.toml"))?;
// Test that the {templates,static} folder exist for that theme
let theme_path = path.join("themes").join(theme);
let theme_path = path.join("themes").join(&theme);
if !theme_path.join("templates").exists() {
bail!("Theme `{}` is missing a templates folder", theme);
}
@ -97,8 +100,10 @@ impl Site {
bail!("Theme `{}` is missing a static folder", theme);
}
let theme_tpl_glob = format!("{}/{}", path.to_string_lossy().replace("\\", "/"), "themes/**/*.html");
let tera_themes = Tera::new(&theme_tpl_glob).chain_err(|| "Error parsing templates from themes")?;
tera.extend(&tera_themes)?;
let mut tera_theme = Tera::parse(&theme_tpl_glob).chain_err(|| "Error parsing templates from themes")?;
rewrite_theme_paths(&mut tera_theme, &theme);
tera_theme.build_inheritance_chains().unwrap();
tera.extend(&tera_theme)?;
}
let site = Site {

View file

@ -0,0 +1,5 @@
{% extends "index.html" %}
A child
{% include "included.html" %}

View file

@ -0,0 +1 @@
I am included.

View file

@ -0,0 +1,3 @@
{% macro twice(str) %}
{{str}}-{{str}}
{% endmacro twice %}

View file

@ -0,0 +1,3 @@
{% import "macros.html" as macros %}
{{ macros::twice(str="hey") }}

View file

@ -21,12 +21,6 @@ lazy_static! {
pub static ref GUTENBERG_TERA: Tera = {
let mut tera = Tera::default();
tera.add_raw_templates(vec![
// adding default built-ins templates for index/page/section so
// users don't get an error when they run gutenberg after init
("index.html", include_str!("builtins/index.html")),
("page.html", include_str!("builtins/page.html")),
("section.html", include_str!("builtins/section.html")),
("rss.xml", include_str!("builtins/rss.xml")),
("sitemap.xml", include_str!("builtins/sitemap.xml")),
("robots.txt", include_str!("builtins/robots.txt")),

View file

@ -3,7 +3,9 @@ use tera::{Tera, Context};
use errors::Result;
/// Renders the given template with the given context, but also ensures that, if the default file
/// is not found, it will look up for the equivalent template for the current theme if there is one
/// is not found, it will look up for the equivalent template for the current theme if there is one.
/// Lastly, if it's a default template (index, section or page), it will just return an empty string
/// to avoid an error if there isn't a template with that name
pub fn render_template(name: &str, tera: &Tera, context: &Context, theme: Option<String>) -> Result<String> {
if tera.templates.contains_key(name) {
return tera
@ -17,5 +19,49 @@ pub fn render_template(name: &str, tera: &Tera, context: &Context, theme: Option
.map_err(|e| e.into());
}
if name == "index.html" || name == "section.html" || name == "page.html" {
return Ok(String::new());
}
bail!("Tried to render `{}` but the template wasn't found", name)
}
/// Rewrites the path from extend/macros of the theme used to ensure
/// that they will point to the right place (theme/templates/...)
/// Include is NOT supported as it would be a pain to add and using blocks
/// or macros is always better anyway for themes
pub fn rewrite_theme_paths(tera: &mut Tera, theme: &str) {
// We want to match the paths in the templates to the new names
for tpl in tera.templates.values_mut() {
// First the parent if there is none
if let Some(ref p) = tpl.parent.clone() {
tpl.parent = Some(format!("{}/templates/{}", theme, p));
}
// Next the macros import
let mut updated = vec![];
for &(ref filename, ref namespace) in &tpl.imported_macro_files {
updated.push((format!("{}/templates/{}", theme, filename), namespace.to_string()));
}
tpl.imported_macro_files = updated;
}
}
#[cfg(test)]
mod tests {
use tera::Tera;
use super::{rewrite_theme_paths};
#[test]
fn can_rewrite_all_paths_of_theme() {
let mut tera = Tera::parse("templates/*.html").unwrap();
rewrite_theme_paths(&mut tera, "hyde");
// special case to make the test work: we also rename the files to
// match the imports
for (key, val) in tera.templates.clone() {
tera.templates.insert(format!("hyde/templates/{}", key), val.clone());
}
tera.build_inheritance_chains().unwrap();
}
}

View file

@ -0,0 +1,5 @@
{% extends "index.html" %}
A child
{% include "included.html" %}

View file

@ -0,0 +1 @@
I am included.

View file

@ -0,0 +1 @@
Some base template, used in tests to check whether path rewriting works.

View file

@ -0,0 +1,3 @@
{% macro twice(str) %}
{{str}}-{{str}}
{% endmacro twice %}

View file

@ -0,0 +1,3 @@
{% import "macros.html" as macros %}
{{ macros::twice(str="hey") }}