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
@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
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
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
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
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
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
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
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),
        )
    )