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

use rocket::serde::Serialize;

use crate::template::TemplateInfo;

#[cfg(feature = "tera")]
mod tera;
#[cfg(feature = "tera")]
use ::tera::Tera;

#[cfg(feature = "handlebars")]
mod handlebars;
#[cfg(feature = "handlebars")]
use ::handlebars::Handlebars;

#[cfg(feature = "minijinja")]
mod minijinja;
#[cfg(feature = "minijinja")]
use ::minijinja::Environment;

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` 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` 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>,

    /// The minijinja templating engine.
    ///
    /// This field is only available when the `minijinja` feature is enabled.
    /// When calling methods on the [`Environment`] instance, ensure you use
    /// types imported from `rocket_dyn_templates::minijinja` to avoid version
    /// mismatches.
    #[cfg(feature = "minijinja")]
    pub minijinja: Environment<'static>,
}

impl Engines {
    pub(crate) const ENABLED_EXTENSIONS: &'static [&'static str] = &[
        #[cfg(feature = "tera")] Tera::EXT,
        #[cfg(feature = "handlebars")] Handlebars::EXT,
        #[cfg(feature = "minijinja")] Environment::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
            },
            #[cfg(feature = "minijinja")]
            minijinja: match inner::<Environment<'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);
            }
        }

        #[cfg(feature = "minijinja")] {
            if info.engine_ext == Environment::EXT {
                return Engine::render(&self.minijinja, 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()
        }
    }
}