Initial upload: PatternForge CLI tool with pattern detection and boilerplate generation
This commit is contained in:
233
src/patternforge/template.py
Normal file
233
src/patternforge/template.py
Normal file
@@ -0,0 +1,233 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader, TemplateSyntaxError
|
||||
|
||||
from patternforge.config import Config
|
||||
|
||||
|
||||
class TemplateManager:
|
||||
def __init__(self, config: Config) -> None:
|
||||
self.config = config
|
||||
self._ensure_templates_dir()
|
||||
|
||||
def _ensure_templates_dir(self) -> None:
|
||||
self.config.templates_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _get_template_path(self, name: str) -> Path:
|
||||
return self.config.templates_dir / f"{name}.yaml"
|
||||
|
||||
def _get_pattern_path(self, name: str) -> Path:
|
||||
return self.config.patterns_dir / f"{name}.yaml"
|
||||
|
||||
def create_template(
|
||||
self,
|
||||
name: str,
|
||||
pattern_file: str,
|
||||
custom_template: str | None = None,
|
||||
description: str = "",
|
||||
) -> None:
|
||||
pattern_path = Path(pattern_file)
|
||||
if not pattern_path.exists():
|
||||
raise FileNotFoundError(f"Pattern file not found: {pattern_file}")
|
||||
|
||||
with open(pattern_path) as f:
|
||||
patterns = yaml.safe_load(f)
|
||||
|
||||
template_content = ""
|
||||
if custom_template:
|
||||
with open(custom_template) as f:
|
||||
template_content = f.read()
|
||||
else:
|
||||
template_content = self._generate_default_template(patterns)
|
||||
|
||||
template_data = {
|
||||
"name": name,
|
||||
"description": description,
|
||||
"created": datetime.now().isoformat(),
|
||||
"language": patterns.get("language", "unknown"),
|
||||
"patterns": patterns,
|
||||
"template": template_content,
|
||||
}
|
||||
|
||||
template_path = self._get_template_path(name)
|
||||
with open(template_path, "w") as f:
|
||||
yaml.dump(template_data, f, default_flow_style=False)
|
||||
|
||||
def _generate_default_template(self, patterns: dict[str, Any]) -> str:
|
||||
language = patterns.get("language", "python")
|
||||
|
||||
if language in ["python"]:
|
||||
return '''# Generated by PatternForge
|
||||
# Based on analyzed patterns from {{ project_name }}
|
||||
|
||||
{% if class_name %}
|
||||
class {{ class_name }}:
|
||||
"""{{ class_docstring }}"""
|
||||
|
||||
def __init__(self{% if init_params %}, {{ init_params }}{% endif %}):
|
||||
{% for field in fields %}
|
||||
self.{{ field }} = {{ field }}
|
||||
{% endfor %}
|
||||
{%- if methods %}
|
||||
{% for method in methods %}
|
||||
def {{ method.name }}(self{% if method.params %}, {{ method.params }}{% endif %}):
|
||||
"""{{ method.docstring }}"""
|
||||
pass
|
||||
{% endfor %}
|
||||
{%- endif %}
|
||||
{% elif function_name %}
|
||||
def {{ function_name }}({% if params %}{{ params }}{% endif %}):
|
||||
"""{{ docstring }}"""
|
||||
pass
|
||||
{% else %}
|
||||
# {{ entity_name }} - generated boilerplate
|
||||
{% endif %}
|
||||
'''
|
||||
elif language in ["javascript", "typescript"]:
|
||||
return '''// Generated by PatternForge
|
||||
{% if class_name %}
|
||||
export class {{ class_name }} {
|
||||
{% for field in fields %}
|
||||
private {{ field }};
|
||||
{% endfor %}
|
||||
|
||||
constructor({% if params %}{{ params }}{% endif %}) {
|
||||
{% for field in fields %}
|
||||
this.{{ field }} = {{ field }};
|
||||
{% endfor %}
|
||||
}
|
||||
{% if methods %}
|
||||
{% for method in methods %}
|
||||
{{ method.name }}({% if method.params %}{{ method.params }}{% endif %}) {
|
||||
// {{ method.docstring }}
|
||||
}
|
||||
{% endfor %}
|
||||
{%- endif %}
|
||||
}
|
||||
{% elif function_name %}
|
||||
export function {{ function_name }}({% if params %}{{ params }}{% endif %}) {
|
||||
// {{ docstring }}
|
||||
}
|
||||
{% else %}
|
||||
// {{ entity_name }} - generated boilerplate
|
||||
{% endif %}
|
||||
'''
|
||||
else:
|
||||
return '''// Generated by PatternForge
|
||||
// {{ entity_name }}
|
||||
|
||||
{% if class_name %}
|
||||
class {{ class_name }} {
|
||||
{% for field in fields %}
|
||||
{{ field }};
|
||||
{% endfor %}
|
||||
|
||||
constructor({% if params %}{{ params }}{% endif %}) {
|
||||
{% for field in fields %}
|
||||
this.{{ field }} = {{ field }};
|
||||
{% endfor %}
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
// {{ entity_name }}
|
||||
{% endif %}
|
||||
'''
|
||||
|
||||
def list_templates(self) -> list[dict[str, Any]]:
|
||||
templates: list[dict[str, Any]] = []
|
||||
if not self.config.templates_dir.exists():
|
||||
return templates
|
||||
for f in self.config.templates_dir.glob("*.yaml"):
|
||||
with open(f) as fp:
|
||||
data = yaml.safe_load(fp)
|
||||
if data:
|
||||
templates.append({
|
||||
"name": data.get("name", f.stem),
|
||||
"description": data.get("description", ""),
|
||||
"created": data.get("created", ""),
|
||||
"language": data.get("language", ""),
|
||||
})
|
||||
return templates
|
||||
|
||||
def get_template(self, name: str) -> dict[str, Any] | None:
|
||||
path = self._get_template_path(name)
|
||||
if not path.exists():
|
||||
return None
|
||||
with open(path) as f:
|
||||
return yaml.safe_load(f)
|
||||
|
||||
def remove_template(self, name: str) -> bool:
|
||||
path = self._get_template_path(name)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
return True
|
||||
return False
|
||||
|
||||
def render_template(
|
||||
self,
|
||||
name: str,
|
||||
context: dict[str, Any],
|
||||
) -> str:
|
||||
template_data = self.get_template(name)
|
||||
if not template_data:
|
||||
raise ValueError(f"Template not found: {name}")
|
||||
|
||||
template_str = template_data.get("template", "")
|
||||
try:
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(str(self.config.templates_dir)),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
template = env.from_string(template_str)
|
||||
return template.render(**context)
|
||||
except TemplateSyntaxError as e:
|
||||
raise ValueError(f"Template syntax error: {e}")
|
||||
|
||||
def export_patterns(
|
||||
self,
|
||||
source: str,
|
||||
destination: str,
|
||||
format: str = "yaml",
|
||||
) -> None:
|
||||
src = Path(source)
|
||||
dst = Path(destination)
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if src.is_file():
|
||||
with open(src) as f:
|
||||
data = yaml.safe_load(f)
|
||||
if format == "json":
|
||||
with open(dst, "w") as f:
|
||||
json.dump(data, f, indent=2)
|
||||
else:
|
||||
with open(dst, "w") as f:
|
||||
yaml.dump(data, f, default_flow_style=False)
|
||||
else:
|
||||
for f in src.glob("*.yaml"):
|
||||
with open(f) as fp:
|
||||
data = yaml.safe_load(fp)
|
||||
out_path = dst / f.name
|
||||
if format == "json":
|
||||
with open(out_path.with_suffix(".json"), "w") as fp:
|
||||
json.dump(data, fp, indent=2)
|
||||
else:
|
||||
with open(out_path, "w") as fp:
|
||||
yaml.dump(data, fp, default_flow_style=False)
|
||||
|
||||
def import_patterns(self, source: str) -> None:
|
||||
src = Path(source)
|
||||
if src.is_file():
|
||||
name = src.stem
|
||||
import_path = self._get_pattern_path(name)
|
||||
import_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
import_path.write_bytes(src.read_bytes())
|
||||
else:
|
||||
for f in src.glob("*.yaml"):
|
||||
import_path = self._get_pattern_path(f.stem)
|
||||
import_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
import_path.write_bytes(f.read_bytes())
|
||||
Reference in New Issue
Block a user