|
20 | 20 | from rich.table import Table |
21 | 21 | from rich.text import Text |
22 | 22 |
|
23 | | -from hyperbase.builders import hedge |
| 23 | +from hyperbase.builders import hedge, split_edge_str |
24 | 24 | from hyperbase.constants import EdgeType |
25 | 25 | from hyperbase.hyperedge import Atom, Hyperedge |
26 | 26 | from hyperbase.parsers import Parser, get_parser, list_parsers |
@@ -367,6 +367,13 @@ def __init__(self, parser_name: str, settings: dict[str, Any]) -> None: |
367 | 367 | ), |
368 | 368 | "handler": self.cmd_types, |
369 | 369 | }, |
| 370 | + "transform": { |
| 371 | + "help": ( |
| 372 | + "Rewrite loaded edges by pattern " |
| 373 | + "(e.g. /transform (X/P.{x} (Y/Ta Z)) ((Y/Mx X/P.{s}) Z))" |
| 374 | + ), |
| 375 | + "handler": self.cmd_transform, |
| 376 | + }, |
370 | 377 | } |
371 | 378 |
|
372 | 379 | self.session = PromptSession( |
@@ -978,6 +985,107 @@ def cmd_types(self, args: list) -> bool: |
978 | 985 | ) |
979 | 986 | return False |
980 | 987 |
|
| 988 | + def cmd_transform(self, args: list) -> bool: |
| 989 | + if not args: |
| 990 | + self.console.print( |
| 991 | + "[red]Error:[/red] /transform requires two patterns: " |
| 992 | + "[cyan]/transform <origin> <target>[/cyan]" |
| 993 | + ) |
| 994 | + return False |
| 995 | + if not self.edges: |
| 996 | + self.console.print("[yellow]No edges loaded.[/yellow]") |
| 997 | + self.console.print( |
| 998 | + "[dim]Use[/dim] [cyan]/load <path>[/cyan] [dim]first.[/dim]" |
| 999 | + ) |
| 1000 | + return False |
| 1001 | + |
| 1002 | + text = " ".join(args) |
| 1003 | + try: |
| 1004 | + tokens = split_edge_str(text) |
| 1005 | + except ValueError as e: |
| 1006 | + self.console.print(f"[red]Error:[/red] {e}") |
| 1007 | + return False |
| 1008 | + if len(tokens) != 2: |
| 1009 | + self.console.print( |
| 1010 | + "[red]Error:[/red] expected exactly two patterns " |
| 1011 | + f"(got {len(tokens)}): [cyan]/transform <origin> <target>[/cyan]" |
| 1012 | + ) |
| 1013 | + return False |
| 1014 | + |
| 1015 | + try: |
| 1016 | + origin = hedge(tokens[0]) |
| 1017 | + target = hedge(tokens[1]) |
| 1018 | + except Exception as e: |
| 1019 | + self.console.print(f"[red]Error:[/red] failed to parse pattern: {e}") |
| 1020 | + return False |
| 1021 | + if origin is None or target is None: |
| 1022 | + self.console.print("[red]Error:[/red] could not parse patterns") |
| 1023 | + return False |
| 1024 | + |
| 1025 | + try: |
| 1026 | + choice = ( |
| 1027 | + self.session.prompt( |
| 1028 | + "Dry run? [Y/n] (n applies the rewrite to loaded edges): " |
| 1029 | + ) |
| 1030 | + .strip() |
| 1031 | + .lower() |
| 1032 | + ) |
| 1033 | + except (KeyboardInterrupt, EOFError): |
| 1034 | + self.console.print("[dim](aborted)[/dim]") |
| 1035 | + return False |
| 1036 | + dry_run = choice not in ("n", "no") |
| 1037 | + |
| 1038 | + changes: list[tuple[int, Hyperedge, Hyperedge]] = [] |
| 1039 | + new_edges: list[Hyperedge] = [] |
| 1040 | + try: |
| 1041 | + for idx, edge in enumerate(self.edges): |
| 1042 | + transformed = edge.transform(origin, target) |
| 1043 | + if transformed != edge: |
| 1044 | + changes.append((idx, edge, transformed)) |
| 1045 | + new_edges.append(transformed) |
| 1046 | + except ValueError as e: |
| 1047 | + self.console.print(f"[red]Error:[/red] {e}") |
| 1048 | + return False |
| 1049 | + except Exception as e: |
| 1050 | + self.console.print(f"[red]Error:[/red] transform failed: {e}") |
| 1051 | + return False |
| 1052 | + |
| 1053 | + mode = "dry run" if dry_run else "applied" |
| 1054 | + self.console.print( |
| 1055 | + f"[green]{len(changes)}[/green] of [cyan]{len(self.edges)}[/cyan] " |
| 1056 | + f"edge(s) affected ([bold]{mode}[/bold])" |
| 1057 | + ) |
| 1058 | + if not dry_run: |
| 1059 | + self.edges = new_edges |
| 1060 | + |
| 1061 | + if not changes: |
| 1062 | + return False |
| 1063 | + |
| 1064 | + page_size = int(self.settings.get("search_page_size", 10)) |
| 1065 | + if page_size < 1: |
| 1066 | + page_size = 10 |
| 1067 | + |
| 1068 | + self._paginate( |
| 1069 | + changes, |
| 1070 | + lambda n, change: self._render_transform_change(n, *change), |
| 1071 | + page_size, |
| 1072 | + ) |
| 1073 | + return False |
| 1074 | + |
| 1075 | + def _render_transform_change( |
| 1076 | + self, |
| 1077 | + n: int, |
| 1078 | + idx: int, |
| 1079 | + before: Hyperedge, |
| 1080 | + after: Hyperedge, |
| 1081 | + ) -> None: |
| 1082 | + self.console.print(f"[bold]#{n}[/bold] [dim](edge {idx})[/dim]") |
| 1083 | + self.console.print(" [dim red]before:[/dim red]") |
| 1084 | + self.console.print(self.formatter.format(before)) |
| 1085 | + self.console.print(" [dim green]after:[/dim green]") |
| 1086 | + self.console.print(self.formatter.format(after)) |
| 1087 | + self.console.print() |
| 1088 | + |
981 | 1089 | def _render_types_row(self, n: int, type_str: str, count: int) -> None: |
982 | 1090 | color = "white" |
983 | 1091 | if type_str: |
|
0 commit comments