CLI & Logger - rich-color-ext
Author: Max Ludden
Description: Extend Rich with CSS color names and 3-digit hex parsing

Skip to content

# CLI & Logger

Command-line interface for rich-color-ext.

Provides a tiny CLI to inspect CSS colors and to (un)install the runtime patch that extends Rich's color parsing.

## `get_css_color_map()` `cached`

Return CSS color mapping with compatibility fallback.

Source code in `src/rich_color_ext/cli.py`

     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
    @lru_cache(maxsize=1024)
    def get_css_color_map() -> dict[str, str]:
        """Return CSS color mapping with compatibility fallback."""

        global _CSS_CACHE  # pylint: disable=global-statement
        if _CSS_CACHE is not None:
            return _CSS_CACHE

        # Prefer a package-level loader if present (newer layout exposes get_css_map
        # on the package itself).
        pkg_loader = getattr(_pkg, "get_css_map", None)
        if callable(pkg_loader):
            try:
                result = pkg_loader()
                if isinstance(result, dict):
                    _CSS_CACHE = {str(k).lower(): str(v) for k, v in result.items()}
                else:
                    _CSS_CACHE = {}
                return _CSS_CACHE
            except (TypeError, ValueError, AttributeError, KeyError):
                # fall through to other loaders
                pass
        css_mod: ModuleType | None
        try:
            css_mod = importlib.import_module("rich_color_ext._css_colors")
        except ModuleNotFoundError:  # pragma: no cover - legacy distribution fallback
            css_mod = None
        if css_mod is not None:
            loader = None
            for attr in ("get_css_color_map", "get_css_map"):
                candidate = getattr(css_mod, attr, None)
                if callable(candidate):
                    loader = candidate
                    break
            if loader is not None:
                result = loader()
                normalized: dict[str, str] = {}
                if isinstance(result, dict):
                    normalized = {str(k).lower(): str(v) for k, v in result.items()}
                else:
                    items = getattr(result, "items", None)
                    if callable(items):
                        for key, value in items():  # type: ignore[call-arg]
                            normalized[str(key).lower()] = str(value)
                _CSS_CACHE = normalized
                return _CSS_CACHE
        try:
            raw = (
                importlib.resources.files("rich_color_ext")
                .joinpath("colors.json")
                .read_text(encoding="utf-8")
            )
        except (FileNotFoundError, ModuleNotFoundError):
            _CSS_CACHE = {}
            return _CSS_CACHE
        try:
            data = json.loads(raw)
        except json.JSONDecodeError:
            _CSS_CACHE = {}
            return _CSS_CACHE
        if isinstance(data, dict):
            _CSS_CACHE = {str(k).lower(): str(v) for k, v in data.items()}
            return _CSS_CACHE
        if isinstance(data, list):
            mapped: dict[str, str] = {}
            for item in data:
                if isinstance(item, dict):
                    name = item.get("name") or item.get("color")
                    value = item.get("hex") or item.get("value")
                    if isinstance(name, str) and isinstance(value, str):
                        mapped[name.lower()] = value
            _CSS_CACHE = mapped
            return _CSS_CACHE
        _CSS_CACHE = {}
        return _CSS_CACHE

## `install_panel()`

Print installation message panel.

Source code in `src/rich_color_ext/cli.py`

    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    def install_panel() -> Panel:
        """Print installation message panel."""
        return Panel(
            "rich-color-ext [b i #99ff00]installed![/]",
            title="[#ffffff]rich-color-ext[/]",
            subtitle="[dim #00ff00]Success[/]",
            border_style="bold #008800",
            expand=False,
            subtitle_align="right",
            padding=(1, 4),
        )

## `list_colors()`

List all available CSS color names.

Source code in `src/rich_color_ext/cli.py`

    128
    129
    130
    131
    def list_colors() -> Iterable[str]:
        """List all available CSS color names."""
        css_map = get_css_color_map()
        return sorted(css_map.keys())

## `main(argv=None)`

Main entry point for the CLI.

Parameters:

Name | Type | Description | Default
--- | --- | --- | ---
`argv` | `list[str] | None` | Command-line arguments (default: None, uses sys.argv). | `None`

Returns:

Type | Description
--- | ---
`int` | Exit status code.

