1use crate::{Code, Concat, Format, FormatCode};
2
3#[derive(derivative::Derivative)]
5#[derivative(Debug, Clone, PartialEq)]
6pub struct List {
7 concat_body: Concat,
9 pub separator: String,
11 pub trailing: Trailing,
13 #[derivative(Debug = "ignore", PartialEq = "ignore")]
15 inline_condition: Option<fn(&List) -> bool>,
16}
17
18#[derive(Debug, Clone, PartialEq)]
20pub enum Trailing {
21 IfMultiLine,
23 Always,
25 Never,
27}
28
29impl List {
30 pub fn empty<TSep: ToString>(sep: TSep) -> Self {
32 Self {
33 separator: sep.to_string(),
34 concat_body: Concat::empty(),
35 trailing: Trailing::IfMultiLine,
36 inline_condition: None,
37 }
38 }
39
40 pub fn new<TSep, TBody>(sep: TSep, body: TBody) -> Self
42 where
43 TSep: ToString,
44 TBody: IntoIterator,
45 TBody::Item: Into<Code>,
46 {
47 Self {
48 separator: sep.to_string(),
49 concat_body: Concat::new(body),
50 trailing: Trailing::IfMultiLine,
51 inline_condition: None,
52 }
53 }
54
55 pub fn no_trail(mut self) -> Self {
57 self.trailing = Trailing::Never;
58 self
59 }
60
61 pub fn always_trail(mut self) -> Self {
63 self.trailing = Trailing::Always;
64 self
65 }
66
67 pub fn inline_when(mut self, condition: fn(&List) -> bool) -> Self {
69 self.inline_condition = Some(condition);
70 self
71 }
72
73 pub fn inlined(mut self) -> Self {
75 self.inline_condition = Some(|_| true);
76 self
77 }
78
79 pub fn never_inlined(mut self) -> Self {
81 self.inline_condition = Some(|_| false);
82 self
83 }
84
85 #[inline]
87 pub fn body(&self) -> &[Code] {
88 &self.concat_body
89 }
90
91 #[inline]
93 pub fn is_empty(&self) -> bool {
94 self.concat_body.is_empty()
95 }
96
97 pub fn should_inline(&self) -> bool {
99 if let Some(condition) = self.inline_condition {
100 condition(self)
101 } else {
102 self.should_inline_intrinsic()
103 }
104 }
105
106 pub fn should_inline_intrinsic(&self) -> bool {
110 self.body().len() == 1 && self.body()[0].should_inline()
111 }
112}
113
114impl From<List> for Code {
115 #[inline]
116 fn from(x: List) -> Self {
117 Code::List(x)
118 }
119}
120
121impl std::fmt::Display for List {
122 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123 write!(f, "{}", self.format())
124 }
125}
126
127impl FormatCode for List {
128 fn size_hint(&self) -> usize {
129 self.concat_body.size_hint()
130 }
131
132 fn format_into_vec_with(
133 &self,
134 format: &Format,
135 out: &mut Vec<String>,
136 connect: bool,
137 indent: &str,
138 ) {
139 let should_inline = self.should_inline();
140
141 let mut first_appended = false;
144 let mut previous_allow_connect = connect;
146
147 let mut previous_size = out.len();
148 let initial_size = previous_size;
149
150 for code in self.body().iter().filter(|c| !c.is_empty()) {
151 if let Some(last) = out.last_mut() {
153 if first_appended {
154 last.push_str(&self.separator);
155 }
156 }
157 let connect = if first_appended {
158 should_inline
159 || (previous_allow_connect && {
160 match code {
162 Code::Block(b) => !b.should_inline(),
163 _ => true,
164 }
165 })
166 } else {
167 connect
169 };
170 code.format_into_vec_with(format, out, connect, indent);
172 let new_size = out.len();
175 previous_allow_connect = new_size > previous_size + 1;
176 previous_size = new_size;
177 first_appended = true;
178 }
179
180 let should_trail = match self.trailing {
181 Trailing::IfMultiLine => previous_size > initial_size + 1,
182 Trailing::Always => true,
183 Trailing::Never => false,
184 };
185 if should_trail {
186 if let Some(last) = out.last_mut() {
187 last.push_str(&self.separator);
188 }
189 }
190 }
191}
192
193#[macro_export]
224macro_rules! clist {
225 ($sep:expr => []) => {
226 $crate::List::empty($sep)
227 };
228 ($sep:expr => [ $( $body:expr ),* $(,)? ]) => {
229 $crate::List::new($sep, [ $($crate::Code::from($body)),* ])
230 };
231 ($sep:expr => $body:expr) => {
232 $crate::List::new($sep, $body)
233 };
234}
235
236#[cfg(test)]
237mod test {
238 use indoc::indoc;
239
240 use crate::{cblock, Block, Code, List};
241
242 #[test]
243 fn empty() {
244 let code = clist!("," => []);
245 assert_eq!("", code.to_string());
246 }
247
248 #[test]
249 fn one() {
250 let code = clist!("," => ["hello"]);
251 assert_eq!("hello", code.to_string());
252 }
253
254 #[test]
255 fn one_trail() {
256 let code = clist!("," => ["hello"]).always_trail();
257 assert_eq!("hello,", code.to_string());
258 }
259
260 #[test]
261 fn many() {
262 let code = clist!("," => ["hello", "hello2"]);
263 assert_eq!("hello,\nhello2,", code.to_string());
264 }
265
266 #[test]
267 fn many_no_trail() {
268 let code = clist!("," => ["hello", "hello2"]).no_trail();
269 assert_eq!("hello,\nhello2", code.to_string());
270 }
271
272 #[test]
273 fn with_blocks() {
274 let expected = indoc! {"
276 { a },
277 {
278 hello,
279 hello2
280 }, {
281 foo,
282 bar,
283 },
284 { b },
285 { c },"};
286
287 let expected_inline = indoc! {"
288 { a }, {
289 hello,
290 hello2
291 }, {
292 foo,
293 bar,
294 }, { b }, { c },"};
295
296 fn should_inline_block(c: &Block) -> bool {
297 if let Some(Code::Line(s)) = c.body().first() {
298 s.len() == 1
299 } else {
300 false
301 }
302 }
303 let code = clist!("," => [
304 cblock!("{", ["a"], "}").inline_when(should_inline_block),
305 cblock!("{", [clist!("," => ["hello", "hello2"]).no_trail()], "}"),
306 cblock!("{", [clist!("," => ["foo", "bar"])], "}"),
307 cblock!("{", ["b"], "}").inline_when(should_inline_block),
308 cblock!("{", ["c"], "}").inline_when(should_inline_block),
309 ]);
310 assert_eq!(expected, code.to_string());
311 assert_eq!(expected_inline, code.inline_when(|_| true).to_string());
312 }
313
314 #[test]
315 fn multiple_levels() {
316 let expected = indoc! {"
317 a, b, c,
318 d, e, f,
319 x, y, z,"};
320 fn always(_: &List) -> bool {
321 true
322 }
323 let code = clist!("," => [
324 clist!("," => ["a", "b", "c"]).inline_when(always),
325 clist!("," => ["d", "e", "f"]).inline_when(always),
326 clist!("," => ["x", "y", "z"]).inline_when(always),
327 ]);
328 assert!(!code.should_inline());
329 assert_eq!(expected, code.to_string());
330 }
331}