feat(rawcli): flexible entry + parser (Phase 2)
- parser.py: classify a line into control / function-call / raw payload; parse loose hex/binary with intermixed 0x/0b tokens (whitespace- insensitive, defaulting to the session entry mode) - entry.py: byte_space() byte-group formatting + key bindings — Ctrl-/ toggles hex/binary entry mode (breadcrumb updates), digits auto-space into byte groups as you type - app.py: dispatch() drives the loop via parse_line (testable without a live prompt); identify/call are stubbed for Phases 3/4 14 new tests (parser tokens/intermix/errors, byte_space, dispatch). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,8 @@ from __future__ import annotations
|
||||
import sys
|
||||
|
||||
from .session import RawSession
|
||||
from .parser import parse_line
|
||||
from .entry import install_key_bindings
|
||||
from .trace_view import render_exchange
|
||||
|
||||
_HELP = """\
|
||||
@@ -45,12 +47,48 @@ def _raw_exchange(device, payload: bytes) -> bytes | None:
|
||||
return None
|
||||
|
||||
|
||||
def dispatch(state: RawSession, line: str) -> bool:
|
||||
"""Handle one input line. Returns False to exit the REPL, True to keep going. Testable
|
||||
without a live prompt."""
|
||||
try:
|
||||
cmd = parse_line(line, state.entry_mode)
|
||||
except ValueError as exc:
|
||||
print(f"rawcli: {exc}")
|
||||
return True
|
||||
|
||||
if cmd.kind == "control":
|
||||
if cmd.name in ("quit", "exit", "q"):
|
||||
return False
|
||||
if cmd.name == "help":
|
||||
print(_HELP)
|
||||
else:
|
||||
# identify / transponder / close land in Phase 3
|
||||
print(f"rawcli: '{cmd.name}' is not wired yet (coming in a later phase)")
|
||||
return True
|
||||
|
||||
if cmd.kind == "call":
|
||||
# function-style transponder commands land in Phase 4 (the command catalog)
|
||||
print(f"rawcli: {cmd.name}(...) needs an identified transponder + catalog (Phase 4)")
|
||||
return True
|
||||
|
||||
# raw payload
|
||||
if not cmd.payload:
|
||||
return True
|
||||
response = _raw_exchange(state.device, cmd.payload)
|
||||
print(render_exchange(cmd.payload, response, protocol="hf14a",
|
||||
is_tty=sys.stdout.isatty()))
|
||||
return True
|
||||
|
||||
|
||||
def run(port=None) -> int:
|
||||
from prompt_toolkit import PromptSession
|
||||
from prompt_toolkit.key_binding import KeyBindings
|
||||
from prompt_toolkit.patch_stdout import patch_stdout
|
||||
|
||||
state = RawSession(device=_connect(port))
|
||||
session = PromptSession()
|
||||
kb = KeyBindings()
|
||||
install_key_bindings(kb, state)
|
||||
session = PromptSession(key_bindings=kb)
|
||||
|
||||
def message():
|
||||
return state.breadcrumb() + " "
|
||||
@@ -58,23 +96,12 @@ def run(port=None) -> int:
|
||||
with patch_stdout():
|
||||
while True:
|
||||
try:
|
||||
line = session.prompt(message).strip()
|
||||
line = session.prompt(message)
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
if not line:
|
||||
if not line.strip():
|
||||
continue
|
||||
if line in ("quit", "exit", "q"):
|
||||
if not dispatch(state, line):
|
||||
break
|
||||
if line == "help":
|
||||
print(_HELP)
|
||||
continue
|
||||
try:
|
||||
payload = bytes.fromhex(line.lower().replace("0x", "").replace(" ", ""))
|
||||
except ValueError:
|
||||
print(f"rawcli: could not parse {line!r} as hex")
|
||||
continue
|
||||
response = _raw_exchange(state.device, payload)
|
||||
print(render_exchange(payload, response, protocol="hf14a",
|
||||
is_tty=sys.stdout.isatty()))
|
||||
print("bye")
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user