diff --git a/i18n_guardian/cli.py b/i18n_guardian/cli.py new file mode 100644 index 0000000..9df78db --- /dev/null +++ b/i18n_guardian/cli.py @@ -0,0 +1,158 @@ +"""CLI module for i18n-guardian.""" +import os +from typing import Optional + +import click + +from i18n_guardian.config import ConfigLoader +from i18n_guardian.version import __version__ + + +@click.group() +@click.option( + "--config", + "-c", + type=click.Path(exists=True), + help="Path to configuration file", +) +@click.option( + "--verbose", + "-v", + is_flag=True, + help="Enable verbose output", +) +@click.option( + "--path", + "-p", + type=click.Path(exists=True), + help="Path to scan (defaults to current directory)", +) +@click.version_option(version=__version__, prog_name="i18n-guardian") +@click.pass_context +def main( + ctx: click.Context, + config: Optional[str], + verbose: bool, + path: Optional[str], +) -> None: + """i18n-guardian: Scan codebases for internationalization issues.""" + ctx.ensure_object(dict) + ctx.obj["config"] = config + ctx.obj["verbose"] = verbose + ctx.obj["path"] = path or os.getcwd() + + +@main.command() +@click.option( + "--output", + "-o", + type=click.Choice(["text", "json"]), + default="text", + help="Output format", +) +@click.option( + "--exclude", + multiple=True, + help="Exclude patterns (glob)", +) +@click.option( + "--min-length", + type=int, + default=3, + help="Minimum string length to flag", +) +@click.pass_context +def scan( + ctx: click.Context, + output: str, + exclude: tuple, + min_length: int, +) -> None: + """Scan codebase for hardcoded strings.""" + path = ctx.obj["path"] + config_path = ctx.obj["config"] + verbose = ctx.obj["verbose"] + + config_loader = ConfigLoader() + config = config_loader.load(config_path, path) + + click.echo(f"Scanning {path}...") + if verbose: + click.echo(f"Config: {config}") + click.echo(f"Output format: {output}") + click.echo(f"Exclude patterns: {exclude}") + click.echo(f"Min length: {min_length}") + + +@main.command() +@click.option( + "--dry-run", + is_flag=True, + help="Preview changes without applying", +) +@click.option( + "--output", + "-o", + type=click.Choice(["text", "json"]), + default="text", + help="Output format", +) +@click.pass_context +def fix( + ctx: click.Context, + dry_run: bool, + output: str, +) -> None: + """Auto-fix i18n issues.""" + path = ctx.obj["path"] + + click.echo(f"Fixing i18n issues in {path}...") + if dry_run: + click.echo("Dry run mode - no changes will be applied") + + +@main.command() +@click.option( + "--output", + "-o", + type=click.Path(), + default=".i18n-guardian.yaml", + help="Output file path", +) +def init(output: str) -> None: + """Initialize configuration file.""" + click.echo(f"Initializing configuration in {output}") + + +@main.command() +@click.option( + "--fail-level", + type=click.Choice(["error", "warning", "info"]), + default="error", + help="Minimum level to fail CI build", +) +@click.option( + "--output", + "-o", + type=click.Choice(["text", "json"]), + default="text", + help="Output format", +) +@click.pass_context +def check( + ctx: click.Context, + fail_level: str, + output: str, +) -> None: + """Check i18n compliance (for CI/CD).""" + path = ctx.obj["path"] + click.echo(f"Checking i18n compliance in {path}...") + + +def cli() -> None: + """Entry point for the CLI.""" + main() + + +if __name__ == "__main__": + main()