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 typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from jinja2 import (
|
||||
Environment,
|
||||
FileSystemLoader,
|
||||
PackageLoader,
|
||||
TemplateSyntaxError,
|
||||
)
|
||||
from jinja2 import BaseLoader, Environment, FileSystemLoader, TemplateSyntaxError
|
||||
|
||||
from scaffoldforge.parsers import IssueData
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
template_dir: Path to custom template directory.
|
||||
template_dir: Optional custom template directory.
|
||||
"""
|
||||
self.template_dir = template_dir
|
||||
self.env = self._create_environment()
|
||||
self._loaded_templates: dict[str, dict[str, str]] = {}
|
||||
self._templates: dict[str, Any] = {}
|
||||
self._loaded: dict[str, dict[str, Any]] = {}
|
||||
|
||||
def _create_environment(self) -> Environment:
|
||||
"""Create Jinja2 environment with appropriate loader."""
|
||||
if self.template_dir and Path(self.template_dir).exists():
|
||||
loader: FileSystemLoader | PackageLoader = FileSystemLoader(self.template_dir)
|
||||
def load_templates(self, language: str, template_name: str = "default") -> None:
|
||||
"""Load templates for a specific language and template.
|
||||
|
||||
Args:
|
||||
language: Programming language.
|
||||
template_name: Name of the template set.
|
||||
"""
|
||||
if self.template_dir:
|
||||
base_dir = self.template_dir
|
||||
else:
|
||||
loader = PackageLoader("scaffoldforge", "templates")
|
||||
base_dir = str(Path(__file__).parent)
|
||||
|
||||
return Environment(
|
||||
loader=loader,
|
||||
autoescape=True,
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
template_path = Path(base_dir) / language / template_name
|
||||
|
||||
def load_templates(
|
||||
self, language: str, template_name: str = "default"
|
||||
) -> dict[str, str]:
|
||||
"""Load templates for a specific language and template type.
|
||||
if not template_path.exists():
|
||||
return
|
||||
|
||||
Args:
|
||||
language: Programming language.
|
||||
template_name: Template variant name.
|
||||
loader = FileSystemLoader(str(template_path))
|
||||
env = Environment(loader=loader, autoescape=True)
|
||||
|
||||
Returns:
|
||||
Dictionary mapping template names to rendered content.
|
||||
"""
|
||||
key = f"{language}/{template_name}"
|
||||
if key in self._loaded_templates:
|
||||
return self._loaded_templates[key]
|
||||
self._templates[language] = env
|
||||
self._loaded[language] = {
|
||||
"path": str(template_path),
|
||||
"templates": list(env.list_templates()),
|
||||
}
|
||||
|
||||
templates: dict[str, 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]:
|
||||
def list_available_templates(self, language: str) -> list[str]:
|
||||
"""List available templates for a language.
|
||||
|
||||
Args:
|
||||
@@ -138,32 +57,49 @@ class TemplateEngine:
|
||||
Returns:
|
||||
List of template names.
|
||||
"""
|
||||
templates_dir = Path(__file__).parent / language
|
||||
if not templates_dir.exists():
|
||||
if self.template_dir:
|
||||
base_dir = self.template_dir
|
||||
else:
|
||||
base_dir = str(Path(__file__).parent)
|
||||
|
||||
template_path = Path(base_dir) / language
|
||||
|
||||
if not template_path.exists():
|
||||
return []
|
||||
|
||||
templates: list[str] = []
|
||||
for item in templates_dir.iterdir():
|
||||
if item.is_dir():
|
||||
templates.append(item.name)
|
||||
return sorted(templates)
|
||||
return [
|
||||
d.name
|
||||
for d in template_path.iterdir()
|
||||
if d.is_dir() and not d.name.startswith("_")
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def list_available_languages() -> list[str]:
|
||||
"""List all available programming languages.
|
||||
def render(
|
||||
self, template_name: str, context: dict[str, Any], language: str
|
||||
) -> str:
|
||||
"""Render a template with context.
|
||||
|
||||
Args:
|
||||
template_name: Name of the template file.
|
||||
context: Context dictionary for template rendering.
|
||||
language: Programming language.
|
||||
|
||||
Returns:
|
||||
List of language identifiers.
|
||||
Rendered template string.
|
||||
"""
|
||||
templates_dir = Path(__file__).parent
|
||||
if not templates_dir.exists():
|
||||
return []
|
||||
if language not in self._templates:
|
||||
self.load_templates(language)
|
||||
|
||||
languages: list[str] = []
|
||||
for item in templates_dir.iterdir():
|
||||
if item.is_dir() and not item.name.startswith("_"):
|
||||
languages.append(item.name)
|
||||
return sorted(languages)
|
||||
if language not in self._templates:
|
||||
raise ValueError(f"Templates not loaded for language: {language}")
|
||||
|
||||
env = self._templates[language]
|
||||
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]:
|
||||
"""Generate template context from issue data.
|
||||
@@ -172,51 +108,54 @@ class TemplateEngine:
|
||||
issue_data: IssueData object.
|
||||
|
||||
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_kebab": self._to_kebab_case(project_name),
|
||||
"project_name_snake": self._to_snake_case(project_name),
|
||||
"project_name_pascal": self._to_pascal_case(project_name),
|
||||
"project_name_kebab": project_name.lower().replace("_", "-"),
|
||||
"project_name_snake": project_name.lower().replace("-", "_"),
|
||||
"project_name_pascal": "".join(
|
||||
word.capitalize() for word in re.findall(r"[a-zA-Z]+", project_name)
|
||||
),
|
||||
"issue_number": issue_data.number,
|
||||
"issue_title": issue_data.title,
|
||||
"issue_url": issue_data.url,
|
||||
"repository": issue_data.repository,
|
||||
"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(),
|
||||
"completed_items": issue_data.get_completed_items(),
|
||||
"requirements": issue_data.requirements,
|
||||
"acceptance_criteria": issue_data.acceptance_criteria,
|
||||
"checklist": issue_data.checklist,
|
||||
}
|
||||
|
||||
def _generate_project_name(self, issue_data: IssueData) -> str:
|
||||
"""Generate a project name from issue title.
|
||||
return context
|
||||
|
||||
def _sanitize_name(self, name: str) -> str:
|
||||
"""Sanitize a string for use as a project name.
|
||||
|
||||
Args:
|
||||
issue_data: IssueData object.
|
||||
name: Original name.
|
||||
|
||||
Returns:
|
||||
Project name string.
|
||||
Sanitized name.
|
||||
"""
|
||||
title = issue_data.title
|
||||
title = re.sub(r"[^a-zA-Z0-9\s]", "", title)
|
||||
title = re.sub(r"\s+", "_", title.strip())
|
||||
return title.lower()[:50]
|
||||
import re
|
||||
|
||||
def _to_kebab_case(self, text: str) -> str:
|
||||
"""Convert text to kebab-case."""
|
||||
return re.sub(r"[^a-zA-Z0-9]+", "-", text).strip("-").lower()
|
||||
name = re.sub(r"[^a-zA-Z0-9\s]", "", name)
|
||||
name = re.sub(r"\s+", " ", name.strip())
|
||||
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:
|
||||
"""Convert text to PascalCase."""
|
||||
words = re.findall(r"[a-zA-Z0-9]+", text)
|
||||
return "".join(word.title() for word in words)
|
||||
def get_template_engine(template_dir: str | None = None) -> TemplateEngine:
|
||||
"""Get a template engine instance.
|
||||
|
||||
Args:
|
||||
template_dir: Optional custom template directory.
|
||||
|
||||
Returns:
|
||||
TemplateEngine instance.
|
||||
"""
|
||||
return TemplateEngine(template_dir)
|
||||
Reference in New Issue
Block a user