jens/
block.rs

1use crate::parser::{segment::Segment, template::Template};
2use std::fmt;
3
4/// When mapping over an iterable, this returns the location of the current iteration
5pub enum IteratorLocation {
6    /// This item is the first item in the iterable
7    First,
8
9    /// This item is not the first or last item, but the nth item
10    Nth(usize),
11
12    /// This item is the last in the iterable
13    Last,
14
15    /// This item is the only item in the iterable (ie, the first AND last)
16    Only,
17}
18
19/// Replace every character in a string with a space, but preserve tabs
20fn 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/// Represents a segment of a line, potentially containing another block
32#[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/// Represents a single line inside a block of text
70#[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/// A `Block` is one or many lines of text. More blocks can be embedded within a
105/// line, in which case the indentation of the previous line will be preserved when
106/// outputting new lines.
107#[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    /// Run a function that maps over each item in an iterator, then join the results.
154    ///
155    /// Provides an `IteratorLocation` for checking whether the current item is the
156    /// first/last/only/nth item in the list.
157    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    /// Repeat a template for each element of some iterable value.
179    ///
180    /// Deprecated in favor of `join_map`.
181    #[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    /// Join multiple blocks into a single block
191    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            // Add correct amount of whitespace at the beginning of the block
214            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}