spin_templates/
renderer.rs1use anyhow::anyhow;
2use lazy_static::lazy_static;
3use std::{collections::HashMap, path::PathBuf};
4
5use crate::writer::{TemplateOutput, TemplateOutputs};
6
7pub(crate) struct TemplateRenderer {
10 pub render_operations: Vec<RenderOperation>,
11 pub parameter_values: HashMap<String, String>,
12}
13
14pub(crate) enum TemplateContent {
15 Template(liquid::Template),
16 Binary(Vec<u8>),
17}
18
19pub(crate) enum RenderOperation {
20 AppendToml(PathBuf, TemplateContent),
21 MergeToml(PathBuf, MergeTarget, TemplateContent), WriteFile(PathBuf, TemplateContent),
23 CreateDirectory(PathBuf, std::sync::Arc<liquid::Template>),
24}
25
26pub(crate) enum MergeTarget {
27 Application(&'static str),
28}
29
30impl TemplateRenderer {
31 pub(crate) fn render(self) -> anyhow::Result<TemplateOutputs> {
32 let globals = self.renderer_globals();
33
34 let outputs = self
35 .render_operations
36 .into_iter()
37 .map(|so| so.render(&globals))
38 .collect::<anyhow::Result<Vec<_>>>()?;
39
40 if outputs.is_empty() {
41 return Err(anyhow!("Nothing to create"));
42 }
43
44 Ok(TemplateOutputs::new(outputs))
45 }
46
47 fn renderer_globals(&self) -> liquid::Object {
48 let mut object = liquid::Object::new();
49
50 for (k, v) in &self.parameter_values {
51 object.insert(
52 k.to_owned().into(),
53 liquid_core::Value::Scalar(v.to_owned().into()),
54 );
55 }
56
57 object
58 }
59}
60
61impl RenderOperation {
62 fn render(self, globals: &liquid::Object) -> anyhow::Result<TemplateOutput> {
63 match self {
64 Self::WriteFile(path, content) => {
65 let rendered = content.render(globals)?;
66 Ok(TemplateOutput::WriteFile(path, rendered))
67 }
68 Self::AppendToml(path, content) => {
69 let rendered = content.render(globals)?;
70 let rendered_text = String::from_utf8(rendered)?;
71 Ok(TemplateOutput::AppendToml(path, rendered_text))
72 }
73 Self::MergeToml(path, target, content) => {
74 let rendered = content.render(globals)?;
75 let rendered_text = String::from_utf8(rendered)?;
76 let MergeTarget::Application(target_table) = target;
77 Ok(TemplateOutput::MergeToml(path, target_table, rendered_text))
78 }
79 Self::CreateDirectory(path, template) => {
80 let rendered = template.render(globals)?;
81 let path = path.join(rendered); Ok(TemplateOutput::CreateDirectory(path))
83 }
84 }
85 }
86}
87
88impl TemplateContent {
89 pub(crate) fn infer_from_bytes(
90 raw: Vec<u8>,
91 parser: &liquid::Parser,
92 ) -> anyhow::Result<TemplateContent> {
93 match string_from_bytes(&raw) {
94 None => Ok(TemplateContent::Binary(raw)),
95 Some(s) => {
96 match parser.parse(&s) {
97 Ok(t) => Ok(TemplateContent::Template(t)),
98 Err(e) => match understand_liquid_error(e) {
99 TemplateParseFailure::Other(_e) => {
100 Ok(TemplateContent::Binary(raw))
102 }
103 TemplateParseFailure::UnknownFilter(id) => {
104 Err(anyhow!("internal error in template: unknown filter '{id}'"))
105 }
106 },
107 }
108 }
109 }
110 }
111
112 fn render(self, globals: &liquid::Object) -> anyhow::Result<Vec<u8>> {
113 match self {
114 Self::Template(t) => {
115 let text = t.render(globals)?;
116 Ok(text.bytes().collect())
117 }
118 Self::Binary(v) => Ok(v),
119 }
120 }
121}
122
123fn string_from_bytes(bytes: &[u8]) -> Option<String> {
126 match std::str::from_utf8(bytes) {
127 Ok(s) => Some(s.to_owned()),
128 Err(_) => None, }
130}
131
132enum TemplateParseFailure {
133 UnknownFilter(String),
134 Other(liquid::Error),
135}
136
137lazy_static! {
138 static ref UNKNOWN_FILTER: regex::Regex =
139 regex::Regex::new("requested filter=(\\S+)").expect("Invalid unknown filter regex");
140}
141
142fn understand_liquid_error(e: liquid::Error) -> TemplateParseFailure {
143 let err_str = e.to_string();
144
145 match err_str.lines().next() {
147 None => TemplateParseFailure::Other(e),
148 Some("liquid: Unknown filter") => match UNKNOWN_FILTER.captures(&err_str) {
149 None => TemplateParseFailure::Other(e),
150 Some(captures) => match captures.get(1) {
151 None => TemplateParseFailure::Other(e),
152 Some(id) => TemplateParseFailure::UnknownFilter(id.as_str().to_owned()),
153 },
154 },
155 _ => TemplateParseFailure::Other(e),
156 }
157}