use super::inline;
use super::plugin::ParserHooks;
use crate::markdown::ast::options::AstOptions;
use crate::markdown::ast::tokens::span::Span;
use crate::markdown::ast::tokens::token::{Block, Table, TableCell, TableCellAlignment, TableRow};
pub fn is_table_delimiter(line: &str) -> bool {
let stripped = line.trim();
if stripped.is_empty() {
return false;
}
let s = stripped.strip_prefix('|').unwrap_or(stripped);
let s = s.strip_suffix('|').unwrap_or(s);
let cells: Vec<&str> = s.split('|').collect();
if cells.is_empty() {
return false;
}
cells.iter().all(|c| {
let t = c.trim();
!t.is_empty() && t.chars().all(|ch| ch == '-' || ch == ':') && t.contains('-')
})
}
pub fn is_table_start(lines: &[&str], pos: usize) -> bool {
if pos + 1 >= lines.len() {
return false;
}
let header = lines[pos];
let delimiter = lines[pos + 1];
header.contains('|') && is_table_delimiter(delimiter)
}
pub fn parse_delimiter_alignments(line: &str) -> Vec<TableCellAlignment> {
let stripped = line.trim();
let s = stripped.strip_prefix('|').unwrap_or(stripped);
let s = s.strip_suffix('|').unwrap_or(s);
s.split('|')
.map(|cell| {
let t = cell.trim();
let left = t.starts_with(':');
let right = t.ends_with(':');
match (left, right) {
(true, true) => TableCellAlignment::Center,
(true, false) => TableCellAlignment::Left,
(false, true) => TableCellAlignment::Right,
(false, false) => TableCellAlignment::Default,
}
})
.collect()
}
pub fn split_table_row(line: &str) -> Vec<String> {
let stripped = line.trim();
let s = stripped.strip_prefix('|').unwrap_or(stripped);
let s = s.strip_suffix('|').unwrap_or(s);
s.split('|').map(|c| c.trim().to_string()).collect()
}
#[allow(clippy::too_many_arguments)]
pub fn build_table(
header_line: &str,
_delimiter_line: &str,
alignments: Vec<TableCellAlignment>,
body_lines: &[&str],
options: Option<&AstOptions>,
hooks: &ParserHooks,
) -> Block {
let header_cells: Vec<String> = split_table_row(header_line);
let header = TableRow {
cells: header_cells
.iter()
.map(|c| TableCell {
children: inline::parse_inline_with_hooks(c, options, None, hooks),
})
.collect(),
};
let rows: Vec<TableRow> = body_lines
.iter()
.map(|line| {
let cells: Vec<String> = split_table_row(line);
TableRow {
cells: cells
.iter()
.map(|c| TableCell {
children: inline::parse_inline_with_hooks(c, options, None, hooks),
})
.collect(),
}
})
.collect();
Block::Table {
table: Table {
header,
rows,
alignments,
},
attrs: None,
pos: Span::default(),
}
}
#[cfg(test)]
mod tests {
use super::{
build_table, is_table_delimiter, is_table_start, parse_delimiter_alignments,
split_table_row,
};
use crate::markdown::ast::tokens::token::{Block, Inline, TableCellAlignment};
#[test]
fn delimiter_rows_are_recognised() {
assert!(is_table_delimiter("| --- | --- |"));
assert!(is_table_delimiter(":---:"));
assert!(is_table_delimiter(" | :--- | ---: | "));
assert!(!is_table_delimiter("| :: |"));
assert!(!is_table_delimiter("| text | --- |"));
assert!(!is_table_delimiter(""));
}
#[test]
fn table_start_needs_header_pipe_and_delimiter() {
let lines = ["| a | b |", "| --- | --- |", "| 1 | 2 |"];
assert!(is_table_start(&lines, 0));
assert!(!is_table_start(&["no pipes", "---"], 0));
assert!(!is_table_start(&lines, 2));
assert!(!is_table_start(&lines, 3));
}
#[test]
fn alignments_map_from_colons() {
let aligns = parse_delimiter_alignments("| :--- | --- | :---: | ---: |");
assert_eq!(
aligns,
vec![
TableCellAlignment::Left,
TableCellAlignment::Default,
TableCellAlignment::Center,
TableCellAlignment::Right,
]
);
}
#[test]
fn rows_split_on_pipes() {
assert_eq!(
split_table_row("| a | b |"),
vec!["a".to_string(), "b".to_string()]
);
assert_eq!(
split_table_row("a | b"),
vec!["a".to_string(), "b".to_string()]
);
assert_eq!(split_table_row("| a |"), vec!["a".to_string()]);
}
#[test]
fn build_table_parses_header_and_rows_as_inline() {
let block = build_table(
"| Name | **Bold** |",
"| --- | --- |",
vec![TableCellAlignment::Default, TableCellAlignment::Default],
&["| peisar | yes |"],
None,
&crate::markdown::ast::parsers::plugin::ParserHooks::empty(),
);
match block {
Block::Table { table, .. } => {
assert_eq!(table.header.cells.len(), 2);
assert_eq!(table.rows.len(), 1);
assert!(matches!(
table.header.cells[1].children.as_slice(),
[Inline::Emphasis { .. }]
));
assert!(matches!(
table.rows[0].cells[0].children.as_slice(),
[Inline::Text { value, .. }] if value == "peisar"
));
}
other => panic!("expected Table, got {other:?}"),
}
}
}