From 2f9e825bd4fa2cce3623f65f1958a66d9c6766eb Mon Sep 17 00:00:00 2001 From: michael Date: Tue, 14 Jul 2026 13:11:47 -0700 Subject: [PATCH] feat(rawcli): colorized breadcrumb prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Color-code the prompt so the session state reads at a glance, using the trace formatter's palette so prompt and trace look like one UI: [ raw / hf / NTAG213 / 0x ] dim ^bold ^cyan ^bold-green ^yellow(hex) | magenta(binary) dim - bold `raw`; cyan RF field; bold-green identified transponder; entry mode yellow for hex, magenta for binary (so the base is obvious and flips color on Ctrl-/); dim brackets/separators. - session.colored_breadcrumb() returns the ANSI form; breadcrumb() stays plain (stripping the ANSI yields it — asserted). Prompt message renders it via ANSI(). Co-Authored-By: Claude Opus 4.8 --- pm3py/cli/rawcli/app.py | 2 +- pm3py/cli/rawcli/session.py | 23 +++++++++++++++++++++++ tests/test_rawcli.py | 12 ++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pm3py/cli/rawcli/app.py b/pm3py/cli/rawcli/app.py index 1cbfa48..fe39993 100644 --- a/pm3py/cli/rawcli/app.py +++ b/pm3py/cli/rawcli/app.py @@ -195,7 +195,7 @@ def run(port=None) -> int: ) def message(): - return state.breadcrumb() + " " + return ANSI(state.colored_breadcrumb() + " ") with patch_stdout(): while True: diff --git a/pm3py/cli/rawcli/session.py b/pm3py/cli/rawcli/session.py index a92b728..d68a99c 100644 --- a/pm3py/cli/rawcli/session.py +++ b/pm3py/cli/rawcli/session.py @@ -8,6 +8,15 @@ from typing import Any HEX = "hex" BIN = "bin" +# ANSI palette aligned with pm3py/trace/format.py so the prompt reads as part of the same UI +_DIM = "\033[2m" +_CYAN = "\033[36m" +_YELLOW = "\033[33m" +_MAGENTA = "\033[35m" +_BGREEN = "\033[1;32m" +_BOLD = "\033[1m" +_RESET = "\033[0m" + class RawSession: """Mutable rawcli session state. @@ -43,3 +52,17 @@ class RawSession: segments.append(self.transponder) segments.append(self.entry_prefix) return "[" + " / ".join(segments) + "]" + + def colored_breadcrumb(self) -> str: + """The breadcrumb with ANSI color (same palette as the trace): bold ``raw``, cyan field, + bold-green identified tag, and the entry mode in yellow (hex) or magenta (binary) so the + current base is obvious at a glance. Stripping the ANSI yields :meth:`breadcrumb`.""" + segments = [f"{_BOLD}raw{_RESET}"] + if self.field: + segments.append(f"{_CYAN}{self.field}{_RESET}") + if self.transponder: + segments.append(f"{_BGREEN}{self.transponder}{_RESET}") + mode_color = _YELLOW if self.entry_mode == HEX else _MAGENTA + segments.append(f"{mode_color}{self.entry_prefix}{_RESET}") + sep = f"{_DIM} / {_RESET}" + return f"{_DIM}[{_RESET}" + sep.join(segments) + f"{_DIM}]{_RESET}" diff --git a/tests/test_rawcli.py b/tests/test_rawcli.py index 7636ecd..9b07997 100644 --- a/tests/test_rawcli.py +++ b/tests/test_rawcli.py @@ -46,6 +46,18 @@ class TestBreadcrumb: s.field = "lf" assert s.breadcrumb() == "[raw / lf / 0x]" + def test_colored_matches_plain(self): + s = RawSession() + s.field, s.transponder = "hf", "NTAG213" + assert "\x1b[" in s.colored_breadcrumb() # actually colored + assert _plain(s.colored_breadcrumb()) == s.breadcrumb() # same content, ANSI stripped + + def test_colored_mode_color_changes(self): + s = RawSession() + hex_prompt = s.colored_breadcrumb() + s.toggle_entry_mode() + assert hex_prompt != s.colored_breadcrumb() # hex vs binary colored differently + class TestTraceView: def test_command_and_response_lines(self):