diff --git a/regex_humanizer/interactive.py b/regex_humanizer/interactive.py new file mode 100644 index 0000000..c9d9251 --- /dev/null +++ b/regex_humanizer/interactive.py @@ -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 - Explain a regex pattern in English + test - Generate test cases for a pattern + flavor - Set the regex flavor (pcre, javascript, python) + set - Same as 'flavor' + load - Load a pattern from a file + save - 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 ") + 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 ") + 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 ") + 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 ") + 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()