xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/rendering/template_engine.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
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