From 192ab228fa163691b09de4885e555d4d4ace1102 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Mon, 2 Feb 2026 22:25:58 +0000 Subject: [PATCH] Initial upload: PatternForge CLI tool with pattern detection and boilerplate generation --- src/patternforge/cli.py | 155 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/patternforge/cli.py diff --git a/src/patternforge/cli.py b/src/patternforge/cli.py new file mode 100644 index 0000000..19401a1 --- /dev/null +++ b/src/patternforge/cli.py @@ -0,0 +1,155 @@ + +import click +from rich.console import Console +from rich.table import Table + +from patternforge.analyzer import CodeAnalyzer +from patternforge.config import Config +from patternforge.generator import BoilerplateGenerator +from patternforge.template import TemplateManager + +console = Console() + + +@click.group() +@click.option("--config", "-c", type=click.Path(exists=True), help="Config file path") +@click.pass_context +def main(ctx: click.Context, config: str | None) -> None: + ctx.ensure_object(dict) + cfg = Config.load(config) if config else Config.load() + ctx.obj["config"] = cfg + + +@main.command("analyze") +@click.argument("path", type=click.Path(exists=True, file_okay=True, dir_okay=True)) +@click.option("--language", "-l", required=True, help="Programming language") +@click.option("--output", "-o", required=True, type=click.Path(), help="Output file for patterns") +@click.option("--recursive/--no-recursive", default=True, help="Analyze directories recursively") +@click.pass_context +def analyze(ctx: click.Context, path: str, language: str, output: str, recursive: bool) -> None: + """Analyze a codebase and extract patterns.""" + config: Config = ctx.obj["config"] + console.print(f"Analyzing [cyan]{path}[/] for [cyan]{language}[/] patterns...") + analyzer = CodeAnalyzer(language, config) + patterns = analyzer.analyze(path, recursive) + analyzer.save_patterns(output, patterns) + console.print(f"Patterns saved to [green]{output}[/]") + console.print(f"Detected: {patterns.get('summary', {})}") + + +@click.group() +def template() -> None: + """Template management commands.""" + pass + + +@template.command("create") +@click.argument("name", type=str) +@click.option("--pattern", "-p", type=click.Path(exists=True), required=True, help="Pattern file") +@click.option("--template", "-t", type=click.Path(exists=True), help="Custom Jinja2 template file") +@click.option("--description", "-d", type=str, default="", help="Template description") +@click.pass_context +def template_create( + ctx: click.Context, name: str, pattern: str, template: str | None, description: str +) -> None: + """Create a new template from detected patterns.""" + config: Config = ctx.obj["config"] + manager = TemplateManager(config) + manager.create_template(name, pattern, template, description) + console.print(f"Template [green]{name}[/] created successfully") + + +@template.command("list") +@click.pass_context +def template_list(ctx: click.Context) -> None: + """List all available templates.""" + config: Config = ctx.obj["config"] + manager = TemplateManager(config) + templates = manager.list_templates() + if not templates: + console.print("[yellow]No templates found[/]") + return + table = Table(title="Templates") + table.add_column("Name") + table.add_column("Description") + table.add_column("Created") + for t in templates: + table.add_row(t["name"], t.get("description", ""), t.get("created", "")) + console.print(table) + + +@template.command("remove") +@click.argument("name", type=str) +@click.option("--force/--no-force", default=False, help="Skip confirmation") +@click.pass_context +def template_remove(ctx: click.Context, name: str, force: bool) -> None: + """Remove a template.""" + config: Config = ctx.obj["config"] + if not force: + if not click.confirm(f"Remove template [cyan]{name}[/]?"): + return + manager = TemplateManager(config) + manager.remove_template(name) + console.print(f"Template [green]{name}[/] removed") + + +main.add_command(template, "template") + + +@main.command("generate") +@click.argument("template", type=str) +@click.option("--output", "-o", type=click.Path(), required=True, help="Output directory") +@click.option( + "--data", "-d", type=click.Path(exists=True), help="JSON data file for template variables" +) +@click.option("--name", "-n", help="Name for the generated files") +@click.pass_context +def generate( + ctx: click.Context, template: str, output: str, data: str | None, name: str | None +) -> None: + """Generate boilerplate from a template.""" + config: Config = ctx.obj["config"] + generator = BoilerplateGenerator(config) + generator.generate(template, output, data, name) + console.print(f"Boilerplate generated at [green]{output}[/]") + + +@main.command("export") +@click.argument("source", type=click.Path(exists=True)) +@click.argument("destination", type=click.Path()) +@click.option("--format", "-f", type=click.Choice(["yaml", "json"]), default="yaml") +@click.pass_context +def export(ctx: click.Context, source: str, destination: str, format: str) -> None: + """Export patterns or templates for team sharing.""" + config: Config = ctx.obj["config"] + manager = TemplateManager(config) + manager.export_patterns(source, destination, format) + console.print(f"Exported to [green]{destination}[/]") + + +@main.command("import") +@click.argument("source", type=click.Path(exists=True)) +@click.pass_context +def import_patterns(ctx: click.Context, source: str) -> None: + """Import patterns from team repository.""" + config: Config = ctx.obj["config"] + manager = TemplateManager(config) + manager.import_patterns(source) + console.print(f"Imported from [green]{source}[/]") + + +@main.command("config") +@click.pass_context +def show_config(ctx: click.Context) -> None: + """Show current configuration.""" + config: Config = ctx.obj["config"] + table = Table(title="Configuration") + table.add_column("Setting") + table.add_column("Value") + for key, value in config.to_dict().items(): + table.add_row(key, str(value)) + console.print(table) + + +if __name__ == "__main__": + main()