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
6 changes: 6 additions & 0 deletions src/multimark/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -62,6 +63,7 @@ def main(
hardbreaks: bool,
sourcepos: bool,
footnotes: bool,
width: int,
) -> None:
"""Convert CommonMark/GFM Markdown to various output formats.

Expand All @@ -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)
35 changes: 35 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<p>hello</p>" 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)
71 changes: 71 additions & 0 deletions tests/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pre> instead of <code>."""
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(
"<code", "<pre"
)
# Without the flag, language class is on <code>
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(
"<custom-element>text</custom-element>\n",
options=Options.LIBERAL_HTML_TAG | Options.UNSAFE,
)
assert "<custom-element>" 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 "<del>" in result_double
assert "<del>" 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

5 changes: 5 additions & 0 deletions tests/test_latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
12 changes: 12 additions & 0 deletions tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ---


Expand Down
Loading