Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conquer the land of clicketing snakes #13

Merged
merged 17 commits into from
Feb 28, 2022
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
18 changes: 10 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Version 1.2.0.dev0

- New CLI functionality to richifiy via prefix any other tool using click, by @pawamoy [[#13](https://github.com/ewels/rich-click/pull/13)]

## Version 1.1.1 (2022-02-28)

Hotfix patch release to remove an accidental `from turtle import st` that crept in due to a pesky VSCode plugin.
Expand All @@ -11,17 +13,17 @@ Many thanks to [@ashb](httpsd://github.com/ashb) for spotting.

- Added support for `HEADER_TEXT` and `FOOTER_TEXT` to go before and after help output
- Catch Abort exceptions from `cmd+c` and print nicely using `ABORTED_TEXT`
- Handle missing `click.types._NumberRangeBase` in click 7x [#16](https://github.com/ewels/rich-click/issues/16)
- Fix compatibility issue for rich 10.6 (`group` vs `render_group` import) [#16](https://github.com/ewels/rich-click/issues/16)
- Require at least click v7.0 (released 2018) [#16](https://github.com/ewels/rich-click/issues/16)
- Require at least rich v10 (released March 2021) [#16](https://github.com/ewels/rich-click/issues/16)
- Unwrap single newlines in option and group-command help texts [#23](https://github.com/ewels/rich-click/issues/23)
- Add click `\b` escape marker functionality into help text rendering [#24](https://github.com/ewels/rich-click/issues/24)
- Fix syntax in example in README file by @fridex [#15](https://github.com/ewels/rich-click/pull/15)
- Handle missing `click.types._NumberRangeBase` in click 7x [[#16](https://github.com/ewels/rich-click/issues/16)]
- Fix compatibility issue for rich 10.6 (`group` vs `render_group` import) [[#16](https://github.com/ewels/rich-click/issues/16)]
- Require at least click v7.0 (released 2018) [[#16](https://github.com/ewels/rich-click/issues/16)]
- Require at least rich v10 (released March 2021) [[#16](https://github.com/ewels/rich-click/issues/16)]
- Unwrap single newlines in option and group-command help texts [[#23](https://github.com/ewels/rich-click/issues/23)]
- Add click `\b` escape marker functionality into help text rendering [[#24](https://github.com/ewels/rich-click/issues/24)]
- Fix syntax in example in README file by @fridex [[#15](https://github.com/ewels/rich-click/pull/15)]

## Version 1.0.0 (2022-02-18)

- _**Major change:**_ New usage, so that we can avoid having to do monkey patching [#10](https://github.com/ewels/rich-click/pull/10).
- _**Major change:**_ New usage, so that we can avoid having to do monkey patching [[#10](https://github.com/ewels/rich-click/pull/10).]
- Now use with `import rich_click as click`
- Add ability to create groups of options with separate panels
- Show positional arguments in their own panel by default
Expand Down
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ click, formatted with rich, with minimal customisation required.

- 🌈 Rich command-line formatting of click help and error messages
- 💫 Nice styles be default, usage is simply `import rich_click as click`
- 💻 CLI tool to run on other people's packages (prefix the command with `rich-click`)
- 🎁 Group commands and options into named panels
- ❌ Well formatted error messages
- 🔢 Easily give custom sort order for options and commands
Expand All @@ -31,6 +32,8 @@ python -m pip install rich-click

## Usage

### Import as click

To use `rich-click`, switch out your normal `click` import with `rich-click`, using the same namespace:

```python
Expand All @@ -44,11 +47,29 @@ That's it ✨ Then continue to use `click` as you would normally.
The intention is to maintain most / all of the normal click functionality and arguments.
If you spot something that breaks or is missing once you start using the plugin, please create an issue about it.

Alternatively, if you prefer you can `RichGroup` or `RichCommand` with the `cls` argument in your click usage instead.
### Declarative

If you prefer, you can `RichGroup` or `RichCommand` with the `cls` argument in your click usage instead.
This means that you can continue to use the unmodified `click` package in parallel.

> See [`examples/02_declarative.py`](examples/02_declarative.py) for an example.

### Command-line usage

Rich-click comes with a CLI tool that allows you to format the click help output from _any_ package.
As long as that tool is using click and isn't already passing custom `cls` objects, it should work.
Hoever, please consider it an experimental feature at this point.

To use, simply prefix to your normal command.
For example, to get richified click help text from a package called `awesometool`, you could run:

```console
$ rich-click awesometool --help

Usage: awesometool [OPTIONS]
..more richified output below..
```

## Customisation

There are a large number of customisation options in rich-click.
Expand Down
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ package_dir =
include_package_data = True
python_requires = >= 3.6

[options.entry_points]
console_scripts =
rich-click = rich_click.cli:main

[options.packages.find]
where = src

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
setup(
name="rich-click",
install_requires=[
"click>=7.0",
"click>=7",
"rich>=10",
"importlib-metadata; python_version < '3.8'",
],
)
6 changes: 2 additions & 4 deletions src/rich_click/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
__version__ = "1.2.0.dev0"

from click import *
from click import group as click_group
from click import command as click_command
from .rich_click import RichGroup
from .rich_click import RichCommand

Expand All @@ -18,8 +20,6 @@ def group(*args, cls=RichGroup, **kwargs):

Defines the group() function so that it uses the RichGroup class by default
"""
from click import group as click_group

return click_group(*args, cls=cls, **kwargs)


Expand All @@ -28,6 +28,4 @@ def command(*args, cls=RichCommand, **kwargs):

Defines the command() function so that it uses the RichCommand class by default
"""
from click import command as click_command

return click_command(*args, cls=cls, **kwargs)
15 changes: 15 additions & 0 deletions src/rich_click/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Entry-point module for the command line prefixer,
called in case you use `python -m rich_click`.

Why does this file exist, and why `__main__`? For more info, read:

- https://www.python.org/dev/peps/pep-0338/
- https://docs.python.org/3/using/cmdline.html#cmdoption-m
"""

from rich_click.cli import main

if __name__ == "__main__":
# main will run a Click command which will either exit or raise
main()
129 changes: 129 additions & 0 deletions src/rich_click/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""The command line interface."""

import sys
from textwrap import dedent
from importlib import import_module

try:
from importlib.metadata import entry_points
except ImportError:
# Support Python <3.8
from importlib_metadata import entry_points

import click
from rich.console import Console
from rich.padding import Padding
from rich.panel import Panel
from rich.text import Text
from rich_click import group as rich_group, command as rich_command
from rich_click.rich_click import (
STYLE_HELPTEXT_FIRST_LINE,
STYLE_HELPTEXT,
STYLE_USAGE_COMMAND,
STYLE_USAGE,
STYLE_ERRORS_PANEL_BORDER,
ERRORS_PANEL_TITLE,
ALIGN_ERRORS_PANEL,
)

console = Console()


def _print_usage():
console.print(
Padding(
Text.from_markup(
f"[{STYLE_USAGE}]Usage[/]: rich-click [SCRIPT | MODULE:FUNCTION] [-- SCRIPT_ARGS...]"
),
1,
),
style=STYLE_USAGE_COMMAND,
)


def _print_help():
help_paragraphs = dedent(main.__doc__).split("\n\n")
help_paragraphs = [x.replace("\n", " ").strip() for x in help_paragraphs]
console.print(
Padding(
Text.from_markup(help_paragraphs[0].strip()),
(0, 1),
),
style=STYLE_HELPTEXT_FIRST_LINE,
)
console.print(
Padding(
Text.from_markup("\n\n".join(help_paragraphs[1:]).strip()),
(0, 1),
),
style=STYLE_HELPTEXT,
)


def main(args=None):
"""
The [link=https://github.com/ewels/rich-click]rich-click[/] CLI
provides attractive help output from any tool using [link=https://click.palletsprojects.com/]click[/],
formatted with [link=https://github.com/Textualize/rich]rich[/].

The rich-click command line tool can be prepended before any Python package
using native click to provide attractive richified click help output.

For example, if you have a package called [blue]my_package[/] that uses click,
you can run:

[blue] rich-click my_package --help [/]

It only works if the package is using vanilla click without customised [cyan]group()[/]
or [cyan]command()[/] classes.
If in doubt, please suggest to the authors that they use rich_click within their
tool natively - this will always give a better experience.
"""
args = args or sys.argv[1:]
if not args or args == ["--help"]:
# Print usage if we got no args, or only --help
_print_usage()
_print_help()
sys.exit(0)
else:
script_name = args[0]
scripts = {script.name: script for script in entry_points().get("console_scripts")}
if script_name in scripts:
# a valid script was passed
script = scripts[script_name]
module_path, function_name = script.value.split(":", 1)
prog = script_name
elif ":" in script_name:
# the path to a function was passed
module_path, function_name = args[0].split(":", 1)
prog = module_path.split(".", 1)[0]
else:
_print_usage()
console.print(
Panel(
Text.from_markup(f"No such script: [bold]{script_name}[/]"),
border_style=STYLE_ERRORS_PANEL_BORDER,
title=ERRORS_PANEL_TITLE,
title_align=ALIGN_ERRORS_PANEL,
)
)
console.print(
Padding(
"Please run [yellow bold]rich-click --help[/] for usage information.",
(0, 1),
),
style="dim",
)
sys.exit(1)
if len(args) > 1:
if args[1] == "--":
del args[1]
sys.argv = [prog, *args[1:]]
# patch click before importing the program function
click.group = rich_group
click.command = rich_command
# import the program function
module = import_module(module_path)
function = getattr(module, function_name)
# simply run it: it should be patched as well
return function()
pawamoy marked this conversation as resolved.
Show resolved Hide resolved