katana-markdown-model 0.2.1

Renderer-neutral Markdown document model for the KatanA ecosystem
Documentation
use super::engine::ParserCursor;
use crate::{DollarMathBlockNode, KmmNodeKind};

const DOLLAR_MATH_DELIMITER: &str = "$$";

impl ParserCursor<'_> {
    pub(super) fn dollar_math_block(&mut self) -> KmmNodeKind {
        if let Some(expression) = one_line_dollar_math(self.current().text.trim()) {
            self.line += 1;
            return KmmNodeKind::DollarMathBlock(DollarMathBlockNode { expression });
        }

        self.line += 1;
        let mut expression = Vec::new();
        while self.line < self.index.lines().len() {
            let text = self.current().text.trim();
            if text == DOLLAR_MATH_DELIMITER {
                self.line += 1;
                break;
            }
            expression.push(self.current().text.clone());
            self.line += 1;
        }
        KmmNodeKind::DollarMathBlock(DollarMathBlockNode {
            expression: expression.join("\n"),
        })
    }

    pub(super) fn is_dollar_math_block(&self) -> bool {
        let trimmed = self.current().text.trim();
        trimmed == DOLLAR_MATH_DELIMITER || one_line_dollar_math(trimmed).is_some()
    }
}

fn one_line_dollar_math(trimmed: &str) -> Option<String> {
    if !trimmed.starts_with(DOLLAR_MATH_DELIMITER) || !trimmed.ends_with(DOLLAR_MATH_DELIMITER) {
        return None;
    }
    let inner_start = DOLLAR_MATH_DELIMITER.len();
    let inner_end = trimmed.len().checked_sub(DOLLAR_MATH_DELIMITER.len())?;
    if inner_start >= inner_end {
        return None;
    }
    let expression = trimmed[inner_start..inner_end].trim().to_string();
    (!expression.is_empty()).then_some(expression)
}