logparse_pretty_print/tree/mod.rs
1use crate::{FmtWrite, PrettyBuilder, RenderAnnotated, Text, helpers::PrettySequence, render};
2use alloc::rc::Rc;
3use color_ansi::AnsiStyle;
4use core::{
5 fmt::{Debug, Formatter},
6 ops::{Add, AddAssign},
7};
8use std::{borrow::Cow, io::Write};
9
10mod display;
11mod into;
12
13/// The concrete document type. This type is not meant to be used directly. Instead use the static
14/// functions on `Doc` or the methods on an `DocAllocator`.
15///
16/// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how
17/// it is used
18#[derive(Default)]
19pub enum PrettyTree<'a, T = Cow<'a, str>> {
20 /// Nothing to show
21 #[default]
22 Nil,
23 /// A hard line break
24 Hardline,
25 /// A dynamic text document, all newlines are hard line breaks
26 Text(T),
27 /// A document with ansi styles
28 Annotated {
29 /// The style to use for the text
30 style: Rc<AnsiStyle>,
31 /// The text to display
32 body: Rc<Self>,
33 },
34 /// Concatenates two documents
35 Append {
36 /// The first document
37 lhs: Rc<Self>,
38 /// The second document
39 rhs: Rc<Self>,
40 },
41 /// Concatenates two documents with a space in between
42 Group {
43 /// The first document
44 items: Rc<Self>,
45 },
46 /// Concatenates two documents with a line in between
47 MaybeInline {
48 /// The first document
49 block: Rc<Self>,
50 /// The second document
51 inline: Rc<Self>,
52 },
53 /// Concatenates two documents with a line in between
54 Nest {
55 /// The first document
56 space: isize,
57 /// The second document
58 doc: Rc<Self>,
59 },
60 /// Stores the length of a string document that is not just ascii
61 RenderLength {
62 /// The length of the string
63 length: usize,
64 /// The document
65 body: Rc<Self>,
66 },
67 /// Concatenates two documents with a line in between
68 Union {
69 /// The first document
70 lhs: Rc<Self>,
71 /// The second document
72 rhs: Rc<Self>,
73 },
74 /// Concatenates two documents with a line in between
75 Column {
76 /// The first document
77 invoke: Rc<dyn Fn(usize) -> Self + 'a>,
78 },
79 /// Concatenates two documents with a line in between
80 Nesting {
81 /// The first document
82 invoke: Rc<dyn Fn(usize) -> Self + 'a>,
83 },
84 /// Concatenates two documents with a line in between
85 Fail,
86}
87
88#[allow(non_upper_case_globals)]
89impl<'a, T: Text<'a>> PrettyTree<'a, T> {
90 /// A line acts like a `\n` but behaves like `space` if it is grouped on a single line.
91 #[inline]
92 pub fn line_or_space() -> Self {
93 Self::Hardline.flat_alt(T::space())
94 }
95 /// A line acts like `\n` but behaves like `nil` if it is grouped on a single line.
96 #[inline]
97 pub fn line_or_comma() -> Self {
98 Self::Hardline.flat_alt(PrettyTree::Text(T::comma_space()))
99 }
100 /// Acts like `line` but behaves like `nil` if grouped on a single line
101 #[inline]
102 pub fn line_or_nil() -> Self {
103 Self::Hardline.flat_alt(Self::Nil)
104 }
105}
106
107impl<'a, T> PrettyTree<'a, T> {
108 /// The given text, which must not contain line breaks.
109 #[inline]
110 pub fn text(data: impl Into<T>) -> Self {
111 Self::Text(data.into())
112 }
113}
114
115impl<'a, T: Text<'a>> PrettyTree<'a, T> {
116 /// Writes a rendered document to a `std::io::Write` object.
117 #[inline]
118 #[cfg(feature = "std")]
119 pub fn render<W>(&self, width: usize, out: &mut W) -> std::io::Result<()>
120 where
121 W: ?Sized + std::io::Write,
122 {
123 self.render_raw(width, &mut crate::IoWrite::new(out))
124 }
125
126 /// Writes a rendered document to a `std::io::Write` object.
127 #[inline]
128 #[cfg(feature = "std")]
129 pub fn render_vec(&self, width: usize, out: &mut Vec<T>) -> Result<(), &'static str> {
130 self.render_raw(width, &mut crate::VecWrite::new(out))
131 }
132
133 /// Writes a rendered document to a `std::fmt::Write` object.
134 #[inline]
135 pub fn render_fmt<W>(&self, width: usize, out: &mut W) -> core::fmt::Result
136 where
137 W: ?Sized + core::fmt::Write,
138 {
139 self.render_raw(width, &mut FmtWrite::new(out))
140 }
141
142 /// Writes a rendered document to a `RenderAnnotated<A>` object.
143 #[inline]
144 pub fn render_raw<W>(&self, width: usize, out: &mut W) -> Result<(), W::Error>
145 where
146 W: RenderAnnotated<'a, T>,
147 W: ?Sized,
148 {
149 render::best(Rc::new(self.clone()), width, out)
150 }
151}
152
153impl<'a, T: Text<'a>> PrettyTree<'a, T> {
154 /// The given text, which must not contain line breaks.
155 #[inline]
156 #[cfg(feature = "std")]
157 pub fn render_colored<W: Write>(&self, width: usize, out: W) -> std::io::Result<()> {
158 render::best(Rc::new(self.clone()), width, &mut crate::TerminalWriter::new(out))
159 }
160}
161
162impl<'a, T: Text<'a>> PrettyBuilder<'a, T> for PrettyTree<'a, T> {
163 /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line.
164 ///
165 /// ```
166 /// use pretty::{Arena, DocAllocator};
167 ///
168 /// let arena = Arena::<()>::new();
169 /// let body = arena.line().append("x");
170 /// let doc = arena
171 /// .text("let")
172 /// .append(arena.line())
173 /// .append("x")
174 /// .group()
175 /// .append(body.clone().flat_alt(arena.line().append("in").append(body)))
176 /// .group();
177 ///
178 /// assert_eq!(doc.1.pretty(100).to_string(), "let x in x");
179 /// assert_eq!(doc.1.pretty(8).to_string(), "let x\nx");
180 /// ```
181 #[inline]
182 fn flat_alt<E>(self, flat: E) -> Self
183 where
184 E: Into<PrettyTree<'a, T>>,
185 {
186 Self::MaybeInline { block: Rc::new(self), inline: Rc::new(flat.into()) }
187 }
188 /// Indents `self` by `adjust` spaces from the current cursor position
189 ///
190 /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
191 /// like `RefDoc` or `RcDoc`
192 ///
193 /// ```rust
194 /// use pretty::DocAllocator;
195 ///
196 /// let arena = pretty::Arena::<()>::new();
197 /// let doc = arena
198 /// .text("prefix")
199 /// .append(arena.text(" "))
200 /// .append(arena.reflow("The indent function indents these words!").indent(4));
201 /// assert_eq!(
202 /// doc.1.pretty(24).to_string(),
203 /// "
204 /// prefix The indent
205 /// function
206 /// indents these
207 /// words!"
208 /// .trim_start(),
209 /// );
210 /// ```
211 #[inline]
212 fn indent(self, adjust: usize) -> Self {
213 let spaces = {
214 use crate::render::SPACES;
215 let mut doc = PrettyTree::Nil;
216 let mut remaining = adjust;
217 while remaining != 0 {
218 let i = SPACES.len().min(remaining);
219 remaining -= i;
220 doc = doc.append(PrettyTree::text(T::from_static_spaces(&SPACES[..i])))
221 }
222 doc
223 };
224 spaces.append(self).hang(adjust.try_into().unwrap())
225 }
226
227 /// Increase the indentation level of this document.
228 #[inline]
229 fn nest(self, offset: isize) -> Self {
230 if let Self::Nil = self {
231 return self;
232 }
233 if offset == 0 {
234 return self;
235 }
236 Self::Nest { space: offset, doc: Rc::new(self) }
237 }
238}
239
240impl<'a, T: Text<'a>> PrettyTree<'a, T> {
241 /// Append the given document after this document.
242 #[inline]
243 pub fn append<E>(self, follow: E) -> Self
244 where
245 E: Into<PrettyTree<'a, T>>,
246 {
247 let rhs = follow.into();
248 match (&self, &rhs) {
249 (Self::Nil, _) => rhs,
250 (_, Self::Nil) => self,
251 _ => Self::Append { lhs: Rc::new(self), rhs: Rc::new(rhs) },
252 }
253 }
254 /// Allocate a document that intersperses the given separator `S` between the given documents
255 /// `[A, B, C, ..., Z]`, yielding `[A, S, B, S, C, S, ..., S, Z]`.
256 ///
257 /// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse).
258 ///
259 /// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr
260 /// like `RefDoc` or `RcDoc`
261 #[inline]
262 pub fn join<I, T1, T2>(terms: I, joint: T2) -> PrettyTree<'a, T>
263 where
264 I: IntoIterator<Item = T1>,
265 T1: Into<PrettyTree<'a, T>>,
266 T2: Into<PrettyTree<'a, T>>,
267 {
268 let joint = joint.into();
269 let mut iter = terms.into_iter().map(|s| s.into());
270 let mut terms = PrettySequence::new(0);
271 terms += iter.next().unwrap_or(PrettyTree::Nil);
272 for term in iter {
273 terms += joint.clone();
274 terms += term;
275 }
276 terms.into()
277 }
278 /// Allocate a document that intersperses the given separator `S` between the given documents
279 pub fn concat<I>(docs: I) -> Self
280 where
281 I: IntoIterator,
282 I::Item: Into<PrettyTree<'a, T>>,
283 {
284 let mut head = Self::Nil;
285 for item in docs.into_iter() {
286 head += item.into();
287 }
288 head
289 }
290
291 /// Mark this document as a group.
292 ///
293 /// Groups are layed out on a single line if possible. Within a group, all basic documents with
294 /// several possible layouts are assigned the same layout, that is, they are all layed out
295 /// horizontally and combined into a one single line, or they are each layed out on their own
296 /// line.
297 #[inline]
298 pub fn group(self) -> Self {
299 match self {
300 Self::Group { .. } | Self::Text(_) | Self::Nil => self,
301 _ => Self::Group { items: Rc::new(self) },
302 }
303 }
304
305 /// Mark this document as a comment.
306 #[inline]
307 pub fn annotate(self, style: Rc<AnsiStyle>) -> Self {
308 Self::Annotated { style, body: Rc::new(self) }
309 }
310 /// Mark this document as a hard line break.
311 #[inline]
312 pub fn union<E>(self, other: E) -> Self
313 where
314 E: Into<PrettyTree<'a, T>>,
315 {
316 Self::Union { lhs: Rc::new(self), rhs: Rc::new(other.into()) }
317 }
318
319 /// Lays out `self` so with the nesting level set to the current column
320 ///
321 /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
322 /// like `RefDoc` or `RcDoc`
323 ///
324 /// ```rust
325 /// use pretty::{docs, DocAllocator};
326 ///
327 /// let arena = &pretty::Arena::<()>::new();
328 /// let doc = docs![
329 /// arena,
330 /// "lorem",
331 /// " ",
332 /// arena.intersperse(["ipsum", "dolor"].iter().cloned(), arena.line_()).align(),
333 /// arena.hardline(),
334 /// "next",
335 /// ];
336 /// assert_eq!(doc.1.pretty(80).to_string(), "lorem ipsum\n dolor\nnext");
337 /// ```
338 #[inline]
339 pub fn align(self) -> Self {
340 Self::Column {
341 invoke: Rc::new(move |col| {
342 let self_ = self.clone();
343 Self::Nesting { invoke: Rc::new(move |nest| self_.clone().nest(col as isize - nest as isize)) }
344 }),
345 }
346 }
347
348 /// Lays out `self` with a nesting level set to the current level plus `adjust`.
349 ///
350 /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
351 /// like `RefDoc` or `RcDoc`
352 ///
353 /// ```rust
354 /// use pretty::DocAllocator;
355 ///
356 /// let arena = pretty::Arena::<()>::new();
357 /// let doc = arena
358 /// .text("prefix")
359 /// .append(arena.text(" "))
360 /// .append(arena.reflow("Indenting these words with nest").hang(4));
361 /// assert_eq!(
362 /// doc.1.pretty(24).to_string(),
363 /// "prefix Indenting these\n words with\n nest",
364 /// );
365 /// ```
366 #[inline]
367 pub fn hang(self, adjust: isize) -> Self {
368 self.nest(adjust).align()
369 }
370
371 /// Lays out `self` and provides the column width of it available to `f`
372 ///
373 /// NOTE: The doc pointer type, `D` may need to be cloned. Consider using cheaply cloneable ptr
374 /// like `RefDoc` or `RcDoc`
375 ///
376 /// ```rust
377 /// use pretty::DocAllocator;
378 ///
379 /// let arena = pretty::Arena::<()>::new();
380 /// let doc = arena
381 /// .text("prefix ")
382 /// .append(arena.column(|l| arena.text("| <- column ").append(arena.as_string(l)).into_doc()));
383 /// assert_eq!(doc.1.pretty(80).to_string(), "prefix | <- column 7");
384 /// ```
385 #[inline]
386 pub fn width<F>(self, f: F) -> Self
387 where
388 F: Fn(isize) -> Self + Clone + 'static,
389 T: Clone,
390 {
391 Self::Column {
392 invoke: Rc::new(move |start| {
393 let f = f.clone();
394 self.clone() + Self::Column { invoke: Rc::new(move |end| f(end as isize - start as isize)) }
395 }),
396 }
397 }
398
399 /// Puts `self` between `before` and `after`
400 #[inline]
401 pub fn enclose<E, F>(self, before: E, after: F) -> Self
402 where
403 E: Into<Self>,
404 F: Into<Self>,
405 {
406 before.into().append(self).append(after.into())
407 }
408
409 /// Puts `self` between `before` and `after` if `cond` is true
410 pub fn single_quotes(self) -> Self {
411 self.enclose("'", "'")
412 }
413
414 /// Puts `self` between `before` and `after` if `cond` is true
415 pub fn double_quotes(self) -> Self {
416 self.enclose("\"", "\"")
417 }
418 /// Puts `self` between `before` and `after` if `cond` is true
419 pub fn parens(self) -> Self {
420 self.enclose("(", ")")
421 }
422 /// Puts `self` between `before` and `after` if `cond` is true
423 pub fn angles(self) -> Self {
424 self.enclose("<", ">")
425 }
426 /// Puts `self` between `before` and `after` if `cond` is true
427 pub fn braces(self) -> Self {
428 self.enclose("{", "}")
429 }
430 /// Puts `self` between `before` and `after` if `cond` is true
431 pub fn brackets(self) -> Self {
432 self.enclose("[", "]")
433 }
434}