1 //! A template engine backed by [Tera] for rendering Files.
2
3 use std::collections::HashMap;
4
5 use anyhow::{Context as AnyhowContext, Result};
6 use serde_json::{from_value, to_value, Value};
7 use tera::{self, Tera};
8
9 use crate::config::RenderConfig;
10 use crate::context::Context;
11 use crate::rendering::{
12 render_crate_bazel_label, render_crate_bazel_repository, render_crate_build_file,
13 render_module_label, Platforms,
14 };
15 use crate::select::Select;
16 use crate::utils::sanitize_repository_name;
17
18 pub(crate) struct TemplateEngine {
19 engine: Tera,
20 context: tera::Context,
21 }
22
23 impl TemplateEngine {
new(render_config: &RenderConfig) -> Self24 pub(crate) fn new(render_config: &RenderConfig) -> Self {
25 let mut tera = Tera::default();
26 tera.add_raw_templates(vec![
27 (
28 "partials/module/aliases_map.j2",
29 include_str!(concat!(
30 env!("CARGO_MANIFEST_DIR"),
31 "/src/rendering/templates/partials/module/aliases_map.j2"
32 )),
33 ),
34 (
35 "partials/module/deps_map.j2",
36 include_str!(concat!(
37 env!("CARGO_MANIFEST_DIR"),
38 "/src/rendering/templates/partials/module/deps_map.j2"
39 )),
40 ),
41 (
42 "partials/module/repo_git.j2",
43 include_str!(concat!(
44 env!("CARGO_MANIFEST_DIR"),
45 "/src/rendering/templates/partials/module/repo_git.j2"
46 )),
47 ),
48 (
49 "partials/module/repo_http.j2",
50 include_str!(concat!(
51 env!("CARGO_MANIFEST_DIR"),
52 "/src/rendering/templates/partials/module/repo_http.j2"
53 )),
54 ),
55 (
56 "partials/header.j2",
57 include_str!(concat!(
58 env!("CARGO_MANIFEST_DIR"),
59 "/src/rendering/templates/partials/header.j2"
60 )),
61 ),
62 (
63 "module_bzl.j2",
64 include_str!(concat!(
65 env!("CARGO_MANIFEST_DIR"),
66 "/src/rendering/templates/module_bzl.j2"
67 )),
68 ),
69 (
70 "vendor_module.j2",
71 include_str!(concat!(
72 env!("CARGO_MANIFEST_DIR"),
73 "/src/rendering/templates/vendor_module.j2"
74 )),
75 ),
76 ])
77 .unwrap();
78
79 tera.register_function(
80 "crate_build_file",
81 crate_build_file_fn_generator(render_config.build_file_template.clone()),
82 );
83 tera.register_function(
84 "crate_label",
85 crate_label_fn_generator(
86 render_config.crate_label_template.clone(),
87 render_config.repository_name.clone(),
88 ),
89 );
90 tera.register_function(
91 "crate_repository",
92 crate_repository_fn_generator(
93 render_config.crate_repository_template.clone(),
94 render_config.repository_name.clone(),
95 ),
96 );
97 tera.register_function(
98 "crates_module_label",
99 module_label_fn_generator(render_config.crates_module_template.clone()),
100 );
101
102 let mut context = tera::Context::new();
103 context.insert("default_select_list", &Select::<String>::default());
104 context.insert("repository_name", &render_config.repository_name);
105 context.insert("vendor_mode", &render_config.vendor_mode);
106 context.insert("regen_command", &render_config.regen_command);
107 context.insert("Null", &tera::Value::Null);
108 context.insert(
109 "default_package_name",
110 &match render_config.default_package_name.as_ref() {
111 Some(pkg_name) => format!("\"{pkg_name}\""),
112 None => "None".to_owned(),
113 },
114 );
115
116 Self {
117 engine: tera,
118 context,
119 }
120 }
121
new_tera_ctx(&self) -> tera::Context122 fn new_tera_ctx(&self) -> tera::Context {
123 self.context.clone()
124 }
125
render_header(&self) -> Result<String>126 pub(crate) fn render_header(&self) -> Result<String> {
127 let context = self.new_tera_ctx();
128 let mut header = self
129 .engine
130 .render("partials/header.j2", &context)
131 .context("Failed to render header comment")?;
132 header.push('\n');
133 Ok(header)
134 }
135
render_module_bzl( &self, data: &Context, platforms: &Platforms, ) -> Result<String>136 pub(crate) fn render_module_bzl(
137 &self,
138 data: &Context,
139 platforms: &Platforms,
140 ) -> Result<String> {
141 let mut context = self.new_tera_ctx();
142 context.insert("context", data);
143 context.insert("platforms", platforms);
144
145 self.engine
146 .render("module_bzl.j2", &context)
147 .context("Failed to render crates module")
148 }
149
render_vendor_module_file(&self, data: &Context) -> Result<String>150 pub(crate) fn render_vendor_module_file(&self, data: &Context) -> Result<String> {
151 let mut context = self.new_tera_ctx();
152 context.insert("context", data);
153
154 self.engine
155 .render("vendor_module.j2", &context)
156 .context("Failed to render vendor module")
157 }
158 }
159
160 /// A convienience wrapper for parsing parameters to tera functions
161 macro_rules! parse_tera_param {
162 ($param:literal, $param_type:ty, $args:ident) => {
163 match $args.get($param) {
164 Some(val) => match from_value::<$param_type>(val.clone()) {
165 Ok(v) => v,
166 Err(_) => {
167 return Err(tera::Error::msg(format!(
168 "The `{}` paramater could not be parsed as a String.",
169 $param
170 )))
171 }
172 },
173 None => {
174 return Err(tera::Error::msg(format!(
175 "No `{}` parameter was passed.",
176 $param
177 )))
178 }
179 }
180 };
181 }
182
183 /// Convert a crate name into a module name by applying transforms to invalid characters.
crate_build_file_fn_generator(template: String) -> impl tera::Function184 fn crate_build_file_fn_generator(template: String) -> impl tera::Function {
185 Box::new(
186 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
187 let name = parse_tera_param!("name", String, args);
188 let version = parse_tera_param!("version", String, args);
189
190 match to_value(render_crate_build_file(&template, &name, &version)) {
191 Ok(v) => Ok(v),
192 Err(_) => Err(tera::Error::msg("Failed to generate crate's BUILD file")),
193 }
194 },
195 )
196 }
197
198 /// Convert a file name to a Bazel label
module_label_fn_generator(template: String) -> impl tera::Function199 fn module_label_fn_generator(template: String) -> impl tera::Function {
200 Box::new(
201 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
202 let file = parse_tera_param!("file", String, args);
203
204 let label = match render_module_label(&template, &file) {
205 Ok(v) => v,
206 Err(e) => return Err(tera::Error::msg(e)),
207 };
208
209 match to_value(label.to_string()) {
210 Ok(v) => Ok(v),
211 Err(_) => Err(tera::Error::msg("Failed to generate crate's BUILD file")),
212 }
213 },
214 )
215 }
216
217 /// Convert a crate name into a module name by applying transforms to invalid characters.
crate_label_fn_generator(template: String, repository_name: String) -> impl tera::Function218 fn crate_label_fn_generator(template: String, repository_name: String) -> impl tera::Function {
219 Box::new(
220 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
221 let name = parse_tera_param!("name", String, args);
222 let version = parse_tera_param!("version", String, args);
223 let target = parse_tera_param!("target", String, args);
224
225 match to_value(sanitize_repository_name(&render_crate_bazel_label(
226 &template,
227 &repository_name,
228 &name,
229 &version,
230 &target,
231 ))) {
232 Ok(v) => Ok(v),
233 Err(_) => Err(tera::Error::msg("Failed to generate crate's label")),
234 }
235 },
236 )
237 }
238
239 /// Convert a crate name into a module name by applying transforms to invalid characters.
crate_repository_fn_generator(template: String, repository_name: String) -> impl tera::Function240 fn crate_repository_fn_generator(template: String, repository_name: String) -> impl tera::Function {
241 Box::new(
242 move |args: &HashMap<String, Value>| -> tera::Result<Value> {
243 let name = parse_tera_param!("name", String, args);
244 let version = parse_tera_param!("version", String, args);
245
246 match to_value(sanitize_repository_name(&render_crate_bazel_repository(
247 &template,
248 &repository_name,
249 &name,
250 &version,
251 ))) {
252 Ok(v) => Ok(v),
253 Err(_) => Err(tera::Error::msg("Failed to generate crate repository name")),
254 }
255 },
256 )
257 }
258