diff --git a/README.md b/README.md
index f3d53590..ddecd34f 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
# mdtablefix
`mdtablefix` reflows Markdown tables so that each column has a uniform width.
-It also wraps paragraphs and list items to 80 columns.
+It can wrap paragraphs and list items to 80 columns when the `--wrap` option is
+used.
The tool ignores fenced code blocks and respects escaped pipes (`\|`),
making it safe for mixed content.
@@ -20,10 +21,11 @@ cargo install --path .
## Command-line usage
```bash
-mdtablefix [--in-place] [FILE...]
+mdtablefix [--wrap] [--in-place] [FILE...]
```
- With file paths provided, the corrected tables are printed to stdout.
+- Use `--wrap` to also reflow paragraphs and list items to 80 columns.
- Use `--in-place` to overwrite files.
- If no files are supplied, input is read from stdin and results are written to stdout.
diff --git a/src/html.rs b/src/html.rs
index f853ed5b..f85d39fc 100644
--- a/src/html.rs
+++ b/src/html.rs
@@ -212,8 +212,8 @@ fn push_html_line(
///
/// # Examples
///
-/// ```ignore
-/// use mdtablefix::html::html_table_to_markdown;
+/// ```no_run
+/// use mdtablefix::html_table_to_markdown;
/// let html_lines = vec![
/// "
".to_string()
/// ];
@@ -260,8 +260,8 @@ pub(crate) fn html_table_to_markdown(lines: &[String]) -> Vec {
///
/// # Examples
///
-/// ```ignore
-/// use mdtablefix::html::convert_html_tables;
+/// ```no_run
+/// use mdtablefix::convert_html_tables;
/// let lines = vec![
/// "".to_string(),
/// " | Header |
".to_string(),
diff --git a/src/lib.rs b/src/lib.rs
index 42816bf7..70a85cff 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,12 @@
mod html;
+#[doc(hidden)]
+#[must_use]
+pub fn html_table_to_markdown(lines: &[String]) -> Vec {
+ html::html_table_to_markdown(lines)
+}
+
pub use html::convert_html_tables;
use regex::Regex;
@@ -249,17 +255,19 @@ static FENCE_RE: std::sync::LazyLock =
static BULLET_RE: std::sync::LazyLock =
std::sync::LazyLock::new(|| Regex::new(r"^(\s*(?:[-*+]|\d+[.)])\s+)(.*)").unwrap());
+
/// Returns `true` if the line is a fenced code block delimiter (e.g., three backticks or "~~~").
///
/// # Examples
///
-/// ```ignore
+/// ```no_run
/// use mdtablefix::is_fence;
/// assert!(is_fence("```"));
/// assert!(is_fence("~~~"));
/// assert!(!is_fence("| foo | bar |"));
/// ```
-pub(crate) fn is_fence(line: &str) -> bool {
+#[doc(hidden)]
+pub fn is_fence(line: &str) -> bool {
FENCE_RE.is_match(line)
}
@@ -303,7 +311,7 @@ fn flush_paragraph(out: &mut Vec, buf: &[(String, bool)], indent: &str,
///
/// # Examples
///
-/// ```ignore
+/// ```no_run
/// use mdtablefix::wrap_text;
/// let input = vec![
/// "This is a long paragraph that should be wrapped to a shorter width.".to_string(),
@@ -322,7 +330,8 @@ fn flush_paragraph(out: &mut Vec, buf: &[(String, bool)], indent: &str,
/// assert_eq!(wrapped[6], "let x = 42;");
/// assert_eq!(wrapped[7], "```");
/// ```
-fn wrap_text(lines: &[String], width: usize) -> Vec {
+#[doc(hidden)]
+pub fn wrap_text(lines: &[String], width: usize) -> Vec {
let mut out = Vec::new();
let mut buf: Vec<(String, bool)> = Vec::new();
let mut indent = String::new();
@@ -417,7 +426,7 @@ fn wrap_text(lines: &[String], width: usize) -> Vec {
///
/// # Examples
///
-/// ```
+/// ```no_run
/// use mdtablefix::process_stream;
/// let input = vec![
/// "".to_string(),
@@ -431,7 +440,7 @@ fn wrap_text(lines: &[String], width: usize) -> Vec {
/// assert!(output.iter().any(|line| line.contains("| foo | bar |")));
/// assert!(output.iter().any(|line| line.len() <= 80));
/// ```
-pub fn process_stream(lines: &[String]) -> Vec {
+fn process_stream_inner(lines: &[String], wrap: bool) -> Vec {
let pre = html::convert_html_tables(lines);
let mut out = Vec::new();
@@ -493,7 +502,17 @@ pub fn process_stream(lines: &[String]) -> Vec {
}
}
- wrap_text(&out, 80)
+ if wrap { wrap_text(&out, 80) } else { out }
+}
+
+#[must_use]
+pub fn process_stream(lines: &[String]) -> Vec {
+ process_stream_inner(lines, true)
+}
+
+#[must_use]
+pub fn process_stream_no_wrap(lines: &[String]) -> Vec {
+ process_stream_inner(lines, false)
}
/// Rewrite a file in place with fixed tables.
@@ -517,3 +536,14 @@ pub fn rewrite(path: &Path) -> std::io::Result<()> {
let fixed = process_stream(&lines);
fs::write(path, fixed.join("\n") + "\n")
}
+
+/// Rewrite a file in place with fixed tables without wrapping text.
+///
+/// # Errors
+/// Returns an error if the file cannot be read or written.
+pub fn rewrite_no_wrap(path: &Path) -> std::io::Result<()> {
+ let text = fs::read_to_string(path)?;
+ let lines: Vec = text.lines().map(str::to_string).collect();
+ let fixed = process_stream_no_wrap(&lines);
+ fs::write(path, fixed.join("\n") + "\n")
+}
diff --git a/src/main.rs b/src/main.rs
index b9aaf7d1..fb489c14 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,8 @@
use clap::Parser;
-use mdtablefix::{process_stream, rewrite};
+use mdtablefix::{process_stream, process_stream_no_wrap, rewrite, rewrite_no_wrap};
use std::fs;
use std::io::{self, Read};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
#[derive(Parser)]
#[command(about = "Reflow broken markdown tables")]
@@ -10,10 +10,29 @@ struct Cli {
/// Rewrite files in place
#[arg(long = "in-place", requires = "files")]
in_place: bool,
+ /// Wrap paragraphs and list items to 80 columns
+ #[arg(long = "wrap")]
+ wrap: bool,
/// Markdown files to fix
files: Vec,
}
+fn process_lines(lines: &[String], wrap: bool) -> Vec {
+ if wrap {
+ process_stream(lines)
+ } else {
+ process_stream_no_wrap(lines)
+ }
+}
+
+fn rewrite_path(path: &Path, wrap: bool) -> std::io::Result<()> {
+ if wrap {
+ rewrite(path)
+ } else {
+ rewrite_no_wrap(path)
+ }
+}
+
/// Entry point for the command-line tool that reflows broken markdown tables.
///
/// Parses command-line arguments to determine whether to process files in place, print fixed output to standard output, or read from standard input. Handles file I/O and error propagation as needed.
@@ -41,18 +60,18 @@ fn main() -> anyhow::Result<()> {
let mut input = String::new();
io::stdin().read_to_string(&mut input)?;
let lines: Vec = input.lines().map(str::to_string).collect();
- let fixed = process_stream(&lines);
+ let fixed = process_lines(&lines, cli.wrap);
println!("{}", fixed.join("\n"));
return Ok(());
}
for path in cli.files {
if cli.in_place {
- rewrite(&path)?;
+ rewrite_path(&path, cli.wrap)?;
} else {
let content = fs::read_to_string(&path)?;
let lines: Vec = content.lines().map(str::to_string).collect();
- let fixed = process_stream(&lines);
+ let fixed = process_lines(&lines, cli.wrap);
println!("{}", fixed.join("\n"));
}
}
diff --git a/tests/integration.rs b/tests/integration.rs
index b54b8edb..2eae6262 100644
--- a/tests/integration.rs
+++ b/tests/integration.rs
@@ -297,6 +297,23 @@ fn test_cli_process_file(broken_table: Vec) {
.stdout("| A | B |\n| 1 | 2 |\n| 3 | 4 |\n");
}
+#[test]
+fn test_cli_wrap_option() {
+ let input = "This line is deliberately made much longer than eighty columns so that \
+ the wrapping algorithm is forced to insert a soft line-break somewhere \
+ in the middle of the paragraph when the --wrap flag is supplied.";
+ let output = Command::cargo_bin("mdtablefix")
+ .unwrap()
+ .arg("--wrap")
+ .write_stdin(format!("{input}\n"))
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let text = String::from_utf8_lossy(&output.stdout);
+ assert!(text.lines().count() > 1, "expected wrapped output on multiple lines");
+ assert!(text.lines().all(|l| l.len() <= 80));
+}
+
#[test]
fn test_uniform_example_one() {
let input = vec![