1#![doc = include_str!("../README.md")]
2
3mod block;
4pub use block::Block;
5mod concat;
6pub use concat::Concat;
7mod list;
8pub use list::{List, Trailing};
9
10#[derive(Debug, Clone, PartialEq)]
14pub enum Code {
15 Line(String),
17 Block(Box<Block>),
19 Concat(Concat),
21 List(List),
23}
24
25impl From<String> for Code {
26 fn from(x: String) -> Self {
27 Code::Line(x)
28 }
29}
30
31impl From<&str> for Code {
32 fn from(x: &str) -> Self {
33 Code::Line(x.to_owned())
34 }
35}
36
37#[derive(derivative::Derivative)]
39#[derivative(Debug, PartialEq, Default)]
40pub struct Format {
41 #[derivative(Default(value = "4"))]
43 pub indent: i32,
44}
45
46impl Format {
47 pub fn indent(indent: i32) -> Self {
49 Self::default().set_indent(indent)
50 }
51 #[inline]
53 pub fn set_indent(mut self, indent: i32) -> Self {
54 self.indent = indent;
55 self
56 }
57 pub fn indent_tab() -> Self {
59 Self::indent(-1)
60 }
61 #[inline]
63 pub fn set_indent_tab(self) -> Self {
64 self.set_indent(-1)
65 }
66}
67
68pub trait FormatCode {
70 fn format(&self) -> String {
72 self.format_with(&Format::default())
73 }
74
75 fn format_with(&self, format: &Format) -> String {
77 self.format_vec_with(format).join("\n")
78 }
79 fn format_vec_with(&self, format: &Format) -> Vec<String> {
81 let size_hint = self.size_hint();
82 let mut out = match size_hint {
83 0 => Vec::new(),
84 n => Vec::with_capacity(n),
85 };
86 self.format_into_vec_with(format, &mut out, false, "");
87 #[cfg(test)]
89 if size_hint > 0 {
90 assert_eq!(out.capacity(), size_hint);
91 }
92 out
93 }
94 fn format_into_vec_with(
96 &self,
97 format: &Format,
98 out: &mut Vec<String>,
99 connect: bool,
100 indent: &str,
101 );
102 fn size_hint(&self) -> usize;
104}
105
106impl std::fmt::Display for Code {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 write!(f, "{}", self.format())
109 }
110}
111
112impl FormatCode for Code {
113 fn format_into_vec_with(
114 &self,
115 format: &Format,
116 out: &mut Vec<String>,
117 connect: bool,
118 indent: &str,
119 ) {
120 match self {
121 Code::Line(line) => append_line(out, line, connect, indent),
122 Code::Block(body) => body.format_into_vec_with(format, out, connect, indent),
123 Code::Concat(body) => body.format_into_vec_with(format, out, connect, indent),
124 Code::List(body) => body.format_into_vec_with(format, out, connect, indent),
125 }
126 }
127
128 fn size_hint(&self) -> usize {
129 match self {
130 Code::Line(_) => 1,
131 Code::Block(body) => body.size_hint(),
132 Code::Concat(body) => body.size_hint(),
133 Code::List(body) => body.size_hint(),
134 }
135 }
136}
137
138pub(crate) fn append_line(out: &mut Vec<String>, line: &str, connect: bool, indent: &str) {
140 if connect {
141 if let Some(last) = out.last_mut() {
142 if !last.is_empty() && last != indent {
143 last.push(' ');
144 }
145 last.push_str(line.as_ref());
146 return;
147 }
148 }
149 if let Some(last) = out.last_mut() {
151 if last.trim().is_empty() {
152 "".clone_into(last);
153 }
154 }
155 if indent.is_empty() {
156 out.push(line.to_owned());
157 } else {
158 out.push(format!("{indent}{line}"));
159 }
160}
161
162impl Code {
163 pub fn should_inline(&self) -> bool {
165 match self {
166 Code::Block(block) => block.should_inline(),
167 Code::List(list) => list.should_inline(),
168 _ => false,
169 }
170 }
171
172 pub fn is_empty(&self) -> bool {
174 match self {
175 Code::Concat(concat) => concat.is_empty(),
176 Code::List(list) => list.is_empty(),
177 _ => false,
178 }
179 }
180}
181
182#[cfg(test)]
183mod test {
184 use indoc::indoc;
185
186 use super::*;
187
188 fn test_case_1() -> Code {
189 cblock!("{", [], "}").into()
190 }
191
192 fn test_case_2() -> Code {
193 cblock!("trait A {", ["fn a();"], "}").into()
194 }
195
196 fn test_case_3() -> Code {
197 cblock!(
198 "fn main() {",
199 [
200 cblock!("if (foo) {", ["println!(\"Hello, world!\");"], "}"),
201 cblock!("else {", [format!("bar({});", "giz")], "}").connected(),
202 ],
203 "}"
204 )
205 .into()
206 }
207
208 fn test_case_4(f1: fn(&Block) -> bool, f2: fn(&List) -> bool) -> Code {
209 let body = vec![
210 Code::from("let x = 1;"),
211 cblock!(
212 "let b = {",
213 [clist!("," => ["1", "2", "3"]).inline_when(f2)],
214 "};"
215 )
216 .inline_when(f1)
217 .into(),
218 cblock!(
219 "let b = {",
220 [clist!("," => ["1", "2", "3", "4"]).inline_when(f2)],
221 "};"
222 )
223 .inline_when(f1)
224 .into(),
225 ];
226 cblock!("while true {", body, "}").into()
227 }
228
229 #[test]
230 fn test1() {
231 let code = test_case_1();
232 assert_eq!("{\n}", code.to_string());
233 }
234
235 #[test]
236 fn test2() {
237 let code = test_case_2();
238 let expected = indoc! {"
239 trait A {
240 fn a();
241 }"};
242 assert_eq!(expected, code.format_with(&Format::indent(3)));
243 let expected = indoc! {"
244 trait A {
245 \tfn a();
246 }"};
247 assert_eq!(expected, code.format_with(&Format::indent_tab()));
248 }
249
250 #[test]
251 fn test3() {
252 let code: Code = test_case_3();
253 let expected = indoc! {"
254 fn main() {
255 if (foo) {
256 println!(\"Hello, world!\");
257 } else {
258 bar(giz);
259 }
260 }"};
261 assert_eq!(expected, code.to_string());
262 }
263
264 #[test]
265 fn test4() {
266 fn should_inline_list(list: &List) -> bool {
267 list.body().len() == 3
268 }
269 let code = test_case_4(Block::should_inline_intrinsic, should_inline_list);
270 let expected = indoc! {"
271 while true {
272 let x = 1;
273 let b = { 1, 2, 3 };
274 let b = {
275 1,
276 2,
277 3,
278 4,
279 };
280 }"};
281 assert_eq!(expected, code.to_string());
282
283 let code = test_case_4(Block::should_inline_intrinsic, |_| true);
284 let expected = indoc! {"
285 while true {
286 let x = 1;
287 let b = { 1, 2, 3 };
288 let b = { 1, 2, 3, 4 };
289 }"};
290 assert_eq!(expected, code.to_string());
291
292 let code = test_case_4(|_| false, |_| true);
293 let expected = indoc! {"
294 while true {
295 let x = 1;
296 let b = {
297 1, 2, 3
298 };
299 let b = {
300 1, 2, 3, 4
301 };
302 }"};
303 assert_eq!(expected, code.to_string());
304 }
305}