use crate::parse::{
alignment,
attributes::attributes,
block::{end_of_block, indent_align},
from_num, opt_either_order,
phrase::phrase,
};
use crate::structs::{
Align, Attributes, BlockHeader, BlockTag, CellKind, Indent, InlineTag,
TableCell, TableHeader, TableRow, VerticalAlign,
};
use nom::{
branch::alt,
bytes::complete::{escaped_transform, tag, take_while1},
character::complete::{char, line_ending, none_of},
combinator::{complete, fail, map_res, opt, value},
multi::{many1, many_till},
sequence::{preceded, terminated, tuple},
IResult,
};
pub fn table(input: &str) -> IResult<&str, BlockTag> {
let (rest, (opt_header, rows)) =
tuple((opt(table_head), table_body))(input)?;
let (attributes, align, indent) = opt_header.unwrap_or((None, None, None));
Ok((
rest,
BlockTag::Table {
header: BlockHeader {
attributes,
indent,
align,
},
rows,
},
))
}
fn table_head(
input: &str,
) -> IResult<&str, (Option<Attributes>, Option<Align>, Option<Indent>)> {
let (rest, (attributes, indent_align)) = terminated(
tuple((opt(attributes), opt(indent_align))),
tuple((char('.'), line_ending)),
)(input)?;
let (indent, align) = indent_align.unwrap_or((None, None));
Ok((rest, (attributes, align, indent)))
}
fn table_body(input: &str) -> IResult<&str, Vec<TableRow>> {
many1(table_row)(input)
}
fn table_row(input: &str) -> IResult<&str, TableRow> {
let (rest, (opt_header, cells)) = tuple((opt(row_head), row_body))(input)?;
let (attributes, h_align, v_align) =
opt_header.unwrap_or((None, None, None));
Ok((
rest,
TableRow {
header: TableHeader {
attributes,
h_align,
v_align,
},
cells,
},
))
}
fn row_head(
input: &str,
) -> IResult<&str, (Option<Attributes>, Option<Align>, Option<VerticalAlign>)> {
let (rest, (attributes, aligns)) = terminated(
tuple((opt(attributes), opt(table_aligns))),
tag(". "),
)(input)?;
let (h_align, v_align) = aligns.unwrap_or((None, None));
Ok((rest, (attributes, h_align, v_align)))
}
fn row_body(input: &str) -> IResult<&str, Vec<TableCell>> {
let (rest, (cells, _end)) = many_till(
table_cell,
tuple((char('|'), alt((line_ending, end_of_block)))),
)(input)?;
Ok((rest, cells))
}
fn table_cell(input: &str) -> IResult<&str, TableCell> {
let (rest, (opt_header, content)) =
preceded(char('|'), tuple((opt(cell_head), cell_body)))(input)?;
let (kind, col_span, row_span, attributes, h_align, v_align) =
opt_header.unwrap_or((CellKind::Data, None, None, None, None, None));
Ok((
rest,
TableCell {
kind,
col_span,
row_span,
header: TableHeader {
attributes,
h_align,
v_align,
},
content,
},
))
}
fn cell_head(
input: &str,
) -> IResult<
&str,
(
CellKind,
Option<usize>,
Option<usize>,
Option<Attributes>,
Option<Align>,
Option<VerticalAlign>,
),
> {
let (rest, (kind, spans, attributes, aligns)) = terminated(
tuple((
alt((
value(CellKind::Header, char('_')),
value(CellKind::Data, tag("")),
)),
opt(cell_spans),
opt(attributes),
opt(table_aligns),
)),
tag(". "),
)(input)?;
let (h_align, v_align) = aligns.unwrap_or((None, None));
let (col_span, row_span) = spans.unwrap_or((None, None));
Ok((
rest,
(kind, col_span, row_span, attributes, h_align, v_align),
))
}
fn cell_body(input: &str) -> IResult<&str, InlineTag> {
let (rest, body) = escaped_transform(
none_of("\\|"),
'\\',
alt((value("\\", tag("\\")), value("|", tag("|")))),
)(input)?;
let phrase_result = complete(phrase)(&*body);
if let Ok((_, content)) = phrase_result {
Ok((rest, content))
} else {
fail(input)
}
}
fn table_aligns(
input: &str,
) -> IResult<&str, (Option<Align>, Option<VerticalAlign>)> {
opt_either_order(alignment, vertical_alignment)(input)
}
fn cell_spans(input: &str) -> IResult<&str, (Option<usize>, Option<usize>)> {
opt_either_order(col_span, row_span)(input)
}
fn col_span(input: &str) -> IResult<&str, usize> {
preceded(
char('\\'),
map_res(take_while1(|c: char| c.is_ascii_digit()), from_num),
)(input)
}
fn row_span(input: &str) -> IResult<&str, usize> {
preceded(
char('/'),
map_res(take_while1(|c: char| c.is_ascii_digit()), from_num),
)(input)
}
fn vertical_alignment(input: &str) -> IResult<&str, VerticalAlign> {
alt((
value(VerticalAlign::Top, tag("^")),
value(VerticalAlign::Middle, tag("-")),
value(VerticalAlign::Bottom, tag("~")),
))(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn table_basic() {
let input = "|hello|";
let result = table(input);
assert_eq!(
result,
Ok((
"",
BlockTag::Table {
header: BlockHeader {
attributes: None,
indent: None,
align: None,
},
rows: vec![TableRow {
header: TableHeader {
attributes: None,
h_align: None,
v_align: None,
},
cells: vec![TableCell {
kind: CellKind::Data,
col_span: None,
row_span: None,
header: TableHeader {
attributes: None,
h_align: None,
v_align: None,
},
content: InlineTag::Plaintext(String::from(
"hello"
))
}]
}]
}
))
);
}
}