"""Main CLI module for Regex Humanizer.""" import json as json_module import click from .parser import parse_regex, ParseError from .converter import convert_to_english, convert_to_english_verbose from .examples import generate_examples from .flavors import ( get_supported_flavors, validate_flavor, detect_flavor, get_compatibility_warnings, ) from .converter.english_to_regex import convert_english_to_regex @click.group(help="Regex Humanizer - Convert regular expressions into human-readable English") @click.version_option(version="0.1.0") def main(): """A CLI tool that converts regular expressions into human-readable English.""" pass @main.command() @click.argument("pattern", type=click.STRING) @click.option( "--flavor", type=click.Choice(["pcre", "javascript", "python", "go"]), default="pcre", help="Regex flavor to use for parsing", ) @click.option( "--verbose/--simple", default=False, help="Show detailed breakdown of the pattern", ) @click.option( "--output-format", type=click.Choice(["text", "json"]), default="text", help="Output format (text or json)", ) @click.option( "--json", "output_format", flag_value="json", help="Output in JSON format", ) def explain(pattern: str, flavor: str, verbose: bool, output_format: str): """Explain a regex pattern in plain English.""" use_json = output_format == "json" if not validate_flavor(flavor): flavors = get_supported_flavors() click.echo(f"Error: Unknown flavor '{flavor}'. Supported: {', '.join(flavors)}", err=True) ctx = click.get_current_context() ctx.exit(2) try: if verbose or use_json: result = convert_to_english_verbose(pattern, flavor) if "Error" in result.get('description', ''): click.echo(result['description'], err=True) ctx = click.get_current_context() ctx.exit(2) if use_json: click.echo(json_module.dumps(result, indent=2)) else: click.echo(f"\nPattern: {result['pattern']}") click.echo(f"Flavor: {result['flavor']}") click.echo(f"\nDescription:\n{result['description']}") if result.get('structure'): click.echo(f"\nStructure:") for item in result['structure']: click.echo(f" - {item}") else: description = convert_to_english(pattern, flavor) if "Error" in description: click.echo(description, err=True) ctx = click.get_current_context() ctx.exit(2) click.echo(f"\nPattern: {pattern}") click.echo(f"Flavor: {flavor}") click.echo(f"\nDescription:\n{description}") warnings = get_compatibility_warnings(pattern, flavor) if warnings: click.echo(f"\nCompatibility warnings:") for w in warnings: click.echo(f" [{w.severity.upper()}] {w.feature}: {w.message}") except ParseError as e: click.echo(f"Error parsing pattern: {e.message} at position {e.position}", err=True) ctx = click.get_current_context() ctx.exit(2) except Exception as e: click.echo(f"Error: {str(e)}", err=True) ctx = click.get_current_context() ctx.exit(2) @main.command() @click.argument("pattern", type=click.STRING) @click.option( "--flavor", type=click.Choice(["pcre", "javascript", "python", "go"]), default="pcre", help="Regex flavor to use for parsing", ) @click.option( "--count", "-n", default=5, type=click.IntRange(1, 20), help="Number of examples to generate", ) @click.option( "--output-format", type=click.Choice(["text", "json"]), default="text", help="Output format (text or json)", ) @click.option( "--json", "output_format", flag_value="json", help="Output in JSON format", ) def generate(pattern: str, flavor: str, count: int, output_format: str): """Generate example strings that match the regex pattern.""" use_json = output_format == "json" if not validate_flavor(flavor): click.echo(f"Error: Unknown flavor '{flavor}'", err=True) ctx = click.get_current_context() ctx.exit(2) try: examples = generate_examples(pattern, count, flavor) if use_json: result = { "pattern": pattern, "flavor": flavor, "examples": examples, } click.echo(json_module.dumps(result, indent=2)) else: click.echo(f"\nPattern: {pattern}") click.echo(f"Flavor: {flavor}") click.echo(f"\nMatching examples:") for i, example in enumerate(examples, 1): click.echo(f" {i}. {example}") if not examples: click.echo(" No examples could be generated.") except Exception as e: click.echo(f"Error: {str(e)}", err=True) ctx = click.get_current_context() ctx.exit(2) @main.command() @click.argument("description", type=click.STRING) @click.option( "--flavor", type=click.Choice(["pcre", "javascript", "python", "go"]), default="pcre", help="Regex flavor to generate", ) @click.option( "--output-format", type=click.Choice(["text", "json"]), default="text", help="Output format (text or json)", ) @click.option( "--json", "output_format", flag_value="json", help="Output in JSON format", ) def from_english(description: str, flavor: str, output_format: str): """Convert an English description to a regex pattern.""" use_json = output_format == "json" try: result = convert_english_to_regex(description, flavor) if use_json: click.echo(json_module.dumps(result, indent=2)) else: click.echo(f"\nEnglish description: {result['input']}") click.echo(f"Generated pattern: {result['output']}") click.echo(f"Flavor: {result['flavor']}") if result.get('warnings'): click.echo(f"\nWarnings:") for w in result['warnings']: click.echo(f" - {w}") if result.get('valid') is False: click.echo(f"\nValidation error: {result.get('error')}") ctx = click.get_current_context() ctx.exit(2) except Exception as e: click.echo(f"Error: {str(e)}", err=True) ctx = click.get_current_context() ctx.exit(2) @main.command() @click.option( "--flavor", type=click.Choice(["pcre", "javascript", "python", "go"]), default="pcre", help="Regex flavor to use", ) def build(flavor: str): """Interactive wizard to build a regex pattern step by step.""" click.echo("\n=== Regex Pattern Builder ===") click.echo(f"Flavor: {flavor}") click.echo("Enter 'quit' to exit, 'back' to go back, 'done' when finished.\n") pattern_parts = [] while True: current_pattern = "".join(p.to_regex() if hasattr(p, 'to_regex') else str(p) for p in pattern_parts) if current_pattern: description = convert_to_english(current_pattern, flavor) click.echo(f"\nCurrent pattern: {current_pattern}") click.echo(f"Meaning: {description}") else: click.echo("\nCurrent pattern: (empty)") click.echo("\nWhat would you like to add?") click.echo(" 1. Literal text") click.echo(" 2. Character class (e.g., [abc])") click.echo(" 3. Character range (e.g., [a-z])") click.echo(" 4. Digit (\\d)") click.echo(" 5. Word character (\\w)") click.echo(" 6. Whitespace (\\s)") click.echo(" 7. Any character (.)") click.echo(" 8. Start of string (^)") click.echo(" 9. End of string ($)") click.echo(" 10. Word boundary (\\b)") choice = click.prompt("Enter your choice (1-10, quit/back/done)", type=str).strip().lower() if choice in ("quit", "exit", "q"): click.echo("Goodbye!") break elif choice in ("back", "b"): if pattern_parts: pattern_parts.pop() click.echo("Removed last element.") else: click.echo("Pattern is already empty.") continue elif choice in ("done", "finish", "d"): if current_pattern: click.echo(f"\nFinal pattern: {current_pattern}") description = convert_to_english(current_pattern, flavor) click.echo(f"Meaning: {description}") examples = generate_examples(current_pattern, 3, flavor) if examples: click.echo(f"Examples: {', '.join(examples)}") else: click.echo("Pattern is empty. Nothing to save.") break elif choice == "1": text = click.prompt("Enter the literal text", type=str) if text: from .parser import Literal pattern_parts.append(Literal(value=text)) elif choice == "2": chars = click.prompt("Enter characters (e.g., 'abc')", type=str) if chars: from .parser import CharacterClass pattern_parts.append(CharacterClass(characters=list(chars))) elif choice == "3": start = click.prompt("Start character (e.g., a)", type=str) end = click.prompt("End character (e.g., z)", type=str) if start and end: from .parser import CharacterClass pattern_parts.append(CharacterClass(ranges=[(start, end)])) elif choice == "4": from .parser import SpecialSequence pattern_parts.append(SpecialSequence(sequence=r"\d")) elif choice == "5": from .parser import SpecialSequence pattern_parts.append(SpecialSequence(sequence=r"\w")) elif choice == "6": from .parser import SpecialSequence pattern_parts.append(SpecialSequence(sequence=r"\s")) elif choice == "7": from .parser import SpecialSequence pattern_parts.append(SpecialSequence(sequence=".")) elif choice == "8": from .parser import Anchor pattern_parts.append(Anchor(kind="^")) elif choice == "9": from .parser import Anchor pattern_parts.append(Anchor(kind="$")) elif choice == "10": from .parser import Anchor pattern_parts.append(Anchor(kind=r"\b")) else: click.echo("Invalid choice. Please enter 1-10 or a command.") @main.command() def flavors(): """List supported regex flavors.""" supported = get_supported_flavors() click.echo("\nSupported regex flavors:") for flavor in supported: click.echo(f" - {flavor}") click.echo() @main.command() @click.argument("pattern", type=click.STRING) def detect(pattern: str): """Detect the flavor of a regex pattern.""" flavor = detect_flavor(pattern) click.echo(f"\nDetected flavor: {flavor}")