Skip to main content

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}