141 lines
5.5 KiB
Python
141 lines
5.5 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
JSON code renderer for code generation.
|
|
"""
|
|
|
|
from .codeRendererBaseTemplate import BaseCodeRenderer
|
|
from modules.datamodels.datamodelDocument import RenderedDocument
|
|
from typing import Dict, Any, List, Optional
|
|
import json
|
|
|
|
class RendererCodeJson(BaseCodeRenderer):
|
|
"""Renders JSON code files."""
|
|
|
|
@classmethod
|
|
def getSupportedFormats(cls) -> List[str]:
|
|
"""Return supported JSON formats."""
|
|
return ['json']
|
|
|
|
@classmethod
|
|
def getFormatAliases(cls) -> List[str]:
|
|
"""Return format aliases."""
|
|
return []
|
|
|
|
@classmethod
|
|
def getPriority(cls) -> int:
|
|
"""Return priority for JSON code renderer."""
|
|
return 85 # Higher than document renderer (80) for code generation
|
|
|
|
@classmethod
|
|
def getOutputStyle(cls, formatName: Optional[str] = None) -> str:
|
|
"""Return output style classification: JSON is structured data format."""
|
|
return 'code'
|
|
|
|
async def renderCodeFiles(
|
|
self,
|
|
codeFiles: List[Dict[str, Any]],
|
|
metadata: Dict[str, Any],
|
|
userPrompt: str = None
|
|
) -> List[RenderedDocument]:
|
|
"""
|
|
Render JSON code files.
|
|
For single file: output as-is
|
|
For multiple files: output separately (each file is independent JSON)
|
|
"""
|
|
renderedDocs = []
|
|
|
|
for codeFile in codeFiles:
|
|
if not self._validateCodeFile(codeFile):
|
|
self.logger.warning(f"Invalid code file: {codeFile.get('filename', 'unknown')}")
|
|
continue
|
|
|
|
filename = codeFile['filename']
|
|
content = codeFile['content']
|
|
|
|
# Validate JSON syntax and extract statistics
|
|
parsed = None
|
|
try:
|
|
parsed = json.loads(content) # Validate JSON
|
|
except json.JSONDecodeError as e:
|
|
self.logger.warning(f"Invalid JSON in {filename}: {e}")
|
|
# Could fix/format JSON here if needed
|
|
|
|
# Format JSON (pretty print)
|
|
try:
|
|
if parsed is None:
|
|
parsed = json.loads(content)
|
|
formattedContent = json.dumps(parsed, indent=2, ensure_ascii=False)
|
|
except Exception:
|
|
formattedContent = content # Use original if formatting fails
|
|
|
|
# Extract JSON statistics for validation
|
|
jsonStats = self._extractJsonStatistics(parsed) if parsed else {}
|
|
|
|
# Merge file-specific metadata with project metadata
|
|
fileMetadata = dict(metadata) if metadata else {}
|
|
fileMetadata.update({
|
|
"filename": filename,
|
|
"fileType": "json",
|
|
"statistics": jsonStats
|
|
})
|
|
|
|
renderedDocs.append(
|
|
RenderedDocument(
|
|
documentData=formattedContent.encode('utf-8'),
|
|
mimeType="application/json",
|
|
filename=filename,
|
|
metadata=fileMetadata
|
|
)
|
|
)
|
|
|
|
return renderedDocs
|
|
|
|
async def render(self, extractedContent: Dict[str, Any], title: str, userPrompt: str = None, aiService=None) -> List[RenderedDocument]:
|
|
"""
|
|
Render method for document generation compatibility.
|
|
Delegates to document renderer if needed, or handles code files directly.
|
|
"""
|
|
# Check if this is code generation (has files array) or document generation (has documents array)
|
|
if "files" in extractedContent:
|
|
# Code generation path - use renderCodeFiles
|
|
files = extractedContent.get("files", [])
|
|
metadata = extractedContent.get("metadata", {})
|
|
return await self.renderCodeFiles(files, metadata, userPrompt)
|
|
else:
|
|
# Document generation path - delegate to document renderer
|
|
# Import here to avoid circular dependency
|
|
from .rendererJson import RendererJson
|
|
documentRenderer = RendererJson(self.services)
|
|
return await documentRenderer.render(extractedContent, title, userPrompt, aiService)
|
|
|
|
def _extractJsonStatistics(self, parsed: Any) -> Dict[str, Any]:
|
|
"""Extract JSON statistics for validation (object count, array count, key count)."""
|
|
try:
|
|
stats = {
|
|
"isArray": isinstance(parsed, list),
|
|
"isObject": isinstance(parsed, dict),
|
|
"itemCount": 0,
|
|
"keyCount": 0
|
|
}
|
|
|
|
if isinstance(parsed, list):
|
|
stats["itemCount"] = len(parsed)
|
|
# Count nested objects/arrays
|
|
objectCount = sum(1 for item in parsed if isinstance(item, dict))
|
|
arrayCount = sum(1 for item in parsed if isinstance(item, list))
|
|
stats["objectCount"] = objectCount
|
|
stats["arrayCount"] = arrayCount
|
|
elif isinstance(parsed, dict):
|
|
stats["keyCount"] = len(parsed)
|
|
stats["keys"] = list(parsed.keys())
|
|
# Count nested objects/arrays
|
|
objectCount = sum(1 for v in parsed.values() if isinstance(v, dict))
|
|
arrayCount = sum(1 for v in parsed.values() if isinstance(v, list))
|
|
stats["objectCount"] = objectCount
|
|
stats["arrayCount"] = arrayCount
|
|
|
|
return stats
|
|
except Exception as e:
|
|
self.logger.warning(f"JSON statistics extraction failed: {e}")
|
|
return {}
|