summaryrefslogtreecommitdiff
path: root/venv/lib/python3.11/site-packages/rich_click/cli.py
blob: a7041a994526fe3a72e276868bb506935a5ad368 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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()