kotlin_poet_rs/spec/
code_block.rs

1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3use crate::io::{CodeBuffer, RenderKotlin};
4use crate::tokens;
5use crate::util::{SemanticConversionError, yolo_from_str};
6
7/// Node of a code block that can be rendered to a Kotlin code.
8/// You can treat these nodes as commands for rendering, like "add atom", "add new line", etc.
9#[derive(Debug, Clone)]
10pub(crate) enum CodeBlockNode {
11    Atom(String),
12    StaticAtom(&'static str),
13    Space,
14    NewLine,
15    Indent(usize),
16    Unindent(usize),
17}
18
19/// Plain list of nodes that can be rendered to a Kotlin code.
20#[derive(Debug, Clone)]
21pub struct CodeBlock {
22    pub(crate) nodes: Vec<CodeBlockNode>,
23}
24
25impl CodeBlock {
26    /// Create an empty code block.
27    pub fn empty() -> CodeBlock {
28        CodeBlock {
29            nodes: vec![],
30        }
31    }
32
33    /// Creates code block with a single atom node.
34    /// Please avoid using it in [RenderKotlin::render], prefer [CodeBlock::empty] and [CodeBlock::push_atom] instead.
35    pub fn atom(text: &str) -> CodeBlock {
36        CodeBlock {
37            nodes: vec![
38                CodeBlockNode::Atom(
39                    text.to_string()
40                )
41            ],
42        }
43    }
44
45    /// Creates code block with a single atom node and empty line.
46    pub fn statement(text: &str) -> CodeBlock {
47        let mut cb = CodeBlock::atom(text);
48        cb.push_new_line();
49        cb
50    }
51
52    /// Pushes [text] as atom and adds new line after it.
53    pub fn push_statement(&mut self, text: &str) {
54        self.push_atom(text);
55        self.push_new_line();
56    }
57
58    /// Embeds all node from [code_block] into [self].
59    pub fn push_renderable<T: RenderKotlin>(&mut self, renderable: &T) {
60        renderable.render_into(self);
61    }
62
63    /// Adds [CodeBlockNode::Indent] with value 1.
64    /// In case there is already [CodeBlockNode::Indent] at the end of the list, increments its value.
65    pub fn push_indent(&mut self) {
66        if let Some(CodeBlockNode::Indent(last_value)) = self.nodes.last_mut() {
67            *last_value += 1;
68            return;
69        }
70        self.nodes.push(CodeBlockNode::Indent(1));
71    }
72
73    /// Adds [CodeBlockNode::Unindent] with value 1
74    /// In case there is already [CodeBlockNode::Unindent] at the end of the list, increments its value.
75    pub fn push_unindent(&mut self) {
76        if let Some(CodeBlockNode::Unindent(last_value)) = self.nodes.last_mut() {
77            *last_value += 1;
78            return;
79        }
80        self.nodes.push(CodeBlockNode::Unindent(1));
81    }
82
83    /// Adds [CodeBlockNode::NewLine]
84    pub fn push_new_line(&mut self) {
85        self.nodes.push(CodeBlockNode::NewLine);
86    }
87
88    /// Adds [CodeBlockNode::Atom]
89    pub fn push_atom(&mut self, text: &str) {
90        if let Some(CodeBlockNode::Atom(inner_buffer)) = self.nodes.last_mut() {
91            inner_buffer.push_str(text);
92            return;
93        }
94        self.nodes.push(CodeBlockNode::Atom(text.to_string()));
95    }
96
97    /// Adds [CodeBlockNode::Atom]
98    pub(crate) fn push_static_atom(&mut self, text: &'static str) {
99        self.nodes.push(CodeBlockNode::StaticAtom(text));
100    }
101
102    /// Adds [CodeBlockNode::Space]
103    pub fn push_space(&mut self) {
104        if matches!(self.nodes.last(), Some(CodeBlockNode::Space)) {
105            return; // no double spaces
106        }
107        self.nodes.push(CodeBlockNode::Space);
108    }
109
110    /// Removes last [CodeBlockNode::Space] if exists
111    pub fn pop_space(&mut self) {
112        if matches!(self.nodes.last(), Some(CodeBlockNode::Space)) {
113            self.nodes.remove(self.nodes.len() - 1);
114        }
115    }
116
117    /// Surrounds first parameter [block] with curly brackets + indent and adds it to [self].
118    pub fn push_curly_brackets<F>(&mut self, block: F)
119    where
120        F: FnOnce(&mut CodeBlock),
121    {
122        let mut inner_code = CodeBlock::empty();
123
124        self.push_static_atom(tokens::CURLY_BRACKET_LEFT);
125        self.push_new_line();
126        self.push_indent();
127        block(&mut inner_code);
128        self.push_renderable(&inner_code);
129        self.push_unindent();
130        self.push_static_atom(tokens::CURLY_BRACKET_RIGHT);
131    }
132
133    /// Surrounds first parameter [block] with round brackets and adds it to [self].
134    pub fn push_round_brackets<F>(&mut self, block: F)
135    where
136        F: FnOnce(&mut CodeBlock),
137    {
138        let mut inner_code = CodeBlock::empty();
139
140        self.push_static_atom(tokens::ROUND_BRACKET_LEFT);
141        block(&mut inner_code);
142        self.push_renderable(&inner_code);
143        self.push_static_atom(tokens::ROUND_BRACKET_RIGHT);
144    }
145
146    /// Surrounds first parameter [block] with angle brackets and adds it to [self].
147    pub fn push_angle_brackets<F>(&mut self, block: F)
148    where
149        F: FnOnce(&mut CodeBlock),
150    {
151        let mut inner_code = CodeBlock::empty();
152
153        self.push_static_atom(tokens::ANGLE_BRACKET_LEFT);
154        block(&mut inner_code);
155        self.push_renderable(&inner_code);
156        self.push_static_atom(tokens::ANGLE_BRACKET_RIGHT);
157    }
158
159    /// Adds all elements from [elements] with comma separation, except for last one
160    pub fn push_comma_separated<F>(&mut self, elements: &[F])
161    where
162        F: RenderKotlin,
163    {
164        let mut code = CodeBlock::empty();
165        let len = elements.len();
166        for (index, renderable) in elements.iter().enumerate() {
167            code.push_renderable(renderable);
168            if index != len - 1 {
169                code.push_static_atom(tokens::COMMA);
170                code.push_space();
171            }
172        }
173
174        self.push_renderable(&code);
175    }
176
177    fn push_indent_into(indent: usize, root_buffer: &mut CodeBuffer) {
178        if matches!(root_buffer.last_char(), Some(tokens::NEW_LINE_CH)) {
179            for _ in 0..indent {
180                root_buffer.push(tokens::INDENT)
181            }
182        }
183    }
184
185    fn render(&self) -> String {
186        let mut root_buffer = CodeBuffer::default();
187        let mut indent = 0;
188
189        for node in &self.nodes {
190            match node {
191                CodeBlockNode::Atom(buffer) => {
192                    Self::push_indent_into(indent, &mut root_buffer);
193                    root_buffer.push(buffer.as_str());
194                }
195                CodeBlockNode::StaticAtom(buffer) => {
196                    Self::push_indent_into(indent, &mut root_buffer);
197                    root_buffer.push(buffer);
198                }
199                CodeBlockNode::Indent(size) => {
200                    indent += size;
201                }
202                CodeBlockNode::Unindent(size) => {
203                    if *size > indent {
204                        indent = 0;
205                        continue;
206                    }
207                    indent -= size;
208                }
209                CodeBlockNode::Space => {
210                    root_buffer.push(tokens::SPACE)
211                }
212                CodeBlockNode::NewLine => {
213                    root_buffer.push(tokens::NEW_LINE);
214                }
215            }
216        }
217
218        root_buffer.trim();
219        root_buffer.into_string()
220    }
221}
222
223impl Display for CodeBlock {
224    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
225        f.write_str(self.render().as_str())
226    }
227}
228
229yolo_from_str!(CodeBlock);
230impl FromStr for CodeBlock {
231    type Err = SemanticConversionError;
232
233    fn from_str(s: &str) -> Result<Self, Self::Err> {
234        Ok(CodeBlock::atom(s))
235    }
236}
237
238impl RenderKotlin for CodeBlock {
239    fn render_into(&self, block: &mut CodeBlock) {
240        block.nodes.extend(self.nodes.iter().cloned());
241    }
242}