Skip to content

Commit

Permalink
Minor improvements and grammar fixes (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
hamdanal authored Mar 11, 2023
1 parent 7a432fa commit 44fc218
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 59 deletions.
59 changes: 30 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
# rich-argparse

![python -m rich_argparse](
https://user-images.githubusercontent.com/93259987/224482407-ea1de764-09f7-415e-acaa-259466ba9c18.svg)

[![tests](https://github.com/hamdanal/rich-argparse/actions/workflows/tests.yml/badge.svg)
](https://github.com/hamdanal/rich-argparse/actions/workflows/tests.yml)
[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/hamdanal/rich-argparse/main.svg)
](https://results.pre-commit.ci/latest/github/hamdanal/rich-argparse/main)

[![Python Version](https://img.shields.io/pypi/pyversions/rich-argparse)
![Release](https://img.shields.io/github/v/release/hamdanal/rich-argparse?sort=semver)
![Status](https://img.shields.io/pypi/status/rich-argparse)
![Downloads](https://pepy.tech/badge/rich-argparse/month)
](https://pypi.org/project/rich-argparse/)

Format [argparse](https://docs.python.org/3/library/argparse.html) help output with
[rich](https://pypi.org/project/rich).

![python -m rich_argparse --help](https://user-images.githubusercontent.com/85891169/221434468-7f19570f-951d-4d0a-9964-af2a62ccc98b.png)
Format argparse help output using [rich](https://pypi.org/project/rich).

## Installation

Expand All @@ -36,26 +35,27 @@ parser = argparse.ArgumentParser(..., formatter_class=RichHelpFormatter)
...
```

`RichHelpFormatter` is the equivalent to `argparse.HelpFormatter` in the way it treats whitespace.
rich-argparse also defines four subclasses of `RichHelpFormatter` that are equivalent to the other
argparse formatters:
rich-argparse defines help formatter classes that produce colorful and easy to read help text. The
formatter classes are equivalent to argparse's built-in formatters:

* `argparse.RawDescriptionHelpFormatter` -> `RawDescriptionRichHelpFormatter`
* `argparse.RawTextHelpFormatter` -> `RawTextRichHelpFormatter`
* `argparse.ArgumentDefaultsHelpFormatter` -> `ArgumentDefaultsRichHelpFormatter`
* `argparse.MetavarTypeHelpFormatter` -> `MetavarTypeRichHelpFormatter`
| `rich_argparse` formatter | `argparse` equivalent |
|---------------------------|-----------------------|
| `RichHelpFormatter` | `HelpFormatter` |
| `RawDescriptionRichHelpFormatter` | `RawDescriptionHelpFormatter` |
| `RawTextRichHelpFormatter` | `RawTextHelpFormatter` |
| `ArgumentDefaultsRichHelpFormatter` | `ArgumentDefaultsHelpFormatter` |
| `MetavarTypeRichHelpFormatter` | `MetavarTypeHelpFormatter` |

For more information on what these formatters do, check the [argparse documentation](
For more information on how these formatters work, check the [argparse documentation](
https://docs.python.org/3/library/argparse.html#formatter-class).

## Output styles

The default styles used by rich-argparse formatters are carefully chosen to work in different
light and dark themes. If the these styles don't suit your taste, read below to know how to change
them.
The default styles used by rich-argparse formatters are carefully chosen to work in different light
and dark themes. If these styles don't suit your taste, read below to learn how to change them.

> **Note**
> This section only mentions `RichHelpFormatter` but the same can be applied its subclasses.
> The examples below only mention `RichHelpFormatter` but apply to all other formatter classes.
### Customize the colors
You can customize the colors in the output by modifying the `styles` dictionary on the formatter
Expand All @@ -67,9 +67,9 @@ class. By default, `RichHelpFormatter` defines the following styles:
'argparse.groups': 'dark_orange', # for group names (e.g. "positional arguments")
'argparse.help': 'default', # for argument's help text (e.g. "show this help message and exit")
'argparse.metavar': 'dark_cyan', # for metavariables (e.g. "FILE" in "--file FILE")
'argparse.syntax': 'bold', # for highlights of back-tick quoted text (e.g. "`some text`"),
'argparse.text': 'default', # for the description, epilog and group descriptions (e.g. "A program to foo")
'argparse.prog': 'grey50', # for %(prog)s in the usage (e.g. "foo" in "Usage: foo [options]")
'argparse.syntax': 'bold', # for highlights of back-tick quoted text (e.g. "`some text`")
'argparse.text': 'default', # for the descriptions and epilog (e.g. "A program to foo")
}
```

Expand All @@ -91,13 +91,12 @@ RichHelpFormatter.group_name_formatter = str.upper

### Special text highlighting

You can highlight patterns in the help text and the description text of your parser's help output
using regular expressions. By default, `RichHelpFormatter` highlights patterns of
`--options-with-hyphens` using the `argparse.args` style and patterns of
`` `back tick quoted text` `` using the `argparse.syntax` style. You can control what patterns get
highlighted by modifying the `RichHelpFormatter.highlights` list.
For example, to disable all highlights, you can clear this list using
`RichHelpFormatter.highlights.clear()`.
You can [highlight patterns](https://rich.readthedocs.io/en/stable/highlighting.html) in the help
text and the description text of your parser's help output using regular expressions. By default,
`RichHelpFormatter` highlights patterns of `--options-with-hyphens` using the `argparse.args` style
and patterns of `` `back tick quoted text` `` using the `argparse.syntax` style. You can control
what patterns are highlighted by modifying the `RichHelpFormatter.highlights` list. To disable all
highlights, you can clear this list using `RichHelpFormatter.highlights.clear()`.

You can also add custom highlight patterns and styles. The following example highlights all
occurrences of `pyproject.toml` in green.
Expand All @@ -118,7 +117,9 @@ parser = argparse.ArgumentParser(..., formatter_class=RichHelpFormatter)
the arguments and their metavars. If you use a custom `usage` message in the parser, this text will
treated as "plain text" and will not be colored by default. You can enable colors in user defined
usage message with [console markup](https://rich.readthedocs.io/en/stable/markup.html) by setting
`RichHelpFormatter.usage_markup = True`.
`RichHelpFormatter.usage_markup = True`. If you enable this option, make sure to [escape](
https://rich.readthedocs.io/en/stable/markup.html#escaping) any square brackets in the usage text.


## Working with subparsers

Expand Down Expand Up @@ -149,7 +150,7 @@ do not inherit the formatter class from the parent parser by default. You have t

`RichHelpFormatter` can be used with third party formatters that do not rely on the **private**
internals of `argparse.HelpFormatter`. For example, [django](https://pypi.org/project/django)
defines a custom help formatter that is used with the built in commands as well as with extension
defines a custom help formatter that is used with its built in commands as well as with extension
libraries and user defined commands. To use rich-argparse in your django project, change your
`manage.py` file as follows:

Expand Down
68 changes: 38 additions & 30 deletions rich_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,24 +49,24 @@ class RichHelpFormatter(argparse.HelpFormatter):
- ``argparse.groups``: for group names (e.g. "positional arguments")
- ``argparse.help``: for argument's help text (e.g. "show this help message and exit")
- ``argparse.metavar``: for meta variables (e.g. "FILE" in "--file FILE")
- ``argparse.syntax``: for highlights of back-tick quoted text (e.g. "``` `some text` ```"),
- ``argparse.text``: for the description, epilog and group descriptions (e.g. "A foo program")
- ``argparse.prog``: for %(prog)s in the usage (e.g. "foo" in "Usage: foo [options]")
- ``argparse.syntax``: for highlights of back-tick quoted text (e.g. "``` `some text` ```"),
- ``argparse.text``: for the descriptions and epilog (e.g. "A foo program")
"""
highlights: ClassVar[list[str]] = [
r"(?:^|\s)(?P<args>-{1,2}[\w]+[\w-]*)", # highlight --words-with-dashes as args
r"`(?P<syntax>[^`]*)`", # highlight text in backquotes as syntax
r"`(?P<syntax>[^`]*)`", # highlight `text in backquotes` as syntax
]
"""A list of regex patterns to highlight in help text.
"""A list of regex patterns to highlight in the help text.
It is used in the description, epilog, group description, and argument help text. By default,
It is used in the description, epilog, groups descriptions, and arguments' help. By default,
it highlights ``--words-with-dashes`` with the `argparse.args` style and
``` `text in backquotes` ``` with the `argparse.syntax` style.
To disable highlighting, clear this list (``RichHelpFormatter.highlights.clear()``).
"""
usage_markup: ClassVar[bool] = False
"""Whether to render the usage string passed to ``ArgumentParser(usage=...)`` as markup.
"""If True, render the usage string passed to ``ArgumentParser(usage=...)`` as markup.
Defaults to ``False`` meaning the text of the usage will be printed verbatim.
Expand Down Expand Up @@ -122,11 +122,14 @@ def __init__(
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
from rich.text import Text

if not self.rich_items and not self.rich_actions: # empty section
# empty section
if not self.rich_items and not self.rich_actions:
return
if self is self.formatter._root_section: # root section
# root section
if self is self.formatter._root_section:
yield from self.rich_items
return
# group section
help_pos = min(self.formatter._action_max_length + 2, self.formatter._max_help_position)
help_width = max(self.formatter._width - help_pos, 11)
if self.heading:
Expand Down Expand Up @@ -205,8 +208,7 @@ def format_help(self) -> str:
self.console.print(self._root_section, highlight=False, soft_wrap=True)
help = capture.get()
if help:
help = self._long_break_matcher.sub("\n\n", help).strip("\n") + "\n"
help = "\n".join(line.rstrip() for line in help.split("\n"))
help = self._long_break_matcher.sub("\n\n", help).rstrip() + "\n"
return help

# ===============
Expand Down Expand Up @@ -382,8 +384,6 @@ class MetavarTypeRichHelpFormatter(argparse.MetavarTypeHelpFormatter, RichHelpFo


if __name__ == "__main__":
from rich import print

RichHelpFormatter.highlights.append(r"(?:^|\s)-{1,2}[\w]+[\w-]* (?P<metavar>METAVAR)\b")
parser = argparse.ArgumentParser(
prog="python -m rich_argparse",
Expand All @@ -392,9 +392,8 @@ class MetavarTypeRichHelpFormatter(argparse.MetavarTypeHelpFormatter, RichHelpFo
"This is a [link https://pypi.org/project/rich]rich[/]-based formatter for "
"[link https://docs.python.org/3/library/argparse.html#formatter-class]"
"argparse's help output[/].\n\n"
"It makes it easy to use the powers of rich like markup and highlights in your CLI "
"help. For example, the first sentence contains clickable hyperlinks thanks to rich's "
"\\[link] markup. Read below for a peek at available features."
"It enables you to use the powers of rich like markup and highlights in your CLI help. "
"Read below for a glance at available features."
),
epilog=":link: Read more at https://github.com/hamdanal/rich-argparse#usage.",
)
Expand All @@ -407,10 +406,9 @@ class MetavarTypeRichHelpFormatter(argparse.MetavarTypeHelpFormatter, RichHelpFo
)
parser.add_argument(
"styles",
nargs="*",
help=(
"All the styles used by this formatter are defined in the `RichHelpFormatter.styles` "
"dictionary and they can be changed. Any rich style can be used."
"All the styles used by this formatter are defined in `RichHelpFormatter.styles`. "
"Modify this dictionary with any rich style to change the look of your CLI's help text."
),
)
parser.add_argument(
Expand All @@ -435,22 +433,22 @@ class MetavarTypeRichHelpFormatter(argparse.MetavarTypeHelpFormatter, RichHelpFo
"--long-option",
metavar="METAVAR",
help=(
"If an option takes a value and has short and long options, it is printed as "
"-s, --long-option METAVAR instead of -s METAVAR, --long-option METAVAR.\n"
"You can see also that words that look like command line options are highlighted by "
"deafult. This example, adds a highlighter regex for the word 'METAVAR' following an "
"option for the sake of demonstrating custom highlights."
"Words that look like --command-line-options are highlighted using the `argparse.args` "
"style. In addition, this example, adds a highlighter regex for the word 'METAVAR' "
"following an option for the sake of demonstrating custom highlights.\n"
"Notice also that if an option takes a value and has short and long options, it is "
"printed as -s, --long-option METAVAR instead of -s METAVAR, --long-option METAVAR."
),
)
group = parser.add_argument_group(
"more options",
"more arguments",
description=(
"This is a custom group. Group names are [italic]*Title Cased*[/] by default and can "
"be changed by setting the `RichHelpFormatter.group_name_formatter` function."
"This is a custom group. Group names are [italic]*Title Cased*[/] by default. Use the "
"`RichHelpFormatter.group_name_formatter` function to change their format."
),
)
group.add_argument(
"--others",
"--more",
nargs="*",
help="This formatter works with subparsers, mutually exclusive groups and hidden arguments.",
)
Expand All @@ -465,6 +463,16 @@ class MetavarTypeRichHelpFormatter(argparse.MetavarTypeHelpFormatter, RichHelpFo
)
mutex.add_argument("--not-rich", action="store_false", dest="rich", help=argparse.SUPPRESS)

args = parser.parse_args()
print("Got the following arguments on the command line:")
print(vars(args))
if "--generate-rich-argparse-preview" in sys.argv: # for internal use only
from rich.console import Console # noqa: F811
from rich.terminal_theme import DIMMED_MONOKAI
from rich.text import Text # noqa: F811

width = 128
parser.formatter_class = lambda prog: RichHelpFormatter(prog, width=width)
text = Text.from_ansi(parser.format_help())
console = Console(record=True, width=width)
console.print(text)
console.save_svg("rich-argparse.svg", title="", theme=DIMMED_MONOKAI)
else:
parser.print_help()

0 comments on commit 44fc218

Please sign in to comment.