Skip to main content

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}