From 00c29714186afb7fddc51cbb106a78b871ea8109 Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 13 Jun 2025 20:32:01 +0100 Subject: [PATCH 1/3] Add uniform column width and tests --- src/lib.rs | 53 +++++++++++++++++++++++++++++++------------- tests/integration.rs | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f26a76ad..839d32d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,11 +15,12 @@ use std::path::Path; /// # Examples /// /// ``` +/// use mdtablefix::split_cells; /// let line = "| cell1 | cell2 | cell3 |"; /// let cells = split_cells(line); /// assert_eq!(cells, vec!["cell1", "cell2", "cell3"]); /// ``` -fn split_cells(line: &str) -> Vec { +pub fn split_cells(line: &str) -> Vec { let mut s = line.trim(); if let Some(stripped) = s.strip_prefix('|') { s = stripped; @@ -65,6 +66,7 @@ fn split_cells(line: &str) -> Vec { /// # Examples /// /// ``` +/// use mdtablefix::reflow_table; /// let lines = vec![ /// "| a | b |".to_string(), /// "| c | d |".to_string(), @@ -131,14 +133,31 @@ pub fn reflow_table(lines: &[String]) -> Vec { return lines.to_vec(); } - let out: Vec = rows + let mut cleaned = Vec::new(); + for mut row in rows { + row.retain(|c| !c.is_empty()); + while row.len() < max_cols { + row.push(String::new()); + } + cleaned.push(row); + } + + let mut widths = vec![0; max_cols]; + for row in &cleaned { + for (idx, cell) in row.iter().enumerate() { + widths[idx] = widths[idx].max(cell.len()); + } + } + + cleaned .into_iter() - .map(|mut r| { - r.retain(|c| !c.is_empty()); - while r.len() < max_cols { - r.push(String::new()); - } - format!("{}| {} |", indent, r.join(" | ")) + .map(|row| { + let padded: Vec = row + .into_iter() + .enumerate() + .map(|(i, c)| format!("{: Vec { /// # Examples /// /// ``` +/// use mdtablefix::process_stream; /// let input = vec![ -/// "| a | b |", -/// "|---|---|", -/// "| 1 | 2 |", -/// "", -/// "```", -/// "code block", -/// "```", +/// String::from("| a | b |"), +/// String::from("|---|---|"), +/// String::from("| 1 | 2 |"), +/// String::from(""), +/// String::from("```"), +/// String::from("code block"), +/// String::from("```"), /// ]; /// let output = process_stream(&input); /// assert_eq!(output[0], "| a | b |"); @@ -253,8 +273,9 @@ pub fn process_stream(lines: &[String]) -> Vec { /// /// # Examples /// -/// ``` +/// ```no_run /// use std::path::Path; +/// use mdtablefix::rewrite; /// let path = Path::new("example.md"); /// rewrite(path).unwrap(); /// ``` diff --git a/tests/integration.rs b/tests/integration.rs index cce3d1ad..9d4b9659 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -175,3 +175,53 @@ fn test_cli_process_file(broken_table: Vec) { .success() .stdout("| A | B |\n| 1 | 2 |\n| 3 | 4 |\n"); } + +#[test] +fn test_uniform_example_one() { + let input = vec![ + "| Logical type | PostgreSQL | SQLite notes |".to_string(), + "|--------------|-------------------------|---------------------------------------------------------------------------------|".to_string(), + "| strings | `TEXT` (or `VARCHAR`) | `TEXT` - SQLite ignores the length specifier anyway |".to_string(), + "| booleans | `BOOLEAN DEFAULT FALSE` | declare as `BOOLEAN`; Diesel serialises to 0 / 1 so this is fine |".to_string(), + "| integers | `INTEGER` / `BIGINT` | ditto |".to_string(), + "| decimals | `NUMERIC` | stored as FLOAT in SQLite; Diesel `Numeric` round-trips, but beware precision |".to_string(), + "| blobs / raw | `BYTEA` | `BLOB` |".to_string(), + ]; + let output = reflow_table(&input); + assert!(!output.is_empty()); + let widths: Vec = output[0] + .trim_matches('|') + .split('|') + .map(|c| c.len()) + .collect(); + for row in output { + let cols: Vec<&str> = row.trim_matches('|').split('|').collect(); + for (i, col) in cols.iter().enumerate() { + assert_eq!(col.len(), widths[i]); + } + } +} + +#[test] +fn test_uniform_example_two() { + let input = vec![ + "| Option | How it works | When to choose it |".to_string(), + "|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|".to_string(), + "| **B. Pure-Rust migrations** | Implement `diesel::migration::Migration` in a Rust file (`up.rs` / `down.rs`) and compile with both `features = [\"postgres\", \"sqlite\"]`. The query builder emits backend-specific SQL at runtime. | You prefer the type-checked DSL and can live with slightly slower compile times. |".to_string(), + "| **C. Lowest-common-denominator SQL** | Write one `up.sql`/`down.sql` that *already* works on both engines. This demands avoiding SERIAL/IDENTITY, JSONB, `TIMESTAMPTZ`, etc. | Simple schemas, embedded use-case only, you are happy to supply integer primary keys manually. |".to_string(), + "| **D. Two separate migration trees** | Maintain `migrations/sqlite` and `migrations/postgres` directories with identical version numbers. Use `embed_migrations!(\"migrations/\")` to compile the right set. | You ship a single binary with migrations baked in. |".to_string(), + ]; + let output = reflow_table(&input); + assert!(!output.is_empty()); + let widths: Vec = output[0] + .trim_matches('|') + .split('|') + .map(|c| c.len()) + .collect(); + for row in output { + let cols: Vec<&str> = row.trim_matches('|').split('|').collect(); + for (i, col) in cols.iter().enumerate() { + assert_eq!(col.len(), widths[i]); + } + } +} From 14c32577dd6af4728dd93904b912af92fe726708 Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 13 Jun 2025 21:28:12 +0100 Subject: [PATCH 2/3] Add test for non-table markdown --- tests/integration.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 9d4b9659..9fd2381b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -225,3 +225,31 @@ fn test_uniform_example_two() { } } } + +#[test] +fn test_non_table_lines_unchanged() { + let input = vec![ + "# Title".to_string(), + "".to_string(), + "Para text.".to_string(), + "".to_string(), + "| a | b |".to_string(), + "| 1 | 22 |".to_string(), + "".to_string(), + "* bullet".to_string(), + "".to_string(), + ]; + let output = process_stream(&input); + let expected = vec![ + "# Title".to_string(), + "".to_string(), + "Para text.".to_string(), + "".to_string(), + "| a | b |".to_string(), + "| 1 | 22 |".to_string(), + "".to_string(), + "* bullet".to_string(), + "".to_string(), + ]; + assert_eq!(output, expected); +} From 954fb00e579de3bf319f6261550c6411b44f22b4 Mon Sep 17 00:00:00 2001 From: Leynos Date: Fri, 13 Jun 2025 21:53:52 +0100 Subject: [PATCH 3/3] Fix clippy issues in tests --- tests/integration.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index 9fd2381b..c20639cb 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -164,7 +164,7 @@ fn test_cli_process_file(broken_table: Vec) { let file_path = dir.path().join("sample.md"); let mut f = File::create(&file_path).unwrap(); for line in &broken_table { - writeln!(f, "{}", line).unwrap(); + writeln!(f, "{line}").unwrap(); } f.flush().unwrap(); drop(f); @@ -192,7 +192,7 @@ fn test_uniform_example_one() { let widths: Vec = output[0] .trim_matches('|') .split('|') - .map(|c| c.len()) + .map(str::len) .collect(); for row in output { let cols: Vec<&str> = row.trim_matches('|').split('|').collect(); @@ -216,7 +216,7 @@ fn test_uniform_example_two() { let widths: Vec = output[0] .trim_matches('|') .split('|') - .map(|c| c.len()) + .map(str::len) .collect(); for row in output { let cols: Vec<&str> = row.trim_matches('|').split('|').collect(); @@ -230,26 +230,26 @@ fn test_uniform_example_two() { fn test_non_table_lines_unchanged() { let input = vec![ "# Title".to_string(), - "".to_string(), + String::new(), "Para text.".to_string(), - "".to_string(), + String::new(), "| a | b |".to_string(), "| 1 | 22 |".to_string(), - "".to_string(), + String::new(), "* bullet".to_string(), - "".to_string(), + String::new(), ]; let output = process_stream(&input); let expected = vec![ "# Title".to_string(), - "".to_string(), + String::new(), "Para text.".to_string(), - "".to_string(), + String::new(), "| a | b |".to_string(), "| 1 | 22 |".to_string(), - "".to_string(), + String::new(), "* bullet".to_string(), - "".to_string(), + String::new(), ]; assert_eq!(output, expected); }