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
Binary file modified .coverage
Binary file not shown.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [unreleased]

### Fixed
- Fixed issue where line breaks in help text weren't preserved when using `pretty` formatting option
- Line breaks in CLI help messages and docstrings are now properly rendered in the generated documentation

## [0.1.4] - 2025-03-15

### Added
Expand Down
265 changes: 265 additions & 0 deletions coverage.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
<?xml version="1.0" ?>
<coverage version="7.8.0" timestamp="1748982683218" lines-valid="226" lines-covered="212" line-rate="0.9381" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
<!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.8.0 -->
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources>
<source>/Users/taylorroberts/GitHub/syn54x/mkdocs-typer2</source>
</sources>
<packages>
<package name="src.mkdocs_typer2" line-rate="0.9381" branch-rate="0" complexity="0">
<classes>
<class name="__init__.py" filename="src/mkdocs_typer2/__init__.py" complexity="0" line-rate="1" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="3" hits="1"/>
</lines>
</class>
<class name="cli.py" filename="src/mkdocs_typer2/cli.py" complexity="0" line-rate="1" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="3" hits="1"/>
<line number="5" hits="1"/>
<line number="8" hits="1"/>
<line number="9" hits="1"/>
<line number="11" hits="1"/>
<line number="14" hits="1"/>
<line number="15" hits="1"/>
<line number="25" hits="1"/>
<line number="26" hits="1"/>
<line number="27" hits="1"/>
<line number="29" hits="1"/>
<line number="31" hits="1"/>
<line number="32" hits="1"/>
<line number="34" hits="1"/>
</lines>
</class>
<class name="markdown.py" filename="src/mkdocs_typer2/markdown.py" complexity="0" line-rate="0.9608" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="6" hits="1"/>
<line number="8" hits="1"/>
<line number="11" hits="1"/>
<line number="12" hits="1"/>
<line number="13" hits="1"/>
<line number="14" hits="1"/>
<line number="16" hits="1"/>
<line number="17" hits="1"/>
<line number="22" hits="1"/>
<line number="23" hits="1"/>
<line number="24" hits="1"/>
<line number="25" hits="1"/>
<line number="27" hits="1"/>
<line number="28" hits="1"/>
<line number="30" hits="1"/>
<line number="31" hits="1"/>
<line number="32" hits="1"/>
<line number="35" hits="1"/>
<line number="36" hits="1"/>
<line number="37" hits="1"/>
<line number="38" hits="1"/>
<line number="39" hits="1"/>
<line number="41" hits="1"/>
<line number="42" hits="1"/>
<line number="46" hits="1"/>
<line number="47" hits="1"/>
<line number="49" hits="1"/>
<line number="50" hits="1"/>
<line number="51" hits="1"/>
<line number="52" hits="1"/>
<line number="53" hits="1"/>
<line number="56" hits="1"/>
<line number="58" hits="1"/>
<line number="60" hits="1"/>
<line number="61" hits="1"/>
<line number="62" hits="1"/>
<line number="64" hits="1"/>
<line number="66" hits="1"/>
<line number="68" hits="1"/>
<line number="69" hits="1"/>
<line number="70" hits="1"/>
<line number="72" hits="1"/>
<line number="74" hits="1"/>
<line number="75" hits="0"/>
<line number="76" hits="0"/>
<line number="79" hits="1"/>
<line number="80" hits="1"/>
</lines>
</class>
<class name="plugin.py" filename="src/mkdocs_typer2/plugin.py" complexity="0" line-rate="1" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="4" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/>
<line number="15" hits="1"/>
<line number="16" hits="1"/>
<line number="19" hits="1"/>
<line number="21" hits="1"/>
</lines>
</class>
<class name="pretty.py" filename="src/mkdocs_typer2/pretty.py" complexity="0" line-rate="0.9195" branch-rate="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="4" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/>
<line number="9" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="1"/>
<line number="12" hits="1"/>
<line number="15" hits="1"/>
<line number="16" hits="1"/>
<line number="17" hits="1"/>
<line number="18" hits="1"/>
<line number="21" hits="1"/>
<line number="22" hits="1"/>
<line number="23" hits="1"/>
<line number="26" hits="1"/>
<line number="27" hits="1"/>
<line number="28" hits="1"/>
<line number="29" hits="1"/>
<line number="30" hits="1"/>
<line number="31" hits="1"/>
<line number="32" hits="1"/>
<line number="33" hits="1"/>
<line number="36" hits="1"/>
<line number="37" hits="1"/>
<line number="38" hits="1"/>
<line number="41" hits="1"/>
<line number="42" hits="1"/>
<line number="43" hits="1"/>
<line number="46" hits="1"/>
<line number="47" hits="1"/>
<line number="50" hits="1"/>
<line number="51" hits="1"/>
<line number="54" hits="1"/>
<line number="60" hits="1"/>
<line number="61" hits="1"/>
<line number="62" hits="1"/>
<line number="64" hits="1"/>
<line number="65" hits="1"/>
<line number="67" hits="1"/>
<line number="69" hits="1"/>
<line number="71" hits="0"/>
<line number="73" hits="1"/>
<line number="74" hits="1"/>
<line number="75" hits="1"/>
<line number="76" hits="1"/>
<line number="77" hits="1"/>
<line number="79" hits="1"/>
<line number="80" hits="1"/>
<line number="81" hits="1"/>
<line number="83" hits="1"/>
<line number="85" hits="1"/>
<line number="86" hits="1"/>
<line number="87" hits="1"/>
<line number="88" hits="1"/>
<line number="89" hits="1"/>
<line number="90" hits="1"/>
<line number="91" hits="1"/>
<line number="93" hits="1"/>
<line number="100" hits="1"/>
<line number="101" hits="1"/>
<line number="103" hits="1"/>
<line number="105" hits="1"/>
<line number="106" hits="1"/>
<line number="107" hits="1"/>
<line number="108" hits="1"/>
<line number="110" hits="1"/>
<line number="111" hits="1"/>
<line number="112" hits="1"/>
<line number="113" hits="1"/>
<line number="114" hits="1"/>
<line number="116" hits="1"/>
<line number="118" hits="1"/>
<line number="119" hits="0"/>
<line number="120" hits="1"/>
<line number="121" hits="1"/>
<line number="122" hits="1"/>
<line number="124" hits="1"/>
<line number="126" hits="1"/>
<line number="127" hits="0"/>
<line number="128" hits="1"/>
<line number="129" hits="1"/>
<line number="130" hits="1"/>
<line number="132" hits="1"/>
<line number="134" hits="1"/>
<line number="135" hits="0"/>
<line number="136" hits="1"/>
<line number="137" hits="1"/>
<line number="138" hits="1"/>
<line number="140" hits="1"/>
<line number="142" hits="1"/>
<line number="143" hits="1"/>
<line number="144" hits="1"/>
<line number="145" hits="1"/>
<line number="151" hits="1"/>
<line number="153" hits="1"/>
<line number="155" hits="1"/>
<line number="156" hits="1"/>
<line number="157" hits="1"/>
<line number="158" hits="1"/>
<line number="161" hits="1"/>
<line number="163" hits="1"/>
<line number="165" hits="1"/>
<line number="166" hits="1"/>
<line number="167" hits="1"/>
<line number="168" hits="1"/>
<line number="173" hits="0"/>
<line number="174" hits="0"/>
<line number="175" hits="0"/>
<line number="176" hits="0"/>
<line number="180" hits="1"/>
<line number="182" hits="1"/>
<line number="185" hits="1"/>
<line number="186" hits="1"/>
<line number="187" hits="1"/>
<line number="189" hits="1"/>
<line number="190" hits="1"/>
<line number="191" hits="1"/>
<line number="193" hits="1"/>
<line number="194" hits="1"/>
<line number="196" hits="1"/>
<line number="197" hits="1"/>
<line number="203" hits="1"/>
<line number="205" hits="1"/>
<line number="206" hits="1"/>
<line number="207" hits="1"/>
<line number="209" hits="1"/>
<line number="210" hits="1"/>
<line number="212" hits="1"/>
<line number="213" hits="1"/>
<line number="222" hits="1"/>
<line number="224" hits="1"/>
<line number="225" hits="1"/>
<line number="226" hits="1"/>
<line number="227" hits="0"/>
<line number="228" hits="0"/>
<line number="229" hits="0"/>
<line number="230" hits="0"/>
<line number="232" hits="1"/>
<line number="233" hits="1"/>
<line number="234" hits="1"/>
<line number="236" hits="1"/>
<line number="239" hits="1"/>
<line number="260" hits="1"/>
<line number="261" hits="1"/>
<line number="263" hits="1"/>
<line number="264" hits="1"/>
<line number="284" hits="1"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
2 changes: 1 addition & 1 deletion src/mkdocs_typer2/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import typer

