1use crate::script::eval_ctx::EvalCtx;
2use miette::Result;
3
4use super::{comment_style::CommentStyle, error::TemplateError, parser::Sp};
5
6#[derive(Debug, Eq, PartialEq, arbitrary::Arbitrary)]
8pub struct TaggedLine<'a> {
9 pub left: &'a str,
10 pub tag: &'a str,
11 pub right: &'a str,
12 pub full_line: Sp<&'a str>,
13}
14
15#[derive(Debug, Eq, PartialEq, arbitrary::Arbitrary)]
19pub struct Block<'a, Expr = Sp<&'a str>> {
20 pub tagged_line: TaggedLine<'a>,
22 pub expr: Expr,
23 pub body: Vec<Element<'a>>,
24}
25
26impl<'a, Expr> Block<'a, Expr> {
27 pub fn map_expr<T>(self, f: impl FnOnce(Expr) -> T) -> Block<'a, T> {
28 Block {
29 tagged_line: self.tagged_line,
30 expr: f(self.expr),
31 body: self.body,
32 }
33 }
34}
35
36#[derive(Debug, Eq, PartialEq, arbitrary::Arbitrary)]
37pub enum Element<'a> {
38 Plain(Sp<&'a str>),
39 Inline {
40 line: TaggedLine<'a>,
42 expr: Sp<&'a str>,
43 is_if: bool,
44 },
45 NextLine {
46 tagged_line: TaggedLine<'a>,
48 expr: Sp<&'a str>,
49 next_line: Sp<&'a str>,
50 is_if: bool,
51 full_span: Sp<&'a str>,
52 },
53 MultiLine {
54 block: Block<'a, Sp<&'a str>>,
55 end: TaggedLine<'a>,
56 full_span: Sp<&'a str>,
57 },
58 Conditional {
59 blocks: Vec<Block<'a, Sp<&'a str>>>,
60 else_block: Option<Block<'a, ()>>,
61 end: TaggedLine<'a>,
62 full_span: Sp<&'a str>,
63 },
64}
65
66impl<'a> Element<'a> {
67 #[allow(unused)]
68 pub fn try_from_str(s: &'a str) -> Result<Self> {
69 use crate::templating::parser;
70 use miette::IntoDiagnostic as _;
71 parser::parse_element(s).into_diagnostic()
72 }
73
74 pub fn full_span(&self) -> &Sp<&str> {
75 match self {
76 Element::Plain(sp) => &sp,
77 Element::Inline { line, .. } => &line.full_line,
78 Element::NextLine { full_span, .. } => full_span,
79 Element::MultiLine { full_span, .. } => full_span,
80 Element::Conditional { full_span, .. } => full_span,
81 }
82 }
83
84 pub fn render(
85 &self,
86 comment_style: &CommentStyle,
87 eval_ctx: &mut EvalCtx,
88 ) -> Result<String, TemplateError> {
89 match self {
90 Element::Plain(s) => Ok(s.as_str().to_string()),
91 Element::Inline { line, expr, is_if } => match is_if {
92 true => {
93 let eval_result = eval_ctx
94 .eval_rhai::<bool>(expr.as_str())
95 .map_err(|e| TemplateError::from_rhai(e, expr.range()))?;
96 Ok(comment_style.toggle_string(line.full_line.as_str(), eval_result))
97 }
98 false => Ok(format!(
99 "{}{}{}",
100 run_transformation_expr(eval_ctx, line.left, expr)?,
101 line.tag,
102 line.right
103 )),
104 },
105 Element::NextLine {
106 tagged_line: line,
107 expr,
108 next_line,
109 is_if,
110 ..
111 } => match is_if {
112 true => Ok(format!(
113 "{}{}",
114 line.full_line.as_str(),
115 &comment_style.toggle_string(
116 next_line.as_str(),
117 eval_ctx
118 .eval_rhai::<bool>(expr.as_str())
119 .map_err(|e| TemplateError::from_rhai(e, expr.range()))?
120 )
121 )),
122 false => Ok(format!(
123 "{}{}",
124 line.full_line.as_str(),
125 run_transformation_expr(eval_ctx, next_line.as_str(), expr)?
126 )),
127 },
128 Element::MultiLine { block, end, .. } => {
129 let rendered_body = render_elements(comment_style, eval_ctx, &block.body)?;
130 Ok(format!(
131 "{}{}{}",
132 block.tagged_line.full_line.as_str(),
133 &run_transformation_expr(eval_ctx, &rendered_body, &block.expr)?,
134 end.full_line.as_str(),
135 ))
136 }
137 Element::Conditional {
138 blocks,
139 else_block,
140 end,
141 ..
142 } => {
143 let mut output = String::new();
144 let mut had_true = false;
145 for block in blocks {
146 let expr_true = !had_true
150 && eval_ctx
151 .eval_rhai::<bool>(block.expr.as_str())
152 .map_err(|e| TemplateError::from_rhai(e, block.expr.range()))?;
153 had_true = had_true || expr_true;
154
155 let rendered_body = if expr_true {
156 render_elements(comment_style, eval_ctx, &block.body)?
157 } else {
158 render_no_eval(&block.body)
159 };
160 output.push_str(block.tagged_line.full_line.as_str());
161 output.push_str(&comment_style.toggle_string(&rendered_body, expr_true));
162 }
163 if let Some(block) = else_block {
164 let expr_true = !had_true;
165 let rendered_body = render_elements(comment_style, eval_ctx, &block.body)?;
166 output.push_str(block.tagged_line.full_line.as_str());
167 output.push_str(&comment_style.toggle_string(&rendered_body, expr_true));
168 }
169 output.push_str(end.full_line.as_str());
170 Ok(output)
171 }
172 }
173 }
174}
175
176pub fn render_elements(
177 comment_style: &CommentStyle,
178 eval_ctx: &mut EvalCtx,
179 elements: &[Element<'_>],
180) -> Result<String, TemplateError> {
181 let mut errs = Vec::new();
182 let mut output = String::new();
183 for element in elements {
184 match element.render(comment_style, eval_ctx) {
185 Ok(rendered) => output.push_str(&rendered),
186 Err(e) => errs.push(e),
187 }
188 }
189 if errs.is_empty() {
190 Ok(output)
191 } else {
192 Err(TemplateError::Multiple(errs))
193 }
194}
195
196pub fn render_no_eval(elements: &[Element<'_>]) -> String {
197 elements.iter().map(|x| x.full_span().as_str()).collect()
198}
199
200fn run_transformation_expr(
201 eval_ctx: &mut EvalCtx,
202 text: &str,
203 expr: &Sp<&str>,
204) -> Result<String, TemplateError> {
205 let result = eval_ctx
206 .eval_text_transformation(text, expr.as_str())
207 .map_err(|e| TemplateError::from_rhai(e, expr.range()))?;
208 let second_pass = eval_ctx
209 .eval_text_transformation(&result, expr.as_str())
210 .map_err(|e| TemplateError::from_rhai(e, expr.range()))?;
211 if result != second_pass {
212 cov_mark::hit!(refuse_nonidempodent_transformation);
213 println!(
214 "Warning: Refusing to apply transformation that is not idempodent: `{}`",
215 expr.as_str()
216 );
217 Ok(text.to_string())
218 } else {
219 Ok(result)
220 }
221}