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