app = typer.Typer(help="A sample CLI")
app = typer.Typer(help="A sample CLI\n\nThis is a multi-line help message.")


@app.command()
Expand Down
21 changes: 13 additions & 8 deletions src/mkdocs_typer2/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ def parse_markdown_to_tree(content: str) -> CommandNode:
and not lines[j].startswith("**")
and not lines[j].startswith("```")
):
if lines[j].strip(): # Only add non-empty lines
desc_lines.append(lines[j].strip())
# Preserve the original line content, including empty lines for line breaks
desc_lines.append(lines[j])
j += 1

if desc_lines:
# Join with newlines to preserve line breaks, then strip trailing whitespace
root.description = "\n".join(desc_lines)

break
Expand Down Expand Up @@ -97,13 +98,14 @@ def parse_markdown_to_tree(content: str) -> CommandNode:
and i > 0
):
# We're in description mode and not at a section marker yet
if line.strip(): # Only add non-empty lines
desc_lines.append(line.strip())
# Preserve the original line content, including empty lines for line breaks
desc_lines.append(line)

elif line.startswith("```console"):
# Usage section - end of description
if in_description and desc_lines:
current_command.description = "\n".join(desc_lines)
# Join with newlines to preserve line breaks, then strip trailing whitespace
current_command.description = "\n".join(desc_lines).rstrip()
in_description = False
in_commands_section = False
# Find the line with usage example
Expand All @@ -116,23 +118,26 @@ def parse_markdown_to_tree(content: str) -> CommandNode:
elif line.startswith("**Arguments**:"):
# End of description section
if in_description and desc_lines:
current_command.description = "\n".join(desc_lines)
# Join with newlines to preserve line breaks, then strip trailing whitespace
current_command.description = "\n".join(desc_lines).rstrip()
in_description = False
current_section = "arguments"
in_commands_section = False