Source code in `src/rich_color_ext/cli.py`

    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    def main(argv: list[str] | None = None) -> int:
        """Main entry point for the CLI.

        Args:
            argv: Command-line arguments (default: None, uses sys.argv).

        Returns:
            Exit status code.
        """
        parser = argparse.ArgumentParser(prog="rich-color-ext")
        parser.add_argument("--version", action="store_true", help="Print version and exit")
        sub = parser.add_subparsers(dest="command")

        sub.add_parser("install", help="Patch Rich to use the extended color parser")
        sub.add_parser("uninstall", help="Restore Rich's original color parser")

        ls = sub.add_parser("list", help="List available CSS color names")
        ls.add_argument(
            "--limit", type=int, default=0, help="Limit number of names printed"
        )
        ls.add_argument("--pretty", action="store_true", help="Show a pretty table")

        search = sub.add_parser("search", help="Search CSS color names by substring")
        search.add_argument("query", help="Substring to search for (case-insensitive)")
        search.add_argument(
            "--limit", type=int, default=0, help="Limit number of names printed"
        )
        search.add_argument("--pretty", action="store_true", help="Show a pretty table")

        show = sub.add_parser("show", help="Show hex and RGB for a CSS color name")
        show.add_argument("name", help="CSS color name to show")

        args = parser.parse_args(argv)
        console = Console()

        if args.version:
            console.print(
                f"\n[bold #99ff00]rich-color-ext[/] [bold #00ffff]v{__version__}[/]"
            )
            return 0

        if args.command == "install":
            install()
            console.print(install_panel())
            return 0

        if args.command == "uninstall":
            uninstall()
            console.print(uninstall_panel())
            return 0

        if args.command == "list":
            if not is_installed():
                install()
                console.print(install_panel())
            names = list_colors()
            if args.limit and args.limit > 0:
                names = list(names)[: args.limit]
            if getattr(args, "pretty", False):
                table = _build_table(names)
                console.print(table)
            else:
                console.print("[d i]Listing available CSS color names...[/]")
                console.print(", ".join([f"[b {n}]{n}[/]" for n in names]))
            return 0

        if args.command == "search":
            q = args.query.lower()
            all_names = list_colors()
            matches = [n for n in all_names if q in n.lower()]
            if args.limit and args.limit > 0:
                matches = matches[: args.limit]
            if getattr(args, "pretty", False):
                table = _build_table(matches)
                console.print(table)
            else:
                for n in matches:
                    console.print(n)
            return 0

        if args.command == "show":
            try:
                panel = show_color(args.name)
            except KeyError:
                console.print(f"Unknown color: {args.name}")
                return 2
            console.print(panel)
            return 0

        parser.print_help()
        return 1

## `show_color(name)`

Show hex and RGB for a given CSS color name.

Returns a Rich Panel object for pretty printing.

Source code in `src/rich_color_ext/cli.py`

    160
    161
    162
    163
    164
    165
    166
    def show_color(name: str) -> Panel:
        """Show hex and RGB for a given CSS color name.

        Returns a Rich Panel object for pretty printing.
        """
        color = CSSColor.from_name(name)
        return color.panel()

## `uninstall_panel()`

Print uninstallation message panel.

Source code in `src/rich_color_ext/cli.py`

    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    def uninstall_panel() -> Panel:
        """Print uninstallation message panel."""
        return Panel(
            "rich-color-ext [b i #ff0099]uninstalled![/]",
            title="[#ffffff]rich-color-ext[/]",
            subtitle="[dim #ff0000]Restored[/]",
            border_style="bold #880000",
            expand=False,
            subtitle_align="right",
            padding=(1, 4),
        )

A Rich-based loguru logger sink.

## `get_logger()`

Get the configured loguru logger.

Source code in `src/rich_color_ext/logger.py`

    90
    91
    92
    def get_logger():
        """Get the configured loguru logger."""
        return log

## `rich_sink(msg)`

A Rich-based loguru sink.

Source code in `src/rich_color_ext/logger.py`

    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
    def rich_sink(msg):
        """A Rich-based loguru sink."""
        record = msg.record
        # inspect(record, all=True, console=console, private=True, dunder=True)
        level_name = record["level"].name
        level_icon = record["level"].icon
        file = record["file"].name
        line = record["line"]
        line_str = f"Line {line}"
        left_pad = " " * LEVEL_STYLES[level_name].get("left", 0)
        right_pad = " " * LEVEL_STYLES[level_name].get("right", 0)
        title_str = f"{level_icon} {left_pad}{level_name}{right_pad} \
     {level_icon}  {file:>12}:{line_str:9}"
        title_text = Text(title_str, style=LEVEL_STYLES[level_name]["title"])
        msg_str = str(record["message"])
        msg_text = Text(msg_str, style=LEVEL_STYLES[level_name]["text"])
        console.print(
            Panel(
                msg_text,
                title=title_text,
                title_align="left",
                border_style=LEVEL_STYLES[level_name]["border"],
                padding=(1, 2),
            )
        )