flatiron/parse/
table.rs

1use crate::parse::{
2    alignment,
3    attributes::attributes,
4    block::{end_of_block, indent_align},
5    from_num, opt_either_order,
6    phrase::phrase,
7};
8use crate::structs::{
9    Align, Attributes, BlockHeader, BlockTag, CellKind, Indent, InlineTag,
10    TableCell, TableHeader, TableRow, VerticalAlign,
11};
12use nom::{
13    branch::alt,
14    bytes::complete::{escaped_transform, tag, take_while1},
15    character::complete::{char, line_ending, none_of},
16    combinator::{complete, fail, map_res, opt, value},
17    multi::{many1, many_till},
18    sequence::{preceded, terminated, tuple},
19    IResult,
20};
21
22pub fn table(input: &str) -> IResult<&str, BlockTag> {
23    let (rest, (opt_header, rows)) =
24        tuple((opt(table_head), table_body))(input)?;
25    let (attributes, align, indent) = opt_header.unwrap_or((None, None, None));
26    Ok((
27        rest,
28        BlockTag::Table {
29            header: BlockHeader {
30                attributes,
31                indent,
32                align,
33            },
34            rows,
35        },
36    ))
37}
38
39fn table_head(
40    input: &str,
41) -> IResult<&str, (Option<Attributes>, Option<Align>, Option<Indent>)> {
42    let (rest, (attributes, indent_align)) = terminated(
43        tuple((opt(attributes), opt(indent_align))),
44        tuple((char('.'), line_ending)),
45    )(input)?;
46    let (indent, align) = indent_align.unwrap_or((None, None));
47    Ok((rest, (attributes, align, indent)))
48}
49
50fn table_body(input: &str) -> IResult<&str, Vec<TableRow>> {
51    many1(table_row)(input)
52}
53
54fn table_row(input: &str) -> IResult<&str, TableRow> {
55    let (rest, (opt_header, cells)) = tuple((opt(row_head), row_body))(input)?;
56    let (attributes, h_align, v_align) =
57        opt_header.unwrap_or((None, None, None));
58    Ok((
59        rest,
60        TableRow {
61            header: TableHeader {
62                attributes,
63                h_align,
64                v_align,
65            },
66            cells,
67        },
68    ))
69}
70
71fn row_head(
72    input: &str,
73) -> IResult<&str, (Option<Attributes>, Option<Align>, Option<VerticalAlign>)> {
74    let (rest, (attributes, aligns)) = terminated(
75        tuple((opt(attributes), opt(table_aligns))),
76        tag(". "),
77    )(input)?;
78    let (h_align, v_align) = aligns.unwrap_or((None, None));
79    Ok((rest, (attributes, h_align, v_align)))
80}
81
82fn row_body(input: &str) -> IResult<&str, Vec<TableCell>> {
83    let (rest, (cells, _end)) = many_till(
84        table_cell,
85        tuple((char('|'), alt((line_ending, end_of_block)))),
86    )(input)?;
87    Ok((rest, cells))
88}
89
90fn table_cell(input: &str) -> IResult<&str, TableCell> {
91    let (rest, (opt_header, content)) =
92        preceded(char('|'), tuple((opt(cell_head), cell_body)))(input)?;
93    let (kind, col_span, row_span, attributes, h_align, v_align) =
94        opt_header.unwrap_or((CellKind::Data, None, None, None, None, None));
95    Ok((
96        rest,
97        TableCell {
98            kind,
99            col_span,
100            row_span,
101            header: TableHeader {
102                attributes,
103                h_align,
104                v_align,
105            },
106            content,
107        },
108    ))
109}
110
111fn cell_head(
112    input: &str,
113) -> IResult<
114    &str,
115    (
116        CellKind,
117        Option<usize>,
118        Option<usize>,
119        Option<Attributes>,
120        Option<Align>,
121        Option<VerticalAlign>,
122    ),
123> {
124    let (rest, (kind, spans, attributes, aligns)) = terminated(
125        tuple((
126            alt((
127                value(CellKind::Header, char('_')),
128                value(CellKind::Data, tag("")),
129            )),
130            opt(cell_spans),
131            opt(attributes),
132            opt(table_aligns),
133        )),
134        tag(". "),
135    )(input)?;
136    let (h_align, v_align) = aligns.unwrap_or((None, None));
137    let (col_span, row_span) = spans.unwrap_or((None, None));
138    Ok((
139        rest,
140        (kind, col_span, row_span, attributes, h_align, v_align),
141    ))
142}
143
144fn cell_body(input: &str) -> IResult<&str, InlineTag> {
145    let (rest, body) = escaped_transform(
146        none_of("\\|"),
147        '\\',
148        alt((value("\\", tag("\\")), value("|", tag("|")))),
149    )(input)?;
150    let phrase_result = complete(phrase)(&*body);
151    if let Ok((_, content)) = phrase_result {
152        Ok((rest, content))
153    } else {
154        fail(input)
155    }
156}
157
158fn table_aligns(
159    input: &str,
160) -> IResult<&str, (Option<Align>, Option<VerticalAlign>)> {
161    opt_either_order(alignment, vertical_alignment)(input)
162}
163
164fn cell_spans(input: &str) -> IResult<&str, (Option<usize>, Option<usize>)> {
165    opt_either_order(col_span, row_span)(input)
166}
167
168fn col_span(input: &str) -> IResult<&str, usize> {
169    preceded(
170        char('\\'),
171        map_res(take_while1(|c: char| c.is_ascii_digit()), from_num),
172    )(input)
173}
174
175fn row_span(input: &str) -> IResult<&str, usize> {
176    preceded(
177        char('/'),
178        map_res(take_while1(|c: char| c.is_ascii_digit()), from_num),
179    )(input)
180}
181
182fn vertical_alignment(input: &str) -> IResult<&str, VerticalAlign> {
183    alt((
184        value(VerticalAlign::Top, tag("^")),
185        value(VerticalAlign::Middle, tag("-")),
186        value(VerticalAlign::Bottom, tag("~")),
187    ))(input)
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn table_basic() {
196        let input = "|hello|";
197        let result = table(input);
198        assert_eq!(
199            result,
200            Ok((
201                "",
202                BlockTag::Table {
203                    header: BlockHeader {
204                        attributes: None,
205                        indent: None,
206                        align: None,
207                    },
208                    rows: vec![TableRow {
209                        header: TableHeader {
210                            attributes: None,
211                            h_align: None,
212                            v_align: None,
213                        },
214                        cells: vec![TableCell {
215                            kind: CellKind::Data,
216                            col_span: None,
217                            row_span: None,
218                            header: TableHeader {
219                                attributes: None,
220                                h_align: None,
221                                v_align: None,
222                            },
223                            content: InlineTag::Plaintext(String::from(
224                                "hello"
225                            ))
226                        }]
227                    }]
228                }
229            ))
230        );
231    }
232}