Initial upload: PatternForge CLI tool with pattern detection and boilerplate generation
This commit is contained in:
151
src/patternforge/generator.py
Normal file
151
src/patternforge/generator.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user