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.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "mkdocs-typer2"
version = "0.1.4"
version = "0.1.5"
description = "Mkdocs plugin for generating Typer CLI docs"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion src/mkdocs_typer2/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def docs(name: str = typer.Option(..., help="The name of the project")):
print(f"Generating docs for {name}")


@app.command()
@app.command(hidden=True)
def hello(
name: Annotated[str, typer.Argument(..., help="The name of the person to greet")],
caps: Annotated[
Expand Down
43 changes: 42 additions & 1 deletion src/mkdocs_typer2/pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@
required: bool = False


class CommandEntry(BaseModel):
name: str
description: str = ""


class CommandNode(BaseModel):
name: str
description: str = ""
usage: Optional[str] = None
arguments: List[Argument] = []
options: List[Option] = []
subcommands: List["CommandNode"] = []
commands: List[CommandEntry] = []


def parse_markdown_to_tree(content: str) -> CommandNode:
Expand Down Expand Up @@ -68,6 +74,7 @@
current_section = None
in_description = False
desc_lines = []
in_commands_section = False

i = 0
while i < len(lines):
Expand All @@ -81,6 +88,7 @@
current_command = new_cmd
in_description = True # Start capturing description for this command
desc_lines = []
in_commands_section = False

elif (
in_description
Expand All @@ -97,6 +105,7 @@
if in_description and desc_lines:
current_command.description = "\n".join(desc_lines)
in_description = False
in_commands_section = False
# Find the line with usage example
j = i
while j < len(lines) and "$ " not in lines[j]:
Expand All @@ -110,19 +119,23 @@
current_command.description = "\n".join(desc_lines)
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)
in_description = False
current_section = "options"
in_commands_section = False

elif line.startswith("**Commands**:"):
# End of description section
# End of description section, start commands section
if in_description and desc_lines:
current_command.description = "\n".join(desc_lines)
in_description = False
current_section = None
in_commands_section = True

elif line.startswith("* `") and current_section == "options":
# Parse option
Expand All @@ -147,6 +160,23 @@
)
current_command.arguments.append(argument)

elif line.startswith("* `") and in_commands_section:
# Parse command name and description
match = re.match(r"\* `(.*?)`: (.*)", line)
if match:
cmd_name, cmd_desc = match.groups()
current_command.commands.append(
CommandEntry(name=cmd_name, description=cmd_desc.strip())
)
else:
# Fallback: just the name
match = re.match(r"\* `(.*?)`:?", line)
if match:
cmd_name = match.group(1)
current_command.commands.append(

Check warning on line 176 in src/mkdocs_typer2/pretty.py

View check run for this annotation

Codecov / codecov/patch

src/mkdocs_typer2/pretty.py#L173-L176

Added lines #L173 - L176 were not covered by tests
CommandEntry(name=cmd_name, description="")
)

i += 1

return root
Expand Down Expand Up @@ -191,6 +221,14 @@

return "\n".join(rows)

def format_commands_table(commands: list[CommandEntry]) -> str:
if not commands:
return "*No commands available*"
rows = [format_table_row("Name", "Description"), format_table_row("---", "---")]
for cmd in commands:
rows.append(format_table_row(f"`{cmd.name}`", cmd.description))
return "\n".join(rows)

def format_usage(cmd_name: str, usage: Optional[str]) -> str:
if not usage:
return "*No usage specified*"
Expand All @@ -213,6 +251,9 @@
"",
"## Options\n",
format_options_table(command_node.options),
"",
"## Commands\n",
format_commands_table(command_node.commands),
]

# Add subcommands if they exist
Expand Down
38 changes: 38 additions & 0 deletions tests/test_pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from mkdocs_typer2.pretty import (
Argument,
CommandEntry,
CommandNode,
Option,
parse_markdown_to_tree,
Expand Down Expand Up @@ -245,3 +246,40 @@ def test_parse_typer_generated_docs():
assert len(hello_cmd.options) > 0
assert any(arg.name == "NAME" for arg in hello_cmd.arguments)
assert any(opt.name == "--caps / --no-caps" for opt in hello_cmd.options)


def test_parse_markdown_with_commands():
markdown = """
# mycli

**Commands**:
* `foo`: Foo command description
* `bar`: Bar command description
"""
result = parse_markdown_to_tree(markdown)
assert len(result.commands) == 2
assert result.commands[0].name == "foo"
assert result.commands[0].description == "Foo command description"
assert result.commands[1].name == "bar"
assert result.commands[1].description == "Bar command description"


def test_tree_to_markdown_with_commands():
cmd = CommandNode(
name="mycli",
description="A test CLI",
commands=[
CommandEntry(name="foo", description="Foo command description"),
CommandEntry(name="bar", description="Bar command description"),
],
)
markdown = tree_to_markdown(cmd)
assert "| Name | Description |" in markdown
assert "`foo`" in markdown and "Foo command description" in markdown
assert "`bar`" in markdown and "Bar command description" in markdown


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