292 lines
8.9 KiB
Python
292 lines
8.9 KiB
Python
"""Interactive REPL mode for exploring regex patterns."""
|
|
|
|
import sys
|
|
import os
|
|
from typing import Optional
|
|
from .parser import parse_regex
|
|
from .translator import translate_regex
|
|
from .test_generator import generate_test_cases
|
|
from .flavors import get_flavor_manager
|
|
|
|
|
|
def format_output(text: str, use_color: bool = True) -> str:
|
|
"""Format output with optional color."""
|
|
if not use_color or not sys.stdout.isatty():
|
|
return text
|
|
|
|
try:
|
|
from pygments import highlight
|
|
from pygments.lexers import RegexLexer
|
|
from pygments.formatters import TerminalFormatter
|
|
|
|
lexer = RegexLexer()
|
|
formatter = TerminalFormatter()
|
|
return highlight(text, lexer, formatter)
|
|
except ImportError:
|
|
return text
|
|
|
|
|
|
class InteractiveSession:
|
|
"""Interactive session for regex exploration."""
|
|
|
|
def __init__(self, flavor: str = "pcre", use_color: bool = True):
|
|
self.flavor = flavor
|
|
self.use_color = use_color
|
|
self.history: list[str] = []
|
|
self.history_file = os.path.expanduser("~/.regex_humanizer_history")
|
|
self._load_history()
|
|
|
|
def _load_history(self):
|
|
"""Load command history from file."""
|
|
if os.path.exists(self.history_file):
|
|
try:
|
|
with open(self.history_file, 'r') as f:
|
|
self.history = [line.strip() for line in f if line.strip()]
|
|
except Exception:
|
|
self.history = []
|
|
|
|
def _save_history(self):
|
|
"""Save command history to file."""
|
|
try:
|
|
os.makedirs(os.path.dirname(self.history_file), exist_ok=True)
|
|
with open(self.history_file, 'w') as f:
|
|
for cmd in self.history[-1000:]:
|
|
f.write(cmd + '\\n')
|
|
except Exception:
|
|
pass
|
|
|
|
def run(self):
|
|
"""Run the interactive session."""
|
|
print("\\nRegex Humanizer - Interactive Mode")
|
|
print("Type 'help' for available commands, 'quit' to exit.\\n")
|
|
|
|
while True:
|
|
try:
|
|
import click
|
|
user_input = click.prompt(
|
|
"regex> ",
|
|
type=str,
|
|
default="",
|
|
show_default=False
|
|
)
|
|
|
|
if not user_input.strip():
|
|
continue
|
|
|
|
self.history.append(user_input)
|
|
self._save_history()
|
|
|
|
self._process_command(user_input.strip())
|
|
|
|
except (KeyboardInterrupt, EOFError):
|
|
print("\\nGoodbye!")
|
|
break
|
|
|
|
def _process_command(self, command: str):
|
|
"""Process a user command."""
|
|
parts = command.split(None, 1)
|
|
cmd = parts[0].lower()
|
|
args = parts[1] if len(parts) > 1 else ""
|
|
|
|
commands = {
|
|
"help": self._cmd_help,
|
|
"quit": self._cmd_quit,
|
|
"exit": self._cmd_quit,
|
|
"explain": self._cmd_explain,
|
|
"test": self._cmd_test,
|
|
"flavor": self._cmd_flavor,
|
|
"set": self._cmd_flavor,
|
|
"load": self._cmd_load,
|
|
"save": self._cmd_save,
|
|
"history": self._cmd_history,
|
|
"clear": self._cmd_clear,
|
|
"example": self._cmd_example,
|
|
}
|
|
|
|
handler = commands.get(cmd)
|
|
if handler:
|
|
handler(args)
|
|
else:
|
|
print(f"Unknown command: {cmd}")
|
|
print("Type 'help' for available commands.")
|
|
|
|
def _cmd_help(self, args: str):
|
|
"""Show help message."""
|
|
help_text = """
|
|
Available Commands:
|
|
explain <pattern> - Explain a regex pattern in English
|
|
test <pattern> - Generate test cases for a pattern
|
|
flavor <name> - Set the regex flavor (pcre, javascript, python)
|
|
set <name> - Same as 'flavor'
|
|
load <filename> - Load a pattern from a file
|
|
save <filename> - Save the last pattern to a file
|
|
history - Show command history
|
|
example - Show an example pattern
|
|
clear - Clear the screen
|
|
quit / exit - Exit the interactive mode
|
|
|
|
Examples:
|
|
explain ^\\d{3}-\\d{4}$
|
|
test [a-z]+
|
|
flavor javascript
|
|
"""
|
|
print(help_text)
|
|
|
|
def _cmd_quit(self, args: str):
|
|
"""Exit the session."""
|
|
print("Goodbye!")
|
|
sys.exit(0)
|
|
|
|
def _cmd_explain(self, args: str):
|
|
"""Explain a regex pattern."""
|
|
if not args:
|
|
print("Usage: explain <pattern>")
|
|
return
|
|
|
|
try:
|
|
pattern = self._expand_pattern(args)
|
|
result = translate_regex(pattern, self.flavor)
|
|
|
|
header = f"Pattern: {pattern}"
|
|
print("\\n" + "=" * (len(header)))
|
|
print(header)
|
|
print("=" * (len(header)))
|
|
print("\\nEnglish Explanation:")
|
|
print("-" * (len(header)))
|
|
print(result)
|
|
print()
|
|
|
|
except Exception as e:
|
|
print(f"Error parsing pattern: {e}")
|
|
|
|
def _cmd_test(self, args: str):
|
|
"""Generate test cases for a pattern."""
|
|
if not args:
|
|
print("Usage: test <pattern>")
|
|
return
|
|
|
|
try:
|
|
pattern = self._expand_pattern(args)
|
|
result = generate_test_cases(pattern, self.flavor, 3, 3)
|
|
|
|
header = f"Pattern: {pattern}"
|
|
print("\\n" + "=" * (len(header)))
|
|
print(header)
|
|
print("=" * (len(header)))
|
|
print(f"\\nFlavor: {self.flavor}")
|
|
|
|
print("\\nMatching strings:")
|
|
print("-" * (len(header)))
|
|
for i, s in enumerate(result["matching"], 1):
|
|
print(f" {i}. {s}")
|
|
|
|
print("\\nNon-matching strings:")
|
|
print("-" * (len(header)))
|
|
for i, s in enumerate(result["non_matching"], 1):
|
|
print(f" {i}. {s}")
|
|
|
|
print()
|
|
|
|
except Exception as e:
|
|
print(f"Error generating tests: {e}")
|
|
|
|
def _cmd_flavor(self, args: str):
|
|
"""Set the current flavor."""
|
|
if not args:
|
|
manager = get_flavor_manager()
|
|
flavors = manager.list_flavors()
|
|
print("Available flavors:")
|
|
for name, desc in flavors:
|
|
marker = " (current)" if name == self.flavor else ""
|
|
print(f" {name}{marker}: {desc}")
|
|
return
|
|
|
|
flavor_name = args.strip().lower()
|
|
manager = get_flavor_manager()
|
|
|
|
if manager.get_flavor(flavor_name):
|
|
self.flavor = flavor_name
|
|
print(f"Flavor set to: {flavor_name}")
|
|
else:
|
|
print(f"Unknown flavor: {flavor_name}")
|
|
print("Available flavors: pcre, javascript, python")
|
|
|
|
def _cmd_load(self, args: str):
|
|
"""Load a pattern from a file."""
|
|
if not args:
|
|
print("Usage: load <filename>")
|
|
return
|
|
|
|
filename = args.strip()
|
|
if not os.path.exists(filename):
|
|
print(f"File not found: {filename}")
|
|
return
|
|
|
|
try:
|
|
with open(filename, 'r') as f:
|
|
pattern = f.read().strip()
|
|
|
|
print(f"Loaded pattern: {pattern}")
|
|
|
|
if hasattr(self, '_last_pattern'):
|
|
pass
|
|
self._last_pattern = pattern
|
|
|
|
except Exception as e:
|
|
print(f"Error reading file: {e}")
|
|
|
|
def _cmd_save(self, args: str):
|
|
"""Save a pattern to a file."""
|
|
if not args:
|
|
print("Usage: save <filename>")
|
|
return
|
|
|
|
pattern = getattr(self, '_last_pattern', None)
|
|
if not pattern:
|
|
print("No pattern to save. Use 'explain' or 'test' first.")
|
|
return
|
|
|
|
try:
|
|
with open(args.strip(), 'w') as f:
|
|
f.write(pattern)
|
|
print(f"Saved pattern to: {args.strip()}")
|
|
except Exception as e:
|
|
print(f"Error writing file: {e}")
|
|
|
|
def _cmd_history(self, args: str):
|
|
"""Show command history."""
|
|
print("Command history:")
|
|
for i, cmd in enumerate(self.history[-50:], 1):
|
|
print(f" {i:3}. {cmd}")
|
|
|
|
def _cmd_clear(self, args: str):
|
|
"""Clear the screen."""
|
|
os.system('cls' if os.name == 'nt' else 'clear')
|
|
|
|
def _cmd_example(self, args: str):
|
|
"""Show an example pattern."""
|
|
examples = [
|
|
r"^\\d{3}-\\d{4}$",
|
|
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
|
r"^(?:http|https)://[^\\s]+$",
|
|
r"\\b\\d{4}-\\d{2}-\\d{2}\\b",
|
|
r"(?i)(hello|hi|greetings)\\s+world!?",
|
|
]
|
|
|
|
import random
|
|
example = random.choice(examples)
|
|
print(f"\\nExample pattern: {example}")
|
|
print("\\nType: explain " + example)
|
|
print("Type: test " + example)
|
|
print()
|
|
|
|
def _expand_pattern(self, pattern: str) -> str:
|
|
"""Expand a pattern from history or args."""
|
|
return pattern
|
|
|
|
|
|
def start_interactive_mode(flavor: str = "pcre"):
|
|
"""Start the interactive mode."""
|
|
session = InteractiveSession(flavor=flavor)
|
|
session.run()
|