Add remaining source files and tests
This commit is contained in:
291
regex_humanizer/interactive.py
Normal file
291
regex_humanizer/interactive.py
Normal file
@@ -0,0 +1,291 @@
|
||||
"""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()
|
||||
Reference in New Issue
Block a user