-
Notifications
You must be signed in to change notification settings - Fork 1k
/
Copy pathtemplates.rs
217 lines (188 loc) · 7.81 KB
/
templates.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
use std::collections::HashMap;
use libs::tera::{Context, Tera};
use errors::{bail, Result};
static DEFAULT_TPL: &str = include_str!("default_tpl.html");
macro_rules! render_default_tpl {
($filename: expr, $url: expr) => {{
let mut context = Context::new();
context.insert("filename", $filename);
context.insert("url", $url);
Tera::one_off(DEFAULT_TPL, &context, true).map_err(std::convert::Into::into)
}};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShortcodeFileType {
Markdown,
Html,
}
#[derive(Debug, Clone)]
pub struct ShortcodeDefinition {
pub file_type: ShortcodeFileType,
pub tera_name: String,
}
impl ShortcodeDefinition {
pub fn new(file_type: ShortcodeFileType, tera_name: &str) -> ShortcodeDefinition {
let tera_name = tera_name.to_string();
ShortcodeDefinition { file_type, tera_name }
}
}
/// Fetches all the shortcodes from the Tera instances
pub fn get_shortcodes(tera: &Tera) -> HashMap<String, ShortcodeDefinition> {
let mut shortcode_definitions = HashMap::new();
for (identifier, template) in tera.templates.iter() {
let (file_type, ext_len) = if template.name.ends_with(".md") {
(ShortcodeFileType::Markdown, "md".len())
} else {
(ShortcodeFileType::Html, "html".len())
};
if template.name.starts_with("shortcodes/") {
let head_len = "shortcodes/".len();
shortcode_definitions.insert(
identifier[head_len..(identifier.len() - ext_len - 1)].to_string(),
ShortcodeDefinition::new(file_type, &template.name),
);
continue;
}
if template.name.starts_with("__zola_builtins/shortcodes/") {
let head_len = "__zola_builtins/shortcodes/".len();
let name = &identifier[head_len..(identifier.len() - ext_len - 1)];
// We don't keep the built-ins one if the user provided one
if shortcode_definitions.contains_key(name) {
continue;
}
shortcode_definitions
.insert(name.to_string(), ShortcodeDefinition::new(file_type, &template.name));
}
}
shortcode_definitions
}
/// 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.
/// 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 let Some(template) = check_template_fallbacks(name, tera, theme) {
return tera.render(template, &context).map_err(std::convert::Into::into);
}
// maybe it's a default one?
match name {
"index.html" | "section.html" => render_default_tpl!(
name,
"https://www.getzola.org/documentation/templates/pages-sections/#section-variables"
),
"page.html" => render_default_tpl!(
name,
"https://www.getzola.org/documentation/templates/pages-sections/#page-variables"
),
"single.html" | "list.html" => {
render_default_tpl!(name, "https://www.getzola.org/documentation/templates/taxonomies/")
}
_ => bail!("Tried to render `{}` but the template wasn't found", name),
}
}
pub fn is_default_template(name: &str, tera: &Tera, theme: &Option<String>) -> Result<bool> {
if check_template_fallbacks(name, tera, theme).is_some() {
return Ok(false);
}
match name {
"index.html" | "section.html" | "page.html" | "single.html" | "list.html" => Ok(true),
_ => bail!("Template not found for {}", name),
}
}
/// Rewrites the path of duplicate templates to include the complete theme path
/// Theme templates will be injected into site templates, with higher priority for site
/// templates. To keep a copy of the template in case it's being extended from a site template
/// of the same name, we reinsert it with the theme path prepended
pub fn rewrite_theme_paths(tera_theme: &mut Tera, theme: &str) {
let theme_basepath = format!("{}/templates/", theme);
let mut new_templates = HashMap::new();
for (key, template) in &tera_theme.templates {
let mut tpl = template.clone();
tpl.name = format!("{}{}", theme_basepath, key);
new_templates.insert(tpl.name.clone(), tpl);
}
// Contrary to tera.extend, hashmap.extend does replace existing keys
// We can safely extend because there's no conflicting paths anymore
tera_theme.templates.extend(new_templates);
}
/// Checks for the presence of a given template. If none is found, also looks for a
/// fallback in theme and default templates. Returns the path of the most specific
/// template found, or none if none are present.
pub fn check_template_fallbacks<'a>(
name: &'a str,
tera: &'a Tera,
theme: &Option<String>,
) -> Option<&'a str> {
// check if it is in the templates
if tera.templates.contains_key(name) {
return Some(name);
}
// check if it is part of a theme
if let Some(ref t) = *theme {
let theme_template_name = format!("{}/templates/{}", t, name);
if let Some((key, _)) = tera.templates.get_key_value(&theme_template_name) {
return Some(key);
}
}
// check if it is part of ZOLA_TERA defaults
let default_name = format!("__zola_builtins/{}", name);
if let Some((key, _)) = tera.templates.get_key_value(&default_name) {
return Some(key);
}
None
}
#[cfg(test)]
mod tests {
use crate::templates::{check_template_fallbacks, get_shortcodes};
use super::rewrite_theme_paths;
use libs::tera::Tera;
#[test]
fn can_rewrite_all_paths_of_theme() {
let mut tera = Tera::parse("test-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());
}
// Adding our fake base
tera.add_raw_template("base.html", "Hello").unwrap();
tera.build_inheritance_chains().unwrap();
assert_eq!(
tera.templates["hyde/templates/index.html"].parent,
Some("base.html".to_string())
);
assert_eq!(
tera.templates["hyde/templates/child.html"].parent,
Some("index.html".to_string())
);
}
#[test]
fn template_fallback_is_successful() {
let mut tera = Tera::parse("test-templates/*.html").unwrap();
tera.add_raw_template("hyde/templates/index.html", "Hello").unwrap();
tera.add_raw_template("hyde/templates/theme-only.html", "Hello").unwrap();
// Check finding existing template
assert_eq!(check_template_fallbacks("index.html", &tera, &None), Some("index.html"));
// Check trying to find non-existant template
assert_eq!(check_template_fallbacks("not-here.html", &tera, &None), None);
// Check theme fallback
assert_eq!(
check_template_fallbacks("theme-only.html", &tera, &Some("hyde".to_string())),
Some("hyde/templates/theme-only.html")
);
}
#[test]
fn can_overwrite_builtin_shortcodes() {
let mut tera = Tera::parse("test-templates/*.html").unwrap();
tera.add_raw_template("__zola_builtins/shortcodes/youtube.html", "Builtin").unwrap();
tera.add_raw_template("shortcodes/youtube.html", "Hello").unwrap();
let definitions = get_shortcodes(&tera);
assert_eq!(definitions["youtube"].tera_name, "shortcodes/youtube.html");
}
}