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
use std::path::Path;
use std::collections::HashMap;

use rocket::serde::Serialize;

use crate::TemplateInfo;

#[cfg(feature = "tera")] use crate::tera::Tera;
#[cfg(feature = "handlebars")] use crate::handlebars::Handlebars;

pub(crate) trait Engine: Send + Sync + Sized + 'static {
    const EXT: &'static str;

    fn init<'a>(templates: impl Iterator<Item = (&'a str, &'a Path)>) -> Option<Self>;
    fn render<C: Serialize>(&self, name: &str, context: C) -> Option<String>;
}

/// A structure exposing access to templating engines.
///
/// Calling methods on the exposed template engine types may require importing
/// types from the respective templating engine library. These types should be
/// imported from the reexported crate at the root of `rocket_dyn_templates` to
/// avoid version mismatches. For instance, when registering a Tera filter, the
/// [`tera::Value`] and [`tera::Result`] types are required. Import them from
/// `rocket_dyn_templates::tera`. The example below illustrates this:
///
/// ```rust
/// # #[cfg(feature = "tera")] {
/// use std::collections::HashMap;
///
/// use rocket_dyn_templates::{Template, Engines};
/// use rocket_dyn_templates::tera::{self, Value};
///
/// fn my_filter(value: &Value, _: &HashMap<String, Value>) -> tera::Result<Value> {
///     # /*
///     ...
///     # */ unimplemented!();
/// }
///
/// fn main() {
///     rocket::build()
///         // ...
///         .attach(Template::custom(|engines: &mut Engines| {
///             engines.tera.register_filter("my_filter", my_filter);
///         }))
///         // ...
///         # ;
/// }
/// # }
/// ```
///
/// [`tera::Value`]: crate::tera::Value
/// [`tera::Result`]: crate::tera::Result
pub struct Engines {
    /// A `Tera` templating engine. This field is only available when the
    /// `tera_templates` feature is enabled. When calling methods on the `Tera`
    /// instance, ensure you use types imported from
    /// `rocket_dyn_templates::tera` to avoid version mismatches.
    #[cfg(feature = "tera")]
    pub tera: Tera,
    /// The Handlebars templating engine. This field is only available when the
    /// `handlebars_templates` feature is enabled. When calling methods on the
    /// `Handlebars` instance, ensure you use types imported from
    /// `rocket_dyn_templates::handlebars` to avoid version mismatches.
    #[cfg(feature = "handlebars")]
    pub handlebars: Handlebars<'static>,
}

impl Engines {
    pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[
        #[cfg(feature = "tera")] Tera::EXT,
        #[cfg(feature = "handlebars")] Handlebars::EXT,
    ];

    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.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)
        }

        Some(Engines {
            #[cfg(feature = "tera")]
            tera: match inner::<Tera>(templates) {
                Some(tera) => tera,
                None => return None
            },
            #[cfg(feature = "handlebars")]
            handlebars: match inner::<Handlebars<'static>>(templates) {
                Some(hb) => hb,
                None => return None
            },
        })
    }

    pub(crate) fn render<C: Serialize>(
        &self,
        name: &str,
        info: &TemplateInfo,
        context: C
    ) -> Option<String> {
        #[cfg(feature = "tera")] {
            if info.engine_ext == Tera::EXT {
                return Engine::render(&self.tera, name, context);
            }
        }

        #[cfg(feature = "handlebars")] {
            if info.engine_ext == Handlebars::EXT {
                return Engine::render(&self.handlebars, name, context);
            }
        }

        None
    }

    /// Returns iterator over template (name, engine_extension).
    pub(crate) fn templates(&self) -> impl Iterator<Item = (&str, &'static str)> {
        #[cfg(all(feature = "tera", feature = "handlebars"))] {
            self.tera.get_template_names()
                .map(|name| (name, Tera::EXT))
                .chain(self.handlebars.get_templates().keys()
                    .map(|name| (name.as_str(), Handlebars::EXT)))
        }

        #[cfg(all(feature = "tera", not(feature = "handlebars")))] {
            self.tera.get_template_names().map(|name| (name, Tera::EXT))
        }

        #[cfg(all(feature = "handlebars", not(feature = "tera")))] {
            self.handlebars.get_templates().keys()
                .map(|name| (name.as_str(), Handlebars::EXT))
        }

        #[cfg(not(any(feature = "tera", feature = "handlebars")))] {
            None.into_iter()
        }
    }
}