Skip to content

Commit e4e8e03

Browse files
committed
First commit
0 parents  commit e4e8e03

File tree

8 files changed

+285
-0
lines changed

8 files changed

+285
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Python-generated files
2+
__pycache__/
3+
*.py[oc]
4+
build/
5+
dist/
6+
wheels/
7+
*.egg-info
8+
9+
# Virtual environments
10+
.venv

.pre-commit-config.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
ci:
2+
autoupdate_schedule: "quarterly"
3+
4+
repos:
5+
- repo: https://github.com/pre-commit/pre-commit-hooks
6+
rev: v5.0.0
7+
hooks:
8+
- id: check-added-large-files
9+
- id: check-case-conflict
10+
- id: check-merge-conflict
11+
- id: check-toml
12+
- id: check-yaml
13+
- id: end-of-file-fixer
14+
- id: mixed-line-ending
15+
- id: trailing-whitespace
16+
- repo: https://github.com/astral-sh/ruff-pre-commit
17+
rev: v0.9.5
18+
hooks:
19+
- id: ruff
20+
args: [--fix]
21+
- id: ruff-format
22+
- repo: https://github.com/pre-commit/mirrors-mypy
23+
rev: v1.15.0
24+
hooks:
25+
- id: mypy
26+
additional_dependencies: ["rich-argparse"]

.vscode/settings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"[python]": {
3+
"editor.defaultFormatter": "charliermarsh.ruff",
4+
"editor.formatOnSave": true,
5+
"editor.codeActionsOnSave": {
6+
"source.organizeImports.ruff": "explicit",
7+
},
8+
},
9+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Ali Hamdan
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# auto-rich-argparse
2+
3+
Automatically use [rich-argparse](https://github.com/hamdanal/rich-argparse) help formatters with
4+
argparse command line tools.
5+
6+
## Usage
7+
8+
Install auto-rich-argparse in the same environment as your command line tool.
9+
10+
_Try it out with your favorite tool using `uvx --with auto-rich-argparse <YOUR_FAVORITE_TOOL> --help`_
11+
12+
### Tools installed with `pipx`
13+
14+
If your tool is installed with `pipx`, use the `pipx inject` command to add auto-rich-argparse to
15+
the tool's environment. For example, to use rich-argparse with `pre-commit`:
16+
17+
```bash
18+
pipx inject pre-commit auto-rich-argparse
19+
```
20+
21+
### Tools installed with `uv tool`
22+
23+
If your tool is installed with `uv tool`, use the `--with` flag to install auto-rich-argparse:
24+
25+
```bash
26+
uv tool install pre-commit --with auto-rich-argparse
27+
```
28+
29+
### Tools in the current project
30+
31+
You can also install auto-rich-argparse in you project's environment and it will be used by all
32+
tools that use argparse in that environment:
33+
34+
```bash
35+
python -m pip install auto-rich-argparse
36+
```
37+
38+
## How it works
39+
40+
auto-rich-argparse works by monkey patching the `argparse.ArgumentParser` class to use a subclass of
41+
`RichHelpFormatter` from rich-argparse instead of the default `argparse.HelpFormatter`. The formatter
42+
tries to be compatible with the tool's formatter by setting the following options:
43+
44+
```python
45+
FormatterClass.help_markup = False
46+
FormatterClass.text_markup = False
47+
FormatterClass.group_name_formatter = str
48+
```
49+
50+
## Known limitations
51+
52+
This doesn't currently work if the tool passes a function as the `formatter_class` argument to
53+
`ArgumentParser` instead of a class.

auto_rich_argparse.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from __future__ import annotations
2+
3+
import functools
4+
from argparse import ArgumentParser, HelpFormatter
5+
6+
import rich_argparse
7+
from rich_argparse import RichHelpFormatter
8+
9+
10+
def make_rich_formatter_class(formatter_class: type[HelpFormatter]) -> type[HelpFormatter]:
11+
for cls in formatter_class.mro():
12+
if cls.__module__ == "argparse":
13+
try:
14+
# Find the corresponding rich formatter class
15+
rich_formatter_class: type[RichHelpFormatter] = getattr(
16+
rich_argparse, cls.__name__.replace("HelpFormatter", "RichHelpFormatter")
17+
)
18+
if formatter_class.__module__ == "argparse":
19+
# If the formatter class itself is from argparse, replace it
20+
formatter_class = type(
21+
rich_formatter_class.__name__, (rich_formatter_class,), {}
22+
)
23+
else:
24+
# Otherwise, inject the rich formatter class into its bases
25+
# This is necessary because we can't inherit from classes compiled with mypyc
26+
formatter_class.__bases__ = (rich_formatter_class,) + formatter_class.__bases__
27+
28+
# Disable rich-argparse's options that might have undesirable effects
29+
setattr(formatter_class, "help_markup", False)
30+
setattr(formatter_class, "text_markup", False)
31+
setattr(formatter_class, "group_name_formatter", str)
32+
33+
return formatter_class
34+
except Exception:
35+
break
36+
return formatter_class
37+
38+
39+
def patch_argument_parser_formatter_class() -> None:
40+
original_init = ArgumentParser.__init__
41+
42+
@functools.wraps(original_init, updated=())
43+
def __init__(*args, **kwargs):
44+
formatter_class = kwargs.get("formatter_class", HelpFormatter)
45+
if (
46+
isinstance(formatter_class, type)
47+
and issubclass(formatter_class, HelpFormatter)
48+
and not issubclass(formatter_class, RichHelpFormatter)
49+
):
50+
kwargs["formatter_class"] = make_rich_formatter_class(formatter_class)
51+
return original_init(*args, **kwargs)
52+
53+
setattr(ArgumentParser, "__init__", __init__)

pyproject.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[project]
2+
name = "auto-rich-argparse"
3+
version = "0.1.0"
4+
description = "Automatically use rich-argparse with argparse command line tools"
5+
readme = "README.md"
6+
license = "MIT"
7+
authors = [
8+
{ name = "Ali Hamdan", email = "[email protected]" }
9+
]
10+
requires-python = ">=3.8"
11+
dependencies = [
12+
"rich-argparse>=1.7.0",
13+
]
14+
15+
[build-system]
16+
requires = ["hatchling"]
17+
build-backend = "hatchling.build"
18+
19+
[tool.hatch.build.targets.sdist]
20+
include = [
21+
"auto_rich_argparse.py",
22+
"LICENSE",
23+
"README.md",
24+
"pyproject.toml",
25+
]
26+
27+
[tool.hatch.build.targets.wheel]
28+
include = ["auto_rich_argparse.py"]
29+
30+
[tool.hatch.build.hooks.autorun]
31+
dependencies = ["hatch-autorun"]
32+
code = "import auto_rich_argparse as _ara; _ara.patch_argument_parser_formatter_class()"
33+
34+
[tool.ruff]
35+
line-length = 100

uv.lock

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)