ewels / rich-click Goto Github PK
View Code? Open in Web Editor NEWFormat click help output nicely with rich.
Home Page: https://ewels.github.io/rich-click/
License: MIT License
Format click help output nicely with rich.
Home Page: https://ewels.github.io/rich-click/
License: MIT License
I want to have autocompletion when instantiating a Typer
object.
Can I open a PR with it? It will be verbose.
The title says it all. Just to make it easier for other packages to depend on rich click :)
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.
I will try to look into it and do a PR.
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
:
With rich-click
, the newlines are lost.
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!
Hello,
it seems that standalone_mode
is not propagated in:
rich-click/src/rich_click/rich_click.py
Line 525 in 8378cdd
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'
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..
Positional arguments are currently not shown (see the click docs for the reason):
rich-click/src/rich_click/core.py
Lines 75 to 78 in 7f83a42
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.
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.
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'
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.
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.
Shouldn't white paragraph separations be left alone?
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?
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 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
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.
Because the completion for rich_click.r
did not show rich_click
, I was confused for a moment.
By default, when trying to modify rich_click options, pylance reports this:
To fix this, you can add this line to the imports of rich_click.py:
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.
rich-click/src/rich_click/rich_click.py
Lines 521 to 528 in 19aabd3
#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
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()
It will only show the first name in the list (in this case the -l
).
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()
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...
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:
These are the versions of rich
and rich-click
installed in the virtualenv:
rich
: 10.16.2rich-click
: 1.2.1I'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. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
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
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. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
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
:
And using rich-click
:
Thanks again for making rich-click
!
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]
Some help texts can be super long. Rich has the ability to use a pager - it would be nice to be able to opt into using this.
In click using the \f
ignores all text after that point in the help. When doing using non markdown mode this works, but when I enable markdown the text shows up.
code_theme="native"
and inline_code_theme="native"
to rich.markdown.Markdown
.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.
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.
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.
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
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. │
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
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'",
],
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
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.
Colours and styles are very much subject to personal tastes.
We should add functionality to customise styling choices, such as colours and box styling.
I was reading the README, tried the COMMAND_GROUPS functionality but nothing happened. After inspecting the lib source, it seems it doesn't have the latest code from the repo main branch
Thank you for creating this lib!!
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..
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.
I don't think that envvar
is considered currently: https://click.palletsprojects.com/en/8.0.x/options/#values-from-environment-variables
See #26 (comment)
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
.
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).",
)
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.
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] │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
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. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.