Add reports, integrations, and utils modules
This commit is contained in:
174
vibeguard/reports/generator.py
Normal file
174
vibeguard/reports/generator.py
Normal file
@@ -0,0 +1,174 @@
|
||||
"""Report generator for VibeGuard."""
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
|
||||
class ReportGenerator:
|
||||
"""Generates reports in various formats."""
|
||||
|
||||
def __init__(self, template_dir: str | None = None) -> None:
|
||||
"""Initialize report generator with templates."""
|
||||
self.template_dir = template_dir or str(
|
||||
Path(__file__).parent.parent / "templates"
|
||||
)
|
||||
self._env = Environment(
|
||||
loader=FileSystemLoader(self.template_dir),
|
||||
autoescape=True,
|
||||
)
|
||||
|
||||
def generate_json(
|
||||
self, issues: list[dict[str, Any]], output_path: str
|
||||
) -> None:
|
||||
"""Generate JSON report."""
|
||||
report = {
|
||||
"version": "0.1.0",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"summary": self._generate_summary(issues),
|
||||
"issues": issues,
|
||||
}
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
|
||||
def load_json(self, input_path: str) -> list[dict[str, Any]]:
|
||||
"""Load issues from JSON file."""
|
||||
with open(input_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
return data.get("issues", [])
|
||||
|
||||
def generate_html(
|
||||
self,
|
||||
issues: list[dict[str, Any]],
|
||||
output_path: str,
|
||||
summary: dict[str, int] | None = None,
|
||||
) -> None:
|
||||
"""Generate HTML report."""
|
||||
if summary is None:
|
||||
summary = self._generate_summary(issues)
|
||||
|
||||
template = self._env.get_template("report.html")
|
||||
html_content = template.render(
|
||||
title="VibeGuard Analysis Report",
|
||||
timestamp=datetime.utcnow().isoformat(),
|
||||
summary=summary,
|
||||
issues=issues,
|
||||
total_issues=len(issues),
|
||||
)
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(html_content)
|
||||
|
||||
def generate_sarif(
|
||||
self, issues: list[dict[str, Any]], output_path: str
|
||||
) -> None:
|
||||
"""Generate SARIF report for GitHub Code Scanning."""
|
||||
sarif = {
|
||||
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
||||
"version": "2.1.0",
|
||||
"runs": [
|
||||
{
|
||||
"tool": {
|
||||
"driver": {
|
||||
"name": "VibeGuard",
|
||||
"version": "0.1.0",
|
||||
"informationUri": "https://github.com/vibeguard/vibeguard",
|
||||
"rules": self._generate_sarif_rules(issues),
|
||||
}
|
||||
},
|
||||
"results": self._generate_sarif_results(issues),
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
json.dump(sarif, f, indent=2)
|
||||
|
||||
def _generate_summary(self, issues: list[dict[str, Any]]) -> dict[str, int]:
|
||||
"""Generate summary statistics."""
|
||||
summary: dict[str, int] = {
|
||||
"critical": 0,
|
||||
"error": 0,
|
||||
"warning": 0,
|
||||
"info": 0,
|
||||
"total": len(issues),
|
||||
}
|
||||
|
||||
for issue in issues:
|
||||
severity = issue.get("severity", "info")
|
||||
summary[severity] = summary.get(severity, 0) + 1
|
||||
|
||||
return summary
|
||||
|
||||
def _generate_sarif_rules(
|
||||
self, issues: list[dict[str, Any]]
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Generate SARIF rule definitions."""
|
||||
rules: dict[str, dict[str, Any]] = {}
|
||||
|
||||
for issue in issues:
|
||||
rule_id = issue.get("pattern", "UNKNOWN")
|
||||
if rule_id not in rules:
|
||||
rules[rule_id] = {
|
||||
"id": rule_id,
|
||||
"name": rule_id,
|
||||
"shortDescription": {
|
||||
"text": issue.get("message", "Unknown issue")
|
||||
},
|
||||
"fullDescription": {
|
||||
"text": issue.get("suggestion", "")
|
||||
},
|
||||
"defaultConfiguration": {
|
||||
"level": self._severity_to_sarif_level(
|
||||
issue.get("severity", "warning")
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
return list(rules.values())
|
||||
|
||||
def _generate_sarif_results(
|
||||
self, issues: list[dict[str, Any]]
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Generate SARIF result objects."""
|
||||
results: list[dict[str, Any]] = []
|
||||
|
||||
for issue in issues:
|
||||
result = {
|
||||
"ruleId": issue.get("pattern", "UNKNOWN"),
|
||||
"message": {
|
||||
"text": issue.get("message", "")
|
||||
},
|
||||
"level": self._severity_to_sarif_level(
|
||||
issue.get("severity", "warning")
|
||||
),
|
||||
"locations": [
|
||||
{
|
||||
"physicalLocation": {
|
||||
"artifactLocation": {
|
||||
"uri": issue.get("file", "")
|
||||
},
|
||||
"region": {
|
||||
"startLine": issue.get("line", 1),
|
||||
},
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
results.append(result)
|
||||
|
||||
return results
|
||||
|
||||
def _severity_to_sarif_level(self, severity: str) -> str:
|
||||
"""Convert VibeGuard severity to SARIF level."""
|
||||
mapping = {
|
||||
"critical": "error",
|
||||
"error": "error",
|
||||
"warning": "warning",
|
||||
"info": "note",
|
||||
}
|
||||
return mapping.get(severity, "warning")
|
||||
Reference in New Issue
Block a user