codize/
lib.rs

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/// Code structure
11///
12/// You should use the macros or `into` conversion instead of constructing this directly.
13#[derive(Debug, Clone, PartialEq)]
14pub enum Code {
15    /// A line of code.
16    Line(String),
17    /// A block of code. See [`Block`]
18    Block(Box<Block>),
19    /// Concatenation of multiple code sections. See [`Concat`]
20    Concat(Concat),
21    /// A list of code segments with separator. See [`List`]
22    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/// Formatting options
38#[derive(derivative::Derivative)]
39#[derivative(Debug, PartialEq, Default)]
40pub struct Format {
41    /// The number of spaces to indent per level. `-1` to use tabs
42    #[derivative(Default(value = "4"))]
43    pub indent: i32,
44}
45
46impl Format {
47    /// Set indent
48    pub fn indent(indent: i32) -> Self {
49        Self::default().set_indent(indent)
50    }
51    /// Set indent
52    #[inline]
53    pub fn set_indent(mut self, indent: i32) -> Self {
54        self.indent = indent;
55        self
56    }
57    /// Set indent to tabs
58    pub fn indent_tab() -> Self {
59        Self::indent(-1)
60    }
61    /// Set indent to tabs
62    #[inline]
63    pub fn set_indent_tab(self) -> Self {
64        self.set_indent(-1)
65    }
66}
67
68/// Enable different formatting options for [`Code`] structures
69pub trait FormatCode {
70    /// Emit self with the default format as a string
71    fn format(&self) -> String {
72        self.format_with(&Format::default())
73    }
74
75    /// Emit self with the format as a string
76    fn format_with(&self, format: &Format) -> String {
77        self.format_vec_with(format).join("\n")
78    }
79    /// Emit self with the format as a vector of lines
80    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        // ensure no reallocation
88        #[cfg(test)]
89        if size_hint > 0 {
90            assert_eq!(out.capacity(), size_hint);
91        }
92        out
93    }
94    /// Emit self with the format in the given output context
95    fn format_into_vec_with(
96        &self,
97        format: &Format,
98        out: &mut Vec<String>,
99        connect: bool,
100        indent: &str,
101    );
102    /// Upperbound for the line count of the code for pre-allocating. Return 0 to skip
103    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
138/// Helper function to append one line to the output within the given context
139pub(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    // when making a new line, make sure the previous line is not indented if it's only whitespaces
150    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    /// Should the code be displayed in one line
164    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    /// Get if this structure will generate any code or not (empty = no code)
173    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}