elif line.startswith("**Options**:"):
# End of description section
if in_description and desc_lines:
current_command.description = "\n".join(desc_lines)
# Join with newlines to preserve line breaks, then strip trailing whitespace
current_command.description = "\n".join(desc_lines).rstrip()
in_description = False
current_section = "options"
in_commands_section = False

elif line.startswith("**Commands**:"):
# End of description section, start commands section
if in_description and desc_lines:
current_command.description = "\n".join(desc_lines)
# Join with newlines to preserve line breaks, then strip trailing whitespace
current_command.description = "\n".join(desc_lines).rstrip()
in_description = False
current_section = None
in_commands_section = True
Expand Down
36 changes: 33 additions & 3 deletions tests/test_pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,36 @@ def test_tree_to_markdown_with_commands():


def test_tree_to_markdown_no_commands():
cmd = CommandNode(name="mycli", description="A test CLI", commands=[])
markdown = tree_to_markdown(cmd)
assert "*No commands available*" in markdown
"""Test that commands table shows 'No commands available' when empty."""
cmd = CommandNode(name="test", commands=[])
result = tree_to_markdown(cmd)
assert "*No commands available*" in result


def test_parse_markdown_with_line_breaks():
"""Test that line breaks in help text are preserved."""
markdown = """# mycli

A test CLI tool

This is a multi-line help message.

```console
$ mycli --help
```

**Arguments**:
* `name`: The name argument [required]

**Options**:
* `--verbose`: Enable verbose mode
"""
tree = parse_markdown_to_tree(markdown)

# Check that line breaks are preserved in the description
assert tree.name == "mycli"
assert "A test CLI tool" in tree.description
assert "This is a multi-line help message." in tree.description
# Verify there's a line break between the two lines
assert "\n" in tree.description
assert tree.description.count("\n") >= 1