libmandoc-rs 0.10.0

Safe Rust interface to the vendored libmandoc parser and reference renderers
Documentation
//! Owned table/column payloads and normalized equations.

use super::*;

#[test]
fn parser_preserves_infix_eqn_operators() {
    let report = Parser::default()
        .parse_bytes(
            "equation.3",
            b".TH EQUATION 3\n.SH DESCRIPTION\n.EQ\nx + {width over 2}\ny sub 1 sup 2\n.EN\n",
        )
        .expect("parse infix eqn operators");
    let equation = find_kind(&report.document.root, NodeKind::Equation)
        .and_then(|node| node.equation.as_deref())
        .expect("normalized equation");

    assert!(equation.contains("width / 2"), "{equation}");
    assert!(equation.contains("y _ 1 ^ 2"), "{equation}");
}

#[test]
fn parser_normalizes_the_common_gnu_ldots_equation_macro() {
    let report = Parser::default()
        .parse_bytes(
            "equation-ldots.3",
            b".TH EQUATION 3\n.SH DESCRIPTION\n.EQ\nx sub 1 ldots x sub n\n.EN\n",
        )
        .expect("parse GNU ldots equation macro");
    let equation = find_kind(&report.document.root, NodeKind::Equation)
        .and_then(|node| node.equation.as_deref())
        .expect("normalized equation");

    assert_eq!(equation, "x _ 1 ... x _ n");
}

#[test]
fn parser_marks_tbl_text_block_cells() {
    let path = source_path("tbl-text-block");
    fs::write(
        &path,
        ".Dd August 19, 2026\n.Dt TBL-TEXT-BLOCK 3\n.Os\n.Sh NAME\n.Nm demo\n.Nd demo\n.Sh ATTRIBUTES\n.TS\nallbox;\nl l.\nInterface\tValue\nT{\n.Nm\nT}\tMT-Safe\n.TE\n",
    )
    .expect("write tbl text block source");
    let document = parse_file(&path, false).expect("parse tbl text block source");
    fs::remove_file(path).expect("remove tbl text block source");
    let row = find_node(&document.root, &|node| {
        node.kind == NodeKind::Table && node.table_cells.iter().any(|cell| cell.text_block)
    })
    .expect("tbl row containing a text block");
    assert_eq!(row.table_cells.len(), 2);
    assert_eq!(row.table_cells[0].text.as_deref(), Some(""));
    assert!(row.table_cells[0].text_block);
    assert!(!row.table_cells[1].text_block);
}

#[test]
fn parser_marks_both_tbl_vertical_continuation_forms() {
    let document = Parser::default()
        .parse_bytes(
            "tbl-vertical-continuations.1",
            b".TH TBL-VERTICAL-CONTINUATIONS 1\n.SH TABLES\n.TS\nl l.\nfirst\tvalue\n\\^\tcontinued\n.TE\n.TS\nl l,\n^ l.\nfirst\tvalue\n\tcontinued\n.TE\n",
        )
        .expect("parse tbl vertical continuations")
        .document;

    let explicit = find_node(&document.root, &|node| {
        node.kind == NodeKind::Table && node.line == 6
    })
    .expect("explicit continuation row");
    assert!(explicit.table_cells[0].vertical_continuation);

    let layout = find_node(&document.root, &|node| {
        node.kind == NodeKind::Table && node.line == 12
    })
    .expect("layout continuation row");
    assert!(layout.table_cells[0].vertical_continuation);
}

#[test]
fn parser_retains_column_list_cells() {
    let report = Parser::default()
        .parse_bytes(
            "columns.3",
            b".Dd August 19, 2026\n.Dt COLUMNS 3\n.Os\n.Sh DESCRIPTION\n\
.Bl -column name type description\n.It Dv CLSET_TIMEOUT Ta \"struct timeval *\" Ta \"set total timeout\"\n.El\n",
        )
        .expect("parse mdoc column list");
    let item = find_macro(&report.document.root, "It").expect("column item");
    let bodies = item
        .children
        .iter()
        .filter(|child| child.kind == NodeKind::Body)
        .collect::<Vec<_>>();

    assert_eq!(bodies.len(), 3);
    assert_eq!(
        bodies
            .iter()
            .map(|body| {
                body.children
                    .iter()
                    .flat_map(|child| child.children.iter())
                    .chain(body.children.iter())
                    .filter_map(|child| child.text.as_deref())
                    .collect::<Vec<_>>()
            })
            .collect::<Vec<_>>(),
        [
            vec!["CLSET_TIMEOUT"],
            vec!["struct timeval *"],
            vec!["set total timeout"],
        ]
    );
}

#[test]
fn parser_copies_table_cells_and_equation_text() {
    let path = source_path("structured-payload-mandoc-session");
    fs::write(
        &path,
        ".TH PAYLOAD 1\n.SH TABLE\n.TS\ntab(|);\nl r.\nleft|right\n.TE\n\
         .SH EQUATION\n.EQ\nx sup 2\n.EN\n",
    )
    .expect("write table and equation source");

    let document = parse_file(&path, false).expect("parse table and equation source");
    fs::remove_file(path).expect("remove table and equation source");

    let table = find_kind(&document.root, NodeKind::Table).expect("table row node");
    assert_eq!(table.table_cells.len(), 2);
    assert_eq!(table.table_cells[0].text.as_deref(), Some("left"));
    assert_eq!(table.table_cells[1].alignment, TableAlignment::Right);
    let equation = find_kind(&document.root, NodeKind::Equation).expect("equation node");
    assert!(
        equation
            .equation
            .as_deref()
            .is_some_and(|value| value.contains('x'))
    );
}