Coder Social home page Coder Social logo

ewels / rich-click Goto Github PK

View Code? Open in Web Editor NEW
564.0 4.0 32.0 8.01 MB

Format click help output nicely with rich.

Home Page: https://ewels.github.io/rich-click/

License: MIT License

Python 100.00%
cli click colored coloured help python rich styled styles

rich-click's Introduction

rich-click logo

Richly rendered command line interfaces in click.

PyPI Test Coverage badge Lint code badge


Documentation  ·  Source Code  ·  Changelog


rich-click is a shim around Click that renders help output nicely using Rich.

  • Click is a "Python package for creating beautiful command line interfaces".
  • Rich is a "Python library for rich text and beautiful formatting in the terminal".

The intention of rich-click is to provide attractive help output from Click, formatted with Rich, with minimal customisation required.

Features

  • 🌈 Rich command-line formatting of click help and error messages
  • 😌 Same API as Click: usage is simply import rich_click as click
  • 💫 Nice styles by default
  • 💻 CLI tool to run on other people's tools (prefix the command with rich-click)
  • 📦 Export help text as HTML or SVG
  • 🎁 Group commands and options into named panels
  • ❌ Well formatted error messages
  • 🔢 Easily give custom sort order for options and commands
  • 🎨 Extensive customisation of styling and behaviour possible

Installation

pip install rich-click

Read the docs for all supported installation methods.

Examples

Simple Example

To use rich-click in your code, replace import click with import rich_click as click in your existing click CLI:

import rich_click as click

@click.command()
@click.option("--count", default=1, help="Number of greetings.")
@click.option("--name", prompt="Your name", help="The person to greet.")
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for _ in range(count):
        click.echo(f"Hello, {name}!")

if __name__ == '__main__':
    hello()

python examples/11_hello.py --help

Screenshot from examples/11_hello.py

More complex example

python examples/03_groups_sorting.py

Screenshot from examples/03_groups_sorting.py

Usage

This is a quick overview of how to use rich-click. Read the docs for more information.

There are a couple of ways to begin using rich-click:

Import rich_click as click

Switch out your normal click import with rich_click, using the same namespace:

import rich_click as click

That's it! ✨ Then continue to use Click as you would normally.

See examples/01_simple.py for an example.

Declarative

If you prefer, you can use 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.

import click
from rich_click import RichCommand

@click.command(cls=RichCommand)
def main():
    """My amazing tool does all the things."""

See examples/02_declarative.py for an example.

rich-click CLI tool

rich-click comes with a CLI tool that allows you to format the Click help output from any package that uses Click.

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

$ rich-click awesometool --help

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

License

This project is licensed under the MIT license.

rich-click's People

Contributors

ajparsons avatar alirezatheh avatar apcamargo avatar browniebroke avatar brutalsimplicity avatar davidbrochart avatar dwreeves avatar ealap avatar ewels avatar fridex avatar github-actions[bot] avatar harens avatar jorricks avatar kianmeng avatar lgprobert avatar likewei92 avatar mcflugen avatar pawamoy avatar schettino72 avatar stealthii avatar taranlu-houzz avatar wfondrie avatar zmoon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

rich-click's Issues

Command panels don't support multi-line strings

I'm used to write down the description of commands within """ because they can get long. However, it seems that rich-click can't render multi-line strings within the panels. Only the first line is shown.

Multi-line:

@cli.command(context_settings=CONTEXT_SETTINGS)
@click.argument("input", type=click.Path(exists=True))
@click.argument("output", type=click.Path())
@click.option(
    "--threads",
    "-t",
    default=multiprocessing.cpu_count(),
    show_default=True,
    help="Number of threads to use",
)
def test_command(input, output, threads):
    """
    Let's build an almighty mountain. Maybe, just to play a little, we'll put a
    little tree here. Look around, look at what we have. Beauty is everywhere,
    you only have to look to see it. The only prerequisite is that it makes you
    happy. If it makes you happy then it's good. We don't have to be concerned
    about it. We just have to let it fall where it will. By now you should be
    quite happy about what's happening here.
    """
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────╮
│  test-command  Let's build an almighty mountain. Maybe, just to play a little, we'll put a       │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

This is fixed when I put the text in a single line:

