From c7c75f9839b14544e944ab9796cc311281505eba Mon Sep 17 00:00:00 2001 From: Leynos Date: Thu, 17 Jul 2025 11:37:38 +0100 Subject: [PATCH 1/4] Refactor reflow_table into helper functions --- src/table.rs | 80 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/src/table.rs b/src/table.rs index c8841df2..80ec931f 100644 --- a/src/table.rs +++ b/src/table.rs @@ -91,44 +91,86 @@ fn rows_mismatched(rows: &[Vec], split_within_line: bool) -> bool { pub(crate) static SEP_RE: std::sync::LazyLock = std::sync::LazyLock::new(|| Regex::new(r"^[\s|:-]+$").unwrap()); -#[must_use] -pub fn reflow_table(lines: &[String]) -> Vec { - if lines.is_empty() { - return Vec::new(); - } +/// Intermediate result from `parse_and_validate`. +struct ParsedTable { + cleaned: Vec>, + output_rows: Vec>, + sep_cells: Option>, + max_cols: usize, +} +/// Returns the leading whitespace of the first line and a vector of trimmed +/// lines with escaped pipe markers removed. +fn extract_indent_and_trim(lines: &[String]) -> (String, Vec) { let indent: String = lines[0].chars().take_while(|c| c.is_whitespace()).collect(); - let mut trimmed: Vec = lines + let trimmed: Vec = lines .iter() .map(|l| l.trim().to_string()) .filter(|l| !l.trim_start().starts_with("\\-")) .collect(); - let sep_idx = trimmed.iter().position(|l| SEP_RE.is_match(l)); - let sep_line = sep_idx.map(|idx| trimmed.remove(idx)); + (indent, trimmed) +} - let (rows, split_within_line) = crate::reflow::parse_rows(&trimmed); +/// Removes and return the first separator line detected in `lines`. +fn extract_separator_line(lines: &mut Vec) -> Option { + let sep_idx = lines.iter().position(|l| SEP_RE.is_match(l)); + sep_idx.map(|idx| lines.remove(idx)) +} +/// Parses table rows and validates column consistency. +fn parse_and_validate(trimmed: &[String], sep_line: Option<&String>) -> Option { + let (rows, split_within_line) = crate::reflow::parse_rows(trimmed); let max_cols = rows.iter().map(Vec::len).max().unwrap_or(0); - - let (sep_cells, sep_row_idx) = - crate::reflow::detect_separator(sep_line.as_ref(), &rows, max_cols); - + let (sep_cells, sep_row_idx) = crate::reflow::detect_separator(sep_line, &rows, max_cols); let cleaned = crate::reflow::clean_rows(rows); - + if rows_mismatched(&cleaned, split_within_line) { + return None; + } let mut output_rows = cleaned.clone(); if let Some(idx) = sep_index_within(sep_row_idx, output_rows.len()) { output_rows.remove(idx); } + Some(ParsedTable { + cleaned, + output_rows, + sep_cells, + max_cols, + }) +} - if rows_mismatched(&cleaned, split_within_line) { - return lines.to_vec(); +/// Calculates column widths and formats the final table output. +fn calculate_and_format( + cleaned: &[Vec], + output_rows: Vec>, + sep_cells: Option>, + max_cols: usize, + indent: &str, +) -> Vec { + let widths = crate::reflow::calculate_widths(cleaned, max_cols); + let out = crate::reflow::format_rows(output_rows, &widths, indent); + crate::reflow::insert_separator(out, sep_cells, &widths, indent) +} + +#[must_use] +pub fn reflow_table(lines: &[String]) -> Vec { + if lines.is_empty() { + return Vec::new(); } - let widths = crate::reflow::calculate_widths(&cleaned, max_cols); + let (indent, mut trimmed) = extract_indent_and_trim(lines); + let sep_line = extract_separator_line(&mut trimmed); - let out = crate::reflow::format_rows(output_rows, &widths, &indent); + let Some(parsed) = parse_and_validate(&trimmed, sep_line.as_ref()) else { + return lines.to_vec(); + }; - crate::reflow::insert_separator(out, sep_cells, &widths, &indent) + calculate_and_format( + &parsed.cleaned, + parsed.output_rows, + parsed.sep_cells, + parsed.max_cols, + &indent, + ) } #[cfg(test)] From 0a35e86ccf53b0d278d64fa33b22c603d3416f86 Mon Sep 17 00:00:00 2001 From: Leynos Date: Thu, 17 Jul 2025 12:16:52 +0100 Subject: [PATCH 2/4] Document helpers and borrow rows --- src/reflow.rs | 6 +++--- src/table.rs | 22 ++++++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/reflow.rs b/src/reflow.rs index e9068fee..a71aad1d 100644 --- a/src/reflow.rs +++ b/src/reflow.rs @@ -70,11 +70,11 @@ pub(crate) fn calculate_widths(rows: &[Vec], max_cols: usize) -> Vec>, widths: &[usize], indent: &str) -> Vec { - rows.into_iter() +pub(crate) fn format_rows(rows: &[Vec], widths: &[usize], indent: &str) -> Vec { + rows.iter() .map(|row| { let padded: Vec = row - .into_iter() + .iter() .enumerate() .map(|(i, c)| format!("{:], split_within_line: bool) -> bool { pub(crate) static SEP_RE: std::sync::LazyLock = std::sync::LazyLock::new(|| Regex::new(r"^[\s|:-]+$").unwrap()); -/// Intermediate result from `parse_and_validate`. +/// Holds the parsed and validated table data. +/// +/// This is produced by [`parse_and_validate`] and passed to +/// [`calculate_and_format`]. +/// +/// * `cleaned` - rows after empty cells are removed +/// * `output_rows` - rows ready for output (separator removed) +/// * `sep_cells` - optional separator cells for formatting +/// * `max_cols` - maximum column count across all rows struct ParsedTable { cleaned: Vec>, output_rows: Vec>, @@ -99,11 +107,13 @@ struct ParsedTable { max_cols: usize, } -/// Returns the leading whitespace of the first line and a vector of trimmed -/// lines with escaped pipe markers removed. +/// Extracts the leading whitespace of the first line and returns trimmed lines. +/// +/// Lines beginning with `\-` are removed after trimming. These lines escape a +/// leading pipe marker and should not be part of the table. fn extract_indent_and_trim(lines: &[String]) -> (String, Vec) { let indent: String = lines[0].chars().take_while(|c| c.is_whitespace()).collect(); - let trimmed: Vec = lines + let trimmed = lines .iter() .map(|l| l.trim().to_string()) .filter(|l| !l.trim_start().starts_with("\\-")) @@ -141,7 +151,7 @@ fn parse_and_validate(trimmed: &[String], sep_line: Option<&String>) -> Option

], - output_rows: Vec>, + output_rows: &[Vec], sep_cells: Option>, max_cols: usize, indent: &str, @@ -166,7 +176,7 @@ pub fn reflow_table(lines: &[String]) -> Vec { calculate_and_format( &parsed.cleaned, - parsed.output_rows, + &parsed.output_rows, parsed.sep_cells, parsed.max_cols, &indent, From ba9d119d3e22c2b8d4523cf7b805d0f905ed48f6 Mon Sep 17 00:00:00 2001 From: Leynos Date: Thu, 17 Jul 2025 22:17:52 +0100 Subject: [PATCH 3/4] Fix docs per review comments --- docs/release-process.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-process.md b/docs/release-process.md index d7992dcd..9bc1a444 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -27,7 +27,7 @@ compiles a release binary for every matrix row. `cross` is installed from a specific git tag to avoid unexpected behavior from its main branch. Each binary is placed in an `artifacts/-` directory -using the naming pattern `mdtablefix--[.exe]`. A SHA-256 checksum is +using the naming pattern `mdtablefix--[.exe]`. An SHA-256 checksum is written alongside each binary for download verification. After every build completes, the artifact is uploaded so that the GitHub From 4bd4257aa1c389304782057a181bb63b7f22e236 Mon Sep 17 00:00:00 2001 From: Leynos Date: Thu, 17 Jul 2025 22:55:30 +0100 Subject: [PATCH 4/4] Replace ASCII ellipsis in table --- docs/rust-testing-with-rstest-fixtures.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/rust-testing-with-rstest-fixtures.md b/docs/rust-testing-with-rstest-fixtures.md index f23ded0f..31251f2a 100644 --- a/docs/rust-testing-with-rstest-fixtures.md +++ b/docs/rust-testing-with-rstest-fixtures.md @@ -1200,8 +1200,8 @@ and Parameterization** | Feature | Standard #[test] Approach | rstest Approach | | ---------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------- | | Fixture Injection | Manual calls to setup functions within each test. | Fixture name as argument in #[rstest] function; fixture defined with #[fixture]. | -| Parameterized Tests (Specific Cases) | Loop inside one test, or multiple distinct #[test] functions. | #[case(...)] attributes on #[rstest] function. | -| Parameterized Tests (Value Combinations) | Nested loops inside one test, or complex manual generation. | #[values(...)] attributes on arguments of #[rstest] function. | +| Parameterized Tests (Specific Cases) | Loop inside one test, or multiple distinct #[test] functions. | #[case(…)] attributes on #[rstest] function. | +| Parameterized Tests (Value Combinations) | Nested loops inside one test, or complex manual generation. | #[values(…)] attributes on arguments of #[rstest] function. | | Async Fixture Setup | Manual async block and .await calls inside test. | async fn fixtures, with #[future] and #[awt] for ergonomic .awaiting. | | Reusing Parameter Sets | Manual duplication of cases or custom helper macros. | rstest_reuse crate with #[template] and #[apply] attributes. |