Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<os>-<arch>` directory
using the naming pattern `mdtablefix-<os>-<arch>[.exe]`. A SHA-256 checksum is
using the naming pattern `mdtablefix-<os>-<arch>[.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
Expand Down
4 changes: 2 additions & 2 deletions docs/rust-testing-with-rstest-fixtures.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

Expand Down
6 changes: 3 additions & 3 deletions src/reflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ pub(crate) fn calculate_widths(rows: &[Vec<String>], max_cols: usize) -> Vec<usi
widths
}

pub(crate) fn format_rows(rows: Vec<Vec<String>>, widths: &[usize], indent: &str) -> Vec<String> {
rows.into_iter()
pub(crate) fn format_rows(rows: &[Vec<String>], widths: &[usize], indent: &str) -> Vec<String> {
rows.iter()
.map(|row| {
let padded: Vec<String> = row
.into_iter()
.iter()
.enumerate()
.map(|(i, c)| format!("{:<width$}", c, width = widths[i]))
.collect();
Expand Down
90 changes: 71 additions & 19 deletions src/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,44 +91,96 @@ fn rows_mismatched(rows: &[Vec<String>], split_within_line: bool) -> bool {
pub(crate) static SEP_RE: std::sync::LazyLock<Regex> =
std::sync::LazyLock::new(|| Regex::new(r"^[\s|:-]+$").unwrap());

#[must_use]
pub fn reflow_table(lines: &[String]) -> Vec<String> {
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<Vec<String>>,
output_rows: Vec<Vec<String>>,
sep_cells: Option<Vec<String>>,
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<String>) {
let indent: String = lines[0].chars().take_while(|c| c.is_whitespace()).collect();
let mut trimmed: Vec<String> = 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<String>) -> Option<String> {
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<ParsedTable> {
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(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: calculate_and_format takes ownership of output_rows, which may be unnecessary.

Consider changing the parameter to borrow output_rows instead of taking ownership to avoid unnecessary allocations or moves.

Suggested implementation:

+fn calculate_and_format(
+    cleaned: &[Vec<String>],
+    output_rows: &[Vec<String>],

You will also need to update all call sites of calculate_and_format in this file (and possibly others) to pass &output_rows instead of output_rows. If the function is public or used in other modules, those usages must be updated as well.

cleaned: &[Vec<String>],
output_rows: &[Vec<String>],
sep_cells: Option<Vec<String>>,
max_cols: usize,
indent: &str,
) -> Vec<String> {
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<String> {
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)]
Expand Down