@cli.command(context_settings=CONTEXT_SETTINGS)
@click.argument("input", type=click.Path(exists=True))
@click.argument("output", type=click.Path())
@click.option(
    "--threads",
    "-t",
    default=multiprocessing.cpu_count(),
    show_default=True,
    help="Number of threads to use",
)
def test_command(input, output, threads):
    """
    Let's build an almighty mountain. Maybe, just to play a little, we'll put a little tree here. Look around, look at what we have. Beauty is everywhere, you only have to look to see it. The only prerequisite is that it makes you happy. If it makes you happy then it's good. We don't have to be concerned about it. We just have to let it fall where it will. By now you should be quite happy about what's happening here.
    """
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────╮
│  test-command  Let's build an almighty mountain. Maybe, just to play a little, we'll put a       │
│                little tree here. Look around, look at what we have. Beauty is everywhere, you    │
│                only have to look to see it. The only prerequisite is that it makes you happy.    │
│                If it makes you happy then it's good. We don't have to be concerned about it. We  │
│                just have to let it fall where it will. By now you should be quite happy about    │
│                what's happening here.                                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Add tests!

Suddenly a lot of people seem to be interested in using this package, and I'm now responsible for not breaking stuff.

Breaking up the code into smaller functions and writing some tests would certainly help my confidence when making changes..

Columns of different option groups do not align

When I split my options across different groups the columns are not aligned between them. In the example below, the metavar and the description columns of the Basic options and Advanced options groups are not aligned.

╭─ Basic options ──────────────────────────────────────────────────────────────────────────────────╮
│  --restart                          Overwrite existing intermediate files. [default: False]      │
│  --threads          -t     INTEGER  Number of threads to use. [default: 16]                      │
│  --verbose/--quiet  -v/-q           Display the execution progress. [default: verbose]           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Advanced options ───────────────────────────────────────────────────────────────────────────────╮
│  --sensitivity  -s  FLOAT  Search sensitivity. [default: 3.5]                                    │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Add support for Typer

This looks great! Thank you for sharing this.

I tried using the import in a Typer project and (unsurprisingly) it didn't work. Even if there's no official code support for using this with Typer, some docs would be super helpful since I would love to use it for my CLI.

`click.Command` option `hidden=True` is ignored

click.Command in Click 8.1 has a option hidden=True to make the command hidden from help message, which is similar to the behavior like hidden argument in click.Option.

While hidden=True works well with click command option, it is ignored when commands have this option. All commands with this option are shown in help message.

This symptom is always reproducible.

Thanks

Customise all the things

Colours and styles are very much subject to personal tastes.

We should add functionality to customise styling choices, such as colours and box styling.

Add option to default to "standard" click

Building a cli tool with rich-click is super cool - at least if you as a developer like rich formatting on the terminal.
But then there are users, who like your tool, but don’t like all the fancy stuff and that’s a reasonable opinion.

Therefore rich-click should add an option (maybe even by default) that lets you disable all the richiness and actually replaces the rich_click import with an actual click import, e.g., something like that

@click.option(
    "--rich/--no-rich",
    is_flag=True,
    default=True,
    help="Use rich terminal output (default: rich).",
)

Support error messages

Currently, error messages from non-existent flags etc are not formatted. It would be good to give these consistent formatting.

image

I would be surprised if there are also other types of Click output which are also missing.

rich markup shows when using sphinx-click

Currently, the click-man-page rendering and sphinx html docs get pretty messed up when using rich-click.
Here's an example of a man page output:

ISO(1)                                        iso Manual                                       ISO(1)

NAME
       iso - Main Isomer CLI

SYNOPSIS
       iso [OPTIONS] COMMAND [ARGS]...

DESCRIPTION
       [bold cyan on blue] :diamonds: Isomer[/][white on blue] Management Tool [/]

       This tool supports various operations to manage Isomer instances.

       [yellow]  :warning: Most of the commands are [u]grouped[/u].[/]

       To obtain more information about the groups' available subcommands/groups, try:

       [bright_cyan]  iso [/]

       To display details of a command or its subgroups, try:

       [bright_cyan]  iso   [..]  --help[/]

       To get a map of all available commands, try:

       [bright_cyan]  iso cmdmap[/]

OPTIONS
       -e, --env, --environment [blue|green|current|other]
              Override environment to act on (CAUTION!)

Notice the --env option - it is not immediately obvious if that is weird markup or actual option choices.

I think it would make sense to somehow throw all the markup out in manpages. With sphinx-generated html or similar things that could render them - not sure how to proceed.

Wrapping not enabled on some columns, cutting off content

Great project, thanks for sharing it!

