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
// Copyright 2021 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// Count the number of tokens. Used to have a fixed-sized array for the
/// templates list.
macro_rules! count {
() => (0_usize);
( $x:tt $($xs:tt)* ) => (1_usize + count!($($xs)*));
}
/// Macro that helps generating helper function that renders a specific template
/// with a strongly-typed context. It also register the template in a static
/// array to help detecting missing templates at startup time.
///
/// The syntax looks almost like a function to confuse syntax highlighter as
/// little as possible.
#[macro_export]
macro_rules! register_templates {
{
$(
extra = { $( $extra_template:expr ),* $(,)? };
)?
$(
// Match any attribute on the function, such as #[doc], #[allow(dead_code)], etc.
$( #[ $attr:meta ] )*
// The function name
pub fn $name:ident
// Optional list of generics. Taken from
// https://newbedev.com/rust-macro-accepting-type-with-generic-parameters
$(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)?
// Type of context taken by the template
( $param:ty )
{
// The name of the template file
$template:expr
}
)*
} => {
/// List of registered templates
static TEMPLATES: [&'static str; count!( $( $template )* )] = [ $( $template, )* ];
impl Templates {
$(
$(#[$attr])?
///
/// # Errors
///
/// Returns an error if the template fails to render.
pub fn $name
$(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)?
(&self, context: &$param)
-> Result<String, TemplateError> {
let ctx = ::minijinja::value::Value::from_serialize(context);
let env = self.environment.load();
let tmpl = env.get_template($template)
.map_err(|source| TemplateError::Missing { template: $template, source })?;
tmpl.render(ctx)
.map_err(|source| TemplateError::Render { template: $template, source })
}
)*
}
/// Helps rendering each template with sample data
pub mod check {
use super::*;
$(
#[doc = concat!("Render the `", $template, "` template with sample contexts")]
///
/// # Errors
///
/// Returns an error if the template fails to render with any of the sample.
pub fn $name
$(< $( $lt $( : $clt $(+ $dlt )* + TemplateContext )? ),+ >)?
(templates: &Templates, now: chrono::DateTime<chrono::Utc>, rng: &mut impl rand::Rng)
-> anyhow::Result<()> {
let samples: Vec< $param > = TemplateContext::sample(now, rng);
let name = $template;
for sample in samples {
let context = serde_json::to_value(&sample)?;
::tracing::info!(name, %context, "Rendering template");
templates. $name (&sample)
.with_context(|| format!("Failed to render template {:?} with context {}", name, context))?;
}
Ok(())
}
)*
}
};
}