@@ -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" | |||
@@ -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); | |||
} | |||
} |
@@ -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) | |||
} | |||
} |
@@ -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 { | |||
@@ -0,0 +1,5 @@ | |||
{% extends "index.html" %} | |||
A child | |||
{% include "included.html" %} |
@@ -0,0 +1 @@ | |||
I am included. |
@@ -0,0 +1,3 @@ | |||
{% macro twice(str) %} | |||
{{str}}-{{str}} | |||
{% endmacro twice %} |
@@ -0,0 +1,3 @@ | |||
{% import "macros.html" as macros %} | |||
{{ macros::twice(str="hey") }} |
@@ -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")), | |||
@@ -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(); | |||
} | |||
} |
@@ -0,0 +1,5 @@ | |||
{% extends "index.html" %} | |||
A child | |||
{% include "included.html" %} |
@@ -0,0 +1 @@ | |||
I am included. |
@@ -0,0 +1 @@ | |||
Some base template, used in tests to check whether path rewriting works. |
@@ -0,0 +1,3 @@ | |||
{% macro twice(str) %} | |||
{{str}}-{{str}} | |||
{% endmacro twice %} |
@@ -0,0 +1,3 @@ | |||
{% import "macros.html" as macros %} | |||
{{ macros::twice(str="hey") }} |