From 1840582a5da9db1209de880932e8760cef7cab30 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Tue, 9 Jun 2026 16:33:45 -0400 Subject: [PATCH 1/5] Add --width option for output wrapping --- src/multimark/_cli.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/multimark/_cli.py b/src/multimark/_cli.py index 34acc4c..2339015 100644 --- a/src/multimark/_cli.py +++ b/src/multimark/_cli.py @@ -51,6 +51,7 @@ @click.option("--hardbreaks", is_flag=True, help="Render softbreaks as hard line breaks.") @click.option("--sourcepos", is_flag=True, help="Include source position attributes (html/xml only).") @click.option("--footnotes", is_flag=True, help="Enable footnote parsing.") +@click.option("--width", type=int, default=0, help="Wrap output at this column width (latex/man/commonmark only).") @click.version_option(__version__, prog_name="multimark") def main( file, @@ -62,6 +63,7 @@ def main( hardbreaks: bool, sourcepos: bool, footnotes: bool, + width: int, ) -> None: """Convert CommonMark/GFM Markdown to various output formats. @@ -83,5 +85,9 @@ def main( if format in ("html", "xml"): kwargs["sourcepos"] = sourcepos + # width is only supported by latex, man, and commonmark renderers + if format in ("latex", "man", "commonmark"): + kwargs["width"] = width + result = renderer(text, **kwargs) output.write(result) From b20115c057db65cd74e4a3d88531f7344dfcfd8b Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Tue, 9 Jun 2026 16:33:53 -0400 Subject: [PATCH 2/5] Add tests for --width wrapping across formats --- tests/test_cli.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 22bbbd0..72ec714 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -138,3 +138,38 @@ def test_help(self, runner): assert result.exit_code == 0 assert "Convert CommonMark/GFM" in result.output assert "--to" in result.output + + +class TestWidth: + def test_width_latex(self, runner): + long_text = "word " * 50 + "\n" + result = runner.invoke(main, ["--to", "latex", "--width", "40"], input=long_text) + assert result.exit_code == 0 + content_lines = [l for l in result.output.split("\n") if "word" in l] + assert all(len(l) <= 40 for l in content_lines) + + def test_width_man(self, runner): + long_text = "word " * 50 + "\n" + result = runner.invoke(main, ["--to", "man", "--width", "60"], input=long_text) + assert result.exit_code == 0 + content_lines = [l for l in result.output.split("\n") if "word" in l] + assert all(len(l) <= 60 for l in content_lines) + + def test_width_commonmark(self, runner): + long_text = "word " * 50 + "\n" + result = runner.invoke(main, ["--to", "commonmark", "--width", "72"], input=long_text) + assert result.exit_code == 0 + content_lines = [l for l in result.output.split("\n") if "word" in l] + assert all(len(l) <= 72 for l in content_lines) + + def test_width_ignored_for_html(self, runner): + result = runner.invoke(main, ["--to", "html", "--width", "40"], input="hello\n") + assert result.exit_code == 0 + assert "

hello

" in result.output + + def test_width_default_no_wrap(self, runner): + long_text = "word " * 50 + "\n" + result = runner.invoke(main, ["--to", "latex"], input=long_text) + assert result.exit_code == 0 + content_lines = [l for l in result.output.split("\n") if "word" in l] + assert any(len(l) > 80 for l in content_lines) From 6190482026e22ab5b0ad2856178d8056fde19622 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Tue, 9 Jun 2026 16:34:02 -0400 Subject: [PATCH 3/5] Add tests for GFM options and footnotes --- tests/test_html.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/test_html.py b/tests/test_html.py index 262f38c..a02c792 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -101,3 +101,74 @@ def test_newline_variations(): """Handles different line ending styles.""" assert markdown_to_html("hello\r\nworld\n") == markdown_to_html("hello\nworld\n") + +# --- GFM-specific Options flags --- + + +def test_github_pre_lang(): + """GITHUB_PRE_LANG adds language as a class on
 instead of ."""
+    md = "```python\ncode\n```\n"
+    result = markdown_to_html(md, options=Options.GITHUB_PRE_LANG)
+    assert 'lang="python"' in result or 'class="language-python"' in result.replace(
+        "
+    default = markdown_to_html(md)
+    assert 'class="language-python"' in default
+
+
+def test_liberal_html_tag():
+    """LIBERAL_HTML_TAG allows non-standard HTML tags."""
+    result = markdown_to_html(
+        "text\n",
+        options=Options.LIBERAL_HTML_TAG | Options.UNSAFE,
+    )
+    assert "" in result
+
+
+def test_full_info_string():
+    """FULL_INFO_STRING preserves the full info string on code blocks."""
+    md = "```python extra-info\ncode\n```\n"
+    result = markdown_to_html(md, options=Options.FULL_INFO_STRING)
+    assert "extra-info" in result or "data-meta" in result
+
+
+def test_strikethrough_double_tilde():
+    """STRIKETHROUGH_DOUBLE_TILDE requires ~~ (not ~) for strikethrough."""
+    md_double = "~~deleted~~\n"
+    md_single = "~deleted~\n"
+    result_double = markdown_to_html(
+        md_double,
+        extensions=["strikethrough"],
+        options=Options.STRIKETHROUGH_DOUBLE_TILDE,
+    )
+    result_single = markdown_to_html(
+        md_single,
+        extensions=["strikethrough"],
+        options=Options.STRIKETHROUGH_DOUBLE_TILDE,
+    )
+    assert "" in result_double
+    assert "" not in result_single
+
+
+def test_table_prefer_style_attributes():
+    """TABLE_PREFER_STYLE_ATTRIBUTES uses style= instead of align=."""
+    md = "| left | center | right |\n|:-----|:------:|------:|\n| a | b | c |\n"
+    result = markdown_to_html(
+        md, extensions=["table"], options=Options.TABLE_PREFER_STYLE_ATTRIBUTES
+    )
+    assert "style=" in result
+
+
+# --- Footnotes keyword argument ---
+
+
+def test_footnotes_keyword():
+    """footnotes=True enables footnote parsing without extensions list."""
+    md = "Text[^1]\n\n[^1]: A footnote.\n"
+    result = markdown_to_html(md, footnotes=True)
+    assert "footnote" in result.lower()
+    # Without footnotes, the marker is treated as regular text
+    result_no = markdown_to_html(md, footnotes=False)
+    assert "[^1]" in result_no
+

From 1cce237ce2bdfb425abbb639064ae55fb824e730 Mon Sep 17 00:00:00 2001
From: Richard Iannone 
Date: Tue, 9 Jun 2026 16:34:10 -0400
Subject: [PATCH 4/5] Add test for empty input producing empty output

---
 tests/test_latex.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tests/test_latex.py b/tests/test_latex.py
index 15e6129..589c7be 100644
--- a/tests/test_latex.py
+++ b/tests/test_latex.py
@@ -49,6 +49,11 @@ def test_unicode_passthrough():
     assert "é" in result
 
 
+def test_empty_input():
+    """Empty string produces empty output."""
+    assert markdown_to_latex("") == ""
+
+
 def test_empty_input():
     assert markdown_to_latex("") == "\n"
 

From 83dc9de9ea496f7e8b40e5481c7b915eff9b61e2 Mon Sep 17 00:00:00 2001
From: Richard Iannone 
Date: Tue, 9 Jun 2026 16:34:17 -0400
Subject: [PATCH 5/5] Add tests for sourcepos option handling

---
 tests/test_xml.py | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/tests/test_xml.py b/tests/test_xml.py
index e3d6b15..9ea61db 100644
--- a/tests/test_xml.py
+++ b/tests/test_xml.py
@@ -38,6 +38,18 @@ def test_sourcepos():
     assert "sourcepos=\"" in result
 
 
+def test_sourcepos_keyword():
+    """sourcepos=True adds sourcepos attributes (keyword form)."""
+    result = markdown_to_xml("Hello\n", sourcepos=True)
+    assert 'sourcepos="1:1-1:5"' in result
+
+
+def test_sourcepos_on_multiple_blocks():
+    """sourcepos is present on each block-level element."""
+    result = markdown_to_xml("# Title\n\nParagraph\n", sourcepos=True)
+    assert result.count('sourcepos="') >= 3  # document, heading, paragraph
+
+
 # --- Edge cases ---