mirror of https://github.com/rwf2/Rocket.git
Discover manually registered templates.
This includes one breaking change: the default Content-Type of templates without an identifying extension is now 'Text'. This is to prevent Tera templates from rendering as HTML without being escaped. Resolves #1637.
This commit is contained in:
parent
d03a07b183
commit
1c600bda30
|
@ -1,11 +1,15 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::collections::HashMap;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::templates::{Engines, TemplateInfo};
|
||||
|
||||
use rocket::http::ContentType;
|
||||
use normpath::PathExt;
|
||||
|
||||
pub(crate) type Callback =
|
||||
Box<dyn Fn(&mut Engines) -> Result<(), Box<dyn Error>> + Send + Sync + 'static>;
|
||||
|
||||
pub(crate) struct Context {
|
||||
/// The root of the template directory.
|
||||
pub root: PathBuf,
|
||||
|
@ -15,11 +19,13 @@ pub(crate) struct Context {
|
|||
pub engines: Engines,
|
||||
}
|
||||
|
||||
pub(crate) use self::manager::ContextManager;
|
||||
|
||||
impl Context {
|
||||
/// Load all of the templates at `root`, initialize them using the relevant
|
||||
/// template engine, and store all of the initialized state in a `Context`
|
||||
/// structure, which is returned if all goes well.
|
||||
pub fn initialize(root: &Path) -> Option<Context> {
|
||||
pub fn initialize(root: &Path, callback: &Callback) -> Option<Context> {
|
||||
let root = match root.normalize() {
|
||||
Ok(root) => root.into_path_buf(),
|
||||
Err(e) => {
|
||||
|
@ -46,18 +52,134 @@ impl Context {
|
|||
|
||||
let data_type = data_type_str.as_ref()
|
||||
.and_then(|ext| ContentType::from_extension(ext))
|
||||
.unwrap_or(ContentType::HTML);
|
||||
.unwrap_or(ContentType::Text);
|
||||
|
||||
templates.insert(name, TemplateInfo {
|
||||
path: path.to_path_buf(),
|
||||
extension: ext.to_string(),
|
||||
path: Some(path.clone()),
|
||||
engine_ext: ext,
|
||||
data_type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Engines::init(&templates)
|
||||
.map(|engines| Context { root: root.into(), templates, engines } )
|
||||
let mut engines = Engines::init(&templates)?;
|
||||
if let Err(e) = callback(&mut engines) {
|
||||
error_!("Template customization callback failed.");
|
||||
error_!("{}", e);
|
||||
return None;
|
||||
}
|
||||
|
||||
for (name, engine_ext) in engines.templates() {
|
||||
if !templates.contains_key(name) {
|
||||
let data_type = Path::new(name).extension()
|
||||
.and_then(|osstr| osstr.to_str())
|
||||
.and_then(|ext| ContentType::from_extension(ext))
|
||||
.unwrap_or(ContentType::Text);
|
||||
|
||||
let info = TemplateInfo { path: None, engine_ext, data_type };
|
||||
templates.insert(name.to_string(), info);
|
||||
}
|
||||
}
|
||||
|
||||
Some(Context { root, templates, engines })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
mod manager {
|
||||
use std::ops::Deref;
|
||||
use crate::templates::Context;
|
||||
|
||||
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure
|
||||
/// additionally provides a method to reload the context at runtime.
|
||||
pub(crate) struct ContextManager(Context);
|
||||
|
||||
impl ContextManager {
|
||||
pub fn new(ctxt: Context) -> ContextManager {
|
||||
ContextManager(ctxt)
|
||||
}
|
||||
|
||||
pub fn context<'a>(&'a self) -> impl Deref<Target=Context> + 'a {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn is_reloading(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
mod manager {
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::{RwLock, Mutex};
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
|
||||
use notify::{raw_watcher, RawEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
use super::{Callback, Context};
|
||||
|
||||
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure
|
||||
/// additionally provides a method to reload the context at runtime.
|
||||
pub(crate) struct ContextManager {
|
||||
/// The current template context, inside an RwLock so it can be updated.
|
||||
context: RwLock<Context>,
|
||||
/// A filesystem watcher and the receive queue for its events.
|
||||
watcher: Option<(RecommendedWatcher, Mutex<Receiver<RawEvent>>)>,
|
||||
}
|
||||
|
||||
impl ContextManager {
|
||||
pub fn new(ctxt: Context) -> ContextManager {
|
||||
let (tx, rx) = channel();
|
||||
let watcher = raw_watcher(tx).and_then(|mut watcher| {
|
||||
watcher.watch(ctxt.root.canonicalize()?, RecursiveMode::Recursive)?;
|
||||
Ok(watcher)
|
||||
});
|
||||
|
||||
let watcher = match watcher {
|
||||
Ok(watcher) => Some((watcher, Mutex::new(rx))),
|
||||
Err(e) => {
|
||||
warn!("Failed to enable live template reloading: {}", e);
|
||||
debug_!("Reload error: {:?}", e);
|
||||
warn_!("Live template reloading is unavailable.");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
ContextManager { watcher, context: RwLock::new(ctxt), }
|
||||
}
|
||||
|
||||
pub fn context(&self) -> impl Deref<Target=Context> + '_ {
|
||||
self.context.read().unwrap()
|
||||
}
|
||||
|
||||
pub fn is_reloading(&self) -> bool {
|
||||
self.watcher.is_some()
|
||||
}
|
||||
|
||||
fn context_mut(&self) -> impl DerefMut<Target=Context> + '_ {
|
||||
self.context.write().unwrap()
|
||||
}
|
||||
|
||||
/// Checks whether any template files have changed on disk. If there
|
||||
/// have been changes since the last reload, all templates are
|
||||
/// reinitialized from disk and the user's customization callback is run
|
||||
/// again.
|
||||
pub fn reload_if_needed(&self, callback: &Callback) {
|
||||
let templates_changes = self.watcher.as_ref()
|
||||
.map(|(_, rx)| rx.lock().expect("fsevents lock").try_iter().count() > 0);
|
||||
|
||||
if let Some(true) = templates_changes {
|
||||
info_!("Change detected: reloading templates.");
|
||||
let root = self.context().root.clone();
|
||||
if let Some(new_ctxt) = Context::initialize(&root, &callback) {
|
||||
*self.context_mut() = new_ctxt;
|
||||
} else {
|
||||
warn_!("An error occurred while reloading templates.");
|
||||
warn_!("Existing templates will remain active.");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::path::Path;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
@ -7,10 +8,10 @@ use crate::templates::TemplateInfo;
|
|||
#[cfg(feature = "tera_templates")] use crate::templates::tera::Tera;
|
||||
#[cfg(feature = "handlebars_templates")] use crate::templates::handlebars::Handlebars;
|
||||
|
||||
pub(crate) trait Engine: Send + Sync + 'static {
|
||||
pub(crate) trait Engine: Send + Sync + Sized + 'static {
|
||||
const EXT: &'static str;
|
||||
|
||||
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Self> where Self: Sized;
|
||||
fn init<'a>(templates: impl Iterator<Item = (&'a str, &'a Path)>) -> Option<Self>;
|
||||
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String>;
|
||||
}
|
||||
|
||||
|
@ -74,11 +75,11 @@ impl Engines {
|
|||
pub(crate) fn init(templates: &HashMap<String, TemplateInfo>) -> Option<Engines> {
|
||||
fn inner<E: Engine>(templates: &HashMap<String, TemplateInfo>) -> Option<E> {
|
||||
let named_templates = templates.iter()
|
||||
.filter(|&(_, i)| i.extension == E::EXT)
|
||||
.map(|(k, i)| (k.as_str(), i))
|
||||
.collect::<Vec<_>>();
|
||||
.filter(|&(_, i)| i.engine_ext == E::EXT)
|
||||
.filter_map(|(k, i)| Some((k.as_str(), i.path.as_ref()?)))
|
||||
.map(|(k, p)| (k, p.as_path()));
|
||||
|
||||
E::init(&*named_templates)
|
||||
E::init(named_templates)
|
||||
}
|
||||
|
||||
Some(Engines {
|
||||
|
@ -101,20 +102,37 @@ impl Engines {
|
|||
info: &TemplateInfo,
|
||||
context: C
|
||||
) -> Option<String> {
|
||||
#[cfg(feature = "tera_templates")]
|
||||
{
|
||||
if info.extension == Tera::EXT {
|
||||
#[cfg(feature = "tera_templates")] {
|
||||
if info.engine_ext == Tera::EXT {
|
||||
return Engine::render(&self.tera, name, context);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "handlebars_templates")]
|
||||
{
|
||||
if info.extension == Handlebars::EXT {
|
||||
#[cfg(feature = "handlebars_templates")] {
|
||||
if info.engine_ext == Handlebars::EXT {
|
||||
return Engine::render(&self.handlebars, name, context);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn templates(&self) -> impl Iterator<Item = (&str, &'static str)> {
|
||||
#[cfg(all(feature = "tera_templates", feature = "handlebars_templates"))] {
|
||||
self.tera.templates.keys()
|
||||
.map(|name| (name.as_str(), Tera::EXT))
|
||||
.chain(self.handlebars.get_templates().keys()
|
||||
.map(|name| (name.as_str(), Handlebars::EXT)))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "tera_templates", not(feature = "handlebars_templates")))] {
|
||||
self.tera.templates.keys()
|
||||
.map(|name| (name.as_str(), Tera::EXT))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "handlebars_templates", not(feature = "tera_templates")))] {
|
||||
self.handlebars.get_templates().keys()
|
||||
.map(|name| (name.as_str(), Handlebars::EXT))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,119 +1,9 @@
|
|||
use std::error::Error;
|
||||
|
||||
use crate::templates::{DEFAULT_TEMPLATE_DIR, Context, Engines};
|
||||
use crate::templates::context::{Callback, ContextManager};
|
||||
|
||||
use rocket::{Rocket, Build, Orbit};
|
||||
use rocket::fairing::{self, Fairing, Info, Kind};
|
||||
|
||||
pub(crate) use self::context::ContextManager;
|
||||
|
||||
type Callback = Box<dyn Fn(&mut Engines) -> Result<(), Box<dyn Error>>+ Send + Sync + 'static>;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
mod context {
|
||||
use std::ops::Deref;
|
||||
use crate::templates::Context;
|
||||
|
||||
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure
|
||||
/// additionally provides a method to reload the context at runtime.
|
||||
pub(crate) struct ContextManager(Context);
|
||||
|
||||
impl ContextManager {
|
||||
pub fn new(ctxt: Context) -> ContextManager {
|
||||
ContextManager(ctxt)
|
||||
}
|
||||
|
||||
pub fn context<'a>(&'a self) -> impl Deref<Target=Context> + 'a {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn is_reloading(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
mod context {
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::{RwLock, Mutex};
|
||||
use std::sync::mpsc::{channel, Receiver};
|
||||
|
||||
use notify::{raw_watcher, RawEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
use super::{Callback, Context};
|
||||
|
||||
/// Wraps a Context. With `cfg(debug_assertions)` active, this structure
|
||||
/// additionally provides a method to reload the context at runtime.
|
||||
pub(crate) struct ContextManager {
|
||||
/// The current template context, inside an RwLock so it can be updated.
|
||||
context: RwLock<Context>,
|
||||
/// A filesystem watcher and the receive queue for its events.
|
||||
watcher: Option<(RecommendedWatcher, Mutex<Receiver<RawEvent>>)>,
|
||||
}
|
||||
|
||||
impl ContextManager {
|
||||
pub fn new(ctxt: Context) -> ContextManager {
|
||||
let (tx, rx) = channel();
|
||||
let watcher = raw_watcher(tx).and_then(|mut watcher| {
|
||||
watcher.watch(ctxt.root.canonicalize()?, RecursiveMode::Recursive)?;
|
||||
Ok(watcher)
|
||||
});
|
||||
|
||||
let watcher = match watcher {
|
||||
Ok(watcher) => Some((watcher, Mutex::new(rx))),
|
||||
Err(e) => {
|
||||
warn!("Failed to enable live template reloading: {}", e);
|
||||
debug_!("Reload error: {:?}", e);
|
||||
warn_!("Live template reloading is unavailable.");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
ContextManager { watcher, context: RwLock::new(ctxt), }
|
||||
}
|
||||
|
||||
pub fn context(&self) -> impl Deref<Target=Context> + '_ {
|
||||
self.context.read().unwrap()
|
||||
}
|
||||
|
||||
pub fn is_reloading(&self) -> bool {
|
||||
self.watcher.is_some()
|
||||
}
|
||||
|
||||
fn context_mut(&self) -> impl DerefMut<Target=Context> + '_ {
|
||||
self.context.write().unwrap()
|
||||
}
|
||||
|
||||
/// Checks whether any template files have changed on disk. If there
|
||||
/// have been changes since the last reload, all templates are
|
||||
/// reinitialized from disk and the user's customization callback is run
|
||||
/// again.
|
||||
pub fn reload_if_needed(&self, callback: &Callback) {
|
||||
let templates_changes = self.watcher.as_ref()
|
||||
.map(|(_, rx)| rx.lock().expect("fsevents lock").try_iter().count() > 0);
|
||||
|
||||
if let Some(true) = templates_changes {
|
||||
info_!("Change detected: reloading templates.");
|
||||
let root = self.context().root.clone();
|
||||
if let Some(mut new_ctxt) = Context::initialize(&root) {
|
||||
match callback(&mut new_ctxt.engines) {
|
||||
Ok(()) => *self.context_mut() = new_ctxt,
|
||||
Err(e) => {
|
||||
warn_!("The template customization callback returned an error:");
|
||||
warn_!("{}", e);
|
||||
warn_!("The existing templates will remain active.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn_!("An error occurred while reloading templates.");
|
||||
warn_!("The existing templates will remain active.");
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The TemplateFairing initializes the template system on attach, running
|
||||
/// custom_callback after templates have been loaded. In debug mode, the fairing
|
||||
/// checks for modifications to templates before every request and reloads them
|
||||
|
@ -155,21 +45,11 @@ impl Fairing for TemplateFairing {
|
|||
}
|
||||
};
|
||||
|
||||
match Context::initialize(&path) {
|
||||
Some(mut ctxt) => {
|
||||
match (self.callback)(&mut ctxt.engines) {
|
||||
Ok(()) => Ok(rocket.manage(ContextManager::new(ctxt))),
|
||||
Err(e) => {
|
||||
error_!("The template customization callback returned an error:");
|
||||
error_!("{}", e);
|
||||
Err(rocket)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error_!("Launch will be aborted due to failed template initialization.");
|
||||
Err(rocket)
|
||||
}
|
||||
if let Some(ctxt) = Context::initialize(&path, &self.callback) {
|
||||
Ok(rocket.manage(ContextManager::new(ctxt)))
|
||||
} else {
|
||||
error_!("Template initialization failed. Aborting launch.");
|
||||
Err(rocket)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::templates::{Engine, TemplateInfo};
|
||||
use serde::Serialize;
|
||||
use crate::templates::Engine;
|
||||
|
||||
pub use crate::templates::handlebars::Handlebars;
|
||||
|
||||
impl Engine for Handlebars<'static> {
|
||||
const EXT: &'static str = "hbs";
|
||||
|
||||
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Handlebars<'static>> {
|
||||
fn init<'a>(templates: impl Iterator<Item = (&'a str, &'a Path)>) -> Option<Self> {
|
||||
let mut hb = Handlebars::new();
|
||||
for &(name, info) in templates {
|
||||
let path = &info.path;
|
||||
let mut ok = true;
|
||||
for (name, path) in templates {
|
||||
if let Err(e) = hb.register_template_file(name, path) {
|
||||
error!("Error in Handlebars template '{}'.", name);
|
||||
info_!("{}", e);
|
||||
error!("Handlebars template '{}' failed to register.", name);
|
||||
error_!("{}", e);
|
||||
info_!("Template path: '{}'.", path.to_string_lossy());
|
||||
return None;
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
Some(hb)
|
||||
ok.then(|| hb)
|
||||
}
|
||||
|
||||
fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String> {
|
||||
|
|
|
@ -2,7 +2,7 @@ use rocket::{Request, Rocket, Ignite, Sentinel};
|
|||
use rocket::http::Status;
|
||||
use rocket::request::{self, FromRequest};
|
||||
|
||||
use crate::templates::ContextManager;
|
||||
use crate::templates::context::ContextManager;
|
||||
|
||||
/// Request guard for dynamically querying template metadata.
|
||||
///
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
//! features = ["handlebars_templates", "tera_templates"]
|
||||
//! ```
|
||||
//!
|
||||
//! 1. Write your template files in Handlebars (extension: `.hbs`) or tera
|
||||
//! 1. Write your template files in Handlebars (extension: `.hbs`) and/or tera
|
||||
//! (extensions: `.tera`) in the templates directory (default:
|
||||
//! `{rocket_root}/templates`).
|
||||
//!
|
||||
|
@ -34,7 +34,7 @@
|
|||
//! ```
|
||||
//!
|
||||
//! 3. Return a [`Template`] using [`Template::render()`], supplying the name
|
||||
//! of the template file minus the last two extensions, from a handler.
|
||||
//! of the template file **minus the last two extensions**, from a handler.
|
||||
//!
|
||||
//! ```rust
|
||||
//! # #[macro_use] extern crate rocket;
|
||||
|
@ -49,6 +49,25 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Naming
|
||||
//!
|
||||
//! Templates discovered by Rocket are _renamed_ from their file name to their
|
||||
//! file name **without the last two extensions**. As such, refer to a template
|
||||
//! with file name `foo.html.hbs` or `foo.html.tera` as `foo`. See
|
||||
//! [Discovery](#discovery) for more.
|
||||
//!
|
||||
//! Templates that are _not_ discovered by Rocket, such as those registered
|
||||
//! directly via [`Template::custom()`], are _not_ renamed.
|
||||
//!
|
||||
//! ## Content Type
|
||||
//!
|
||||
//! The `Content-Type` of the response is automatically determined by the
|
||||
//! non-engine extension of the template name or `text/plain` if there is no
|
||||
//! extension or the extension is unknown. For example, for a discovered
|
||||
//! template with file name `foo.html.hbs` or a manually registered template
|
||||
//! with name ending in `foo.html`, the `Content-Type` is automatically set to
|
||||
//! [`ContentType::HTML`].
|
||||
//!
|
||||
//! ## Discovery
|
||||
//!
|
||||
//! Template names passed in to [`Template::render()`] must correspond to a
|
||||
|
@ -66,10 +85,10 @@
|
|||
//! | Engine | Version | Extension |
|
||||
//! |--------------|---------|-----------|
|
||||
//! | [Tera] | 1 | `.tera` |
|
||||
//! | [Handlebars] | 2 | `.hbs` |
|
||||
//! | [Handlebars] | 3 | `.hbs` |
|
||||
//!
|
||||
//! [Tera]: https://docs.rs/crate/tera/1
|
||||
//! [Handlebars]: https://docs.rs/crate/handlebars/2
|
||||
//! [Handlebars]: https://docs.rs/crate/handlebars/3
|
||||
//!
|
||||
//! Any file that ends with one of these extension will be discovered and
|
||||
//! rendered with the corresponding templating engine. The _name_ of the
|
||||
|
@ -124,11 +143,10 @@ mod metadata;
|
|||
|
||||
pub use self::engine::Engines;
|
||||
pub use self::metadata::Metadata;
|
||||
pub(crate) use self::context::Context;
|
||||
pub(crate) use self::fairing::ContextManager;
|
||||
|
||||
use self::engine::Engine;
|
||||
use self::fairing::TemplateFairing;
|
||||
use self::context::{Context, ContextManager};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, to_value};
|
||||
|
@ -205,10 +223,10 @@ pub struct Template {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct TemplateInfo {
|
||||
/// The complete path, including `template_dir`, to this template.
|
||||
path: PathBuf,
|
||||
/// The complete path, including `template_dir`, to this template, if any.
|
||||
path: Option<PathBuf>,
|
||||
/// The extension for the engine of this template.
|
||||
extension: String,
|
||||
engine_ext: &'static str,
|
||||
/// The extension before the engine extension in the template, if any.
|
||||
data_type: ContentType
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
use serde::Serialize;
|
||||
use std::path::Path;
|
||||
use std::error::Error;
|
||||
|
||||
use crate::templates::{Engine, TemplateInfo};
|
||||
use serde::Serialize;
|
||||
use crate::templates::Engine;
|
||||
|
||||
pub use crate::templates::tera::{Context, Tera};
|
||||
|
||||
impl Engine for Tera {
|
||||
const EXT: &'static str = "tera";
|
||||
|
||||
fn init(templates: &[(&str, &TemplateInfo)]) -> Option<Tera> {
|
||||
fn init<'a>(templates: impl Iterator<Item = (&'a str, &'a Path)>) -> Option<Self> {
|
||||
// Create the Tera instance.
|
||||
let mut tera = Tera::default();
|
||||
let ext = [".html.tera", ".htm.tera", ".xml.tera", ".html", ".htm", ".xml"];
|
||||
tera.autoescape_on(ext.to_vec());
|
||||
|
||||
// Collect into a tuple of (name, path) for Tera.
|
||||
let tera_templates = templates.iter()
|
||||
.map(|&(name, info)| (&info.path, Some(name)))
|
||||
.collect::<Vec<_>>();
|
||||
// Collect into a tuple of (name, path) for Tera. If we register one at
|
||||
// a time, it will complain about unregistered base templates.
|
||||
let files = templates.map(|(name, path)| (path, Some(name)));
|
||||
|
||||
// Finally try to tell Tera about all of the templates.
|
||||
if let Err(e) = tera.add_template_files(tera_templates) {
|
||||
if let Err(e) = tera.add_template_files(files) {
|
||||
error!("Failed to initialize Tera templating.");
|
||||
|
||||
let mut error = Some(&e as &dyn Error);
|
||||
|
|
|
@ -29,12 +29,10 @@ pub fn hello(name: &str) -> Template {
|
|||
|
||||
#[get("/about")]
|
||||
pub fn about() -> Template {
|
||||
Template::render("hbs/about", &TemplateContext {
|
||||
title: "About",
|
||||
name: None,
|
||||
items: vec!["Some", "Important", "Info"],
|
||||
parent: "hbs/layout",
|
||||
})
|
||||
let mut map = std::collections::HashMap::new();
|
||||
map.insert("title", "About");
|
||||
map.insert("parent", "hbs/layout");
|
||||
Template::render("hbs/about.html", &map)
|
||||
}
|
||||
|
||||
#[catch(404)]
|
||||
|
@ -62,4 +60,14 @@ fn wow_helper(
|
|||
|
||||
pub fn customize(hbs: &mut Handlebars) {
|
||||
hbs.register_helper("wow", Box::new(wow_helper));
|
||||
hbs.register_template_string("hbs/about.html", r#"
|
||||
{{#*inline "page"}}
|
||||
|
||||
<section id="about">
|
||||
<h1>About - Here's another page!</h1>
|
||||
</section>
|
||||
|
||||
{{/inline}}
|
||||
{{~> (parent)~}}
|
||||
"#).expect("valid HBS template");
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ fn index() -> Html<&'static str> {
|
|||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.mount("/", routes![index])
|
||||
.mount("/tera", routes![tera::index, tera::hello])
|
||||
.mount("/tera", routes![tera::index, tera::hello, tera::about])
|
||||
.mount("/hbs", routes![hbs::index, hbs::hello, hbs::about])
|
||||
.register("/hbs", catchers![hbs::not_found])
|
||||
.register("/tera", catchers![tera::not_found])
|
||||
|
|
|
@ -7,7 +7,7 @@ use rocket_contrib::templates::{Template, tera::Tera};
|
|||
#[derive(serde::Serialize)]
|
||||
struct TemplateContext<'r> {
|
||||
title: &'r str,
|
||||
name: &'r str,
|
||||
name: Option<&'r str>,
|
||||
items: Vec<&'r str>
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,19 @@ pub fn index() -> Redirect {
|
|||
#[get("/hello/<name>")]
|
||||
pub fn hello(name: &str) -> Template {
|
||||
Template::render("tera/index", &TemplateContext {
|
||||
name,
|
||||
title: "Hello",
|
||||
name: Some(name),
|
||||
items: vec!["One", "Two", "Three"],
|
||||
})
|
||||
}
|
||||
|
||||
#[get("/about")]
|
||||
pub fn about() -> Template {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("title", "About");
|
||||
Template::render("tera/about.html", &map)
|
||||
}
|
||||
|
||||
#[catch(404)]
|
||||
pub fn not_found(req: &Request<'_>) -> Template {
|
||||
let mut map = HashMap::new();
|
||||
|
@ -32,6 +39,14 @@ pub fn not_found(req: &Request<'_>) -> Template {
|
|||
Template::render("tera/error/404", &map)
|
||||
}
|
||||
|
||||
pub fn customize(_tera: &mut Tera) {
|
||||
/* register helpers, and so on */
|
||||
pub fn customize(tera: &mut Tera) {
|
||||
tera.add_raw_template("tera/about.html", r#"
|
||||
{% extends "tera/base" %}
|
||||
|
||||
{% block content %}
|
||||
<section id="about">
|
||||
<h1>About - Here's another page!</h1>
|
||||
</section>
|
||||
{% endblock content %}
|
||||
"#).expect("valid Tera template");
|
||||
}
|
||||
|
|
|
@ -82,4 +82,5 @@ fn tera() {
|
|||
test_root("tera");
|
||||
test_name("tera");
|
||||
test_404("tera");
|
||||
test_about("tera");
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
{{#*inline "page"}}
|
||||
|
||||
<section id="about">
|
||||
<h1>About - Here's another page!</h1>
|
||||
<ul>
|
||||
{{#each items}}
|
||||
<li>{{ this }}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
{{/inline}}
|
||||
{{~> (parent)~}}
|
|
@ -5,9 +5,12 @@
|
|||
<title>Tera Demo - {{ title }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/tera/hello/Unknown">Hello</a> | <a href="/tera/about">About</a>
|
||||
|
||||
{% block content %}{% endblock content %}
|
||||
|
||||
<footer>
|
||||
<a href="/">Home</a>
|
||||
</footer>
|
||||
</body>
|
||||
<footer>
|
||||
<a href="/">Home</a>
|
||||
</footer>
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue