Skip to main content

cdk_from_cfn/code/
mod.rs

1use std::borrow::Cow;
2use std::cell::RefCell;
3use std::io;
4use std::io::Write;
5use std::rc::Rc;
6
7/// A `CodeBuffer` is a buffer that can be used to generate code without having
8/// to keep track of identation. A `CodeBuffer` contains either plain text which
9/// will be indented accoridng to the buffer's own indent, or nested
10/// `CodeBuffer`s which will be intended according to their own indent, on top
11/// of the containing buffer's indent.
12pub struct CodeBuffer {
13    indent: Cow<'static, str>,
14    content: RefCell<Vec<CodeBufferContent>>,
15}
16
17impl CodeBuffer {
18    /// Creates a new `CodeBuffer` with no identation.
19    pub const fn new() -> Self {
20        Self::with_indent(Cow::Borrowed(""))
21    }
22
23    /// Adds a single newline character to this code buffer.
24    #[inline]
25    pub fn newline(&self) {
26        self.line(Cow::Borrowed(""))
27    }
28
29    /// Adds text into the buffer, followed by a new line.
30    pub fn line(&self, text: impl Into<Cow<'static, str>>) {
31        let mut content = self.content.borrow_mut();
32        content.push(CodeBufferContent::String(text.into(), true));
33    }
34
35    /// Adds text into the buffer, as-is.
36    pub fn text(&self, text: impl Into<Cow<'static, str>>) {
37        let mut content = self.content.borrow_mut();
38        content.push(CodeBufferContent::String(text.into(), false));
39    }
40
41    /// Creates a new indented sub-buffer at the current position.
42    #[inline]
43    pub fn indent(&self, indent: Cow<'static, str>) -> Rc<CodeBuffer> {
44        self.indent_with_options(IndentOptions {
45            indent,
46            leading: None,
47            trailing: None,
48            trailing_newline: false,
49        })
50    }
51
52    /// Creates a new indented sub-buffer at the current position.
53    pub fn indent_with_options(&self, options: IndentOptions) -> Rc<CodeBuffer> {
54        let mut content = self.content.borrow_mut();
55        if let Some(leading) = options.leading {
56            content.push(CodeBufferContent::String(leading, true));
57        }
58
59        let idx = content.len();
60        content.push(CodeBufferContent::Buffer(Rc::new(CodeBuffer::with_indent(
61            Cow::Owned(format!("{}{}", self.indent, options.indent)),
62        ))));
63
64        if let Some(trailing) = options.trailing {
65            content.push(CodeBufferContent::String(
66                trailing,
67                options.trailing_newline,
68            ));
69        } else if options.trailing_newline {
70            content.push(CodeBufferContent::String(Cow::Borrowed(""), true));
71        }
72
73        content[idx].as_buffer()
74    }
75
76    /// Creates a new un-indented sub-buffer at the current position.
77    #[inline]
78    pub fn section(&self, trailing_newline: bool) -> Rc<CodeBuffer> {
79        self.indent_with_options(IndentOptions {
80            indent: Cow::Borrowed(""),
81            leading: None,
82            trailing: None,
83            trailing_newline,
84        })
85    }
86
87    /// Writes the content of this `CodeBuffer` into the provided writer.
88    pub fn write(self, writer: &mut dyn io::Write) -> io::Result<()> {
89        self.inner_write(&mut IndentedWriter::new(writer))
90    }
91
92    fn inner_write(&self, writer: &mut IndentedWriter) -> io::Result<()> {
93        writer.with_indent(&self.indent, move |writer| {
94            for item in self.content.borrow().iter() {
95                item.write(writer)?;
96            }
97            Ok(())
98        })
99    }
100}
101
102impl CodeBuffer {
103    const fn with_indent(indent: Cow<'static, str>) -> Self {
104        Self {
105            indent,
106            content: RefCell::new(Vec::new()),
107        }
108    }
109}
110
111impl Default for CodeBuffer {
112    #[inline(always)]
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118/// Options for creating indented blocks.
119pub struct IndentOptions {
120    /// The indentation to add to the current indentation.
121    pub indent: Cow<'static, str>,
122    /// The text to add before the indented block begins. It'll be suffixed with
123    /// a newline character.
124    pub leading: Option<Cow<'static, str>>,
125    /// The text to add after the indentated block ends.
126    pub trailing: Option<Cow<'static, str>>,
127    /// Whether a newline should be inserted after the block ends (after the
128    /// trailing, if one is provided).
129    pub trailing_newline: bool,
130}
131
132enum CodeBufferContent {
133    String(Cow<'static, str>, bool),
134    Buffer(Rc<CodeBuffer>),
135}
136
137impl CodeBufferContent {
138    fn as_buffer(&self) -> Rc<CodeBuffer> {
139        match self {
140            Self::Buffer(buffer) => buffer.clone(),
141            _ => unreachable!("expected a buffer"),
142        }
143    }
144
145    fn write(&self, writer: &mut IndentedWriter) -> io::Result<()> {
146        match self {
147            Self::String(string, newline) => {
148                if *newline {
149                    writeln!(writer, "{string}")
150                } else {
151                    write!(writer, "{string}")
152                }
153            }
154            Self::Buffer(buffer) => buffer.inner_write(writer),
155        }
156    }
157}
158
159struct IndentedWriter<'a> {
160    indent: &'a [u8],
161    writer: &'a mut dyn io::Write,
162
163    after_newline: bool,
164}
165
166impl<'a> IndentedWriter<'a> {
167    fn new(writer: &'a mut dyn io::Write) -> Self {
168        Self {
169            indent: &[],
170            writer,
171            after_newline: true,
172        }
173    }
174
175    fn with_indent(
176        &mut self,
177        indent: &str,
178        cb: impl FnOnce(&mut IndentedWriter<'_>) -> io::Result<()>,
179    ) -> io::Result<()> {
180        let mut delegate = IndentedWriter {
181            indent: indent.as_bytes(),
182            writer: self.writer,
183            after_newline: self.after_newline,
184        };
185
186        let result = cb(&mut delegate);
187        self.after_newline = delegate.after_newline;
188        result
189    }
190}
191
192impl io::Write for IndentedWriter<'_> {
193    fn write(&mut self, mut buf: &[u8]) -> io::Result<usize> {
194        // Short-circuit to the raw writer if there is no indentation.
195        if self.indent.is_empty() {
196            return self.writer.write(buf);
197        }
198
199        let mut written = 0;
200        while !buf.is_empty() {
201            let (slice, nl) = match buf.iter().position(|&b| b == b'\n') {
202                None => (buf, false),
203                Some(idx) => (&buf[..=idx], true),
204            };
205            debug_assert!(!slice.is_empty(), "the slice cannot be empty");
206
207            if self.after_newline && (slice.len() > 1 || !nl) {
208                // Note: this does not count as written output here...
209                self.writer.write_all(self.indent)?;
210                self.after_newline = false;
211            }
212
213            let out = self.writer.write(slice)?;
214            written += out;
215
216            if out < slice.len() {
217                // We couldn't write it all, so we'll return here...
218                break;
219            }
220
221            self.after_newline = nl;
222            buf = &buf[slice.len()..];
223        }
224
225        Ok(written)
226    }
227
228    #[inline]
229    fn flush(&mut self) -> io::Result<()> {
230        self.writer.flush()
231    }
232}