indented_blocks/
lib.rs

1#![forbid(unsafe_code, unconditional_recursion)]
2#![no_std]
3#![recursion_limit = "8"]
4
5extern crate alloc;
6extern crate core;
7
8use alloc::{borrow, string::String, vec, vec::Vec};
9
10#[cfg(test)]
11mod tests;
12
13pub mod indention;
14use indention::{Indented, IntoUnindented};
15
16pub mod view;
17pub use view::View;
18
19#[derive(Clone, Debug)]
20#[cfg_attr(any(feature = "extra-traits", test), derive(PartialEq))]
21pub struct Block<S> {
22    pub head: S,
23    pub subs: Vec<Block<S>>,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
27#[cfg_attr(feature = "extra-traits", derive(PartialOrd, Ord, Hash))]
28pub enum EmptyLinesMode {
29    Skip,
30    HonorIndent,
31    PrevIndent,
32}
33
34impl<A> Block<A> {
35    pub fn map_ref<B, F>(&self, mut f: F) -> Block<B>
36    where
37        F: FnMut(&A) -> B,
38    {
39        fn intern_<A, B, F: FnMut(&A) -> B>(this: &Block<A>, f: &mut F) -> Block<B> {
40            let head = f(&this.head);
41            Block {
42                head,
43                subs: this.subs.iter().map(move |i| intern_(i, f)).collect(),
44            }
45        }
46        intern_(self, &mut f)
47    }
48
49    pub fn map<B, F>(self, mut f: F) -> Block<B>
50    where
51        F: FnMut(A) -> B,
52    {
53        fn intern_<A, B, F: FnMut(A) -> B>(this: Block<A>, f: &mut F) -> Block<B> {
54            let Block { head, subs } = this;
55            let head = f(head);
56            Block {
57                head,
58                subs: subs.into_iter().map(move |i| intern_(i, f)).collect(),
59            }
60        }
61        intern_(self, &mut f)
62    }
63}
64
65impl<'a, T: borrow::ToOwned> Block<&'a T> {
66    #[inline]
67    pub fn to_owned(&self) -> Block<T::Owned> {
68        self.map_ref(|&i| i.to_owned())
69    }
70}
71
72/// This function is a simple wrapper around [`parse_nested_blocks_from_lines`].
73pub fn parse_nested_blocks(
74    s: &str,
75    empty_lines_mode: EmptyLinesMode,
76) -> Vec<Block<Indented<&str>>> {
77    parse_nested_blocks_from_lines(s.lines(), empty_lines_mode)
78}
79
80/// This function does the same as [`parse_nested_blocks`], but expects
81/// an [`Iterator`] over lines as an argument, which allows pre-filtering
82/// of the passed lines without additional allocations
83/// (e.g. to filter comments and such)
84///
85/// It returns all parsed blocks, with the lines split into indention
86/// and main text. (see also: the [`Indention`] type)
87pub fn parse_nested_blocks_from_lines<'a, LI>(
88    lines: LI,
89    empty_lines_mode: EmptyLinesMode,
90) -> Vec<Block<Indented<&'a str>>>
91where
92    LI: Iterator<Item = &'a str>,
93{
94    let mut base = vec![];
95    let mut stack: Vec<Block<Indented<&str>>> = vec![];
96
97    let mut lit = lines.map(|i| Block {
98        head: Indented::new(i),
99        subs: Vec::new(),
100    });
101
102    loop {
103        let i = lit.next();
104
105        let i_indent = if let Some(j) = &i {
106            match empty_lines_mode {
107                _ if !j.head.data.is_empty() => Some(j),
108                EmptyLinesMode::HonorIndent => Some(j),
109                EmptyLinesMode::PrevIndent => stack.last(),
110                EmptyLinesMode::Skip => continue,
111            }
112            .map(|x| x.head.indent)
113        } else {
114            None
115        }
116        .unwrap_or("");
117
118        // reduce scope if necessary
119        while let Some(old_top) = stack.pop() {
120            let old_top_indent = old_top.head.indent;
121            if i_indent.starts_with(old_top_indent) && i_indent.len() != old_top_indent.len() {
122                stack.push(old_top);
123                break;
124            } else {
125                // merge block with parent
126                stack
127                    .last_mut()
128                    .map(|top2| &mut top2.subs)
129                    .unwrap_or(&mut base)
130                    .push(old_top);
131            }
132        }
133
134        assert!(i_indent.starts_with(stack.last().map(|top| top.head.indent).unwrap_or("")));
135        match i {
136            Some(j) => stack.push(j),
137            None => break,
138        }
139    }
140
141    assert!(stack.is_empty());
142    base
143}
144
145pub fn blocks_to_string<'ast, BIter, B, S, F>(blks: BIter, single_indent: &str) -> String
146where
147    BIter: core::iter::IntoIterator<Item = B>,
148    B: IntoUnindented<Output = view::View<'ast, S, F>> + 'ast,
149    S: 'ast,
150    F: Clone + view::ViewFn<&'ast S>,
151    F::Output: alloc::string::ToString,
152{
153    blks.into_iter()
154        .map(IntoUnindented::into_unindented)
155        .fold(String::new(), |mut acc, x| {
156            x.append_to_string(&mut acc, single_indent, 0);
157            acc
158        })
159}