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}