Skip to content
Open
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
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
[![fair-software badge](https://img.shields.io/badge/fair--software.eu-%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8F%20%20%E2%97%8B-yellow)](https://fair-software.eu)
[![CI](https://github.com/i-VRESSE/mkdocs_rich_argparse/actions/workflows/build.yml/badge.svg)](https://github.com/i-VRESSE/mkdocs_rich_argparse/actions/workflows/build.yml)

An MkDocs plugin to generate documentation for a [rich argparse parser](https://pypi.org/project/rich-argparse/).
It renders commands, sub commands and sub-sub commands which can have rich help messages.
An MkDocs plugin to generate documentation for any argparse parser with [rich-argparse](https://pypi.org/project/rich-argparse/) styling.
It renders commands, sub commands and sub-sub commands with colored output.

The plugin works with **any** `argparse.ArgumentParser` - you don't need to modify your parser to use `RichHelpFormatter`.

## Installation

Expand Down Expand Up @@ -40,6 +42,25 @@ See the [example/](example/) directory for a minimal example and a custom styled

[![Screenshot of example](https://github.com/i-VRESSE/mkdocs_rich_argparse/raw/main/example/screenshot.png)](https://github.com/i-VRESSE/mkdocs_rich_argparse/raw/main/example/screenshot.png)

### Custom Styles

You can customize the colors used in the generated documentation:

```yaml
plugins:
- mkdocs-rich-argparse:
module: my_module
factory: my_factory_function
styles:
prog: "#00ff00" # Program name in usage
args: "#ff0000" # Arguments and options
groups: "#00ffff" # Group headers
metavar: "#ff00ff" # Metavariables (e.g., FILE)
help: white # Help text
text: white # Description text
default: grey50 # Default values
```
Comment on lines +54 to +62
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "syntax" style option is defined in the RichArgparseStyles config class but is missing from the README documentation. This creates an inconsistency between the available configuration options and the documentation.

Consider adding "syntax" to the list of style options in the README's Custom Styles section, or if it's intentionally excluded, add a comment explaining why.

Copilot uses AI. Check for mistakes.

### Colors in Continuous Integration

If you are building the mkdocs site in a Continuous Integration (CI) environment, the colors might not render correctly. To fix this add [TTY_COMPATIBLE=1 and TTY_INTERACTIVE=0 environment variables](https://rich.readthedocs.io/en/stable/console.html#environment-variables) to your CI configuration.
Expand Down
17 changes: 14 additions & 3 deletions src/mkdocs_rich_argparse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import sys
from collections.abc import Callable
from textwrap import dedent
from typing import cast
import mkdocs.config.base
import mkdocs.config.config_options
import mkdocs.plugins
Expand All @@ -27,7 +28,14 @@ def _capture_help(parser: argparse.ArgumentParser) -> str:
# Based on https://github.com/hamdanal/rich-argparse/blob/e28584ac56ddd46f4079d037c27f24f0ec4eccb4/rich_argparse/_argparse.py#L545
# but with export instead of save

text = Text.from_ansi(parser.format_help())
# Temporarily set RichHelpFormatter to get colored output
original_formatter = parser.formatter_class
parser.formatter_class = RichHelpFormatter
try:
text = Text.from_ansi(parser.format_help())
finally:
parser.formatter_class = original_formatter

console = Console(file=io.StringIO(), record=True)
console.print(text, crop=False)
code_format = dedent("""\
Expand Down Expand Up @@ -58,7 +66,8 @@ def argparser_to_markdown(parser: argparse.ArgumentParser, heading: str = "CLI R
subparsers_actions = [action for action in parser._actions if isinstance(action, argparse._SubParsersAction)]
current_subparsers_action = subparsers_actions[0]

for sub_cmd_name, sub_cmd_parser in current_subparsers_action.choices.items():
for sub_cmd_name, _sub_cmd_parser in current_subparsers_action.choices.items():
sub_cmd_parser = cast("argparse.ArgumentParser", _sub_cmd_parser)
sub_cmd_help_text = _capture_help(sub_cmd_parser)

lines.extend(
Expand All @@ -77,7 +86,8 @@ def argparser_to_markdown(parser: argparse.ArgumentParser, heading: str = "CLI R
]
if sub_subparsers_actions:
sub_current_subparsers_action = sub_subparsers_actions[0]
for sub_sub_cmd_name, sub_sub_cmd_parser in sub_current_subparsers_action.choices.items():
for sub_sub_cmd_name, _sub_sub_cmd_parser in sub_current_subparsers_action.choices.items():
sub_sub_cmd_parser = cast("argparse.ArgumentParser", _sub_sub_cmd_parser)
sub_sub_cmd_help_text = _capture_help(sub_sub_cmd_parser)

lines.extend(
Expand Down Expand Up @@ -133,6 +143,7 @@ def _load_obj(module: str, attribute: str, path: str | None) -> Callable:
class RichArgparseStyles(mkdocs.config.base.Config):
"""Configuration for styles applied to the generated documentation."""

prog = mkdocs.config.config_options.Optional(mkdocs.config.config_options.Type(str))
args = mkdocs.config.config_options.Optional(mkdocs.config.config_options.Type(str))
groups = mkdocs.config.config_options.Optional(mkdocs.config.config_options.Type(str))
# Overwrite default colors as on mkdocs black text is not visible in dark mode
Expand Down
73 changes: 73 additions & 0 deletions tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from textwrap import dedent
import pytest
from rich_argparse import RichHelpFormatter
from mkdocs_rich_argparse import RichArgparseStyles
from mkdocs_rich_argparse import argparser_to_markdown


Expand Down Expand Up @@ -93,3 +94,75 @@ def test_argparser_to_markdown_with_color(monkeypatch: pytest.MonkeyPatch, sampl

assert len(result) > 3500
assert '<span style="color: #008080; text-decoration-color: #008080">' in result


@pytest.fixture
def plain_parser():
"""Parser using default HelpFormatter (the common case)."""
parser = argparse.ArgumentParser(prog="plainprog", description="A plain parser.")
parser.add_argument("--verbose", action="store_true", help="Enable verbose mode")
subparsers = parser.add_subparsers(title="commands", dest="command")
sub = subparsers.add_parser("run", help="Run something")
sub.add_argument("target", help="Target to run")
return parser


def test_plain_parser_gets_colors(monkeypatch: pytest.MonkeyPatch, plain_parser: argparse.ArgumentParser):
"""Verify parsers without RichHelpFormatter still get colored output."""
monkeypatch.setenv("FORCE_COLOR", "1")

result = argparser_to_markdown(plain_parser)

# Should have color spans from RichHelpFormatter
assert '<span style="color:' in result
# Should have the program name
assert "plainprog" in result


def test_plain_parser_formatter_not_mutated(plain_parser: argparse.ArgumentParser):
"""Verify original formatter_class is restored after capture."""
original_formatter = plain_parser.formatter_class

argparser_to_markdown(plain_parser)

assert plain_parser.formatter_class is original_formatter
assert plain_parser.formatter_class is not RichHelpFormatter


def test_subparser_formatter_not_mutated(plain_parser: argparse.ArgumentParser):
"""Verify subparser formatter_class is also restored."""
# Get the subparser
subparsers_action = next(a for a in plain_parser._actions if isinstance(a, argparse._SubParsersAction))
run_parser = subparsers_action.choices["run"]
assert isinstance(run_parser, argparse.ArgumentParser)
original_formatter = run_parser.formatter_class

argparser_to_markdown(plain_parser)

assert run_parser.formatter_class is original_formatter
assert run_parser.formatter_class is not RichHelpFormatter


def test_prog_style_option(monkeypatch: pytest.MonkeyPatch, plain_parser: argparse.ArgumentParser):
"""Verify prog style option is applied to RichHelpFormatter."""
monkeypatch.setenv("FORCE_COLOR", "1")

# Configure prog style
styles = RichArgparseStyles()
styles.prog = "#ff0000"
styles.apply()

result = argparser_to_markdown(plain_parser)

# Should have the custom prog color in the output
assert "#ff0000" in result or "rgb(255,0,0)" in result.lower() or "color: #ff0000" in result.lower()


def test_all_style_options_recognized():
"""Verify all style options are recognized by the config."""
styles = RichArgparseStyles()

# All these should be valid attributes
expected_styles = ["prog", "args", "groups", "help", "metavar", "syntax", "text", "default"]
for style_name in expected_styles:
assert hasattr(styles, style_name), f"Missing style option: {style_name}"