Initial upload: ScaffoldForge CLI tool with full codebase, tests, and CI/CD
This commit is contained in:
222
scaffoldforge/generators/code.py
Normal file
222
scaffoldforge/generators/code.py
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
"""Code generation functionality."""
|
||||||
|
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
from scaffoldforge.parsers import IssueData
|
||||||
|
from scaffoldforge.templates import TemplateEngine
|
||||||
|
from scaffoldforge.generators.models import FileSpec
|
||||||
|
|
||||||
|
|
||||||
|
class CodeGenerator:
|
||||||
|
"""Generator for file content using templates."""
|
||||||
|
|
||||||
|
DEFAULT_FILES = {
|
||||||
|
"python": ["main.py", "utils.py", "models.py", "cli.py"],
|
||||||
|
"javascript": ["index.js", "utils.js"],
|
||||||
|
"go": ["main.go", "utils.go"],
|
||||||
|
"rust": ["main.rs", "lib.rs"],
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_FILES = {
|
||||||
|
"python": ["pyproject.toml", "requirements.txt"],
|
||||||
|
"javascript": ["package.json", ".eslintrc.json"],
|
||||||
|
"go": ["go.mod"],
|
||||||
|
"rust": ["Cargo.toml"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, template_engine: TemplateEngine, issue_data: IssueData):
|
||||||
|
"""Initialize the code generator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
template_engine: TemplateEngine instance.
|
||||||
|
issue_data: Parsed issue data.
|
||||||
|
"""
|
||||||
|
self.template_engine = template_engine
|
||||||
|
self.issue_data = issue_data
|
||||||
|
|
||||||
|
def generate_all_files(
|
||||||
|
self, language: str, issue_data: IssueData
|
||||||
|
) -> List[FileSpec]:
|
||||||
|
"""Generate all project files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: Programming language.
|
||||||
|
issue_data: Parsed issue data.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of FileSpec objects.
|
||||||
|
"""
|
||||||
|
files = []
|
||||||
|
context = self.template_engine.get_template_context(issue_data)
|
||||||
|
|
||||||
|
files.extend(self._generate_source_files(language, context))
|
||||||
|
files.extend(self._generate_config_files(language, context))
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
def _generate_source_files(
|
||||||
|
self, language: str, context: Dict[str, Any]
|
||||||
|
) -> List[FileSpec]:
|
||||||
|
"""Generate source code files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: Programming language.
|
||||||
|
context: Template context.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of FileSpec objects.
|
||||||
|
"""
|
||||||
|
files = []
|
||||||
|
source_templates = self.DEFAULT_FILES.get(language, [])
|
||||||
|
|
||||||
|
for template_name in source_templates:
|
||||||
|
try:
|
||||||
|
content = self.template_engine.render(
|
||||||
|
template_name, context, language
|
||||||
|
)
|
||||||
|
extension = self._get_extension(language)
|
||||||
|
filename = f"{template_name}{extension}" if not template_name.endswith(extension) else template_name
|
||||||
|
path = self._get_source_path(template_name, language)
|
||||||
|
files.append(FileSpec(path=path, content=content))
|
||||||
|
except ValueError:
|
||||||
|
files.append(self._create_empty_file(template_name, language, context))
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
def _generate_config_files(
|
||||||
|
self, language: str, context: Dict[str, Any]
|
||||||
|
) -> List[FileSpec]:
|
||||||
|
"""Generate configuration files.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: Programming language.
|
||||||
|
context: Template context.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of FileSpec objects.
|
||||||
|
"""
|
||||||
|
files = []
|
||||||
|
config_templates = self.CONFIG_FILES.get(language, [])
|
||||||
|
|
||||||
|
for config_name in config_templates:
|
||||||
|
try:
|
||||||
|
content = self.template_engine.render(
|
||||||
|
config_name, context, language
|
||||||
|
)
|
||||||
|
files.append(FileSpec(path=config_name, content=content))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
def _create_empty_file(
|
||||||
|
self, template_name: str, language: str, context: Dict[str, Any]
|
||||||
|
) -> FileSpec:
|
||||||
|
"""Create an empty file with TODO comments.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
template_name: Name of the template.
|
||||||
|
language: Programming language.
|
||||||
|
context: Template context.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
FileSpec object.
|
||||||
|
"""
|
||||||
|
extension = self._get_extension(language)
|
||||||
|
filename = f"{template_name}{extension}"
|
||||||
|
path = self._get_source_path(template_name, language)
|
||||||
|
|
||||||
|
todo_items = self.issue_data.get_todo_items()
|
||||||
|
|
||||||
|
content = self._generate_todo_content(
|
||||||
|
language, template_name, todo_items
|
||||||
|
)
|
||||||
|
|
||||||
|
return FileSpec(path=path, content=content)
|
||||||
|
|
||||||
|
def _generate_todo_content(
|
||||||
|
self, language: str, template_name: str, todo_items: List[str]
|
||||||
|
) -> str:
|
||||||
|
"""Generate TODO comments for a file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: Programming language.
|
||||||
|
template_name: Name of the template.
|
||||||
|
todo_items: List of TODO items.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TODO comment content.
|
||||||
|
"""
|
||||||
|
todo_header = f"# TODO Items from GitHub Issue #{self.issue_data.number}"
|
||||||
|
lines = [todo_header]
|
||||||
|
|
||||||
|
for item in todo_items[:5]:
|
||||||
|
lines.append(f"# TODO: {item}")
|
||||||
|
|
||||||
|
if language == "python":
|
||||||
|
return f'"""{template_name} - {self.issue_data.title}"""\n\n' + "\n".join(
|
||||||
|
lines
|
||||||
|
)
|
||||||
|
elif language in ("javascript",):
|
||||||
|
return f"/**\n * {template_name}\n */\n\n" + "\n".join(lines)
|
||||||
|
elif language == "go":
|
||||||
|
return f"// {template_name}\n\n" + "\n".join(lines)
|
||||||
|
elif language == "rust":
|
||||||
|
return f"// {template_name}\n\n" + "\n".join(lines)
|
||||||
|
else:
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _get_extension(self, language: str) -> str:
|
||||||
|
"""Get file extension for language.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
language: Programming language.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
File extension with dot.
|
||||||
|
"""
|
||||||
|
extensions = {
|
||||||
|
"python": ".py",
|
||||||
|
"javascript": ".js",
|
||||||
|
"go": ".go",
|
||||||
|
"rust": ".rs",
|
||||||
|
}
|
||||||
|
return extensions.get(language, ".txt")
|
||||||
|
|
||||||
|
def _get_source_path(self, template_name: str, language: str) -> str:
|
||||||
|
"""Get the path for a source file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
template_name: Name of the template.
|
||||||
|
language: Programming language.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
File path.
|
||||||
|
"""
|
||||||
|
src_templates = {"models", "utils", "handlers", "lib"}
|
||||||
|
if template_name.lower() in src_templates:
|
||||||
|
return f"src/{template_name}{self._get_extension(language)}"
|
||||||
|
return f"{template_name}{self._get_extension(language)}"
|
||||||
|
|
||||||
|
def generate_single_file(
|
||||||
|
self, filename: str, language: str, extra_context: Dict[str, Any] = None
|
||||||
|
) -> str:
|
||||||
|
"""Generate content for a single file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename: Name of the file.
|
||||||
|
language: Programming language.
|
||||||
|
extra_context: Additional template context.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Generated file content.
|
||||||
|
"""
|
||||||
|
context = self.template_engine.get_template_context(self.issue_data)
|
||||||
|
if extra_context:
|
||||||
|
context.update(extra_context)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.template_engine.render(filename, context, language)
|
||||||
|
except ValueError:
|
||||||
|
empty_file = self._create_empty_file(filename, language, {})
|
||||||
|
return empty_file.content
|
||||||
Reference in New Issue
Block a user