tui_markup/generator/helper/
flatten.rs

1use crate::{
2    generator::{Tag, TagConvertor},
3    parser::{Item, ItemC},
4};
5
6use super::unescape;
7
8/// Requirements trait for style to used in [`flatten`] function.
9pub trait FlattenableStyle: Default + Clone {
10    /// Patch self with other style, so multi style can be flatten into one.
11    #[must_use]
12    fn patch(self, other: Self) -> Self;
13}
14
15/// Requirements trait for span to used in [`flatten`] function.
16pub trait FlattenableSpan<'a, S: FlattenableStyle> {
17    /// Create a span from str and a optional style.
18    ///
19    /// In flatten process, we will store each plaintext with current state of style.
20    fn with_style(s: &'a str, style: Option<S>) -> Self;
21}
22
23#[allow(clippy::needless_pass_by_value)] // Style usually is a small type
24fn plain_text<'a, R, S>(escaped: &'a str, style: Option<S>) -> Vec<R>
25where
26    R: FlattenableSpan<'a, S>,
27    S: FlattenableStyle,
28{
29    unescape(escaped).map(|s| R::with_style(s, style.clone())).collect()
30}
31
32fn element<'a, C, R, S>(tags: Vec<Tag<'a, C>>, children: Vec<ItemC<'a, C>>, style: Option<S>) -> Vec<R>
33where
34    C: TagConvertor<'a>,
35    R: FlattenableSpan<'a, S>,
36    S: FlattenableStyle + From<Tag<'a, C>>,
37{
38    let style = tags.into_iter().map(S::from).fold(style.unwrap_or_default(), S::patch);
39    items(children, Some(style))
40}
41
42fn item<'a, C, R, S>(item: ItemC<'a, C>, style: Option<S>) -> Vec<R>
43where
44    C: TagConvertor<'a>,
45    R: FlattenableSpan<'a, S>,
46    S: FlattenableStyle + From<Tag<'a, C>>,
47{
48    match item {
49        Item::PlainText(t) => plain_text(t.fragment(), style),
50        Item::Element(tags, children) => element(tags, children, style),
51    }
52}
53
54#[allow(clippy::needless_pass_by_value)] // Style usually is a small type
55fn items<'a, C, R, S>(items: Vec<ItemC<'a, C>>, style: Option<S>) -> Vec<R>
56where
57    C: TagConvertor<'a>,
58    R: FlattenableSpan<'a, S>,
59    S: FlattenableStyle + From<Tag<'a, C>>,
60{
61    items
62        .into_iter()
63        .flat_map(|x| item(x, style.clone()).into_iter())
64        .collect()
65}
66
67/// Flatten a line of ast tree item into a vector of target spans.
68///
69/// ## Why need this
70///
71/// Each line of markup source will be convert into a `Vec<Item>` after parsing and tag conversion stage.
72///
73/// But [Item] itself is a tree, so there are many tree to process.
74///
75/// With this function, We can flatten each line into a flat `Vec<Span>`.
76///
77/// ## How can I use this?
78///
79/// If these requirements are met:
80///
81/// - [Tag] of a convertor `C` can be converted into a uniform `Style` struct, by impl the `From<Tag<C>>` trait.
82/// - the `Style` type can impl [`FlattenableStyle`] trait.
83/// - You have a struct `Span` can impl [`FlattenableSpan`] trait to store styled text span.
84///
85/// Then this function can be used to convert `Vec<ItemC<C>>` into `Vec<Span>`.
86pub fn flatten<'a, C, R, S>(line: Vec<ItemC<'a, C>>) -> Vec<R>
87where
88    C: TagConvertor<'a>,
89    R: FlattenableSpan<'a, S>,
90    S: FlattenableStyle + From<Tag<'a, C>>,
91{
92    items(line, None)
93}