diff --git a/src/patternforge/generator.py b/src/patternforge/generator.py new file mode 100644 index 0000000..bae999d --- /dev/null +++ b/src/patternforge/generator.py @@ -0,0 +1,151 @@ +import json +from pathlib import Path +from typing import Any + +from patternforge.config import Config +from patternforge.template import TemplateManager + + +class BoilerplateGenerator: + def __init__(self, config: Config) -> None: + self.config = config + self.template_manager = TemplateManager(config) + + def _to_snake_case(self, name: str) -> str: + import re + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) + return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower() + + def _to_camel_case(self, name: str) -> str: + components = name.replace("-", "_").split("_") + return components[0].lower() + "".join(x.title() for x in components[1:]) + + def _to_pascal_case(self, name: str) -> str: + camel = self._to_camel_case(name) + if not camel: + return "" + return camel[0].upper() + camel[1:] + + def _infer_context( + self, + template_name: str, + entity_name: str | None, + ) -> dict[str, Any]: + template = self.template_manager.get_template(template_name) + if not template: + raise ValueError(f"Template not found: {template_name}") + + name = entity_name or template_name + patterns = template.get("patterns", {}) + primary_naming = patterns.get("summary", {}).get("primary_naming", "snake_case") + + context: dict[str, Any] = { + "project_name": "generated-project", + "entity_name": name, + "class_name": "", + "function_name": "", + "fields": [], + "methods": [], + "params": "", + "docstring": f"Generated {name}", + "class_docstring": f"Auto-generated class for {name}", + } + + if primary_naming == "PascalCase": + context["class_name"] = self._to_pascal_case(name) + context["function_name"] = self._to_camel_case(name) + elif primary_naming == "camelCase": + context["class_name"] = self._to_pascal_case(name) + context["function_name"] = self._to_camel_case(name) + else: + context["class_name"] = self._to_pascal_case(name) + context["function_name"] = name + + context["fields"] = [ + f"{self._to_camel_case(name)}Id", + f"{self._to_camel_case(name)}Name", + "createdAt", + "updatedAt", + ] + + context["methods"] = [ + {"name": "validate", "params": "", "docstring": "Validate the entity"}, + {"name": "to_dict", "params": "", "docstring": "Convert to dictionary"}, + {"name": "to_json", "params": "", "docstring": "Convert to JSON string"}, + ] + + context["init_params"] = ", ".join( + f"{self._to_camel_case(name)}{field}" + for field in context["fields"] + ) + context["params"] = ", ".join( + f"{self._to_camel_case(name)}{field}" for field in context["fields"][:2] + ) + + return context + + def _get_file_extension(self, template_name: str) -> str: + template = self.template_manager.get_template(template_name) + if not template: + return ".txt" + + lang = template.get("language", "") + extensions = { + "python": ".py", + "javascript": ".js", + "typescript": ".ts", + "java": ".java", + "cpp": ".cpp", + "c": ".c", + "rust": ".rs", + "go": ".go", + "ruby": ".rb", + } + return extensions.get(lang, ".txt") + + def generate( + self, + template_name: str, + output_dir: str, + data_file: str | None = None, + entity_name: str | None = None, + ) -> list[Path]: + output_path = Path(output_dir) + output_path.mkdir(parents=True, exist_ok=True) + + context = self._infer_context(template_name, entity_name) + + if data_file: + with open(data_file) as f: + data = json.load(f) + context.update(data) + + try: + content = self.template_manager.render_template(template_name, context) + except ValueError as e: + raise RuntimeError(f"Failed to render template: {e}") + + ext = self._get_file_extension(template_name) + filename = f"{entity_name or template_name}{ext}" + file_path = output_path / filename + file_path.write_text(content) + + return [file_path] + + def generate_multiple( + self, + template_name: str, + output_dir: str, + entities: list[dict[str, Any]], + ) -> list[Path]: + generated: list[Path] = [] + for entity in entities: + name = entity.get("name", "untitled") + paths = self.generate( + template_name, + output_dir, + entity.get("data_file"), + name, + ) + generated.extend(paths) + return generated