Ctrl K
ComunidadGuías…

tools/palette_probe.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""How much of a CLI's colour a theme can actually own.

Theming a terminal is not one question, it is four, and which one applies is
decided by the application rather than by us:

  default        it printed no colour at all               — the theme owns it
  16 ANSI        "the terminal's idea of red"              — the theme owns it
  greyscale      232-255, "a shade near the background"    — relative, may tint
  cube           16-231, an exact colour from a fixed grid — absolute, hands off
  truecolor      38;2;r;g;b, an exact RGB                  — absolute, hands off

The ladder is specificity. The more precisely an application named a colour,
the less business anyone has changing it: an app asking for colour 82 wants
*that* green, and one asking for red wants whatever red means here. The
greyscale ramp is the interesting middle — it expresses a relationship to the
background rather than a colour, so honouring it against the theme's own
background is closer to the intent than neutral grey.

This measures which of those a CLI actually uses, so the answer to "will my
theme skin it" is observed rather than assumed. Nothing here is per-vendor
knowledge; it counts escape sequences.

    python3 tools/palette_probe.py [cli ...]
"""

from __future__ import annotations

import re
import sys
import time
import uuid
from collections import Counter
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from clique import tmux
from clique.__main__ import config_path
from clique.registry import Registry

SOCKET = "clique-palette"
SGR = re.compile(r"\x1b\[([0-9;]*)m")


def classify(codes: str) -> set[str]:
    """Which colour mechanisms one SGR sequence uses."""
    parts = [p for p in codes.split(";") if p != ""]
    found: set[str] = set()
    i = 0
    while i < len(parts):
        try:
            n = int(parts[i])
        except ValueError:
            i += 1
            continue
        if n in (38, 48) and i + 1 < len(parts):
            kind = parts[i + 1]
            if kind == "2":
                found.add("truecolor")
                i += 5
                continue
            if kind == "5" and i + 2 < len(parts):
                try:
                    index = int(parts[i + 2])
                except ValueError:
                    index = -1
                if index < 16:
                    found.add("16 ANSI")
                elif index < 232:
                    found.add("cube")
                else:
                    found.add("greyscale")
                i += 3
                continue
        elif 30 <= n <= 37 or 40 <= n <= 47 or 90 <= n <= 97 or 100 <= n <= 107:
            found.add("16 ANSI")
        i += 1
    return found


def probe(cli_id: str, reg: Registry, seconds: float = 5.0) -> tuple[Counter, int]:
    cli = reg.get(cli_id)
    where = cli.resolve()
    if not where:
        raise FileNotFoundError(cli.command)
    # A real uuid, not a placeholder: a CLI handed a malformed session id
    # exits immediately, and the probe then reports "not probed" for a reason
    # that has nothing to do with colour.
    argv = reg.launch_argv(cli_id, session_id=str(uuid.uuid4()), name="probe", cwd="/tmp")
    mux = f"sm-probe-{cli_id}"
    tmux.create(mux, "/tmp", argv, socket=SOCKET, width=100, height=30)
    time.sleep(seconds)
    text = tmux.capture(mux, SOCKET, lines=200, styled=True)
    tmux.kill(mux, SOCKET, force=True)

    tally: Counter = Counter()
    for codes in SGR.findall(text):
        for kind in classify(codes):
            tally[kind] += 1
    return tally, len(text)


def main() -> int:
    reg = Registry(config_path(None))
    wanted = sys.argv[1:] or [k for k, v in reg.types().items() if v.resolve()]
    tmux._run(["kill-server"], SOCKET, check=False)
    tmux.bootstrap(SOCKET, history_limit=2000)

    order = ["16 ANSI", "greyscale", "cube", "truecolor"]
    print(f"{'cli':10} " + "".join(f"{k:>11}" for k in order) + "   theme reaches")
    print("-" * 74)
    for cli_id in wanted:
        try:
            tally, _size = probe(cli_id, reg)
        except (FileNotFoundError, tmux.TmuxError) as err:
            print(f"{cli_id:10} — not probed ({type(err).__name__})")
            continue
        row = "".join(f"{tally.get(k, 0):>11}" for k in order)
        owned = tally.get("16 ANSI", 0) + tally.get("greyscale", 0)
        fixed = tally.get("cube", 0) + tally.get("truecolor", 0)
        total = owned + fixed
        share = "everything" if not fixed else (
            f"{round(100 * owned / total)}% of what it paints" if total else "n/a")
        if not total:
            share = "everything (paints nothing)"
        print(f"{cli_id:10} {row}   {share}")

    tmux._run(["kill-server"], SOCKET, check=False)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())