fix: resolve CI linting and type checking errors
Some checks failed
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled

This commit is contained in:
2026-02-04 06:28:13 +00:00
parent 1639e9e5b4
commit b8c47f6b46

View File

@@ -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)