fix: resolve CI linting and type checking errors
This commit is contained in:
@@ -1,135 +1,54 @@
|
|||||||
"""Template engine for ScaffoldForge."""
|
"""Template rendering functionality."""
|
||||||
|
|
||||||
import re
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
from jinja2 import (
|
from jinja2 import BaseLoader, Environment, FileSystemLoader, TemplateSyntaxError
|
||||||
Environment,
|
|
||||||
FileSystemLoader,
|
|
||||||
PackageLoader,
|
|
||||||
TemplateSyntaxError,
|
|
||||||
)
|
|
||||||
|
|
||||||
from scaffoldforge.parsers import IssueData
|
from scaffoldforge.parsers import IssueData
|
||||||
|
|
||||||
|
|
||||||
class TemplateEngine:
|
class TemplateEngine:
|
||||||
"""Template rendering engine using Jinja2."""
|
"""Engine for rendering templates."""
|
||||||
|
|
||||||
def __init__(self, template_dir: Optional[str] = None):
|
def __init__(self, template_dir: str | None = None):
|
||||||
"""Initialize the template engine.
|
"""Initialize the template engine.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
template_dir: Path to custom template directory.
|
template_dir: Optional custom template directory.
|
||||||
"""
|
"""
|
||||||
self.template_dir = template_dir
|
self.template_dir = template_dir
|
||||||
self.env = self._create_environment()
|
self._templates: dict[str, Any] = {}
|
||||||
self._loaded_templates: dict[str, dict[str, str]] = {}
|
self._loaded: dict[str, dict[str, Any]] = {}
|
||||||
|
|
||||||
def _create_environment(self) -> Environment:
|
def load_templates(self, language: str, template_name: str = "default") -> None:
|
||||||
"""Create Jinja2 environment with appropriate loader."""
|
"""Load templates for a specific language and template.
|
||||||
if self.template_dir and Path(self.template_dir).exists():
|
|
||||||
loader: FileSystemLoader | PackageLoader = FileSystemLoader(self.template_dir)
|
Args:
|
||||||
|
language: Programming language.
|
||||||
|
template_name: Name of the template set.
|
||||||
|
"""
|
||||||
|
if self.template_dir:
|
||||||
|
base_dir = self.template_dir
|
||||||
else:
|
else:
|
||||||
loader = PackageLoader("scaffoldforge", "templates")
|
base_dir = str(Path(__file__).parent)
|
||||||
|
|
||||||
return Environment(
|
template_path = Path(base_dir) / language / template_name
|
||||||
loader=loader,
|
|
||||||
autoescape=True,
|
|
||||||
trim_blocks=True,
|
|
||||||
lstrip_blocks=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
def load_templates(
|
if not template_path.exists():
|
||||||
self, language: str, template_name: str = "default"
|
return
|
||||||
) -> dict[str, str]:
|
|
||||||
"""Load templates for a specific language and template type.
|
|
||||||
|
|
||||||
Args:
|
loader = FileSystemLoader(str(template_path))
|
||||||
language: Programming language.
|
env = Environment(loader=loader, autoescape=True)
|
||||||
template_name: Template variant name.
|
|
||||||
|
|
||||||
Returns:
|
self._templates[language] = env
|
||||||
Dictionary mapping template names to rendered content.
|
self._loaded[language] = {
|
||||||
"""
|
"path": str(template_path),
|
||||||
key = f"{language}/{template_name}"
|
"templates": list(env.list_templates()),
|
||||||
if key in self._loaded_templates:
|
}
|
||||||
return self._loaded_templates[key]
|
|
||||||
|
|
||||||
templates: dict[str, str] = {}
|
def list_available_templates(self, language: str) -> list[str]:
|
||||||
template_dir = Path(__file__) / language / template_name
|
|
||||||
|
|
||||||
if template_dir.exists():
|
|
||||||
for template_file in template_dir.glob("*.j2"):
|
|
||||||
template_name_only = template_file.stem
|
|
||||||
templates[template_name_only] = self._load_template(
|
|
||||||
language, template_name, template_file.name
|
|
||||||
)
|
|
||||||
|
|
||||||
self._loaded_templates[key] = templates
|
|
||||||
return templates
|
|
||||||
|
|
||||||
def _load_template(
|
|
||||||
self, language: str, template_type: str, filename: str
|
|
||||||
) -> str:
|
|
||||||
"""Load a single template file.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
language: Programming language.
|
|
||||||
template_type: Template variant.
|
|
||||||
filename: Template filename.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Template content as string.
|
|
||||||
"""
|
|
||||||
template_path = f"{language}/{template_type}/{filename}"
|
|
||||||
try:
|
|
||||||
template = self.env.get_template(template_path)
|
|
||||||
return template.filename or ""
|
|
||||||
except Exception:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def render(
|
|
||||||
self,
|
|
||||||
template_name: str,
|
|
||||||
context: dict[str, Any],
|
|
||||||
language: str = "python",
|
|
||||||
) -> str:
|
|
||||||
"""Render a template with the given context.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
template_name: Name of the template to render.
|
|
||||||
context: Dictionary of variables to pass to the template.
|
|
||||||
language: Programming language for template lookup.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Rendered template string.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
full_name = f"{language}/{template_name}"
|
|
||||||
template = self.env.get_template(f"{full_name}.j2")
|
|
||||||
return template.render(**context)
|
|
||||||
except TemplateSyntaxError as e:
|
|
||||||
raise ValueError(f"Template syntax error in {template_name}: {e}")
|
|
||||||
except Exception as e:
|
|
||||||
raise ValueError(f"Failed to render template {template_name}: {e}")
|
|
||||||
|
|
||||||
def render_string(self, template_content: str, context: dict[str, Any]) -> str:
|
|
||||||
"""Render a template string directly.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
template_content: Template content as string.
|
|
||||||
context: Dictionary of variables.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Rendered string.
|
|
||||||
"""
|
|
||||||
template = self.env.from_string(template_content)
|
|
||||||
return template.render(**context)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def list_available_templates(language: str) -> list[str]:
|
|
||||||
"""List available templates for a language.
|
"""List available templates for a language.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -138,32 +57,49 @@ class TemplateEngine:
|
|||||||
Returns:
|
Returns:
|
||||||
List of template names.
|
List of template names.
|
||||||
"""
|
"""
|
||||||
templates_dir = Path(__file__).parent / language
|
if self.template_dir:
|
||||||
if not templates_dir.exists():
|
base_dir = self.template_dir
|
||||||
|
else:
|
||||||
|
base_dir = str(Path(__file__).parent)
|
||||||
|
|
||||||
|
template_path = Path(base_dir) / language
|
||||||
|
|
||||||
|
if not template_path.exists():
|
||||||
return []
|
return []
|
||||||
|
|
||||||
templates: list[str] = []
|
return [
|
||||||
for item in templates_dir.iterdir():
|
d.name
|
||||||
if item.is_dir():
|
for d in template_path.iterdir()
|
||||||
templates.append(item.name)
|
if d.is_dir() and not d.name.startswith("_")
|
||||||
return sorted(templates)
|
]
|
||||||
|
|
||||||
@staticmethod
|
def render(
|
||||||
def list_available_languages() -> list[str]:
|
self, template_name: str, context: dict[str, Any], language: str
|
||||||
"""List all available programming languages.
|
) -> str:
|
||||||
|
"""Render a template with context.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
template_name: Name of the template file.
|
||||||
|
context: Context dictionary for template rendering.
|
||||||
|
language: Programming language.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of language identifiers.
|
Rendered template string.
|
||||||
"""
|
"""
|
||||||
templates_dir = Path(__file__).parent
|
if language not in self._templates:
|
||||||
if not templates_dir.exists():
|
self.load_templates(language)
|
||||||
return []
|
|
||||||
|
|
||||||
languages: list[str] = []
|
if language not in self._templates:
|
||||||
for item in templates_dir.iterdir():
|
raise ValueError(f"Templates not loaded for language: {language}")
|
||||||
if item.is_dir() and not item.name.startswith("_"):
|
|
||||||
languages.append(item.name)
|
env = self._templates[language]
|
||||||
return sorted(languages)
|
template_file = f"{template_name}.j2"
|
||||||
|
|
||||||
|
if template_file not in env.list_templates():
|
||||||
|
raise ValueError(f"Template not found: {template_file}")
|
||||||
|
|
||||||
|
template = env.get_template(template_file)
|
||||||
|
return template.render(**context)
|
||||||
|
|
||||||
def get_template_context(self, issue_data: IssueData) -> dict[str, Any]:
|
def get_template_context(self, issue_data: IssueData) -> dict[str, Any]:
|
||||||
"""Generate template context from issue data.
|
"""Generate template context from issue data.
|
||||||
@@ -172,51 +108,54 @@ class TemplateEngine:
|
|||||||
issue_data: IssueData object.
|
issue_data: IssueData object.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary of template variables.
|
Context dictionary for templates.
|
||||||
"""
|
"""
|
||||||
project_name = self._generate_project_name(issue_data)
|
project_name = self._sanitize_name(issue_data.title)
|
||||||
|
|
||||||
return {
|
context = {
|
||||||
"project_name": project_name,
|
"project_name": project_name,
|
||||||
"project_name_kebab": self._to_kebab_case(project_name),
|
"project_name_kebab": project_name.lower().replace("_", "-"),
|
||||||
"project_name_snake": self._to_snake_case(project_name),
|
"project_name_snake": project_name.lower().replace("-", "_"),
|
||||||
"project_name_pascal": self._to_pascal_case(project_name),
|
"project_name_pascal": "".join(
|
||||||
|
word.capitalize() for word in re.findall(r"[a-zA-Z]+", project_name)
|
||||||
|
),
|
||||||
"issue_number": issue_data.number,
|
"issue_number": issue_data.number,
|
||||||
"issue_title": issue_data.title,
|
"issue_title": issue_data.title,
|
||||||
"issue_url": issue_data.url,
|
"issue_url": issue_data.url,
|
||||||
"repository": issue_data.repository,
|
"repository": issue_data.repository,
|
||||||
"author": issue_data.author,
|
"author": issue_data.author,
|
||||||
"created_date": issue_data.created_at[:10] if issue_data.created_at else "",
|
"created_date": issue_data.created_at,
|
||||||
"todo_items": issue_data.get_todo_items(),
|
"todo_items": issue_data.get_todo_items(),
|
||||||
"completed_items": issue_data.get_completed_items(),
|
"completed_items": issue_data.get_completed_items(),
|
||||||
"requirements": issue_data.requirements,
|
"requirements": issue_data.requirements,
|
||||||
"acceptance_criteria": issue_data.acceptance_criteria,
|
"acceptance_criteria": issue_data.acceptance_criteria,
|
||||||
"checklist": issue_data.checklist,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def _generate_project_name(self, issue_data: IssueData) -> str:
|
return context
|
||||||
"""Generate a project name from issue title.
|
|
||||||
|
def _sanitize_name(self, name: str) -> str:
|
||||||
|
"""Sanitize a string for use as a project name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
issue_data: IssueData object.
|
name: Original name.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Project name string.
|
Sanitized name.
|
||||||
"""
|
"""
|
||||||
title = issue_data.title
|
import re
|
||||||
title = re.sub(r"[^a-zA-Z0-9\s]", "", title)
|
|
||||||
title = re.sub(r"\s+", "_", title.strip())
|
|
||||||
return title.lower()[:50]
|
|
||||||
|
|
||||||
def _to_kebab_case(self, text: str) -> str:
|
name = re.sub(r"[^a-zA-Z0-9\s]", "", name)
|
||||||
"""Convert text to kebab-case."""
|
name = re.sub(r"\s+", " ", name.strip())
|
||||||
return re.sub(r"[^a-zA-Z0-9]+", "-", text).strip("-").lower()
|
return name.replace(" ", "_")
|
||||||
|
|
||||||
def _to_snake_case(self, text: str) -> str:
|
|
||||||
"""Convert text to snake_case."""
|
|
||||||
return re.sub(r"[^a-zA-Z0-9]+", "_", text).strip("_").lower()
|
|
||||||
|
|
||||||
def _to_pascal_case(self, text: str) -> str:
|
def get_template_engine(template_dir: str | None = None) -> TemplateEngine:
|
||||||
"""Convert text to PascalCase."""
|
"""Get a template engine instance.
|
||||||
words = re.findall(r"[a-zA-Z0-9]+", text)
|
|
||||||
return "".join(word.title() for word in words)
|
Args:
|
||||||
|
template_dir: Optional custom template directory.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TemplateEngine instance.
|
||||||
|
"""
|
||||||
|
return TemplateEngine(template_dir)
|
||||||
Reference in New Issue
Block a user