Add validators, TUI, and file watcher modules
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-01 19:02:46 +00:00
parent 0d410feb68
commit a3bcd07a82

266
src/tui/editor.py Normal file
View File

@@ -0,0 +1,266 @@
"""TUI editor module for interactive data editing."""
from typing import Any
from rich.console import Console
from rich.text import Text
from rich.prompt import Prompt
from rich.box import ROUNDED
from rich.panel import Panel
class DataEditor:
"""Interactive editor for data manipulation."""
def __init__(self, console: Console = None):
"""Initialize the data editor.
Args:
console: Rich Console instance
"""
self.console = console or Console()
self.data = None
self.format_name = None
self.modified = False
def load_data(self, data: Any, format_name: str) -> None:
"""Load data for editing.
Args:
data: Data object to edit
format_name: Format of the data
"""
self.data = data
self.format_name = format_name
self.modified = False
def _display_header(self) -> None:
"""Display editor header."""
header = Text()
header.append("Data Editor\n", style='bold cyan')
header.append("" * 40 + "\n", style='dim')
header.append("Commands:\n", style='yellow')
header.append(" edit <path> <value> - Edit a value\n", style='white')
header.append(" add <path> <value> - Add a new key\n", style='white')
header.append(" delete <path> - Delete a key\n", style='white')
header.append(" show - Show current data\n", style='white')
header.append(" save - Save changes\n", style='white')
header.append(" quit - Exit without saving\n", style='white')
header.append(" help - Show this help\n", style='white')
panel = Panel(header, title="Help", box=ROUNDED, expand=False)
self.console.print(panel)
def _parse_path(self, path: str) -> list:
"""Parse a dot-separated path.
Args:
path: Dot-separated path string
Returns:
List of path segments
"""
return path.split('.')
def _get_nested_value(self, data: Any, path: list) -> Any:
"""Get value from nested dictionary.
Args:
data: Data structure
path: List of path segments
Returns:
Value at the path
"""
current = data
for segment in path:
if isinstance(current, dict) and segment in current:
current = current[segment]
else:
return None
return current
def _set_nested_value(self, data: Any, path: list, value: Any) -> bool:
"""Set value in nested dictionary.
Args:
data: Data structure
path: List of path segments
value: Value to set
Returns:
True if successful, False otherwise
"""
current = data
for i, segment in enumerate(path[:-1]):
if isinstance(current, dict) and segment in current:
current = current[segment]
else:
return False
if isinstance(current, dict):
current[path[-1]] = value
return True
return False
def _delete_nested_value(self, data: Any, path: list) -> bool:
"""Delete value from nested dictionary.
Args:
data: Data structure
path: List of path segments
Returns:
True if successful, False otherwise
"""
current = data
for i, segment in enumerate(path[:-1]):
if isinstance(current, dict) and segment in current:
current = current[segment]
else:
return False
if isinstance(current, dict) and path[-1] in current:
del current[path[-1]]
return True
return False
def _cmd_edit(self, args: list) -> bool:
"""Handle edit command.
Args:
args: Command arguments
Returns:
True if command was handled
"""
if len(args) < 2:
self.console.print("Usage: edit <path> <value>", style='red')
return True
path = self._parse_path(args[0])
value = ' '.join(args[1:])
try:
import json
value = json.loads(value)
except json.JSONDecodeError:
pass
if self._set_nested_value(self.data, path, value):
self.modified = True
self.console.print(f"Updated: {'.'.join(path)}", style='green')
else:
self.console.print(f"Path not found: {'.'.join(path)}", style='red')
return True
def _cmd_add(self, args: list) -> bool:
"""Handle add command.
Args:
args: Command arguments
Returns:
True if command was handled
"""
if len(args) < 2:
self.console.print("Usage: add <path> <value>", style='red')
return True
path = self._parse_path(args[0])
value = ' '.join(args[1:])
try:
import json
value = json.loads(value)
except json.JSONDecodeError:
pass
if self._set_nested_value(self.data, path, value):
self.modified = True
self.console.print(f"Added: {'.'.join(path)}", style='green')
else:
self.console.print(f"Could not add: {'.'.join(path)}", style='red')
return True
def _cmd_delete(self, args: list) -> bool:
"""Handle delete command.
Args:
args: Command arguments
Returns:
True if command was handled
"""
if not args:
self.console.print("Usage: delete <path>", style='red')
return True
path = self._parse_path(args[0])
if self._delete_nested_value(self.data, path):
self.modified = True
self.console.print(f"Deleted: {'.'.join(path)}", style='green')
else:
self.console.print(f"Path not found: {'.'.join(path)}", style='red')
return True
def _cmd_show(self) -> bool:
"""Handle show command."""
from .viewer import DataViewer
viewer = DataViewer()
viewer.view_data(self.data, self.format_name, title="Current Data")
return True
def _cmd_save(self) -> bool:
"""Handle save command."""
self.console.print("Data saved (in-memory)", style='green')
return True
def run(self) -> bool:
"""Run the interactive editor.
Returns:
True if data was modified, False otherwise
"""
self._display_header()
self._cmd_show()
while True:
try:
command = Prompt.ask("\n[cyan]Command[/cyan]").strip()
if not command:
continue
parts = command.split()
cmd = parts[0].lower()
args = parts[1:]
if cmd in ('quit', 'exit', 'q'):
break
elif cmd in ('help', 'h', '?'):
self._display_header()
elif cmd == 'edit':
self._cmd_edit(args)
elif cmd == 'add':
self._cmd_add(args)
elif cmd == 'delete':
self._cmd_delete(args)
elif cmd in ('show', 's'):
self._cmd_show()
elif cmd in ('save', 'w'):
self._cmd_save()
else:
self.console.print(f"Unknown command: {cmd}", style='red')
except KeyboardInterrupt:
self.console.print("\nExiting...", style='yellow')
break
except Exception as e:
self.console.print(f"Error: {e}", style='red')
return self.modified