• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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