1use crate::parser::{segment::Segment, template::Template};
2use std::fmt;
3
4pub enum IteratorLocation {
6 First,
8
9 Nth(usize),
11
12 Last,
14
15 Only,
17}
18
19fn replace_chars_with_whitespace(line: &str) -> String {
21 let mut out = String::with_capacity(line.len());
22 for c in line.chars() {
23 match c {
24 '\t' => out.push('\t'),
25 _ => out.push(' '),
26 }
27 }
28 out
29}
30
31#[derive(Clone, PartialEq, Debug)]
33pub enum LineSegment {
34 Content(String),
35 Placeholder(String),
36 Block(Block),
37 EndOfInput,
38}
39
40impl LineSegment {
41 fn write_to(&self, f: &mut fmt::Formatter, prefix: &str) -> fmt::Result {
42 match self {
43 LineSegment::Content(s) => write!(f, "{}", s),
44 LineSegment::Placeholder(s) => write!(f, "${{{}}}", s),
45 LineSegment::Block(b) => {
46 let prefix = replace_chars_with_whitespace(prefix);
47 b.write_to(f, &prefix)
48 }
49 LineSegment::EndOfInput => Ok(()),
50 }
51 }
52
53 fn replace(&mut self, new_segment: LineSegment) {
54 match self.clone() {
55 LineSegment::Placeholder(_) => {
56 *self = new_segment;
57 }
58 _ => (),
59 }
60 }
61}
62
63impl<T: Into<String>> From<T> for LineSegment {
64 fn from(v: T) -> Self {
65 LineSegment::Content(v.into())
66 }
67}
68
69#[derive(Clone, PartialEq, Debug)]
71pub struct Line(pub Vec<LineSegment>);
72
73impl<T: Into<String>> From<T> for Line {
74 fn from(v: T) -> Self {
75 Line(vec![LineSegment::Content(v.into())])
76 }
77}
78
79impl Line {
80 fn write_to(&self, f: &mut fmt::Formatter, prefix: &str) -> fmt::Result {
81 let mut sub_prefix = String::from(prefix);
82 for segment in &self.0 {
83 match segment {
84 LineSegment::Content(x) => sub_prefix = sub_prefix + x,
85 _ => (),
86 }
87 segment.write_to(f, &sub_prefix)?;
88 }
89 Ok(())
90 }
91
92 pub fn set(&mut self, placeholder_name: &str, content: &Block) {
93 for segment in &mut self.0 {
94 match segment.clone() {
95 LineSegment::Placeholder(ref name) if name == placeholder_name => {
96 segment.replace(LineSegment::Block(content.clone()));
97 }
98 _ => (),
99 }
100 }
101 }
102}
103
104#[derive(Clone, PartialEq, Debug)]
108pub struct Block(pub Vec<Line>);
109
110impl From<&Block> for String {
111 fn from(v: &Block) -> Self {
112 v.into()
113 }
114}
115
116impl<T: Into<String>> From<T> for Block {
117 fn from(v: T) -> Self {
118 Block(vec![Line::from(v.into())])
119 }
120}
121
122impl fmt::Display for Block {
123 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124 self.write_to(f, "")
125 }
126}
127
128impl Block {
129 pub fn empty() -> Self {
130 Block(vec![])
131 }
132
133 pub fn write_to(&self, f: &mut fmt::Formatter, prefix: &str) -> fmt::Result {
134 let mut first_line = true;
135 for line in &self.0 {
136 if !first_line {
137 write!(f, "\n{}", prefix).unwrap();
138 }
139 first_line = false;
140 line.write_to(f, prefix)?;
141 }
142 Ok(())
143 }
144
145 pub fn set<T: Into<Block>>(mut self, placeholder_name: &str, content: T) -> Self {
146 let content: &Block = &content.into();
147 for line in &mut self.0 {
148 line.set(placeholder_name, content);
149 }
150 self
151 }
152
153 pub fn join_map<T, U, F>(iter: T, mapper: F) -> Self
158 where
159 T: IntoIterator<Item = U>,
160 F: Fn(U, IteratorLocation) -> Block,
161 {
162 let items: Vec<U> = iter.into_iter().collect();
163 let count = items.len();
164 match count {
165 0 => Block::empty(),
166 1 => mapper(items.into_iter().next().unwrap(), IteratorLocation::Only),
167 count => Block::join(items.into_iter().enumerate().map(|(i, item)| {
168 let loc = match (i, count) {
169 (0, _) => IteratorLocation::First,
170 (x, y) if x == y - 1 => IteratorLocation::Last,
171 (x, _) => IteratorLocation::Nth(x),
172 };
173 mapper(item, loc)
174 })),
175 }
176 }
177
178 #[deprecated]
182 pub fn for_each<T, U, F>(self, iter: T, mapper: F) -> Self
183 where
184 T: IntoIterator<Item = U>,
185 F: Fn(U, Block) -> Block,
186 {
187 Block::join(iter.into_iter().map(|item| mapper(item, self.clone())))
188 }
189
190 pub fn join<T>(blocks: T) -> Block
192 where
193 T: IntoIterator<Item = Block>,
194 {
195 Block(
196 blocks
197 .into_iter()
198 .map(|block| Line(vec![LineSegment::Block(block)]))
199 .collect(),
200 )
201 }
202}
203
204impl<'a> From<&'a Template> for Block {
205 fn from(t: &'a Template) -> Self {
206 let mut lines: Vec<Line> = Vec::with_capacity(t.lines.len());
207 let indent_ignored = t.indent_ignored;
208
209 for template_line in &t.lines {
210 let mut segments: Vec<LineSegment> =
211 Vec::with_capacity(template_line.segments.len() + 1);
212
213 let indentation_len = template_line.indentation.len();
215 if indentation_len > indent_ignored {
216 let indentation: &str = &template_line.indentation[indent_ignored..];
217 segments.push(LineSegment::Content(String::from(indentation)));
218 }
219
220 for template_segment in &template_line.segments {
221 segments.push(match template_segment {
222 Segment::Placeholder(x) => LineSegment::Placeholder(x.clone()),
223 Segment::Content(x) => LineSegment::Content(x.clone()),
224 })
225 }
226 lines.push(Line(segments));
227 }
228 Block(lines)
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn outputs_a_block_with_correct_indentation() {
238 use insta::assert_snapshot_matches;
239 use std::fmt::Write;
240
241 let arg_list = Block(vec![
242 Line(vec![LineSegment::from("arg1: string,")]),
243 Line(vec![LineSegment::from("arg2: number,")]),
244 Line(vec![LineSegment::from("arg3: Object")]),
245 ]);
246
247 let function_body = Block(vec![
248 Line(vec![LineSegment::from("body();")]),
249 Line(vec![LineSegment::from("body2();")]),
250 ]);
251
252 let function = Block(vec![
253 Line(vec![
254 LineSegment::from("function test("),
255 LineSegment::Block(arg_list),
256 LineSegment::from(") {"),
257 ]),
258 Line(vec![
259 LineSegment::from(" "),
260 LineSegment::Block(function_body.clone()),
261 ]),
262 Line(vec![LineSegment::from("}")]),
263 ]);
264
265 let mut s = String::new();
266 write!(&mut s, "{}", function).unwrap();
267 assert_snapshot_matches!("block.outputs_a_block_with_correct_indentation", s);
268 }
269
270 #[test]
271 fn replaces_a_placeholder() {
272 use insta::assert_debug_snapshot_matches;
273 let block = Block(vec![Line(vec![
274 LineSegment::from("A"),
275 LineSegment::Placeholder(String::from("x")),
276 LineSegment::from("C"),
277 ])]);
278 let block = block.set("x", Block(vec![Line(vec![LineSegment::from("B")])]));
279
280 assert_debug_snapshot_matches!("block.replaces_a_placeholder", block);
281 }
282}