feat(rawcli): render page-argument completion in the active entry mode
The memory-map completion for a page/block argument now follows the entry mode instead of always emitting hex: hex mode READ( -> 0x04 user memory binary mode READ( -> 0b00000100 user memory (full 8 bits — each bit visible) Matching narrows in the same base (a binary partial filters bit-prefix-wise), and a 0x/0b prefix on the argument overrides the session mode. Binary matters here because for the config pages each bit carries its own meaning (CFG0 AUTH0, CFG1 PROT/CFGLCK/AUTHLIM), so seeing the whole pattern is the point. Hardware-verified against a real NTAG216 in binary mode (0b00000000..0b11100110 with roles). Tests for binary rendering, bit-prefix filtering, and prefix-override. 1271 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -65,23 +65,39 @@ class RawCompleter(Completer):
|
|||||||
yield from self._catalog_names(word)
|
yield from self._catalog_names(word)
|
||||||
|
|
||||||
def _page_arg(self, cmd_name: str, partial: str):
|
def _page_arg(self, cmd_name: str, partial: str):
|
||||||
"""Complete a page/block argument with the identified tag's memory landmarks, so
|
"""Complete a page/block argument with the identified tag's memory landmarks, rendered in
|
||||||
``READ(`` lists ``0x00 → UID``, ``0x03 → CC``, ``0xE3 → CFG0`` … as suggestions."""
|
the active entry mode — ``0x04`` in hex, ``0b00000100`` in binary (every bit visible,
|
||||||
|
since for config pages each bit carries its own meaning). ``READ(`` lists the whole map;
|
||||||
|
typing narrows it. A ``0x``/``0b`` prefix on the argument overrides the session mode."""
|
||||||
catalog = catalog_for(self._session)
|
catalog = catalog_for(self._session)
|
||||||
if catalog is None:
|
if catalog is None:
|
||||||
return
|
return
|
||||||
tc = catalog.get(cmd_name)
|
tc = catalog.get(cmd_name)
|
||||||
if tc is None or not tc.params or tc.params[0] not in ("page", "block"):
|
if tc is None or not tc.params or tc.params[0] not in ("page", "block"):
|
||||||
return
|
return
|
||||||
low = partial.lower()
|
mode = self._arg_mode(partial)
|
||||||
|
body = partial.lower()
|
||||||
|
if body.startswith(("0x", "0b")):
|
||||||
|
body = body[2:]
|
||||||
for page, role in landmark_pages(self._session.transponder):
|
for page, role in landmark_pages(self._session.transponder):
|
||||||
val = f"0x{page:02X}"
|
if mode == "bin":
|
||||||
forms = (val.lower(), f"{page:x}", f"{page:02x}", str(page)) # 0x-, bare-hex, decimal
|
val, forms = f"0b{page:08b}", (f"{page:08b}", f"{page:b}")
|
||||||
if low and not any(f.startswith(low) for f in forms):
|
else:
|
||||||
|
val, forms = f"0x{page:02X}", (f"{page:02x}", f"{page:x}", str(page))
|
||||||
|
if body and not any(f.startswith(body) for f in forms):
|
||||||
continue
|
continue
|
||||||
yield Completion(val, start_position=-len(partial),
|
yield Completion(val, start_position=-len(partial),
|
||||||
display=f"{val} {role}", display_meta=role)
|
display=f"{val} {role}", display_meta=role)
|
||||||
|
|
||||||
|
def _arg_mode(self, partial: str) -> str:
|
||||||
|
"""hex/bin for an argument token — a 0x/0b prefix wins, else the session entry mode."""
|
||||||
|
low = partial.lower()
|
||||||
|
if low.startswith("0x"):
|
||||||
|
return "hex"
|
||||||
|
if low.startswith("0b"):
|
||||||
|
return "bin"
|
||||||
|
return "bin" if getattr(self._session, "entry_mode", "hex") == "bin" else "hex"
|
||||||
|
|
||||||
def _first_token(self, word: str):
|
def _first_token(self, word: str):
|
||||||
low = word.lower()
|
low = word.lower()
|
||||||
for verb in sorted(CONTROL_VERBS):
|
for verb in sorted(CONTROL_VERBS):
|
||||||
|
|||||||
@@ -541,6 +541,25 @@ class TestCompleter:
|
|||||||
s.protocol, s.transponder = "hf14a", "MIFARE Classic 1K" # no page layout
|
s.protocol, s.transponder = "hf14a", "MIFARE Classic 1K" # no page layout
|
||||||
assert self._complete(s, "READ(") == []
|
assert self._complete(s, "READ(") == []
|
||||||
|
|
||||||
|
def test_page_arg_binary_mode(self):
|
||||||
|
# in binary entry mode the landmarks render as full 8-bit values (each bit visible)
|
||||||
|
s = RawSession()
|
||||||
|
s.protocol, s.transponder = "hf14a", "NTAG216"
|
||||||
|
s.entry_mode = "bin"
|
||||||
|
by_text = {c.text: c.display_meta_text for c in self._complete(s, "READ(")}
|
||||||
|
assert by_text["0b00000100"] == "user memory" # 0x04
|
||||||
|
assert "PWD" in by_text["0b11100101"] # 0xE5
|
||||||
|
assert "0x04" not in by_text # not hex form in binary mode
|
||||||
|
# a binary partial filters bit-prefix-wise
|
||||||
|
assert [c.text for c in self._complete(s, "READ(0b111001")] == \
|
||||||
|
["0b11100100", "0b11100101", "0b11100110"] # 0xE4..0xE6
|
||||||
|
|
||||||
|
def test_page_arg_prefix_overrides_mode(self):
|
||||||
|
# a 0b prefix forces binary rendering even though the session is in hex mode
|
||||||
|
s = RawSession()
|
||||||
|
s.protocol, s.transponder = "hf14a", "NTAG216" # default hex
|
||||||
|
assert [c.text for c in self._complete(s, "READ(0b000001")] == ["0b00000100"]
|
||||||
|
|
||||||
|
|
||||||
class TestMemoryHint:
|
class TestMemoryHint:
|
||||||
def _sess(self, tp="NTAG213"):
|
def _sess(self, tp="NTAG213"):
|
||||||
|
|||||||
Reference in New Issue
Block a user