mdtablefix 0.5.1

`mdtablefix` unb0rks and reflows Markdown tables so that each column has a uniform width. When the `--wrap` option is used, it also wraps paragraphs and list items to 80 columns.
Documentation
//! Property tests for inline span grouping invariants.
//!
//! Validates that `determine_token_span` partitions token streams without gaps
//! or overlaps and reports Unicode display widths that match grouped text.

use proptest::prelude::*;
use unicode_width::UnicodeWidthStr;

use super::super::{inline::determine_token_span, tokenize::segment_inline};

fn inline_text_strategy() -> impl Strategy<Value = String> {
    prop_oneof![
        prop::string::string_regex(r"[\w\s.,;:!?`()\[\]^-]{0,80}")
            .expect("invalid regex for span grouping property strategy"),
        Just("`code`.[^1]".to_string()),
        Just("[text](url).[^2]".to_string()),
        Just("(`code`).[^3]".to_string()),
        Just("See (`code`).[^4] for details.".to_string()),
        Just("pattern([1](https://github.com/leynos/mdtablefix/pull/url))".to_string()),
        Just(
            concat!(
                "pattern([1](https://github.com/leynos/mdtablefix/pull/url))",
                "([2](https://github.com/leynos/mdtablefix/issues/325))"
            )
            .to_string()
        ),
    ]
}

/// Builds the `inline_citation_strategy` inputs for citation-specific cases.
///
/// This strategy produces a single `pattern()` citation, multiple
/// back-to-back `pattern()` citations, and a `reference()` citation so the
/// property exercises those citation forms directly. Unlike
/// `inline_text_strategy`, which generates general inline text, this helper
/// keeps the input space focused on the parenthesized inline-link shapes that
/// validate citation coupling.
fn inline_citation_strategy() -> impl Strategy<Value = String> {
    prop_oneof![
        Just("pattern([1](https://github.com/leynos/mdtablefix/pull/url))".to_string()),
        Just(
            concat!(
                "pattern([1](https://github.com/leynos/mdtablefix/pull/url))",
                "([2](https://github.com/leynos/mdtablefix/issues/325))"
            )
            .to_string()
        ),
        Just("reference([42](https://github.com/leynos/mdtablefix/tree/main))".to_string()),
    ]
}

/// Reconstructs span text by partitioning `tokens` with `determine_token_span`.
///
/// The `tokens: &[String]` input is the segmented inline token stream. The
/// helper advances by each `end` index returned from `determine_token_span` and
/// returns a `Vec<String>` whose entries are the joined tokens for consecutive
/// spans, making span-boundary validation explicit in property assertions.
fn grouped_spans(tokens: &[String]) -> Vec<String> {
    let mut spans = Vec::new();
    let mut index = 0;
    while index < tokens.len() {
        let (end, _) = determine_token_span(tokens, index);
        spans.push(tokens[index..end].join(""));
        index = end;
    }
    spans
}

proptest! {
    #[test]
    fn determine_token_span_partitions_segmented_tokens(input in inline_text_strategy()) {
        let tokens = segment_inline(&input);
        if tokens.is_empty() {
            return Ok(());
        }

        let mut index = 0;
        while index < tokens.len() {
            let (end, width) = determine_token_span(&tokens, index);
            prop_assert!(end > index, "span must advance at least one token");
            let grouped = tokens[index..end].join("");
            prop_assert_eq!(width, UnicodeWidthStr::width(grouped.as_str()));
            index = end;
        }
        prop_assert_eq!(index, tokens.len());
    }

    #[test]
    fn determine_token_span_keeps_inline_citation_chain_atomic(
        prefix in prop::string::string_regex(r"[\w ]{0,32}")
            .expect("invalid regex for citation prefix strategy"),
        citation in inline_citation_strategy(),
        suffix in prop::string::string_regex(r"[\w .,;:!?]{0,32}")
            .expect("invalid regex for citation suffix strategy"),
    ) {
        let input = format!("{prefix}{citation}{suffix}");
        let tokens = segment_inline(&input);
        let spans = grouped_spans(&tokens);

        prop_assert!(
            spans.iter().any(|span| span.contains(&citation)),
            "expected citation chain to stay within one span; tokens={tokens:?}, spans={spans:?}",
        );
    }
}