Skip to content

Commit 34ebffa

Browse files
committed
REPL commands /parse-results, /tag and /tags
1 parent 6865753 commit 34ebffa

2 files changed

Lines changed: 102 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
- readers provide source information.
1212
- `hyperbase.parsers.badness` module for parser-agnostic combined structural + token-matching validation.
1313
- built-in REPL setting `check_badness` to render a badness panel after each parse, available regardless of the active parser plugin.
14-
- more REPL commands: /load, /unload, /save, /save-parse, /search, /count, /count-csv, /types, /transform, /classify and /unload-parser.
14+
- more REPL commands: /load, /unload, /save, /save-parse, /search, /count, /count-csv, /types, /transform, /classify, /unload-parser, /parse-results, /tag and /tags.
1515
- parser plugin settings have an optional flag to indicate that no parser reload is nodes, REPL adjusted to accommodate this mechanism.
1616

1717
### Changed

src/hyperbase/cli/repl.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,24 @@ def __init__(self, parser_name: str | None, settings: dict[str, Any]) -> None:
397397
"help": "Append the last parse results to the file in save_parses_to",
398398
"handler": self.cmd_save_parse,
399399
},
400+
"parse-results": {
401+
"help": "Show the current parse results JSON",
402+
"handler": self.cmd_parse_results,
403+
},
404+
"tag": {
405+
"help": (
406+
"Add a boolean tag (name=true) to the current parse results' "
407+
"extra dict"
408+
),
409+
"handler": self.cmd_tag,
410+
},
411+
"tags": {
412+
"help": (
413+
"List keys in the current parse results' extra dict whose "
414+
"value is True"
415+
),
416+
"handler": self.cmd_tags,
417+
},
400418
"edges": {
401419
"help": "Show in-memory edges (count and source file)",
402420
"handler": self.cmd_edges,
@@ -974,6 +992,89 @@ def cmd_save_parse(self, args: list) -> bool:
974992
)
975993
return False
976994

995+
def cmd_parse_results(self, args: list) -> bool:
996+
if not self.last_parse_result:
997+
self.console.print(
998+
"[yellow]No parse results.[/yellow] [dim]Parse some text first.[/dim]"
999+
)
1000+
return False
1001+
payload = [r.to_dict() for r in self.last_parse_result]
1002+
self.console.print(
1003+
json.dumps(payload, indent=2, ensure_ascii=False, default=str)
1004+
)
1005+
return False
1006+
1007+
def _choose_parse_indices(self) -> list[int] | None:
1008+
"""Return indices into self.last_parse_result to act on, or None on abort."""
1009+
assert self.last_parse_result is not None
1010+
n = len(self.last_parse_result)
1011+
if n == 1:
1012+
return [0]
1013+
try:
1014+
answer = self.session.prompt(
1015+
f"{n} parse results. (a)ll or index 0..{n - 1}: "
1016+
).strip()
1017+
except (KeyboardInterrupt, EOFError):
1018+
self.console.print("[dim](aborted)[/dim]")
1019+
return None
1020+
if answer.lower() in ("a", "all"):
1021+
return list(range(n))
1022+
try:
1023+
idx = int(answer)
1024+
except ValueError:
1025+
self.console.print(
1026+
f"[red]Error:[/red] expected 'all' or an integer in [0, {n - 1}]"
1027+
)
1028+
return None
1029+
if not 0 <= idx < n:
1030+
self.console.print(
1031+
f"[red]Error:[/red] index {idx} out of range [0, {n - 1}]"
1032+
)
1033+
return None
1034+
return [idx]
1035+
1036+
def cmd_tag(self, args: list) -> bool:
1037+
if len(args) != 1:
1038+
self.console.print("[dim]Usage:[/dim] [cyan]/tag <name>[/cyan]")
1039+
return False
1040+
name = args[0]
1041+
if not self.last_parse_result:
1042+
self.console.print(
1043+
"[yellow]No parse results.[/yellow] [dim]Parse some text first.[/dim]"
1044+
)
1045+
return False
1046+
indices = self._choose_parse_indices()
1047+
if indices is None:
1048+
return False
1049+
for i in indices:
1050+
self.last_parse_result[i].extra[name] = True
1051+
self.console.print(
1052+
f"[green]✓[/green] Tagged [cyan]'{name}'[/cyan] on "
1053+
f"[cyan]{len(indices)}[/cyan] result(s)"
1054+
)
1055+
return False
1056+
1057+
def cmd_tags(self, args: list) -> bool:
1058+
if not self.last_parse_result:
1059+
self.console.print(
1060+
"[yellow]No parse results.[/yellow] [dim]Parse some text first.[/dim]"
1061+
)
1062+
return False
1063+
indices = self._choose_parse_indices()
1064+
if indices is None:
1065+
return False
1066+
if len(indices) == 1:
1067+
extra = self.last_parse_result[indices[0]].extra
1068+
keys = [k for k, v in extra.items() if v is True]
1069+
self.console.print(", ".join(keys) if keys else "[dim](none)[/dim]")
1070+
else:
1071+
for i in indices:
1072+
extra = self.last_parse_result[i].extra
1073+
keys = [k for k, v in extra.items() if v is True]
1074+
rendered = ", ".join(keys) if keys else "[dim](none)[/dim]"
1075+
self.console.print(f"[cyan][{i}][/cyan] {rendered}")
1076+
return False
1077+
9771078
def _build_tok_pos_tree(self, edge: Hyperedge) -> Hyperedge:
9781079
"""Build a tok_pos mirror tree where each atom is replaced by its
9791080
``tok_pos`` (or ``-1`` for synthetic atoms with no position)."""

0 commit comments

Comments
 (0)