katana-markdown-engine 0.1.0

Renderer-neutral Markdown document model for the KatanA ecosystem
Documentation
use crate::{SourceSpan, TableAlignment, TableCell, TableNode, TableRow};

pub(crate) fn table_node(rows: Vec<TableRow>) -> TableNode {
    let alignments = rows
        .get(1)
        .map(|row| row.cells.iter().map(|cell| alignment(&cell.text)).collect())
        .unwrap_or_default();
    TableNode { alignments, rows }
}

pub(crate) fn table_row(
    line: &str,
    line_start: usize,
    cell_span: impl Fn(usize, usize) -> SourceSpan,
) -> TableRow {
    TableRow {
        cells: split_table_cell_ranges(line)
            .into_iter()
            .map(|range| {
                let text = line[range.start..range.end].trim().to_string();
                TableCell {
                    text,
                    source: cell_span(line_start + range.start, line_start + range.end),
                }
            })
            .collect(),
    }
}

pub(crate) fn table_separator(line: &str) -> bool {
    split_table_cells(line)
        .iter()
        .all(|cell| cell.chars().all(|it| matches!(it, '-' | ':' | ' ')))
}

struct CellRange {
    start: usize,
    end: usize,
}

fn split_table_cell_ranges(line: &str) -> Vec<CellRange> {
    let content_start = usize::from(line.starts_with('|'));
    let content_end = line
        .strip_suffix('|')
        .map(str::len)
        .unwrap_or_else(|| line.len());
    let mut ranges = Vec::new();
    let mut start = content_start;
    for (index, character) in line[content_start..content_end].char_indices() {
        if character == '|' {
            ranges.push(CellRange {
                start,
                end: content_start + index,
            });
            start = content_start + index + character.len_utf8();
        }
    }
    ranges.push(CellRange {
        start,
        end: content_end,
    });
    ranges
}

fn split_table_cells(line: &str) -> Vec<String> {
    split_table_cell_ranges(line)
        .into_iter()
        .map(|range| line[range.start..range.end].trim().to_string())
        .collect()
}

fn alignment(cell: &str) -> TableAlignment {
    match (cell.starts_with(':'), cell.ends_with(':')) {
        (true, true) => TableAlignment::Center,
        (true, false) => TableAlignment::Left,
        (false, true) => TableAlignment::Right,
        (false, false) => TableAlignment::Unspecified,
    }
}