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