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 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. | 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()); -#[must_use] -pub fn reflow_table(lines: &[String]) -> Vec { - if lines.is_empty() { - return Vec::new(); - } +/// 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>, + sep_cells: Option>, + max_cols: usize, +} +/// 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 mut trimmed: Vec = lines + let trimmed = 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)]