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

Import as #10

Merged
merged 12 commits into from
Feb 17, 2022
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Version 0.4.0.dev0

Major change: New usage, so that we can avoid having to do monkey patching [#10](https://github.com/ewels/rich-click/pull/10).

- Add ability to create groups of options with separate panels
- Show positional arguments in their own panel by default
- Add config `GROUP_ARGUMENTS_OPTIONS` option to group with options
Expand Down
90 changes: 20 additions & 70 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@
The intention of `rich-click` is to provide attractive help output from
click, formatted with rich, with minimal customisation required.

## Features

- Rich formatting of click help and error messages
- Nice styles be default, with simple import
- Ability to give custom sort order for options and commands
- Group commands and options into named panels
- Extensive styling and behaviour customisation available

## Screenshots

<table>
<tr>
<th>Native click help</th>
<th>With <code>rich-click</code></th>
</tr>
<tr>
<td><img src="docs/images/example_with_just_click.png"></td>
<td><img src="docs/images/example_with_rich-click.png"></td>
</tr>
</table>
![rich-click](docs/images/example_with_rich-click.png)

## Installation

Expand All @@ -31,65 +30,16 @@ python -m pip install rich-click

## Usage

There are two main ways to set up `rich-click` formatting for your tool:
monkey patching or declarative.
Which you choose will depend on your use-case and your personal disposition!

Note that the intention is to maintain most / all of the normal click functionality and arguments.
If you spot something that is missing once you start using the plugin, please
create an issue about it.

### The path of least typing

Monkey patching is [probably bad](https://en.wikipedia.org/wiki/Monkey_patch#Pitfalls)
and you should only use this method if you are a Responsible Developer.
It's also good if you're lazy, as it requires very little typing.

Assuming that you're already using click, you only need to add a few lines:

```python
import rich_click
click.Command.format_help = rich_click.rich_format_help
click.Group.format_help = rich_click.rich_format_help
click.ClickException.show = rich_click.rich_format_error
click.UsageError.show = rich_click.rich_format_error
```

This _overwrites_ the default `click` methods with those from the `rich-click` package.
As such, no other changes are needed - just continue to use `click` as you would
normally and it will use these custom methods to print your help output.

### The good and proper way

If using monkey-patching in your project makes your palms sweaty and your pulse race,
then you'll be pleased to know that you can also use `rich-click` in a nicely
declarative and verbose manner:
To use `rich-click`, import it instead of `click` but under the same namespace:

```python
import click
import rich_click

class RichClickGroup(click.Group):
def format_help(self, ctx, formatter):
rich_click.rich_format_help(self, ctx, formatter)
class RichClickCommand(click.Command):
def format_help(self, ctx, formatter):
rich_click.rich_format_help(self, ctx, formatter)

@click.group(cls=RichClickGroup)
@click.option('--debug/--no-debug', default=False)
def cli(debug):
click.echo(f"Debug mode is {'on' if debug else 'off'}")

@cli.command(cls=RichClickCommand)
def sync():
click.echo('Syncing')
import rich_click as click
```

_(example based on the [click docs](https://click.palletsprojects.com/en/8.0.x/commands/))_
That's it. Then continue to use `click` as you would normally.

Here we are making new `Group` and `Command` child classes that are based on click.
We define our custom `format_help()` functions and then tell click to to use these classes with the `cls` argument.
The intention is to maintain most / all of the normal click functionality and arguments.
If you spot something that is missing once you start using the plugin, please create an issue about it.

## Groups and sorting

Expand All @@ -99,8 +49,8 @@ It accepts a list of options / commands which means you can also choose a custom
For example, you can produce something that looks like this:
![command groups](docs/images/command_groups.png)

- To do this with options (flags), set `rich_click.core.OPTION_GROUPS`.
- To do this with subcommands (groups), set `rich_click.core.COMMAND_GROUPS`.
- To do this with options (flags), set `click.rich_click.OPTION_GROUPS`.
- To do this with subcommands (groups), set `click.rich_click.COMMAND_GROUPS`.

### Options

Expand Down Expand Up @@ -129,7 +79,7 @@ Here we create two groups of commands for the base command of `mytool`.
Any subcommands not listed will automatically be printed in a panel at the end labelled "Commands" as usual.

```python
rich_click.core.COMMAND_GROUPS = {
click.rich_click.COMMAND_GROUPS = {
"mytool": [
{
"name": "Commands for uploading",
Expand All @@ -150,7 +100,7 @@ If you omit `name` it will use `Commands` (can be configured with `COMMANDS_PANE
If you use multiple nested subcommands, you can specify their commands using the top-level dictionary keys:

```python
rich_click.core.COMMAND_GROUPS = {
click.rich_click.COMMAND_GROUPS = {
"mytool": ["commands": ["sync", "auth"]],
"mytool sync": [
{
Expand All @@ -173,13 +123,13 @@ You can customise most things that are related to formatting.
For example, to limit the maximum width of the help output to 100 characters:

```python
rich_click.core.MAX_WIDTH = 100
click.rich_click.MAX_WIDTH = 100
```

To print the option flags in a different colour, use:

```python
rich_click.core.STYLE_OPTION = "magenta"
click.rich_click.STYLE_OPTION = "magenta"
```

<details><summary>Full list of config options</summary>
Expand Down
43 changes: 43 additions & 0 deletions examples/01_simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import rich_click


@rich_click.group()
@rich_click.option(
"--debug/--no-debug", "-d/-n", default=False, help="Enable debug mode"
)
def cli(debug):
"""
My amazing tool does all the things.

This is a minimal example based on documentation
from the 'click' package.

You can try using --help at the top level and also for
specific group subcommands.
"""
print(f"Debug mode is {'on' if debug else 'off'}")


@cli.command()
@rich_click.option(
"--type",
required=True,
default="files",
show_default=True,
help="Type of file to sync",
)
@rich_click.option("--all", is_flag=True, help="Sync all the things?")
def sync(type, all):
"""Synchronise all your files between two places"""
print("Syncing")


@cli.command()
@rich_click.option("--all", is_flag=True, help="Get everything")
def download(all):
"""Pretend to download some files from somewhere"""
print("Downloading")


if __name__ == "__main__":
cli()
40 changes: 0 additions & 40 deletions examples/01_simple_monkeypatch.py

This file was deleted.

16 changes: 3 additions & 13 deletions examples/02_simple_declarative.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
import click
import rich_click
from rich_click import RichGroup, RichCommand


class RichClickGroup(click.Group):
def format_help(self, ctx, formatter):
rich_click.rich_format_help(self, ctx, formatter)


class RichClickCommand(click.Command):
def format_help(self, ctx, formatter):
rich_click.rich_format_help(self, ctx, formatter)


@click.group(cls=RichClickGroup)
@click.group(cls=RichGroup)
@click.option("--debug/--no-debug", default=False)
def cli(debug):
"""
Expand All @@ -27,7 +17,7 @@ def cli(debug):
click.echo(f"Debug mode is {'on' if debug else 'off'}")


@cli.command(cls=RichClickCommand)
@cli.command(cls=RichCommand)
def sync():
"""Synchronise all your files between two places"""
click.echo("Syncing")
Expand Down
50 changes: 50 additions & 0 deletions examples/03_simple_importas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import click
import rich_click

####
## Rich-click cli stuff
####
@rich_click.group()
@rich_click.option(
"--debug/--no-debug", "-d/-n", default=False, help="Enable debug mode"
)
def cli(debug):
"""
My amazing tool does all the things.

This is a minimal example based on documentation
from the 'click' package.

You can try using --help at the top level and also for
specific group subcommands.
"""
print(f"Debug mode is {'on' if debug else 'off'}")


@cli.command()
@rich_click.option("--all", is_flag=True, help="Sync all the things?")
def sync(all):
"""Synchronise all your files between two places"""
print("Syncing")


@cli.command()
@rich_click.option("--all", is_flag=True, help="Get everything")
def download(all):
"""Pretend to download some files from somewhere"""
print("Downloading")


####
## Vanilla click import cli stuff
####
@click.command()
@click.option("--all", is_flag=True, help="Sync all the things?")
def original(all):
"""Synchronise all your files between two places"""
print("Syncing")


if __name__ == "__main__":
cli() # Use rich-click, should be fancy output
# original() # Use vanilla click, should be simple output
17 changes: 15 additions & 2 deletions src/rich_click/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,18 @@

__version__ = "0.4.0.dev0"

from .core import rich_format_help
from .core import rich_format_error
from click import *
from .rich_click import RichGroup
from .rich_click import RichCommand


def group(*args, cls=RichGroup, **kwargs):
from click import group as click_group

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


def command(*args, cls=RichCommand, **kwargs):
from click import command as click_command

return click_command(*args, cls=cls, **kwargs)
Loading