I have some cli values with a lot of possible values.

When --help is rendered via rich-click, the options are cut off.

nbp --help

 Usage: nbp [OPTIONS] FILE...

 Render a Jupyter Notebook in the terminal.

╭─ Options ────────────────────────────────────────────────────────────────────╮
│  *                        file  FILE...               Jupyter notebook       │
│                                                       file(s) to render on   │
│                                                       the terminal. Use a    │
│                                                       dash ('-') or pipe in  │
│                                                       data to the command    │
│                                                       to read from standard  │
│                                                       input.                 │
│                                                       [required]             │
│     --theme               -t    [default|emacs|frie…  The theme to use for   │
│                                                       syntax highlighting.   │
│                                                       Call                   │
│                                                       ``--list-themes`` to   │
│                                                       preview all available  │
│                                                       themes.                │

Screen Shot 2022-03-02 at 9 44 21 PM

Negative `--no-*` option variant not showing up

It seems that the --no-* variants for options are not showing up for me... I took the same example from #31 and it produces the following result when I run it:

image

These are the versions of rich and rich-click installed in the virtualenv:

  • rich: 10.16.2
  • rich-click: 1.2.1

[Feature] `rich_click.cli.patch()`

I have the following use case: I want to take another Python library's CLI, make some slight changes, and add it to my app's CLI with click.Group.add_command().

In order to get that library using Rich-Click, I need to patch manually. This is my __main__.py:

# __main__.py
from rich_click import command as rich_command
from rich_click import group as rich_group
from rich_click import RichCommand
from rich_click import RichGroup

import click

click.group = rich_group
click.command = rich_command
click.Command = RichCommand
click.Group = RichGroup

# this is where my CLI is.
from app.cli import cli


cli()

Instead, what we could do is have a function called patch() that does this. Here is what I am thinking:

# __main__.py
from rich_click.cli import patch

patch()

# this is where my CLI is.
from app.cli import cli

cli()

This doesn't need to be a majorly advertised feature-- using it the way I want to is a bit niche-- and it can and should take a back seat to the rich-click CLI. The main use of this would be internally inside rich_click.cli.main, where the following lines of code...

    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
    click.Group = RichGroup
    click.Command = RichCommand
    # 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()

... could instead look like this ...

    if len(args) > 1:
        if args[1] == "--":
            del args[1]
    sys.argv = [prog, *args[1:]]
    # patch click before importing the program function
    patch()
    # 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()

where patch() is defined as (you guessed it):

def patch() -> None:
    """Patch Click internals to use Rich-Click types."""
    click.group = rich_group
    click.command = rich_command
    click.Group = RichGroup
    click.Command = RichCommand

But I suggest separating out the patching function so that it can be exposed for niche situations like the case I'm working on.

WDYT?

options inherited from context settings aren't applied

Thanks for the library, love that its a drop in replacement

Just creating this to track this here/let you know.

Defaults set in context settings aren't detected by command/groups

Minimal example:

import os

USE_RICH = "USE_RICH" in os.environ

if USE_RICH:
    import rich_click as click  # type: ignore[import]
else:
    import click  # type: ignore[no-redef]

DEFAULT_CONTEXT_SETTINGS = {"show_default": True}


@click.command(context_settings=DEFAULT_CONTEXT_SETTINGS)
@click.option("--use-context-settings", default="should work", help="help text shows")
@click.option("--overwrite-to-succeed", default="does work", show_default=True, help="shows here too")
def main(use_context_settings, overwrite_to_succeed) -> None:
    click.echo(use_context_settings)
    click.echo(overwrite_to_succeed)

if __name__ == "__main__":
    main()

image

Would be willing to create a PR for this at some point in the future, I would imagine one just has to look these up in the parent context instead of just checking the parameters passed, but may be a bit more complicated than that...

Newline control

When printing help messages, the paragraph structure gets mangled by rich_click in the sense that separation between paragraphs is not respected. The following MWE illustrates what I mean.

import rich_click as click

click.rich_click.USE_RICH_MARKUP = True
# click.rich_click.USE_MARKDOWN = True

@click.command()
@click.option("-d", "--dummy", help="Does nothing")
def mwe(dummy: str):
    """This is a simple echo clone.

    This paragraph should be separated from the summary by one line.

    This sentence should also have a gap of one blank line with the previous one.
    """
    if dummy:
        click.echo(dummy)


mwe()

The output is as follows. The first instance is the output of this MWE directly, the second is when USE_MARKDOWN is uncommented.
image

Shouldn't white paragraph separations be left alone?

System info
  • OS: Arch linux
  • Terminal: Konsole 21.12.3 (connected to Windows Prompt via SSH)
  • python: 3.10.4
  • rich_click: 1.2.1
  • rich: 12.0.1
  • click: 8.0.4

Option with more than 2 names not showing all names

Hi,

not sure if this is a bug or an intended feature.
I can see why when formatting multiple long name and short name can be a nightmare to support.

If we run the following code, only the short name version is displayed.

import rich_click as click

@click.command()
@click.option(
    "-l",
    "--long",
    "--long-argument",
    help="""Long argument with multiple name.""",
)
def main(long_argument) -> None:
    print(long_argument)


if __name__ == "__main__":
    main()

image

It will only show the first name in the list (in this case the -l).

`rich_click.rich_click` does not show up in auto-complete in VSCode

Not sure if this is an issue with my own VSCode setup, but for some reason, I am not getting proper autocompletions for rich_click on lines after the import. As I type the import I do get the expected completions, but after that, when using the imported module, they don't show up.

image

image

Because the completion for rich_click.r did not show rich_click, I was confused for a moment.

Edge case: custom exception types

Came across an edge case issue in DDS:

  File "/home/sjunnebo/.conda/envs/dds/lib/python3.8/site-packages/rich_click/rich_click.py", line 449, in rich_format_error
    if self.ctx is not None:
AttributeError: 'DDSCLIException' object has no attribute 'ctx'

This seems to be because there is a custom exception type based on Click which is being used. I can't imagine that this comes up a lot, but it would be good to cope with missing ctx in the error handling..

Fix pylance type errors

By default, when trying to modify rich_click options, pylance reports this:
image

To fix this, you can add this line to the imports of rich_click.py:
image

After:
image

I'm not sure the implications of using a dot import here, and I'm also not sure if there's another way to fix this, but this is a pretty simple fix. If you wouldn't like to add this I could just patch it in my own install for development purposes, but I'm sure some others could benefit from this.

Enable help for positional arguments

Click deliberately does not support help for positional arguments.

I think it would be good to support this if people want, it is a bit more relevant with the rich-click tabular output.

To make this work, we need to enable an additional argument for the click.argument() function though, which is a little involved. Others have asked for this feature and some have done their own implementations, see pallets/click#587 (comment)

Should be able to recreate something like this in rich-click to add support for argument help.

Groups of commands, groups of groups

Something I've wanted to do before is to split up commands visually within the help output.

It would be super nice to be able to provide a dictionary of lists, where the dict keys are used for panel titles and contain the options present in the list. Any options not covered could go in the normal Options panel at the end.

This could also be a nice way to handle custom order of commands.

Discrepancy in rich dependency on conda-forge

Currently in the rich-click meta.yaml in the conda-forge feedstock there is a limit on rich to less than version 12. This differs from the setup.py/pip install. Should this be amended to rich<13?

Should be able to change this without releasing a new version and just incrementing the build number

meta.yaml:

  run:
    - python >=3.6
    - rich >=10,<12
    - click >=7,<9

setup.py:

    install_requires=[
        "click>=7",
        "rich>=10",
        "importlib-metadata; python_version < '3.8'",
    ],

Not compatible with Click 7.x

First of all, amazing project! Really loving the look & feel.
I think adoption by the community will be fast as the number of stars is already growing quickly.

I tried out the the package with Click 7.1.2 and received the following error:

  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Path/to/my/venv/lib/python3.8/site-packages/rich_click/rich_click.py", line 512, in main
    return super().main(*args, standalone_mode=False, **kwargs)
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 781, in main
    with self.make_context(prog_name, args, **extra) as ctx:
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 700, in make_context
    self.parse_args(ctx, args)
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 1212, in parse_args
    rest = Command.parse_args(self, ctx, args)
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 1048, in parse_args
    value, args = param.handle_parse_result(ctx, opts, args)
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 1630, in handle_parse_result
    value = invoke_param_callback(self.callback, ctx, self, value)
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 123, in invoke_param_callback
    return callback(ctx, param, value)
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 950, in show_help
    echo(ctx.get_help(), color=ctx.color)
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 570, in get_help
    return self.command.get_help(self)
  File "/Path/to/my/venv/lib/python3.8/site-packages/click/core.py", line 975, in get_help
    self.format_help(ctx, formatter)
  File "/Path/to/my/venv/lib/python3.8/site-packages/rich_click/rich_click.py", line 520, in format_help
    rich_format_help(self, ctx, formatter)
  File "/Path/to/my/venv/lib/python3.8/site-packages/rich_click/rich_click.py", line 333, in rich_format_help
    isinstance(param.type, click.types._NumberRangeBase)
AttributeError: module 'click.types' has no attribute '_NumberRangeBase'

It appears this _NumberRangeBase comes from Click 8 onwards.
I think Click 8 has been around long enough for people to adopt it, and compatibility issues to upgrade from 7.x to 8.x are hardly present, it feels fine to me to keep the minimum requirement to be Click 8.0.0.
However, currently there is no lower bound, therefore I'd argue we should set it to click>8.0 in the requirements to make this more explicit.

Footer and header text

The rich-cli package has a nice little bit of footer text after each help text. It would be nice to be able to configure something similar. Also header texts.

Flag option not showing correctly if short name are first

Hi,

if you run the following code, the boolean flag will not display correctly. We put the short name first instead of last like in your examples.

import rich_click as click


@click.command()
@click.option(
    "-d/-n",
    "--debug/--no-debug",
    default=False,
    help="""Enable debug mode.
    Newlines are removed by default.
    Double newlines are preserved.""",
)
def main(debug) -> None:
    print(debug)


if __name__ == "__main__":
    main()

We get the following result.

image

I will try to look into it and do a PR.

Standalone mode always False

Hello,

it seems that standalone_mode is not propagated in:

return super().main(*args, standalone_mode=False, **kwargs)

Is this normal?

Because I often have:

int(main_cli.main(prog_name=PROG_NAME, **kwargs))
TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType'

Would it be possible to apply the Rich formatting without monkey patching

Would it be possible to apply the Rich formatting without monkey patching?

I was thinking that the rich_click namespace could replicate the click namespace. But supply customized version of Click objects (via inheritance).

This is what we have currently:

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')

But I think it could be as simple (for the user at least) as this:

import rich_click as click 

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

@cli.command()
def sync():
    click.echo('Syncing')

Thoughts?

Originally posted by @willmcgugan in #7

Allow adding extra lines to separate commands/parameters

This one might be niche, not that useful, and (maybe) annoying to implement. I decided to post it here anyway.

I noticed that when I have several commands, the help dialogue looks very busy and difficult to read:

╭─ Modules ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│  download-database          Download the latest version of Genomad's database and save it in the DESTINATION         │
│                             directory.                                                                               │
│  annotate                   Predict the genes in the INPUT file (FASTA format), annotate them using Genomad's        │
│                             markers (located in the DATABASE directory), and write the results to the OUTPUT         │
│                             directory.                                                                               │
│  find-proviruses            Find integrated viruses within the sequences in INPUT file using the Genomad markers     │
│                             (located in the DATABASE directory) and write the results to the OUTPUT directory. This  │
│                             command depends on the data generated by the annotate module.                            │
│  marker-classification      Classify the sequences in the INPUT file (FASTA format) based on the presence of         │
│                             Genomad markers (located in the DATABASE directory) and write the results to the OUTPUT  │
│                             directory. This command depends on the data generated by the annotate module.            │
│  nn-classification          Classify the sequences in the INPUT file (FASTA format) using the Genomad neural         │
│                             network and write the results to the OUTPUT directory.                                   │
│  aggregated-classification  Aggregate the results of the marker-classification and nn-classification modules to      │
│                             classify the sequences in the INPUT file (FASTA format) and write the results to the     │
│                             OUTPUT directory.                                                                        │
│  score-calibration          Performs score calibration of the sequences in the INPUT file (FASTA format) using the   │
│                             batch correction method and write the results to the OUTPUT directory. This module       │
│                             requires that at least one of the classification modules was classified previously       │
│                             (marker-classification, nn-classification, aggregated-classification).                   │
│  summary                    Generates a classification report file for the sequences in the INPUT file (FASTA        │
│                             format) and write it to the OUTPUT directory.                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

If I add an additional line to separate the commands, the dialogue looks much better.

╭─ Modules ────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│  download-database          Download the latest version of Genomad's database and save it in the DESTINATION         │
│                             directory.                                                                               │
│                                                                                                                      │
│  annotate                   Predict the genes in the INPUT file (FASTA format), annotate them using Genomad's        │
│                             markers (located in the DATABASE directory), and write the results to the OUTPUT         │
│                             directory.                                                                               │
│                                                                                                                      │
│  find-proviruses            Find integrated viruses within the sequences in INPUT file using the Genomad markers     │
│                             (located in the DATABASE directory) and write the results to the OUTPUT directory. This  │
│                             command depends on the data generated by the annotate module.                            │
│                                                                                                                      │
│  marker-classification      Classify the sequences in the INPUT file (FASTA format) based on the presence of         │
│                             Genomad markers (located in the DATABASE directory) and write the results to the OUTPUT  │
│                             directory. This command depends on the data generated by the annotate module.            │
│                                                                                                                      │
│  nn-classification          Classify the sequences in the INPUT file (FASTA format) using the Genomad neural         │
│                             network and write the results to the OUTPUT directory.                                   │
│                                                                                                                      │
│  aggregated-classification  Aggregate the results of the marker-classification and nn-classification modules to      │
│                             classify the sequences in the INPUT file (FASTA format) and write the results to the     │
│                             OUTPUT directory.                                                                        │
│                                                                                                                      │
│  score-calibration          Performs score calibration of the sequences in the INPUT file (FASTA format) using the   │
│                             batch correction method and write the results to the OUTPUT directory. This module       │
│                             requires that at least one of the classification modules was classified previously       │
│                             (marker-classification, nn-classification, aggregated-classification).                   │
│                                                                                                                      │
│  summary                    Generates a classification report file for the sequences in the INPUT file (FASTA        │
│                             format) and write it to the OUTPUT directory.                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Prevent rewrapping with `\b`

Hi @ewels, rich-click is an awesome package and I love that it's a drop-in replacement for click. However, I'm running into a small issue with rewrapping. click uses the \b flag to prevent rewrapping for a paragraph, which allows you to preserve newlines in a command's help. rich-click doesn't seem to support that flag and removes newlines.

Say you have a command foo:

@click.command()
def foo():
    """This is a command.

    \b
    Examples
        $ foo
        $ foo bar
    """
    return

Here's the output of foo --help using just click:

image

With rich-click, the newlines are lost.

image

Using Markdown instead might be an alternative, but would require rewriting docstrings and would break compatibility with click. Any chance of support being added for \b in rich-click?

Thanks!

Default value gets moved to its own line when the description is 2+ lines long

I'm not really sure if this is the expected behavior. If so, please ignore this issue.

When an option descriptions is 2+ lines long, the default value text is moved to an additional line, even when it would fit by the side of the description.

Current behavior:

╭─ Advanced options ───────────────────────────────────────────────────────────────────────────────╮
│  --sensitivity  -s  FLOAT  See there how easy that is. It's amazing what you can do with a       │
│                            little love in your heart.                                            │
│                            [default: 3.5]                                                        │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

What I expected:

╭─ Advanced options ───────────────────────────────────────────────────────────────────────────────╮
│  --sensitivity  -s  FLOAT  See there how easy that is. It's amazing what you can do with a       │
│                            little love in your heart. [default: 3.5]                             │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Vanilla click:

  -s, --sensitivity FLOAT      See there how easy that is. It's amazing what
                               you can do with love.  [default: 3.5]

Support for changing the Markdown code_theme

  • Currently, code-blocks are rendered poorly on my light-themed editor.
  • I'd like to be able to pass code_theme="native" and inline_code_theme="native" to rich.markdown.Markdown.
  • Would it be possible to add some extra configuration options?

Return value from Typer function is printed only when installed

I apologize in advance for how obscure this bug is.

I'll start by taking the simple Typer demo and modifying it to have download return a string:

import rich_click.typer as typer

app = typer.Typer()

@app.command()
def sync(
    type: str = typer.Option("files", help="Type of file to sync"),
    all: bool = typer.Option(False, help="Sync all the things?"),
):
    print("Syncing")


@app.command()
def download(all: bool = typer.Option(False, help="Get everything")):
    return "Shouldn't appear" # <-- RETURNS A STRING NOW


if __name__ == "__main__":
    app()

When run directly, everything is normal:

$ python vdsearch/test.py download
$

Now let's pip install -e . with testy mapped to test:app

$ testy download
Shouldn't appear
$

I've gotten around this bug by defining wrapper functions for my functions but it would be nice to get to the bottom of what's going on.

`rich-click` doesn't support `click.Choice`

I'm writing a command which has a --composition parameter that uses click.Choice:

@click.option(
    "--composition",
    type=click.Choice(["auto", "metagenome"], case_sensitive=False),
)

In vanilla click, the possible choices appear as expected:

Options:
  --version                       Show the version and exit.
  --composition [auto|metagenome]
  -v, --verbose / -q, --quiet     Display the execution log.  [default:
                                  verbose]
  -h, --help                      Show this message and exit.

rich-click omits the options:

╭─ Basic options ──────────────────────────────────────────────────────────────────────────────────╮
│  --composition                                                                                   │
│  --verbose/--quiet  -v/-q  Display the execution log. [default: verbose]                         │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Other ──────────────────────────────────────────────────────────────────────────────────────────╮
│  --help     -h  Show this message and exit.                                                      │
│  --version      Show the version and exit.                                                       │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

Command `short_help` is ignored

Hi @ewels, I came across another discrepancy between click and rich-click that's giving me a little trouble: how command short help is handled.

click generates short help from the first sentence of the command's help unless short_help is explicitly passed to the command, in which case that will be used. rich-click seems to use the first paragraph instead, and you can't overwrite it using short_help.

Here's a minimum reproducible example using rich-click==1.2.0.

@click.group()
def foo():
    """
    Help for group.
    """
    pass

@foo.command()
def command():
    """
    Help for command. This sentence shouldn't be included in the short help.
    """
    pass

@foo.command(short_help="This won't be used by rich-click")
def command2():
    """
    This should be overwritten by the explicitly passed short-help.
    """
    pass

Output of foo --help using click:

image

And using rich-click:

image

Thanks again for making rich-click!

Attribute error when ERRORS_SUGGESTION is not set

if getattr(self, "ctx", None) is not None:
console.print(self.ctx.get_usage())
if ERRORS_SUGGESTION:
console.print(ERRORS_SUGGESTION, style=STYLE_ERRORS_SUGGESTION)
elif (
ERRORS_SUGGESTION is None
and self.ctx is not None
and self.ctx.command.get_help_option(self.ctx) is not None

#27 still is an issue if click.rich_click.ERRORS_SUGGESTION is not set due to the second access of self.ctx in L527. Fixing this would also resolve #21 since then I could raise a ClickException directly. Right now, I have to manually set ERRORS_SUGGESTION to '' to get it to not throw an error:

import logging
import shutil
from typing import Callable, Optional
import rich_click as click

def check_executable_exists(
    name: str, display_name: Optional[str] = None, test_command: str = None
):
    """
    Check if an executable exists in the system path.
    """
    if shutil.which(name) is None:
        logging.error(f"Unable to find {display_name or name} executable.")
        click.rich_click.ERRORS_SUGGESTION = ""
        raise click.ClickException(
            f"Unable to find {display_name or name} executable. Are you sure that it's installed? "
            f"To test if it is, run the following command: {test_command or name}"
        )
[16:54:53] ERROR    Unable to find seqkit executable.                                                                               utils.py:17
╭─ Error ──────────────────────────────────────────────────────────────────────────────╮
│ Unable to find seqkit executable. Are you sure that it's installed? To test if it    │
│ is, run the following command: seqkit                                                │
╰──────────────────────────────────────────────────────────────────────────────────────╯
If you have any questions or need help, please contact me at https://benjamindlee.com

`rich_cli` does not properly clean indentations for `click >= 8.1.0`

rich_click is not cleaning indentations if click version is ≥ 8.10. The cause is probably this change in the backend.

This code:

def cli():
    """
    Foo: bar

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
    tempor incididunt ut labore et dolore magna aliqua. Pellentesque habitant
    morbi tristique senectus. Leo a diam sollicitudin tempor.
    """

Is generating this:

 Foo: bar
 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod     tempor incididunt ut
 labore et dolore magna aliqua. Pellentesque habitant     morbi tristique senectus. Leo a diam
 sollicitudin tempor.

Instead of this:

 Foo: bar
 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
 et dolore magna aliqua. Pellentesque habitant morbi tristique senectus. Leo a diam sollicitudin
 tempor.

Use of emojis in Markdown

Rich does not support emoji in markdown out of the box.
But there is a simple workaround:

from rich.emoji import Emoji
markdown = "# Hello World :thumbs_up:"
markdown = Emoji.replace(markdown)

Taken from Textualize/rich#231

Parse markdown strings

It would be great if help text could be written using markdown.

This could have some weird effects if people aren't expecting it, so should probably be opt-in.

Typer AttributeError in latest release

Originally posted by @taranlu-houzz in #59 (comment)


Hey @ewels, I just updated to the latest release of rich-click, but I am now getting this error (the first part of the help was printed before hitting this error):

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/---/cli.py", line 267, in run_app
    APP()
  File "/---/lib/python3.10/site-packages/typer/main.py", line 214, in __call__
    return get_command(self)(*args, **kwargs)
  File "/---/lib/python3.10/site-packages/click/core.py", line 1130, in __call__
    return self.main(*args, **kwargs)
  File "/---/lib/python3.10/site-packages/rich_click/rich_group.py", line 21, in main
    return super().main(*args, standalone_mode=False, **kwargs)
  File "/---/lib/python3.10/site-packages/click/core.py", line 1055, in main
    rv = self.invoke(ctx)
  File "/---/lib/python3.10/site-packages/click/core.py", line 1655, in invoke
    sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)
  File "/---/lib/python3.10/site-packages/click/core.py", line 920, in make_context
    self.parse_args(ctx, args)
  File "/---/lib/python3.10/site-packages/click/core.py", line 1378, in parse_args
    value, args = param.handle_parse_result(ctx, opts, args)
  File "/---/lib/python3.10/site-packages/click/core.py", line 2360, in handle_parse_result
    value = self.process_value(ctx, value)
  File "/---/lib/python3.10/site-packages/click/core.py", line 2322, in process_value
    value = self.callback(ctx, self, value)
  File "/---/lib/python3.10/site-packages/click/core.py", line 1273, in show_help
    echo(ctx.get_help(), color=ctx.color)
  File "/---/lib/python3.10/site-packages/click/core.py", line 699, in get_help
    return self.command.get_help(self)
  File "/---/lib/python3.10/site-packages/click/core.py", line 1298, in get_help
    self.format_help(ctx, formatter)
  File "/---/lib/python3.10/site-packages/rich_click/rich_command.py", line 32, in format_help
    rich_format_help(self, ctx, formatter)
  File "/---/lib/python3.10/site-packages/rich_click/rich_click.py", line 459, in rich_format_help
    _get_parameter_help(param, ctx),
  File "/---/lib/python3.10/site-packages/rich_click/rich_click.py", line 245, in _get_parameter_help
    if param.allow_from_autoenv and ctx.auto_envvar_prefix is not None and param.name is not None:
AttributeError: 'TyperArgument' object has no attribute 'allow_from_autoenv'

Is `click.MultiCommand` supported?

Here's a minimal example using click.MultiCommand for lazy loading of subcommands. Is this already supported and I just haven't figured out how to use it, or is this not supported yet?

import rich_click as click

class Cli(click.MultiCommand):
    def list_commands(self, ctx):
        return ["foo"]

    def get_command(self, ctx, name):
        if name == "foo":
            from foo import bar
            return bar
        raise NotImplementedError(f"The command '{name}' is not implemented.")

@click.group(cls=Cli)
def cli():
    pass

Support abort messages

From what I can see #2 styles usage errors, not runtime errors. This leads to strange error printouts during runtime when the usage will get printed out along with the error panel. Is there a way to just write out the error message?

[16:54:27] INFO     Removing duplicate sequences...                                                                                 dedup.py:25
           ERROR    Unable to find Seqkit executable.                                                                               utils.py:14
Usage: bdltest easy-search [OPTIONS] FASTA
Try 'bdltest easy-search --help' for help.
╭─ Error ──────────────────────────────────────────────────────────────────────────────╮
│ Unable to find Seqkit executable. Are you sure that it's installed? To test if it    │
│ is, run the following command: seqkit version                                        │
╰──────────────────────────────────────────────────────────────────────────────────────╯
If you have any questions or need help, please contact me at https://benjamindlee.com

Decorrelate format and printing

I think something is incompatible in the way rich_format_help is not only formatting the output string
but it's printing it too !

I think this function should just return the formatted string (a rich object is ok) and then let the user choose the Console object himself or another way to print the string.

Option to show positional arguments

Positional arguments are currently not shown (see the click docs for the reason):

# Skip positional arguments - they don't have opts or helptext and are covered in usage
# See https://click.palletsprojects.com/en/8.0.x/documentation/#documenting-arguments
if type(param) is click.core.Argument:
continue

It would be nice to be able to opt-in to showing these. Ideally, also with some kind of mechanism to add help text so that we have something more to print.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.