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
53 changes: 37 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> {
pub fn split_cells(line: &str) -> Vec<String> {
let mut s = line.trim();
if let Some(stripped) = s.strip_prefix('|') {
s = stripped;
Expand Down Expand Up @@ -65,6 +66,7 @@ fn split_cells(line: &str) -> Vec<String> {
/// # Examples
///
/// ```
/// use mdtablefix::reflow_table;
/// let lines = vec![
/// "| a | b |".to_string(),
/// "| c | d |".to_string(),
Expand Down Expand Up @@ -131,14 +133,31 @@ pub fn reflow_table(lines: &[String]) -> Vec<String> {
return lines.to_vec();
}

let out: Vec<String> = 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);
}
Comment thread
leynos marked this conversation as resolved.

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<String> = row
.into_iter()
.enumerate()
.map(|(i, c)| format!("{:<width$}", c, width = widths[i]))
.collect();
format!("| {} |", padded.join(" | "))
})
.collect();

Expand All @@ -165,14 +184,15 @@ pub fn reflow_table(lines: &[String]) -> Vec<String> {
/// # 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 |");
Expand Down Expand Up @@ -253,8 +273,9 @@ pub fn process_stream(lines: &[String]) -> Vec<String> {
///
/// # Examples
///
/// ```
/// ```no_run
/// use std::path::Path;
/// use mdtablefix::rewrite;
/// let path = Path::new("example.md");
/// rewrite(path).unwrap();
/// ```
Expand Down
80 changes: 79 additions & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ fn test_cli_process_file(broken_table: Vec<String>) {
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);
Expand All @@ -175,3 +175,81 @@ fn test_cli_process_file(broken_table: Vec<String>) {
.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<usize> = output[0]
.trim_matches('|')
.split('|')
.map(str::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<DB>` 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/<backend>\")` 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<usize> = output[0]
.trim_matches('|')
.split('|')
.map(str::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_non_table_lines_unchanged() {
let input = vec![
"# Title".to_string(),
String::new(),
"Para text.".to_string(),
String::new(),
"| a | b |".to_string(),
"| 1 | 22 |".to_string(),
String::new(),
"* bullet".to_string(),
String::new(),
];
let output = process_stream(&input);
let expected = vec![
"# Title".to_string(),
String::new(),
"Para text.".to_string(),
String::new(),
"| a | b |".to_string(),
"| 1 | 22 |".to_string(),
String::new(),
"* bullet".to_string(),
String::new(),
];
assert_eq!(output, expected);
}
Loading