Skip to content

Commit c281aed

Browse files
committed
REPL command /save-parse
1 parent 7feb6cf commit c281aed

2 files changed

Lines changed: 50 additions & 3 deletions

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, /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 and /unload-parser.
1515

1616
### Changed
1717

src/hyperbase/cli/repl.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from hyperbase.hyperedge import Atom, Hyperedge
2727
from hyperbase.parsers import Parser, get_parser, list_parsers
2828
from hyperbase.parsers.badness import badness_check
29+
from hyperbase.parsers.parse_result import ParseResult
2930
from hyperbase.parsers.repl_api import (
3031
CommandHandler,
3132
PostResultHook,
@@ -70,6 +71,12 @@
7071
"default": 20,
7172
"description": "Number of results shown per page in paginated commands.",
7273
},
74+
"save_parses_to": {
75+
"type": str,
76+
"default": None,
77+
"description": "Path to a .jsonl file where /save-parse appends parse results.",
78+
"is_path": True,
79+
},
7380
}
7481

7582

@@ -328,6 +335,9 @@ def __init__(self, parser_name: str | None, settings: dict[str, Any]) -> None:
328335
self.edges: list[Hyperedge] = []
329336
self.edges_source: Path | None = None
330337

338+
# Last result of parse_text(), exposed for /save-parse.
339+
self.last_parse_result: list[ParseResult] | None = None
340+
331341
# Per-parser registrations -- everything in these collections is
332342
# cleared and re-installed whenever the active parser changes.
333343
self._extra_settings: dict[str, dict[str, Any]] = {}
@@ -383,6 +393,10 @@ def __init__(self, parser_name: str | None, settings: dict[str, Any]) -> None:
383393
"help": "Save in-memory edges to a .jsonl file",
384394
"handler": self.cmd_save,
385395
},
396+
"save-parse": {
397+
"help": "Append the last parse results to the file in save_parses_to",
398+
"handler": self.cmd_save_parse,
399+
},
386400
"edges": {
387401
"help": "Show in-memory edges (count and source file)",
388402
"handler": self.cmd_edges,
@@ -597,6 +611,9 @@ def _all_commands(self) -> dict[str, dict[str, Any]]:
597611

598612
def _path_settings(self) -> frozenset[str]:
599613
names: set[str] = set()
614+
for name, info in BUILTIN_REPL_SETTINGS.items():
615+
if info.get("is_path"):
616+
names.add(name)
600617
if self.parser is not None:
601618
for name, info in type(self.parser).accepted_params().items():
602619
if info.get("is_path"):
@@ -909,6 +926,37 @@ def cmd_save(self, args: list) -> bool:
909926
)
910927
return False
911928

929+
def cmd_save_parse(self, args: list) -> bool:
930+
path_str = self.settings.get("save_parses_to")
931+
if not path_str:
932+
self.console.print(
933+
"[red]Error:[/red] no destination file set. "
934+
"Use [cyan]/set save_parses_to <path>[/cyan] first."
935+
)
936+
return False
937+
if not self.last_parse_result:
938+
self.console.print(
939+
"[yellow]No parse to save.[/yellow] "
940+
"[dim]Parse some text first, then run /save-parse.[/dim]"
941+
)
942+
return False
943+
944+
path = Path(path_str).expanduser()
945+
try:
946+
with open(path, "a") as f:
947+
for result in self.last_parse_result:
948+
f.write(result.to_json() + "\n")
949+
except OSError as e:
950+
self.console.print(f"[red]Error:[/red] failed to write {path}: {e}")
951+
return False
952+
953+
n = len(self.last_parse_result)
954+
self.console.print(
955+
f"[green]✓[/green] Appended [cyan]{n}[/cyan] parse result(s) "
956+
f"to [cyan]{path}[/cyan]"
957+
)
958+
return False
959+
912960
def _build_tok_pos_tree(self, edge: Hyperedge) -> Hyperedge:
913961
"""Build a tok_pos mirror tree where each atom is replaced by its
914962
``tok_pos`` (or ``-1`` for synthetic atoms with no position)."""
@@ -1595,8 +1643,6 @@ def _render_count_row(self, n: int, key: object, count: int) -> None:
15951643
self.console.print()
15961644

15971645
def _load_edges_from_jsonl(self, path: Path) -> tuple[list[Hyperedge], int]:
1598-
from hyperbase.parsers.parse_result import ParseResult
1599-
16001646
edges: list[Hyperedge] = []
16011647
skipped = 0
16021648
with open(path) as f:
@@ -1681,6 +1727,7 @@ def parse_text(self, text: str) -> None:
16811727
start_time = time.perf_counter()
16821728
parse_result = self.parser.parse(text)
16831729
elapsed_time = time.perf_counter() - start_time
1730+
self.last_parse_result = parse_result
16841731

16851732
if not parse_result:
16861733
self.console.print()

0 commit comments

Comments
 (0)