mdtablefix 0.6.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
//! Tests for parallel CLI processing of multiple files.

use std::{fs::File, io::Write};

use assert_cmd::Command;
use rstest::rstest;
use tempfile::tempdir;

#[macro_use]
#[path = "common/mod.rs"]
mod common;

#[path = "support/cli_args.rs"]
mod cli_args;
#[path = "support/fixtures.rs"]
mod fixtures;
use cli_args::run_cli_with_args;
use fixtures::broken_table;

#[rstest]
fn test_cli_parallel_empty_file_list() -> Result<(), Box<dyn std::error::Error>> {
    run_cli_with_args(&[])?.success().stdout("\n");
    Ok(())
}

#[rstest]
fn test_cli_parallel_multiple_files() -> Result<(), Box<dyn std::error::Error>> {
    let dir = tempdir().expect("failed to create temporary directory");
    let mut files = Vec::new();
    let mut expected = String::new();
    for i in 0..4 {
        let path = dir.path().join(format!("file{i}.md"));
        let table = vec![
            format!("| A{i} | B{i} |    |"),
            format!("| {i} | {i} |  | {i} | {i} |"),
        ];
        let mut f = File::create(&path).expect("failed to create temporary file");
        for line in &table {
            writeln!(f, "{line}").expect("failed to write line");
        }
        f.flush().expect("failed to flush file");
        drop(f);
        expected.push_str(&mdtablefix::reflow_table(&table).join("\n"));
        expected.push('\n');
        files.push(path);
    }

    let args: Vec<&str> = files
        .iter()
        .map(|p| p.to_str().expect("path is not valid UTF-8"))
        .collect();
    run_cli_with_args(&args)?.success().stdout(expected);
    Ok(())
}

#[rstest]
fn test_cli_parallel_missing_file_error() {
    let dir = tempdir().expect("failed to create temporary directory");
    let good = dir.path().join("good.md");
    let table = vec![
        "| Q | R |    |".to_string(),
        "| 1 | 2 |  | 3 | 4 |".to_string(),
    ];
    let mut f = File::create(&good).expect("failed to create file");
    for line in &table {
        writeln!(f, "{line}").expect("failed to write line");
    }
    f.flush().expect("failed to flush file");
    drop(f);
    let expected = mdtablefix::reflow_table(&table).join("\n") + "\n";
    let missing = dir.path().join("missing.md");

    // A file that could not be read is an operational error, not drift: the
    // exit contract reserves `2` for it, and the good file is still printed.
    Command::cargo_bin("mdtablefix")
        .expect("failed to create command")
        .arg(&good)
        .arg(&missing)
        .assert()
        .code(2)
        .stdout(expected)
        .stderr(predicates::str::contains("missing.md"));
}

#[rstest]
fn test_cli_parallel_missing_file_in_place(
    broken_table: Vec<String>,
) -> Result<(), Box<dyn std::error::Error>> {
    let dir = tempdir().expect("failed to create temporary directory");
    let good = dir.path().join("good.md");
    let mut f = File::create(&good).expect("failed to create file");
    for line in &broken_table {
        writeln!(f, "{line}").expect("failed to write line");
    }
    f.flush().expect("failed to flush file");
    drop(f);
    let missing = dir.path().join("missing.md");

    let good_str = good.to_str().expect("path is not valid UTF-8");
    let missing_str = missing.to_str().expect("path is not valid UTF-8");
    run_cli_with_args(&["--in-place", good_str, missing_str])?
        .code(2)
        .stderr(predicates::str::contains("missing.md"));
    Ok(())
}