anodizer_core/context/render.rs
1use super::*;
2use anyhow::Context as _;
3
4impl Context {
5 pub fn template_vars(&self) -> &TemplateVars {
6 &self.template_vars
7 }
8
9 pub fn template_vars_mut(&mut self) -> &mut TemplateVars {
10 &mut self.template_vars
11 }
12
13 pub fn render_template(&self, template: &str) -> anyhow::Result<String> {
14 crate::template::render(template, &self.template_vars)
15 }
16
17 /// Render `template` with the FULL version-derived var set (`Version`, `Tag`,
18 /// `Major`/`Minor`/`Patch`, `RawVersion`, `Prerelease`, `BuildMetadata`,
19 /// `Base`) re-derived from `version`/`tag` rather than the context's own git
20 /// version — used by promotion to reconstruct the immutable tag a prior
21 /// release pushed for a specific `--version`. Overriding only `Version`+`Tag`
22 /// would leave `{{ .Major }}` etc. resolving to the CONTEXT version, so a
23 /// `{{ .Major }}.{{ .Minor }}.{{ .Patch }}` tag template would render the
24 /// wrong source tag whenever the context version differs from the target.
25 ///
26 /// A non-semver `version` (no parse) falls back to overriding `Version`+`Tag`
27 /// and BLANKING the seven semver-part vars (`RawVersion`, `Base`, `Major`,
28 /// `Minor`, `Patch`, `Prerelease`, `BuildMetadata`), so a semver-part template
29 /// cannot silently resolve the context version — it renders empty instead of
30 /// inheriting the cloned context's parts. Does not mutate `self`.
31 pub fn render_template_for_version(
32 &self,
33 template: &str,
34 version: &str,
35 tag: &str,
36 ) -> anyhow::Result<String> {
37 let mut vars = self.template_vars.clone();
38 match crate::git::parse_semver(version) {
39 Ok(semver) => set_version_vars(&mut vars, &semver, tag),
40 Err(_) => {
41 vars.set("Version", version);
42 vars.set("Tag", tag);
43 // Blank the semver-derived vars `set_version_vars` writes so a
44 // `{{ .Major }}`-style template renders empty rather than
45 // inheriting the cloned context version's parts (a false match).
46 for key in [
47 "RawVersion",
48 "Base",
49 "Major",
50 "Minor",
51 "Patch",
52 "Prerelease",
53 "BuildMetadata",
54 ] {
55 vars.set(key, "");
56 }
57 }
58 }
59 crate::template::render(template, &vars)
60 }
61
62 /// Render a template if present, returning `None` for `None` input.
63 pub fn render_template_opt(&self, template: Option<&str>) -> anyhow::Result<Option<String>> {
64 template.map(|t| self.render_template(t)).transpose()
65 }
66
67 /// Evaluate a `skip` field, logging at INFO level when it resolves to true.
68 ///
69 /// Returns `Ok(false)` when `skip` is `None` or evaluates falsy. On
70 /// truthy, writes `"{label} skipped"` via `log.status` and returns
71 /// `Ok(true)`. A malformed `skip:` template propagates as `Err` so the
72 /// caller fails fast — silently treating a render error as "not skipped"
73 /// (the prior behavior) shipped configs that the user thought would
74 /// suppress a stage but actually ran it.
75 pub fn skip_with_log(
76 &self,
77 skip: &Option<crate::config::StringOrBool>,
78 log: &StageLogger,
79 label: &str,
80 ) -> anyhow::Result<bool> {
81 let Some(d) = skip else {
82 return Ok(false);
83 };
84 let should_skip = d
85 .try_evaluates_to_true(|s| self.render_template(s))
86 .with_context(|| format!("evaluate skip expression for {label}"))?;
87 if should_skip {
88 log.status(&format!("{} skipped", label));
89 }
90 Ok(should_skip)
91 }
92
93 /// Render a template, failing in strict mode on error, or falling back to the raw string.
94 pub fn render_template_strict(
95 &self,
96 template: &str,
97 label: &str,
98 log: &crate::log::StageLogger,
99 ) -> anyhow::Result<String> {
100 match self.render_template(template) {
101 Ok(rendered) => Ok(rendered),
102 Err(e) => {
103 if self.options.strict {
104 anyhow::bail!("{}: failed to render template: {} (strict mode)", label, e);
105 }
106 log.warn(&format!("failed to render template for {}: {}", label, e));
107 Ok(template.to_string())
108 }
109 }
110 }
111}