diff options
author | cyfraeviolae <cyfraeviolae> | 2024-04-03 03:10:44 -0400 |
---|---|---|
committer | cyfraeviolae <cyfraeviolae> | 2024-04-03 03:10:44 -0400 |
commit | 6d7ba58f880be618ade07f8ea080fe8c4bf8a896 (patch) | |
tree | b1c931051ffcebd2bd9d61d98d6233ffa289bbce /venv/lib/python3.11/site-packages/rich_click | |
parent | 4f884c9abc32990b4061a1bb6997b4b37e58ea0b (diff) |
venv
Diffstat (limited to 'venv/lib/python3.11/site-packages/rich_click')
25 files changed, 1988 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/rich_click/__init__.py b/venv/lib/python3.11/site-packages/rich_click/__init__.py new file mode 100644 index 0000000..18ad228 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__init__.py @@ -0,0 +1,104 @@ +# flake8: noqa: F401 +""" +rich-click is a minimal Python module to combine the efforts of the excellent packages 'rich' and 'click'. + +The intention is to provide attractive help output from click, formatted with rich, with minimal +customisation required. +""" + +__version__ = "1.7.4" + +# Import the entire click API here. +# We need to manually import these instead of `from click import *` to force mypy to recognize a few type annotation overrides for the rich_click decorators. +from click.core import Argument as Argument +from click.core import Command as Command +from click.core import CommandCollection as CommandCollection +from click.core import Context as Context +from click.core import Group as Group +from click.core import Option as Option +from click.core import Parameter as Parameter +from click.decorators import argument as argument +from click.decorators import confirmation_option as confirmation_option +from click.decorators import help_option as help_option +from click.decorators import make_pass_decorator as make_pass_decorator +from click.decorators import option as option +from click.decorators import pass_obj as pass_obj +from click.decorators import password_option as password_option +from click.decorators import version_option as version_option +from click.exceptions import Abort as Abort +from click.exceptions import BadArgumentUsage as BadArgumentUsage +from click.exceptions import BadOptionUsage as BadOptionUsage +from click.exceptions import BadParameter as BadParameter +from click.exceptions import ClickException as ClickException +from click.exceptions import FileError as FileError +from click.exceptions import MissingParameter as MissingParameter +from click.exceptions import NoSuchOption as NoSuchOption +from click.exceptions import UsageError as UsageError +from click.formatting import HelpFormatter as HelpFormatter +from click.formatting import wrap_text as wrap_text +from click.globals import get_current_context as get_current_context +from click.termui import clear as clear +from click.termui import confirm as confirm +from click.termui import echo_via_pager as echo_via_pager +from click.termui import edit as edit +from click.termui import getchar as getchar +from click.termui import launch as launch +from click.termui import pause as pause +from click.termui import progressbar as progressbar +from click.termui import prompt as prompt +from click.termui import secho as secho +from click.termui import style as style +from click.termui import unstyle as unstyle +from click.types import BOOL as BOOL +from click.types import Choice as Choice +from click.types import DateTime as DateTime +from click.types import File as File +from click.types import FLOAT as FLOAT +from click.types import FloatRange as FloatRange +from click.types import INT as INT +from click.types import IntRange as IntRange +from click.types import ParamType as ParamType +from click.types import Path as Path +from click.types import STRING as STRING +from click.types import Tuple as Tuple +from click.types import UNPROCESSED as UNPROCESSED +from click.types import UUID as UUID +from click.utils import echo as echo +from click.utils import format_filename as format_filename +from click.utils import get_app_dir as get_app_dir +from click.utils import get_binary_stream as get_binary_stream +from click.utils import get_text_stream as get_text_stream +from click.utils import open_file as open_file + +from . import rich_click as rich_click + +from rich_click.decorators import command as command +from rich_click.decorators import group as group +from rich_click.decorators import pass_context as pass_context +from rich_click.decorators import rich_config as rich_config +from rich_click.rich_command import RichCommand as RichCommand +from rich_click.rich_command import RichCommandCollection as RichCommandCollection +from rich_click.rich_command import RichGroup as RichGroup +from rich_click.rich_context import RichContext as RichContext +from rich_click.rich_help_configuration import RichHelpConfiguration as RichHelpConfiguration + + +def __getattr__(name: str) -> object: + from rich_click._compat_click import CLICK_IS_BEFORE_VERSION_9X + + if name == "RichMultiCommand" and CLICK_IS_BEFORE_VERSION_9X: + import warnings + + warnings.warn( + "'RichMultiCommand' is deprecated and will be removed in Click 9.0. Use 'RichGroup' instead.", + DeprecationWarning, + stacklevel=2, + ) + from rich_click.rich_command import RichMultiCommand + + return RichMultiCommand + + else: + import click + + return getattr(click, name) diff --git a/venv/lib/python3.11/site-packages/rich_click/__main__.py b/venv/lib/python3.11/site-packages/rich_click/__main__.py new file mode 100644 index 0000000..9406f8f --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__main__.py @@ -0,0 +1,13 @@ +""" +Entry-point module for the command line prefixer, called in case you use `python -m rich_click`. + +Why does this file exist, and why `__main__`? For more info, read: +- https://www.python.org/dev/peps/pep-0338/ +- https://docs.python.org/3/using/cmdline.html#cmdoption-m +""" + +from rich_click.cli import main + +if __name__ == "__main__": + # main will run a Click command which will either exit or raise + main() diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/__init__.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/__init__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..16b88c4 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/__init__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/__main__.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/__main__.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..bc3f2f9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/__main__.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/_compat_click.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/_compat_click.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..a1ddde3 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/_compat_click.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/cli.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/cli.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..7c9ec62 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/cli.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/decorators.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/decorators.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..31e15f2 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/decorators.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_click.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_click.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..a7f037d --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_click.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_command.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_command.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..aa6e009 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_command.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_context.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_context.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..7a7565c --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_context.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_group.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_group.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..cde8f18 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_group.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_help_configuration.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_help_configuration.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..e5349e1 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_help_configuration.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_help_formatter.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_help_formatter.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..3ea5313 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/rich_help_formatter.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/__pycache__/utils.cpython-311.pyc b/venv/lib/python3.11/site-packages/rich_click/__pycache__/utils.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..62c3a8b --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/__pycache__/utils.cpython-311.pyc diff --git a/venv/lib/python3.11/site-packages/rich_click/_compat_click.py b/venv/lib/python3.11/site-packages/rich_click/_compat_click.py new file mode 100644 index 0000000..89a13e7 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/_compat_click.py @@ -0,0 +1,25 @@ +try: + from importlib import metadata # type: ignore[import,unused-ignore] +except ImportError: + # Python < 3.8 + import importlib_metadata as metadata # type: ignore[no-redef,import-not-found] + + +click_version = metadata.version("click") +_major = int(click_version.split(".")[0]) +_minor = int(click_version.split(".")[1]) + + +CLICK_IS_BEFORE_VERSION_8X = _major < 8 +CLICK_IS_BEFORE_VERSION_9X = _major < 9 +CLICK_IS_VERSION_80 = _major == 8 and _minor == 0 + + +if CLICK_IS_BEFORE_VERSION_8X: + import warnings + + warnings.warn( + "rich-click support for click 7.x is deprecated and will be removed soon." + " Please upgrade click to a newer version.", + DeprecationWarning, + ) diff --git a/venv/lib/python3.11/site-packages/rich_click/cli.py b/venv/lib/python3.11/site-packages/rich_click/cli.py new file mode 100644 index 0000000..a7041a9 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/cli.py @@ -0,0 +1,154 @@ +"""The command line interface.""" + +import sys +from importlib import import_module +from textwrap import dedent +from typing import Any, List, Optional + +try: + from importlib import metadata # type: ignore[import,unused-ignore] +except ImportError: + # Python < 3.8 + import importlib_metadata as metadata # type: ignore[no-redef,import-not-found,unused-ignore] + +import click +from rich.console import Console +from rich.padding import Padding +from rich.panel import Panel +from rich.text import Text + +from rich_click.decorators import command as rich_command +from rich_click.decorators import group as rich_group +from rich_click.rich_click import ( + ALIGN_ERRORS_PANEL, + ERRORS_PANEL_TITLE, + STYLE_ERRORS_PANEL_BORDER, + STYLE_HELPTEXT, + STYLE_HELPTEXT_FIRST_LINE, + STYLE_USAGE, + STYLE_USAGE_COMMAND, +) +from rich_click.rich_command import RichCommand, RichCommandCollection, RichGroup, RichMultiCommand + +console = Console() + + +def _print_usage() -> None: + console.print( + Padding( + Text.from_markup(f"[{STYLE_USAGE}]Usage:[/] rich-click [SCRIPT | MODULE:FUNCTION] [-- SCRIPT_ARGS...]"), + 1, + ), + style=STYLE_USAGE_COMMAND, + ) + + +def _print_help() -> None: + help_paragraphs = dedent(main.__doc__ or "").split("\n\n") + help_paragraphs = [x.replace("\n", " ").strip() for x in help_paragraphs] + console.print( + Padding( + Text.from_markup(help_paragraphs[0].strip()), + (0, 1), + ), + style=STYLE_HELPTEXT_FIRST_LINE, + ) + console.print( + Padding( + Text.from_markup("\n\n".join(help_paragraphs[1:]).strip()), + (0, 1), + ), + style=STYLE_HELPTEXT, + ) + + +def patch() -> None: + """Patch Click internals to use Rich-Click types.""" + click.group = rich_group + click.command = rich_command + click.Group = RichGroup # type: ignore[misc] + click.Command = RichCommand # type: ignore[misc] + click.CommandCollection = RichCommandCollection # type: ignore[misc] + if "MultiCommand" in dir(click): + click.MultiCommand = RichMultiCommand # type: ignore[assignment,misc,unused-ignore] + + +def entry_points(*, group: str) -> "metadata.EntryPoints": # type: ignore[name-defined] + """entry_points function that is compatible with Python 3.7+.""" + if sys.version_info >= (3, 10): + return metadata.entry_points(group=group) + + epg = metadata.entry_points() + + if sys.version_info < (3, 8) and hasattr(epg, "select"): + return epg.select(group=group) + + return epg.get(group, []) + + +def main(args: Optional[List[str]] = None) -> Any: + """ + The [link=https://github.com/ewels/rich-click]rich-click[/] CLI provides attractive help output from any + tool using [link=https://click.palletsprojects.com/]click[/], formatted with + [link=https://github.com/Textualize/rich]rich[/]. + + The rich-click command line tool can be prepended before any Python package + using native click to provide attractive richified click help output. + + For example, if you have a package called [blue]my_package[/] that uses click, + you can run: + + [blue] rich-click my_package --help [/] + + It only works if the package is using vanilla click without customised [cyan]group()[/] + or [cyan]command()[/] classes. + If in doubt, please suggest to the authors that they use rich_click within their + tool natively - this will always give a better experience. + """ # noqa: D400, D401 + args = args or sys.argv[1:] + if not args or args == ["--help"]: + # Print usage if we got no args, or only --help + _print_usage() + _print_help() + sys.exit(0) + else: + script_name = args[0] + scripts = {script.name: script for script in entry_points(group="console_scripts")} + if script_name in scripts: + # a valid script was passed + script = scripts[script_name] + module_path, function_name = script.value.split(":", 1) + prog = script_name + elif ":" in script_name: + # the path to a function was passed + module_path, function_name = args[0].split(":", 1) + prog = module_path.split(".", 1)[0] + else: + _print_usage() + console.print( + Panel( + Text.from_markup(f"No such script: [bold]{script_name}[/]"), + border_style=STYLE_ERRORS_PANEL_BORDER, + title=ERRORS_PANEL_TITLE, + title_align=ALIGN_ERRORS_PANEL, + ) + ) + console.print( + Padding( + "Please run [yellow bold]rich-click --help[/] for usage information.", + (0, 1), + ), + style="dim", + ) + sys.exit(1) + if len(args) > 1: + if args[1] == "--": + del args[1] + sys.argv = [prog, *args[1:]] + # patch click before importing the program function + 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() diff --git a/venv/lib/python3.11/site-packages/rich_click/decorators.py b/venv/lib/python3.11/site-packages/rich_click/decorators.py new file mode 100644 index 0000000..3656be8 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/decorators.py @@ -0,0 +1,216 @@ +""" +rich-click is a minimal Python module to combine the efforts of the excellent packages 'rich' and 'click'. + +The intention is to provide attractive help output from click, formatted with rich, with minimal +customisation required. +""" + +from typing import Any, Callable, cast, Dict, Optional, overload, Type, TYPE_CHECKING, TypeVar, Union + +from click import Command +from click import command as click_command +from click import Group +from click import group as click_group +from click import pass_context as click_pass_context +from rich.console import Console +from typing_extensions import Concatenate, ParamSpec + +from . import rich_click # noqa: F401 + +from rich_click._compat_click import CLICK_IS_BEFORE_VERSION_8X +from rich_click.rich_command import RichCommand, RichGroup, RichMultiCommand # noqa: F401 +from rich_click.rich_context import RichContext +from rich_click.rich_help_configuration import RichHelpConfiguration + +# MyPy does not like star imports. Therefore when we are type checking, we import each individual module +# from click here. This way MyPy will recognize the import and not throw any errors. Furthermore, because of +# the TYPE_CHECKING check, it does not influence the start routine at all. + +_AnyCallable = Callable[..., Any] +F = TypeVar("F", bound=Callable[..., Any]) +FC = TypeVar("FC", bound=Union[Command, _AnyCallable]) + + +GrpType = TypeVar("GrpType", bound=Group) + + +# variant: no call, directly as decorator for a function. +@overload +def group(name: _AnyCallable) -> RichGroup: + ... + + +# variant: with positional name and with positional or keyword cls argument: +# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...) +@overload +def group( + name: Optional[str], + cls: Type[GrpType], + **attrs: Any, +) -> Callable[[_AnyCallable], GrpType]: + ... + + +# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) +@overload +def group( + name: None = None, + *, + cls: Type[GrpType], + **attrs: Any, +) -> Callable[[_AnyCallable], GrpType]: + ... + + +# variant: with optional string name, no cls argument provided. +@overload +def group(name: Optional[str] = ..., cls: None = None, **attrs: Any) -> Callable[[_AnyCallable], RichGroup]: + ... + + +def group( + name: Union[str, _AnyCallable, None] = None, + cls: Optional[Type[GrpType]] = None, + **attrs: Any, +) -> Union[Group, Callable[[_AnyCallable], Union[RichGroup, GrpType]]]: + """ + Group decorator function. + + Defines the group() function so that it uses the RichGroup class by default. + """ + if cls is None: + cls = cast(Type[GrpType], RichGroup) + + if callable(name): + return command(cls=cls, **attrs)(name) + + return command(name, cls, **attrs) + + +CmdType = TypeVar("CmdType", bound=Command) + + +# variant: no call, directly as decorator for a function. +@overload +def command(name: _AnyCallable) -> RichCommand: + ... + + +# variant: with positional name and with positional or keyword cls argument: +# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...) +@overload +def command( + name: Optional[str], + cls: Type[CmdType], + **attrs: Any, +) -> Callable[[_AnyCallable], CmdType]: + ... + + +# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) +@overload +def command( + name: None = None, + *, + cls: Type[CmdType], + **attrs: Any, +) -> Callable[[_AnyCallable], CmdType]: + ... + + +# variant: with optional string name, no cls argument provided. +@overload +def command(name: Optional[str] = ..., cls: None = None, **attrs: Any) -> Callable[[_AnyCallable], RichCommand]: + ... + + +def command( + name: Union[Optional[str], _AnyCallable] = None, + cls: Optional[Type[CmdType]] = None, + **attrs: Any, +) -> Union[Command, Callable[[_AnyCallable], Union[RichCommand, CmdType]]]: + """ + Command decorator function. + + Defines the command() function so that it uses the RichCommand class by default. + """ + if cls is None: + cls = cast(Type[CmdType], RichCommand) + + if callable(name): + return click_command(cls=cls, **attrs)(name) + + return click_command(name, cls=cls, **attrs) + + +class NotSupportedError(Exception): + """Not Supported Error.""" + + pass + + +def rich_config( + console: Optional[Console] = None, help_config: Optional[RichHelpConfiguration] = None +) -> Callable[[FC], FC]: + """Use decorator to configure Rich Click settings. + + Args: + console: A Rich Console that will be accessible from the `RichContext`, `RichCommand`, and `RichGroup` instances + Defaults to None. + help_config: Rich help configuration that is used internally to format help messages and exceptions + Defaults to None. + """ + if CLICK_IS_BEFORE_VERSION_8X: + + def decorator_with_warning(obj: FC) -> FC: + import warnings + + warnings.warn( + "`rich_config()` does not work with versions of click prior to version 8.0.0." + " Please update to a newer version of click to use this functionality.", + RuntimeWarning, + ) + return obj + + return decorator_with_warning + + def decorator(obj: FC) -> FC: + extra: Dict[str, Any] = {} + if console is not None: + extra["rich_console"] = console + if help_config is not None: + extra["rich_help_config"] = help_config + + if isinstance(obj, (RichCommand, RichGroup)): + obj.context_settings.update(extra) + elif callable(obj) and not isinstance(obj, (Command, Group)): + if hasattr(obj, "__rich_context_settings__"): + obj.__rich_context_settings__.update(extra) + else: + setattr(obj, "__rich_context_settings__", extra) + else: + raise NotSupportedError("`rich_config` requires a `RichCommand` or `RichGroup`. Try using the cls keyword") + return obj + + return decorator + + +# Users of rich_click would face issues using mypy with this code, +# if not for wrapping `pass_context` with a new function signature: +# +# @click.command() +# @click.pass_context +# def cli(ctx: click.RichContext) -> None: +# ... + + +P = ParamSpec("P") +R = TypeVar("R") + + +def pass_context(f: Callable[Concatenate[RichContext, P], R]) -> Callable[P, R]: + # flake8: noqa: D400,D401 + """Marks a callback as wanting to receive the current context + object as first argument. + """ + return click_pass_context(f) # type: ignore[arg-type] diff --git a/venv/lib/python3.11/site-packages/rich_click/py.typed b/venv/lib/python3.11/site-packages/rich_click/py.typed new file mode 100644 index 0000000..ee90bd6 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. rich-click uses inline types. diff --git a/venv/lib/python3.11/site-packages/rich_click/rich_click.py b/venv/lib/python3.11/site-packages/rich_click/rich_click.py new file mode 100644 index 0000000..215c170 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/rich_click.py @@ -0,0 +1,893 @@ +import inspect +import re +from typing import Dict, Iterable, List, Optional, Tuple, TYPE_CHECKING, Union + +import click +import rich.columns +import rich.markdown +import rich.text + +# Due to how rich_click.cli.patch() works, it is safer to import Command types directly +# rather than use the click module e.g. click.Command +from click import Command, Group +from rich import box +from rich.align import Align +from rich.columns import Columns +from rich.emoji import Emoji +from rich.highlighter import RegexHighlighter +from rich.markdown import Markdown +from rich.padding import Padding +from rich.panel import Panel +from rich.style import StyleType +from rich.table import Table +from rich.text import Text +from typing_extensions import Literal + +from rich_click._compat_click import CLICK_IS_BEFORE_VERSION_8X, CLICK_IS_BEFORE_VERSION_9X, CLICK_IS_VERSION_80 +from rich_click.rich_help_configuration import ( + force_terminal_default, + OptionHighlighter, + RichHelpConfiguration, + terminal_width_default, +) +from rich_click.rich_help_formatter import RichHelpFormatter + +# Support rich <= 10.6.0 +try: + from rich.console import group +except ImportError: + from rich.console import render_group as group # type: ignore[attr-defined,no-redef] + + +if CLICK_IS_BEFORE_VERSION_9X: + from click import MultiCommand +else: + MultiCommand = Group # type: ignore[misc,assignment] + + +# Default styles +STYLE_OPTION: rich.style.StyleType = "bold cyan" +STYLE_ARGUMENT: rich.style.StyleType = "bold cyan" +STYLE_COMMAND: rich.style.StyleType = "bold cyan" +STYLE_SWITCH: rich.style.StyleType = "bold green" +STYLE_METAVAR: rich.style.StyleType = "bold yellow" +STYLE_METAVAR_APPEND: rich.style.StyleType = "dim yellow" +STYLE_METAVAR_SEPARATOR: rich.style.StyleType = "dim" +STYLE_HEADER_TEXT: rich.style.StyleType = "" +STYLE_EPILOG_TEXT: rich.style.StyleType = "" +STYLE_FOOTER_TEXT: rich.style.StyleType = "" +STYLE_USAGE: rich.style.StyleType = "yellow" +STYLE_USAGE_COMMAND: rich.style.StyleType = "bold" +STYLE_DEPRECATED: rich.style.StyleType = "red" +STYLE_HELPTEXT_FIRST_LINE: rich.style.StyleType = "" +STYLE_HELPTEXT: rich.style.StyleType = "dim" +STYLE_OPTION_HELP: rich.style.StyleType = "" +STYLE_OPTION_DEFAULT: rich.style.StyleType = "dim" +STYLE_OPTION_ENVVAR: rich.style.StyleType = "dim yellow" +STYLE_REQUIRED_SHORT: rich.style.StyleType = "red" +STYLE_REQUIRED_LONG: rich.style.StyleType = "dim red" +STYLE_OPTIONS_PANEL_BORDER: rich.style.StyleType = "dim" +ALIGN_OPTIONS_PANEL: rich.align.AlignMethod = "left" +STYLE_OPTIONS_TABLE_SHOW_LINES: bool = False +STYLE_OPTIONS_TABLE_LEADING: int = 0 +STYLE_OPTIONS_TABLE_PAD_EDGE: bool = False +STYLE_OPTIONS_TABLE_PADDING: rich.padding.PaddingDimensions = (0, 1) +STYLE_OPTIONS_TABLE_BOX: rich.style.StyleType = "" +STYLE_OPTIONS_TABLE_ROW_STYLES: Optional[List[rich.style.StyleType]] = None +STYLE_OPTIONS_TABLE_BORDER_STYLE: Optional[rich.style.StyleType] = None +STYLE_COMMANDS_PANEL_BORDER: rich.style.StyleType = "dim" +ALIGN_COMMANDS_PANEL: rich.align.AlignMethod = "left" +STYLE_COMMANDS_TABLE_SHOW_LINES: bool = False +STYLE_COMMANDS_TABLE_LEADING: int = 0 +STYLE_COMMANDS_TABLE_PAD_EDGE: bool = False +STYLE_COMMANDS_TABLE_PADDING: rich.padding.PaddingDimensions = (0, 1) +STYLE_COMMANDS_TABLE_BOX: rich.style.StyleType = "" +STYLE_COMMANDS_TABLE_ROW_STYLES: Optional[List[rich.style.StyleType]] = None +STYLE_COMMANDS_TABLE_BORDER_STYLE: Optional[rich.style.StyleType] = None +STYLE_COMMANDS_TABLE_COLUMN_WIDTH_RATIO: Optional[Union[Tuple[None, None], Tuple[int, int]]] = (None, None) +STYLE_ERRORS_PANEL_BORDER: rich.style.StyleType = "red" +ALIGN_ERRORS_PANEL: rich.align.AlignMethod = "left" +STYLE_ERRORS_SUGGESTION: rich.style.StyleType = "dim" +STYLE_ERRORS_SUGGESTION_COMMAND: rich.style.StyleType = "blue" +STYLE_ABORTED: rich.style.StyleType = "red" +WIDTH: Optional[int] = terminal_width_default() +MAX_WIDTH: Optional[int] = terminal_width_default() +COLOR_SYSTEM: Optional[ + Literal["auto", "standard", "256", "truecolor", "windows"] +] = "auto" # Set to None to disable colors +FORCE_TERMINAL: Optional[bool] = force_terminal_default() + +# Fixed strings +HEADER_TEXT: Optional[str] = None +FOOTER_TEXT: Optional[str] = None +DEPRECATED_STRING: str = "(Deprecated) " +DEFAULT_STRING: str = "[default: {}]" +ENVVAR_STRING: str = "[env var: {}]" +REQUIRED_SHORT_STRING: str = "*" +REQUIRED_LONG_STRING: str = "[required]" +RANGE_STRING: str = " [{}]" +APPEND_METAVARS_HELP_STRING: str = "({})" +ARGUMENTS_PANEL_TITLE: str = "Arguments" +OPTIONS_PANEL_TITLE: str = "Options" +COMMANDS_PANEL_TITLE: str = "Commands" +ERRORS_PANEL_TITLE: str = "Error" +ERRORS_SUGGESTION: Optional[str] = None # Default: Try 'cmd -h' for help. Set to False to disable. +ERRORS_EPILOGUE: Optional[str] = None +ABORTED_TEXT: str = "Aborted." + +# Behaviours +SHOW_ARGUMENTS: bool = False # Show positional arguments +SHOW_METAVARS_COLUMN: bool = True # Show a column with the option metavar (eg. INTEGER) +APPEND_METAVARS_HELP: bool = False # Append metavar (eg. [TEXT]) after the help text +GROUP_ARGUMENTS_OPTIONS: bool = False # Show arguments with options instead of in own panel +OPTION_ENVVAR_FIRST: bool = False # Show env vars before option help text instead of avert +USE_MARKDOWN: bool = False # Parse help strings as markdown +USE_MARKDOWN_EMOJI: bool = True # Parse emoji codes in markdown :smile: +USE_RICH_MARKUP: bool = False # Parse help strings for rich markup (eg. [red]my text[/]) +# Define sorted groups of panels to display subcommands +COMMAND_GROUPS: Dict[str, List[Dict[str, Union[str, List[str]]]]] = {} +# Define sorted groups of panels to display options and arguments +OPTION_GROUPS: Dict[str, List[Dict[str, Union[str, List[str], Dict[str, List[str]]]]]] = {} +USE_CLICK_SHORT_HELP: bool = False # Use click's default function to truncate help text + +highlighter: rich.highlighter.Highlighter = OptionHighlighter() +_formatter: Optional[RichHelpFormatter] = None + + +def _get_rich_formatter(formatter: Optional[click.HelpFormatter] = None) -> RichHelpFormatter: + """Get Rich Help Formatter. + + Resolves the rich help formatter from the following: + - formatter, if exists and is a `RichHelpFormatter` object + - cached, module-level formatter + - active click Context, that is cached as module-level attr + - module-level settings (default) + + Args: + formatter: A possible Rich help formatter + """ + if formatter and isinstance(formatter, RichHelpFormatter): + return formatter + + global _formatter + if _formatter: + return _formatter + ctx = click.get_current_context(True) + if ctx: + formatter = ctx.make_formatter() + if isinstance(formatter, RichHelpFormatter): + _formatter = formatter + return _formatter + + _formatter = RichHelpFormatter(config=get_module_help_configuration()) + return _formatter + + +def _make_rich_rext( + text: str, style: StyleType = "", formatter: Optional[RichHelpFormatter] = None +) -> Union[rich.markdown.Markdown, Text]: + """Take a string, remove indentations, and return styled text. + + By default, return the text as a Rich Text with the request style. + If config.use_rich_markup is True, also parse the text for Rich markup strings. + If config.use_markdown is True, parse as Markdown. + + Only one of config.use_markdown or config.use_rich_markup can be True. + If both are True, config.use_markdown takes precedence. + + Args: + text (str): Text to style + style (str): Rich style to apply + + Returns: + MarkdownElement or Text: Styled text object + """ + formatter = _get_rich_formatter(formatter) + config = formatter.config + # Remove indentations from input text + text = inspect.cleandoc(text) + if config.use_markdown: + if config.use_markdown_emoji: + text = Emoji.replace(text) + return Markdown(text, style=style) + if config.use_rich_markup: + return config.highlighter(Text.from_markup(text, style=style)) + else: + return config.highlighter(Text(text, style=style)) + + +@group() +def _get_help_text( + obj: Union[Command, Group], formatter: Optional[RichHelpFormatter] = None +) -> Iterable[Union[rich.markdown.Markdown, rich.text.Text]]: + """Build primary help text for a click command or group. + + Returns the prose help text for a command or group, rendered either as a + Rich Text object or as Markdown. + If the command is marked as depreciated, the depreciated string will be prepended. + + Args: + obj (click.Command or click.Group): Command or group to build help text for + + Yields: + Text or Markdown: Multiple styled objects (depreciated, usage) + """ + if TYPE_CHECKING: + assert isinstance(obj.help, str) + formatter = _get_rich_formatter(formatter) + config = formatter.config + # Prepend deprecated status + if obj.deprecated: + yield Text(config.deprecated_string, style=config.style_deprecated) + + # Fetch and dedent the help text + help_text = inspect.cleandoc(obj.help) + + # Trim off anything that comes after \f on its own line + help_text = help_text.partition("\f")[0] + + # Get the first paragraph + first_line = help_text.split("\n\n")[0] + # Remove single linebreaks + if not config.use_markdown and not first_line.startswith("\b"): + first_line = first_line.replace("\n", " ") + yield _make_rich_rext(first_line.strip(), config.style_helptext_first_line, formatter) + + # Get remaining lines, remove single line breaks and format as dim + remaining_paragraphs = help_text.split("\n\n")[1:] + if len(remaining_paragraphs) > 0: + if not config.use_markdown: + # Remove single linebreaks + remaining_paragraphs = [ + x.replace("\n", " ").strip() if not x.startswith("\b") else "{}\n".format(x.strip("\b\n")) + for x in remaining_paragraphs + ] + # Join back together + remaining_lines = "\n".join(remaining_paragraphs) + else: + # Join with double linebreaks if markdown + remaining_lines = "\n\n".join(remaining_paragraphs) + + yield _make_rich_rext(remaining_lines, config.style_helptext, formatter) + + +def _get_option_help( + param: Union[click.Argument, click.Option], ctx: click.Context, formatter: Optional[RichHelpFormatter] = None +) -> rich.columns.Columns: + """Build primary help text for a click option or argument. + + Returns the prose help text for an option or argument, rendered either + as a Rich Text object or as Markdown. + Additional elements are appended to show the default and required status if applicable. + + Args: + param (click.Argument or click.Option): Parameter to build help text for + ctx (click.Context): Click Context object + + Returns: + Columns: A columns element with multiple styled objects (help, default, required) + """ + formatter = _get_rich_formatter(formatter) + config = formatter.config + items: List[rich.console.RenderableType] = [] + + if TYPE_CHECKING: + assert isinstance(param.name, str) + + # Get the environment variable first + envvar = getattr(param, "envvar", None) + # https://github.com/pallets/click/blob/0aec1168ac591e159baf6f61026d6ae322c53aaf/src/click/core.py#L2720-L2726 + if envvar is None: + if ( + getattr(param, "allow_from_autoenv", None) + and getattr(ctx, "auto_envvar_prefix", None) is not None + and param.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{param.name.upper()}" + if envvar is not None: + envvar = ", ".join(envvar) if type(envvar) is list else envvar + + # Environment variable config.before help text + if getattr(param, "show_envvar", None) and config.option_envvar_first and envvar is not None: + items.append(Text(config.envvar_string.format(envvar), style=config.style_option_envvar)) + + # Main help text + if getattr(param, "help", None): + if TYPE_CHECKING: + assert isinstance(param, click.Option) + assert hasattr(param, "help") + assert isinstance(param.help, str) + paragraphs = param.help.split("\n\n") + # Remove single linebreaks + if not config.use_markdown: + paragraphs = [ + x.replace("\n", " ").strip() if not x.startswith("\b") else "{}\n".format(x.strip("\b\n")) + for x in paragraphs + ] + items.append(_make_rich_rext("\n".join(paragraphs).strip(), config.style_option_help, formatter)) + + # Append metavar if requested + if config.append_metavars_help: + metavar_str = param.make_metavar() + # Do it ourselves if this is a positional argument + if isinstance(param, click.core.Argument) and re.match(rf"\[?{param.name.upper()}]?", metavar_str): + metavar_str = param.type.name.upper() + # Attach metavar if param is a positional argument, or if it is a non boolean and non flag option + if isinstance(param, click.core.Argument) or (metavar_str != "BOOLEAN" and not param.is_flag): + metavar_str = metavar_str.replace("[", "").replace("]", "") + items.append( + Text( + config.append_metavars_help_string.format(metavar_str), + style=config.style_metavar_append, + overflow="fold", + ) + ) + + # Environment variable config.after help text + if getattr(param, "show_envvar", None) and not config.option_envvar_first and envvar is not None: + items.append(Text(config.envvar_string.format(envvar), style=config.style_option_envvar)) + + # Default value + # Click 7.x, 8.0, and 8.1 all behave slightly differently when handling the default value help text. + if not hasattr(param, "show_default"): + parse_default = False + elif CLICK_IS_BEFORE_VERSION_8X: + parse_default = bool(param.default is not None and (param.show_default or getattr(ctx, "show_default", None))) + elif CLICK_IS_VERSION_80: + show_default_is_str = isinstance(param.show_default, str) + parse_default = bool( + show_default_is_str or (param.default is not None and (param.show_default or ctx.show_default)) + ) + else: + show_default_is_str = False + if param.show_default is not None: + if isinstance(param.show_default, str): + show_default_is_str = show_default = True + else: + show_default = bool(param.show_default) + else: + show_default = bool(getattr(ctx, "show_default", False)) + parse_default = bool(show_default_is_str or (show_default and (param.default is not None))) + + if parse_default: + help_record = param.get_help_record(ctx) + if TYPE_CHECKING: + assert isinstance(help_record, tuple) + default_str_match = re.search(r"\[(?:.+; )?default: (.*)\]", help_record[-1]) + if default_str_match: + # Don't show the required string, as we show that afterwards anyway + default_str = default_str_match.group(1).replace("; required", "") + items.append( + Text( + config.default_string.format(default_str), + style=config.style_option_default, + ) + ) + + # Required? + if param.required: + items.append(Text(config.required_long_string, style=config.style_required_long)) + + # Use Columns - this allows us to group different renderable types + # (Text, Markdown) onto a single line. + return Columns(items) + + +def _make_command_help( + help_text: str, formatter: Optional[RichHelpFormatter] = None +) -> Union[rich.text.Text, rich.markdown.Markdown]: + """Build cli help text for a click group command. + + That is, when calling help on groups with multiple subcommands + (not the main help text when calling the subcommand help). + + Returns the first paragraph of help text for a command, rendered either as a + Rich Text object or as Markdown. + Ignores single newlines as paragraph markers, looks for double only. + + Args: + help_text (str): Help text + + Returns: + Text or Markdown: Styled object + """ + formatter = _get_rich_formatter(formatter) + config = formatter.config + paragraphs = inspect.cleandoc(help_text).split("\n\n") + # Remove single linebreaks + if not config.use_markdown and not paragraphs[0].startswith("\b"): + paragraphs[0] = paragraphs[0].replace("\n", " ") + elif paragraphs[0].startswith("\b"): + paragraphs[0] = paragraphs[0].replace("\b\n", "") + + return _make_rich_rext(paragraphs[0].strip(), config.style_option_help, formatter) + + +def get_rich_usage( + obj: Union[Command, Group], + ctx: click.Context, + formatter: click.HelpFormatter, +) -> None: + """Get usage text for a command.""" + formatter = _get_rich_formatter(formatter) + config = formatter.config + console = formatter.console + + # Highlighter for options and arguments + class UsageHighlighter(RegexHighlighter): + highlights = [ + r"(?P<argument>\w+)", + ] + + usage_highlighter = UsageHighlighter() + + # Print usage + console.print( + Padding( + Columns( + ( + Text("Usage:", style=config.style_usage), + Text(ctx.command_path, style=config.style_usage_command), + usage_highlighter(" ".join(obj.collect_usage_pieces(ctx))), + ) + ), + 1, + ), + ) + + +def rich_format_help( + obj: Command, + ctx: click.Context, + formatter: click.HelpFormatter, +) -> None: + """Print nicely formatted help text using rich. + + Based on original code from rich-cli, by @willmcgugan. + https://github.com/Textualize/rich-cli/blob/8a2767c7a340715fc6fbf4930ace717b9b2fc5e5/src/rich_cli/__main__.py#L162-L236 + + Replacement for the click function format_help(). + Takes a command or group and builds the help text output. + + Args: + obj (click.Command or click.Group): Command or group to build help text for + ctx (click.Context): Click Context object + formatter (click.HelpFormatter): Click HelpFormatter object + """ + formatter = _get_rich_formatter(formatter) + config = formatter.config + console = formatter.console + highlighter = formatter.config.highlighter + + # Header text if we have it + if config.header_text: + console.print( + Padding( + _make_rich_rext(config.header_text, config.style_header_text, formatter), + (1, 1, 0, 1), + ), + ) + + # Print usage + get_rich_usage(obj, ctx, formatter) + + # Print command / group help if we have some + if obj.help: + # Print with some padding + console.print( + Padding( + Align(_get_help_text(obj, formatter), pad=False), + (0, 1, 1, 1), + ) + ) + + # Look through config.option_groups for this command + # stick anything unmatched into a default group at the end + option_groups = config.option_groups.get(ctx.command_path, []).copy() + option_groups.append({"options": []}) + argument_group_options = [] + + for param in obj.get_params(ctx): + # 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 isinstance(param, click.core.Argument) and not config.show_arguments: + continue + + # Skip if option is hidden + if getattr(param, "hidden", False): + continue + + # Already mentioned in a config option group + for option_group in option_groups: + if any([opt in option_group.get("options", []) for opt in param.opts]): + break + + # No break, no mention - add to the default group + else: + if isinstance(param, click.core.Argument) and not config.group_arguments_options: + argument_group_options.append(param.opts[0]) + else: + list_of_option_groups: List[str] = option_groups[-1]["options"] # type: ignore[assignment] + list_of_option_groups.append(param.opts[0]) + + # If we're not grouping arguments and we got some, prepend before default options + if len(argument_group_options) > 0: + extra_option_group = {"name": config.arguments_panel_title, "options": argument_group_options} + option_groups.insert(len(option_groups) - 1, extra_option_group) + + # print("!", option_groups) + + # Print each option group panel + for option_group in option_groups: + options_rows = [] + for opt in option_group.get("options", []): + # Get the param + for param in obj.get_params(ctx): + if any([opt in param.opts]): + break + # Skip if option is not listed in this group + else: + continue + + # Short and long form + opt_long_strs = [] + opt_short_strs = [] + for idx, opt in enumerate(param.opts): + opt_str = opt + try: + opt_str += "/" + param.secondary_opts[idx] + except IndexError: + pass + + if isinstance(param, click.core.Argument): + opt_long_strs.append(opt_str.upper()) + elif "--" in opt: + opt_long_strs.append(opt_str) + else: + opt_short_strs.append(opt_str) + + # Column for a metavar, if we have one + metavar = Text(style=config.style_metavar, overflow="fold") + metavar_str = param.make_metavar() + + if TYPE_CHECKING: + assert isinstance(param.name, str) + assert isinstance(param, click.Option) + + # Do it ourselves if this is a positional argument + if isinstance(param, click.core.Argument) and re.match(rf"\[?{param.name.upper()}]?", metavar_str): + metavar_str = param.type.name.upper() + + # Attach metavar if param is a positional argument, or if it is a non boolean and non flag option + if isinstance(param, click.core.Argument) or ( + metavar_str != "BOOLEAN" and not getattr(param, "is_flag", None) + ): + metavar.append(metavar_str) + + # Range - from + # https://github.com/pallets/click/blob/c63c70dabd3f86ca68678b4f00951f78f52d0270/src/click/core.py#L2698-L2706 # noqa: E501 + try: + # skip count with default range type + if isinstance(param.type, click.types._NumberRangeBase) and not ( + param.count and param.type.min == 0 and param.type.max is None + ): + range_str = param.type._describe_range() + if range_str: + metavar.append(config.range_string.format(range_str)) + except AttributeError: + # click.types._NumberRangeBase is only in Click 8x onwards + pass + + # Required asterisk + required: Union[Text, str] = "" + if param.required: + required = Text(config.required_short_string, style=config.style_required_short) + + # Highlighter to make [ | ] and <> dim + class MetavarHighlighter(RegexHighlighter): + highlights = [ + r"^(?P<metavar_sep>(\[|<))", + r"(?P<metavar_sep>\|)", + r"(?P<metavar_sep>(\]|>)$)", + ] + + metavar_highlighter = MetavarHighlighter() + + rows = [ + required, + highlighter(highlighter(",".join(opt_long_strs))), + highlighter(highlighter(",".join(opt_short_strs))), + metavar_highlighter(metavar), + _get_option_help(param, ctx, formatter), + ] + + # Remove metavar if specified in config + if not config.show_metavars_column: + rows.pop(3) + + options_rows.append(rows) + + if len(options_rows) > 0: + t_styles = { + "show_lines": config.style_options_table_show_lines, + "leading": config.style_options_table_leading, + "box": config.style_options_table_box, + "border_style": config.style_options_table_border_style, + "row_styles": config.style_options_table_row_styles, + "pad_edge": config.style_options_table_pad_edge, + "padding": config.style_options_table_padding, + } + t_styles.update(option_group.get("table_styles", {})) # type: ignore[arg-type] + box_style = getattr(box, t_styles.pop("box"), None) # type: ignore[arg-type] + + options_table = Table( + highlight=True, + show_header=False, + expand=True, + box=box_style, + **t_styles, # type: ignore[arg-type] + ) + # Strip the required column if none are required + if all([x[0] == "" for x in options_rows]): + options_rows = [x[1:] for x in options_rows] + for row in options_rows: + options_table.add_row(*row) + console.print( + Panel( + options_table, + border_style=config.style_options_panel_border, + title=option_group.get("name", config.options_panel_title), + title_align=config.align_options_panel, + ) + ) + + # + # Groups only: + # List click command groups + # + + if isinstance(obj, MultiCommand): + # Look through COMMAND_GROUPS for this command + # stick anything unmatched into a default group at the end + cmd_groups = config.command_groups.get(ctx.command_path, []).copy() + cmd_groups.append({"commands": []}) + for command in obj.list_commands(ctx): + for cmd_group in cmd_groups: + if command in cmd_group.get("commands", []): + break + else: + commands: List[str] = cmd_groups[-1]["commands"] # type: ignore[assignment] + commands.append(command) + + # Print each command group panel + for cmd_group in cmd_groups: + t_styles = { + "show_lines": config.style_commands_table_show_lines, + "leading": config.style_commands_table_leading, + "box": config.style_commands_table_box, + "border_style": config.style_commands_table_border_style, + "row_styles": config.style_commands_table_row_styles, + "pad_edge": config.style_commands_table_pad_edge, + "padding": config.style_commands_table_padding, + } + t_styles.update(cmd_group.get("table_styles", {})) # type: ignore[arg-type] + box_style = getattr(box, t_styles.pop("box"), None) # type: ignore[arg-type] + + commands_table = Table( + highlight=False, + show_header=False, + expand=True, + box=box_style, + **t_styles, # type: ignore[arg-type] + ) + # Define formatting in first column, as commands don't match highlighter regex + # and set column ratio for first and second column, if a ratio has been set + if config.style_commands_table_column_width_ratio is None: + table_column_width_ratio: Union[Tuple[None, None], Tuple[int, int]] = (None, None) + else: + table_column_width_ratio = config.style_commands_table_column_width_ratio + + commands_table.add_column(style=config.style_command, no_wrap=True, ratio=table_column_width_ratio[0]) + commands_table.add_column( + no_wrap=False, + ratio=table_column_width_ratio[1], + ) + for command in cmd_group.get("commands", []): + # Skip if command does not exist + if command not in obj.list_commands(ctx): + continue + cmd = obj.get_command(ctx, command) + if TYPE_CHECKING: + assert cmd is not None + if cmd.hidden: + continue + # Use the truncated short text as with vanilla text if requested + if config.use_click_short_help: + helptext = cmd.get_short_help_str() + else: + # Use short_help function argument if used, or the full help + helptext = cmd.short_help or cmd.help or "" + commands_table.add_row(command, _make_command_help(helptext, formatter)) + if commands_table.row_count > 0: + console.print( + Panel( + commands_table, + border_style=config.style_commands_panel_border, + title=cmd_group.get("name", config.commands_panel_title), + title_align=config.align_commands_panel, + ) + ) + + # Epilogue if we have it + if isinstance(obj, Command) and obj.epilog: + # Remove single linebreaks, replace double with single + lines = obj.epilog.split("\n\n") + epilogue = "\n".join([x.replace("\n", " ").strip() for x in lines]) + console.print(Padding(Align(_make_rich_rext(epilogue, config.style_epilog_text, formatter), pad=False), 1)) + + # Footer text if we have it + if config.footer_text: + console.print(Padding(_make_rich_rext(config.footer_text, config.style_footer_text, formatter), (1, 1, 0, 1))) + + +def rich_format_error(self: click.ClickException, formatter: Optional[RichHelpFormatter] = None) -> None: + """Print richly formatted click errors. + + Called by custom exception handler to print richly formatted click errors. + Mimics original click.ClickException.echo() function but with rich formatting. + + Args: + click.ClickException: Click exception to format. + """ + formatter = _get_rich_formatter(formatter) + console = formatter.console + config = formatter.config + highlighter = formatter.config.highlighter + # Print usage + if getattr(self, "ctx", None) is not None: + if TYPE_CHECKING: + assert hasattr(self, "ctx") + get_rich_usage(self.ctx.command, self.ctx, formatter) + if config.errors_suggestion: + console.print( + Padding( + config.errors_suggestion, + (0, 1, 0, 1), + ), + style=config.style_errors_suggestion, + ) + elif ( + config.errors_suggestion is None + and getattr(self, "ctx", None) is not None + and self.ctx.command.get_help_option(self.ctx) is not None # type: ignore[attr-defined] + ): + cmd_path = self.ctx.command_path # type: ignore[attr-defined] + help_option = self.ctx.help_option_names[0] # type: ignore[attr-defined] + console.print( + Padding( + Columns( + ( + Text("Try"), + Text(f"'{cmd_path} {help_option}'", style=config.style_errors_suggestion_command), + Text("for help"), + ) + ), + (0, 1, 0, 1), + ), + style=config.style_errors_suggestion, + ) + + # A major Python library using click (dbt-core) has its own exception + # logic that subclasses ClickException, but does not use the message + # attribute. Checking for the 'message' attribute works to make the + # rich-click CLI compatible. + if hasattr(self, "message"): + console.print( + Padding( + Panel( + highlighter(self.format_message()), + border_style=config.style_errors_panel_border, + title=config.errors_panel_title, + title_align=config.align_errors_panel, + ), + (0, 0, 1, 0), + ) + ) + if config.errors_epilogue: + console.print(Padding(config.errors_epilogue, (0, 1, 1, 1))) + + +def rich_abort_error(formatter: Optional[RichHelpFormatter] = None) -> None: + """Print richly formatted abort error.""" + formatter = _get_rich_formatter(formatter) + config = formatter.config + formatter.console.print(config.aborted_text, style=config.style_aborted) + + +module_config: Optional[RichHelpConfiguration] = None + + +def get_module_help_configuration() -> RichHelpConfiguration: + """Get Module-level help configuration resolved into a `RichHelpConfiguration` instance.""" + global module_config + if module_config: + return module_config + module_config = RichHelpConfiguration( + STYLE_OPTION, + STYLE_ARGUMENT, + STYLE_COMMAND, + STYLE_SWITCH, + STYLE_METAVAR, + STYLE_METAVAR_APPEND, + STYLE_METAVAR_SEPARATOR, + STYLE_HEADER_TEXT, + STYLE_EPILOG_TEXT, + STYLE_FOOTER_TEXT, + STYLE_USAGE, + STYLE_USAGE_COMMAND, + STYLE_DEPRECATED, + STYLE_HELPTEXT_FIRST_LINE, + STYLE_HELPTEXT, + STYLE_OPTION_HELP, + STYLE_OPTION_DEFAULT, + STYLE_OPTION_ENVVAR, + STYLE_REQUIRED_SHORT, + STYLE_REQUIRED_LONG, + STYLE_OPTIONS_PANEL_BORDER, + ALIGN_OPTIONS_PANEL, + STYLE_OPTIONS_TABLE_SHOW_LINES, + STYLE_OPTIONS_TABLE_LEADING, + STYLE_OPTIONS_TABLE_PAD_EDGE, + STYLE_OPTIONS_TABLE_PADDING, + STYLE_OPTIONS_TABLE_BOX, + STYLE_OPTIONS_TABLE_ROW_STYLES, + STYLE_OPTIONS_TABLE_BORDER_STYLE, + STYLE_COMMANDS_PANEL_BORDER, + ALIGN_COMMANDS_PANEL, + STYLE_COMMANDS_TABLE_SHOW_LINES, + STYLE_COMMANDS_TABLE_LEADING, + STYLE_COMMANDS_TABLE_PAD_EDGE, + STYLE_COMMANDS_TABLE_PADDING, + STYLE_COMMANDS_TABLE_BOX, + STYLE_COMMANDS_TABLE_ROW_STYLES, + STYLE_COMMANDS_TABLE_BORDER_STYLE, + STYLE_COMMANDS_TABLE_COLUMN_WIDTH_RATIO, + STYLE_ERRORS_PANEL_BORDER, + ALIGN_ERRORS_PANEL, + STYLE_ERRORS_SUGGESTION, + STYLE_ERRORS_SUGGESTION_COMMAND, + STYLE_ABORTED, + WIDTH, + MAX_WIDTH, + COLOR_SYSTEM, + FORCE_TERMINAL, + HEADER_TEXT, + FOOTER_TEXT, + DEPRECATED_STRING, + DEFAULT_STRING, + ENVVAR_STRING, + REQUIRED_SHORT_STRING, + REQUIRED_LONG_STRING, + RANGE_STRING, + APPEND_METAVARS_HELP_STRING, + ARGUMENTS_PANEL_TITLE, + OPTIONS_PANEL_TITLE, + COMMANDS_PANEL_TITLE, + ERRORS_PANEL_TITLE, + ERRORS_SUGGESTION, + ERRORS_EPILOGUE, + ABORTED_TEXT, + SHOW_ARGUMENTS, + SHOW_METAVARS_COLUMN, + APPEND_METAVARS_HELP, + GROUP_ARGUMENTS_OPTIONS, + OPTION_ENVVAR_FIRST, + USE_MARKDOWN, + USE_MARKDOWN_EMOJI, + USE_RICH_MARKUP, + COMMAND_GROUPS, + OPTION_GROUPS, + USE_CLICK_SHORT_HELP, + highlighter, + ) + return module_config diff --git a/venv/lib/python3.11/site-packages/rich_click/rich_command.py b/venv/lib/python3.11/site-packages/rich_click/rich_command.py new file mode 100644 index 0000000..eef4eda --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/rich_command.py @@ -0,0 +1,257 @@ +import errno +import os +import sys +import warnings +from functools import wraps +from typing import Any, Callable, cast, Optional, overload, Sequence, TextIO, Type, TYPE_CHECKING, Union + +import click + +# Group, Command, and CommandCollection need to be imported directly, +# or else rich_click.cli.patch() causes a recursion error. +from click import Command, CommandCollection, Group +from click.utils import make_str, PacifyFlushWrapper +from rich.console import Console + +from rich_click._compat_click import CLICK_IS_BEFORE_VERSION_8X, CLICK_IS_BEFORE_VERSION_9X +from rich_click.rich_click import rich_abort_error, rich_format_error, rich_format_help +from rich_click.rich_context import RichContext +from rich_click.rich_help_configuration import RichHelpConfiguration +from rich_click.rich_help_formatter import RichHelpFormatter + + +class RichCommand(click.Command): + """Richly formatted click Command. + + Inherits click.Command and overrides help and error methods + to print richly formatted output. + + This class can be used as a mixin for other click command objects. + """ + + context_class: Type[RichContext] = RichContext + _formatter: Optional[RichHelpFormatter] = None + + @wraps(Command.__init__) + def __init__(self, *args: Any, **kwargs: Any): + """Create Rich Command instance.""" + super().__init__(*args, **kwargs) + self._register_rich_context_settings_from_callback() + + def _register_rich_context_settings_from_callback(self) -> None: + if self.callback is not None: + if hasattr(self.callback, "__rich_context_settings__"): + rich_context_settings = getattr(self.callback, "__rich_context_settings__", {}) + for k, v in rich_context_settings.items(): + self.context_settings.setdefault(k, v) + del self.callback.__rich_context_settings__ + + @property + def console(self) -> Optional[Console]: + """Rich Console. + + This is a separate instance from the help formatter that allows full control of the + console configuration. + + See `rich_config` decorator for how to apply the settings. + """ + return self.context_settings.get("rich_console") + + @property + def help_config(self) -> Optional[RichHelpConfiguration]: + """Rich Help Configuration.""" + return self.context_settings.get("rich_help_config") + + @property + def formatter(self) -> RichHelpFormatter: + """Rich Help Formatter. + + This is separate instance from the formatter used to display help, + but is created from the same `RichHelpConfiguration`. Currently only used + for error reporting. + """ + if self._formatter is None: + self._formatter = RichHelpFormatter(config=self.help_config) + return self._formatter + + def main( + self, + args: Optional[Sequence[str]] = None, + prog_name: Optional[str] = None, + complete_var: Optional[str] = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: Any, + ) -> Any: + # It's not feasible to use super().main() in this context and retain exact parity in behavior. + # The reason why is explained in a comment in click's source code in the "except Exit as e" block. + + if args is None: + if CLICK_IS_BEFORE_VERSION_8X: + from click.utils import get_os_args # type: ignore[attr-defined] + + args: Sequence[str] = get_os_args() # type: ignore[no-redef] + else: + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + from click.utils import _expand_args + + args = _expand_args(args) + else: + args = list(args) + + if TYPE_CHECKING: + assert args is not None + + if prog_name is None: + if CLICK_IS_BEFORE_VERSION_8X: + prog_name = make_str(os.path.basename(sys.argv[0] if sys.argv else __file__)) + else: + from click.utils import _detect_program_name + + prog_name = _detect_program_name() + + # Process shell completion requests and exit early. + if CLICK_IS_BEFORE_VERSION_8X: + from click.core import _bashcomplete # type: ignore[attr-defined] + + _bashcomplete(self, prog_name, complete_var) + else: + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt): + click.echo(file=sys.stderr) + raise click.exceptions.Abort() from None + except click.exceptions.ClickException as e: + rich_format_error(e, self.formatter) + if not standalone_mode: + raise + sys.stderr.write(self.formatter.getvalue()) + sys.exit(e.exit_code) + except OSError as e: + if e.errno == errno.EPIPE: + sys.stdout = cast(TextIO, PacifyFlushWrapper(sys.stdout)) + sys.stderr = cast(TextIO, PacifyFlushWrapper(sys.stderr)) + sys.exit(1) + else: + raise + except click.exceptions.Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + return e.exit_code + except click.exceptions.Abort: + rich_abort_error(self.formatter) + if not standalone_mode: + raise + sys.stderr.write(self.formatter.getvalue()) + sys.exit(1) + + def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: + rich_format_help(self, ctx, formatter) + + +class RichGroup(RichCommand, Group): + """Richly formatted click Group. + + Inherits click.Group and overrides help and error methods + to print richly formatted output. + """ + + command_class: Optional[Type[RichCommand]] = RichCommand + group_class: Optional[Union[Type[Group], Type[type]]] = type + + @wraps(Group.__init__) + def __init__(self, *args: Any, **kwargs: Any) -> None: + """Initialize RichGroup class.""" + Group.__init__(self, *args, **kwargs) + self._register_rich_context_settings_from_callback() + + if CLICK_IS_BEFORE_VERSION_8X: + + @overload + def command(self, __func: Callable[..., Any]) -> Command: + ... + + @overload + def command(self, *args: Any, **kwargs: Any) -> Callable[[Callable[..., Any]], Command]: + ... + + def command(self, *args: Any, **kwargs: Any) -> Union[Callable[[Callable[..., Any]], Command], Command]: + # This method override is required for Click 7.x compatibility. + # (The command_class ClassVar was not added until 8.0.) + if self.command_class is not None: + kwargs.setdefault("cls", self.command_class) + return super().command(*args, **kwargs) # type: ignore[no-any-return] + + @overload + def group(self, __func: Callable[..., Any]) -> Group: + ... + + @overload + def group(self, *args: Any, **kwargs: Any) -> Callable[[Callable[..., Any]], Group]: + ... + + def group(self, *args: Any, **kwargs: Any) -> Union[Callable[[Callable[..., Any]], Group], Group]: + # This method override is required for Click 7.x compatibility. + # (The group_class ClassVar was not added until 8.0.) + if self.group_class is type: + kwargs.setdefault("cls", self.__class__) + elif self.group_class is not None: + kwargs.setdefault("cls", self.group_class) + return super().group(*args, **kwargs) # type: ignore[no-any-return] + + +if CLICK_IS_BEFORE_VERSION_9X: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning, module="click") + from click import MultiCommand + + class RichMultiCommand(RichCommand, MultiCommand): + """Richly formatted click MultiCommand. + + Inherits click.MultiCommand and overrides help and error methods + to print richly formatted output. + """ + + @wraps(MultiCommand.__init__) + def __init__(self, *args: Any, **kwargs: Any) -> None: # type: ignore[no-untyped-def] + """Initialize RichGroup class.""" + from click import MultiCommand + + MultiCommand.__init__(self, *args, **kwargs) + self._register_rich_context_settings_from_callback() + +else: + + class RichMultiCommand(RichGroup): # type: ignore[no-redef] + """This class is deprecated.""" + + +class RichCommandCollection(RichGroup, CommandCollection): + """Richly formatted click CommandCollection. + + Inherits click.CommandCollection and overrides help and error methods + to print richly formatted output. + """ + + @wraps(CommandCollection.__init__) + def __init__(self, *args: Any, **kwargs: Any) -> None: + """Initialize RichCommandCollection class.""" + CommandCollection.__init__(self, *args, **kwargs) + self._register_rich_context_settings_from_callback() diff --git a/venv/lib/python3.11/site-packages/rich_click/rich_context.py b/venv/lib/python3.11/site-packages/rich_click/rich_context.py new file mode 100644 index 0000000..8e5e0a2 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/rich_context.py @@ -0,0 +1,47 @@ +from typing import Any, Optional, Type + +import click +from rich.console import Console + +from rich_click.rich_help_configuration import RichHelpConfiguration +from rich_click.rich_help_formatter import RichHelpFormatter + + +class RichContext(click.Context): + """Click Context class endowed with Rich superpowers.""" + + formatter_class: Type[RichHelpFormatter] = RichHelpFormatter + + def __init__( + self, + *args: Any, + rich_console: Optional[Console] = None, + rich_help_config: Optional[RichHelpConfiguration] = None, + **kwargs: Any, + ) -> None: + """Create Rich Context instance. + + Args: + rich_console: Rich Console. + Defaults to None. + rich_help_config: Rich help configuration. + Defaults to None. + """ + super().__init__(*args, **kwargs) + parent: Optional[RichContext] = kwargs.pop("parent", None) + + if rich_console is None and hasattr(parent, "console"): + rich_console = parent.console # type: ignore[has-type,union-attr] + + self.console = rich_console + + if rich_help_config is None and hasattr(parent, "help_config"): + rich_help_config = parent.help_config # type: ignore[has-type,union-attr] + + self.help_config = rich_help_config + + def make_formatter(self) -> RichHelpFormatter: + """Create the Rich Help Formatter.""" + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width, config=self.help_config + ) diff --git a/venv/lib/python3.11/site-packages/rich_click/rich_group.py b/venv/lib/python3.11/site-packages/rich_click/rich_group.py new file mode 100644 index 0000000..659f2f2 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/rich_group.py @@ -0,0 +1,11 @@ +import warnings + +from rich_click.rich_command import RichGroup + +warnings.warn( + "RichCommand is moving from rich_click.rich_group to rich_click.rich_command in a future version.", + DeprecationWarning, +) + + +__all__ = ["RichGroup"] diff --git a/venv/lib/python3.11/site-packages/rich_click/rich_help_configuration.py b/venv/lib/python3.11/site-packages/rich_click/rich_help_configuration.py new file mode 100644 index 0000000..e011fe1 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/rich_help_configuration.py @@ -0,0 +1,148 @@ +import os +from dataclasses import dataclass, field +from os import getenv +from typing import Any, Dict, List, Optional, Tuple, Union + +import rich.align +import rich.highlighter +import rich.padding +import rich.style +import rich.table +from typing_extensions import Literal + +from rich_click.utils import truthy + + +def force_terminal_default() -> Optional[bool]: + """Use as the default factory for `force_terminal`.""" + env_vars = {"GITHUB_ACTIONS", "FORCE_COLOR", "PY_COLORS"} + if all(i not in os.environ for i in env_vars): + return None + else: + return any(truthy(getenv(i)) for i in env_vars) + + +def terminal_width_default() -> Optional[int]: + """Use as the default factory for `width` and `max_width`.""" + width = getenv("TERMINAL_WIDTH") + if width: + try: + return int(width) + except ValueError: + import warnings + + warnings.warn("Environment variable `TERMINAL_WIDTH` cannot be cast to an integer.", UserWarning) + return None + return None + + +class OptionHighlighter(rich.highlighter.RegexHighlighter): + """Highlights our special options.""" + + highlights = [ + r"(^|\W)(?P<switch>\-\w+)(?![a-zA-Z0-9])", + r"(^|\W)(?P<option>\-\-[\w\-]+)(?![a-zA-Z0-9])", + r"(?P<metavar>\<[^\>]+\>)", + ] + + +@dataclass +class RichHelpConfiguration: + """Rich Help Configuration Class.""" + + # Default styles + style_option: rich.style.StyleType = field(default="bold cyan") + style_argument: rich.style.StyleType = field(default="bold cyan") + style_command: rich.style.StyleType = field(default="bold cyan") + style_switch: rich.style.StyleType = field(default="bold green") + style_metavar: rich.style.StyleType = field(default="bold yellow") + style_metavar_append: rich.style.StyleType = field(default="dim yellow") + style_metavar_separator: rich.style.StyleType = field(default="dim") + style_header_text: rich.style.StyleType = field(default="") + style_epilog_text: rich.style.StyleType = field(default="") + style_footer_text: rich.style.StyleType = field(default="") + style_usage: rich.style.StyleType = field(default="yellow") + style_usage_command: rich.style.StyleType = field(default="bold") + style_deprecated: rich.style.StyleType = field(default="red") + style_helptext_first_line: rich.style.StyleType = field(default="") + style_helptext: rich.style.StyleType = field(default="dim") + style_option_help: rich.style.StyleType = field(default="") + style_option_default: rich.style.StyleType = field(default="dim") + style_option_envvar: rich.style.StyleType = field(default="dim yellow") + style_required_short: rich.style.StyleType = field(default="red") + style_required_long: rich.style.StyleType = field(default="dim red") + style_options_panel_border: rich.style.StyleType = field(default="dim") + align_options_panel: rich.align.AlignMethod = field(default="left") + style_options_table_show_lines: bool = field(default=False) + style_options_table_leading: int = field(default=0) + style_options_table_pad_edge: bool = field(default=False) + style_options_table_padding: rich.padding.PaddingDimensions = field(default_factory=lambda: (0, 1)) + style_options_table_box: rich.style.StyleType = field(default="") + style_options_table_row_styles: Optional[List[rich.style.StyleType]] = field(default=None) + style_options_table_border_style: Optional[rich.style.StyleType] = field(default=None) + style_commands_panel_border: rich.style.StyleType = field(default="dim") + align_commands_panel: rich.align.AlignMethod = field(default="left") + style_commands_table_show_lines: bool = field(default=False) + style_commands_table_leading: int = field(default=0) + style_commands_table_pad_edge: bool = field(default=False) + style_commands_table_padding: rich.padding.PaddingDimensions = field(default_factory=lambda: (0, 1)) + style_commands_table_box: rich.style.StyleType = field(default="") + style_commands_table_row_styles: Optional[List[rich.style.StyleType]] = field(default=None) + style_commands_table_border_style: Optional[rich.style.StyleType] = field(default=None) + style_commands_table_column_width_ratio: Optional[Union[Tuple[None, None], Tuple[int, int]]] = field( + default_factory=lambda: (None, None) + ) + style_errors_panel_border: rich.style.StyleType = field(default="red") + align_errors_panel: rich.align.AlignMethod = field(default="left") + style_errors_suggestion: rich.style.StyleType = field(default="dim") + style_errors_suggestion_command: rich.style.StyleType = field(default="blue") + style_aborted: rich.style.StyleType = field(default="red") + width: Optional[int] = field(default_factory=terminal_width_default) + max_width: Optional[int] = field(default_factory=terminal_width_default) + color_system: Optional[Literal["auto", "standard", "256", "truecolor", "windows"]] = field(default="auto") + force_terminal: Optional[bool] = field(default_factory=force_terminal_default) + + # Fixed strings + header_text: Optional[str] = field(default=None) + footer_text: Optional[str] = field(default=None) + deprecated_string: str = field(default="(Deprecated) ") + default_string: str = field(default="[default: {}]") + envvar_string: str = field(default="[env var: {}]") + required_short_string: str = field(default="*") + required_long_string: str = field(default="[required]") + range_string: str = field(default=" [{}]") + append_metavars_help_string: str = field(default="({})") + arguments_panel_title: str = field(default="Arguments") + options_panel_title: str = field(default="Options") + commands_panel_title: str = field(default="Commands") + errors_panel_title: str = field(default="Error") + errors_suggestion: Optional[str] = field(default=None) + """Defaults to Try 'cmd -h' for help. Set to False to disable.""" + errors_epilogue: Optional[str] = field(default=None) + aborted_text: str = field(default="Aborted.") + + # Behaviours + show_arguments: bool = field(default=False) + """Show positional arguments""" + show_metavars_column: bool = field(default=True) + """Show a column with the option metavar (eg. INTEGER)""" + append_metavars_help: bool = field(default=False) + """Append metavar (eg. [TEXT]) after the help text""" + group_arguments_options: bool = field(default=False) + """Show arguments with options instead of in own panel""" + option_envvar_first: bool = field(default=False) + """Show env vars before option help text instead of after""" + use_markdown: bool = field(default=False) + use_markdown_emoji: bool = field(default=True) + """Parse emoji codes in markdown :smile:""" + use_rich_markup: bool = field(default=False) + """Parse help strings for rich markup (eg. [red]my text[/])""" + command_groups: Dict[str, List[Dict[str, Union[str, Any]]]] = field(default_factory=lambda: {}) + """Define sorted groups of panels to display subcommands""" + option_groups: Dict[str, List[Dict[str, Union[str, Any]]]] = field(default_factory=lambda: {}) + """Define sorted groups of panels to display options and arguments""" + use_click_short_help: bool = field(default=False) + """Use click's default function to truncate help text""" + highlighter: rich.highlighter.Highlighter = field(default_factory=lambda: OptionHighlighter()) + """Rich regex highlighter for help highlighting""" + legacy_windows: Optional[bool] = field(default=None) diff --git a/venv/lib/python3.11/site-packages/rich_click/rich_help_formatter.py b/venv/lib/python3.11/site-packages/rich_click/rich_help_formatter.py new file mode 100644 index 0000000..b068ec5 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/rich_help_formatter.py @@ -0,0 +1,103 @@ +from typing import Any, IO, Optional + +import click +import rich +import rich.highlighter +import rich.markdown +import rich.padding +import rich.text +import rich.theme +from rich.console import Console + +from rich_click.rich_help_configuration import RichHelpConfiguration + + +def create_console(config: RichHelpConfiguration, file: Optional[IO[str]] = None) -> Console: + """Create a Rich Console configured from Rich Help Configuration. + + Args: + config: Rich Help Configuration instance + file: Optional IO stream to write Rich Console output + Defaults to None. + """ + console = Console( + theme=rich.theme.Theme( + { + "option": config.style_option, + "command": config.style_command, + "argument": config.style_argument, + "switch": config.style_switch, + "metavar": config.style_metavar, + "metavar_sep": config.style_metavar_separator, + "usage": config.style_usage, + } + ), + highlighter=config.highlighter, + color_system=config.color_system, + force_terminal=config.force_terminal, + file=file, + width=config.width, + legacy_windows=config.legacy_windows, + ) + if isinstance(config.max_width, int): + console.width = min(config.max_width, console.size.width) + return console + + +def get_module_config() -> RichHelpConfiguration: + """Get the module-level help configuration. + + A function-level import is used to avoid a circular dependency + between the formatter and formatter operations. + """ + from rich_click.rich_click import get_module_help_configuration + + return get_module_help_configuration() + + +class RichHelpFormatter(click.HelpFormatter): + """Rich Help Formatter. + + This class is a container for the help configuration and Rich Console that + are used internally by the help and error printing methods. + """ + + def __init__( + self, + indent_increment: int = 2, + width: Optional[int] = None, + max_width: Optional[int] = None, + *args: Any, + config: Optional[RichHelpConfiguration] = None, + **kwargs: Any, + ) -> None: + """Create Rich Help Formatter. + + Args: + config: Configuration. + Defaults to None. + """ + if config is not None: + # Rich config overrides width and max width if set. + width = config.width or width + max_width = config.max_width or max_width + super().__init__(indent_increment, width, max_width, *args, **kwargs) + self._config = config or get_module_config() + self._console = create_console(self._config) + + @property + def config(self) -> RichHelpConfiguration: + """Rich Help Configuration.""" + return self._config + + @property + def console(self) -> Console: + """Rich Console created from the help configuration. + + This console is meant only for use with the formatter and should + not be created directly + """ + return self._console + + def write(self, string: str) -> None: + return self._console.print(string) diff --git a/venv/lib/python3.11/site-packages/rich_click/utils.py b/venv/lib/python3.11/site-packages/rich_click/utils.py new file mode 100644 index 0000000..128f8b1 --- /dev/null +++ b/venv/lib/python3.11/site-packages/rich_click/utils.py @@ -0,0 +1,16 @@ +from typing import Any, Optional + + +def truthy(o: Any) -> Optional[bool]: + """Check if string or other obj is truthy.""" + if isinstance(o, str): + if o.lower() in {"y", "yes", "t", "true", "1"}: + return True + elif o.lower() in {"n", "no", "f", "false", "0"}: + return False + else: + return None + elif o is None: + return None + else: + return bool(o) |