html_reporter
¶
Full name: tenets.core.reporting.html_reporter
html_reporter¶
HTML report generator module.
This module provides HTML report generation functionality with rich visualizations, interactive charts, and professional styling. It creates standalone HTML reports that can be viewed in any modern web browser.
The HTML reporter generates responsive, interactive reports with embedded JavaScript visualizations and customizable themes.
Classes¶
HTMLTemplate¶
HTMLTemplate(theme: str = 'default', custom_css: Optional[str] = None, include_charts: bool = True)
HTML template generator for reports.
Provides template generation for various report components including the main layout, charts, tables, and interactive elements.
ATTRIBUTE | DESCRIPTION |
---|---|
theme | Visual theme name |
custom_css | Custom CSS styles |
include_charts | Whether to include chart libraries |
Initialize HTML template.
PARAMETER | DESCRIPTION |
---|---|
theme | Theme name TYPE: |
custom_css | Custom CSS styles |
include_charts | Include chart libraries TYPE: |
Source code in tenets/core/reporting/html_reporter.py
def __init__(
self, theme: str = "default", custom_css: Optional[str] = None, include_charts: bool = True
):
"""Initialize HTML template.
Args:
theme: Theme name
custom_css: Custom CSS styles
include_charts: Include chart libraries
"""
self.theme = theme
self.custom_css = custom_css
self.include_charts = include_charts
Functions¶
get_base_template¶
Get base HTML template.
RETURNS | DESCRIPTION |
---|---|
str | Base HTML template TYPE: |
Source code in tenets/core/reporting/html_reporter.py
def get_base_template(self) -> str:
"""Get base HTML template.
Returns:
str: Base HTML template
"""
return """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
{styles}
{scripts}
</head>
<body>
<div class="container">
{header}
{navigation}
<main class="content">
{content}
</main>
{footer}
</div>
{chart_scripts}
</body>
</html>"""
get_styles¶
Get CSS styles for the report.
RETURNS | DESCRIPTION |
---|---|
str | CSS styles TYPE: |
Source code in tenets/core/reporting/html_reporter.py
def get_styles(self) -> str:
"""Get CSS styles for the report.
Returns:
str: CSS styles
"""
base_styles = """
<style>
:root {
--primary-color: #2563eb;
--secondary-color: #64748b;
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--info-color: #06b6d4;
--background: #ffffff;
--surface: #f8fafc;
--text-primary: #1e293b;
--text-secondary: #64748b;
--border: #e2e8f0;
--shadow: rgba(0, 0, 0, 0.1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: var(--text-primary);
background: var(--background);
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Header */
.header {
background: linear-gradient(135deg, var(--primary-color), #8b5cf6);
color: white;
padding: 40px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 10px 30px var(--shadow);
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.header .meta {
opacity: 0.9;
font-size: 0.95rem;
}
.header .score {
display: inline-block;
background: rgba(255, 255, 255, 0.2);
padding: 8px 16px;
border-radius: 20px;
margin-top: 15px;
font-weight: 600;
}
/* Navigation */
.nav {
background: var(--surface);
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 30px;
position: sticky;
top: 20px;
z-index: 100;
box-shadow: 0 2px 10px var(--shadow);
}
.nav ul {
list-style: none;
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.nav a {
color: var(--text-primary);
text-decoration: none;
padding: 8px 16px;
border-radius: 6px;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.nav a:hover {
background: var(--primary-color);
color: white;
}
.nav a.active {
background: var(--primary-color);
color: white;
}
/* Sections */
.section {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 2px 10px var(--shadow);
}
.section h2 {
color: var(--text-primary);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
display: flex;
align-items: center;
gap: 10px;
}
.section h3 {
color: var(--text-primary);
margin: 20px 0 15px;
font-size: 1.2rem;
}
/* Tables */
.table-wrapper {
overflow-x: auto;
margin: 20px 0;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 0.95rem;
}
th {
background: var(--surface);
color: var(--text-primary);
font-weight: 600;
text-align: left;
padding: 12px;
border-bottom: 2px solid var(--border);
}
td {
padding: 12px;
border-bottom: 1px solid var(--border);
}
tr:hover {
background: var(--surface);
}
/* Badges */
.badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 600;
text-transform: uppercase;
}
.badge-critical {
background: var(--danger-color);
color: white;
}
.badge-high {
background: #f97316;
color: white;
}
.badge-medium {
background: var(--warning-color);
color: white;
}
.badge-low {
background: var(--success-color);
color: white;
}
.badge-info {
background: var(--info-color);
color: white;
}
/* Charts */
.chart-container {
margin: 20px 0;
padding: 20px;
background: var(--surface);
border-radius: 8px;
min-height: 300px;
}
.chart-title {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 15px;
text-align: center;
}
/* Code Snippets */
.code-snippet {
background: #1e293b;
color: #e2e8f0;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
overflow-x: auto;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.5;
}
.code-snippet .line-number {
display: inline-block;
width: 40px;
color: #64748b;
text-align: right;
margin-right: 15px;
user-select: none;
}
.code-snippet .highlight {
background: rgba(251, 191, 36, 0.2);
display: block;
}
/* Progress Bars */
.progress {
height: 24px;
background: var(--border);
border-radius: 12px;
overflow: hidden;
margin: 10px 0;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), #8b5cf6);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 0.85rem;
font-weight: 600;
transition: width 0.6s ease;
}
/* Metrics Grid */
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 20px 0;
}
.metric-card {
background: var(--surface);
padding: 20px;
border-radius: 8px;
text-align: center;
transition: transform 0.3s;
position: relative;
}
.metric-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 20px var(--shadow);
}
/* Tooltip styles */
.metric-card[data-tooltip]:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 0.85rem;
z-index: 1000;
margin-bottom: 10px;
max-width: 250px;
white-space: normal;
text-align: left;
line-height: 1.4;
}
.metric-card[data-tooltip]:hover::before {
content: "";
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
border: 6px solid transparent;
border-top-color: #333;
margin-bottom: 4px;
z-index: 1000;
}
.metric-value {
font-size: 2rem;
font-weight: 700;
color: var(--primary-color);
margin: 10px 0;
}
.metric-label {
color: var(--text-secondary);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 1px;
}
/* Collapsible Sections */
.collapsible {
cursor: pointer;
user-select: none;
}
.collapsible::before {
content: '▼';
display: inline-block;
margin-right: 8px;
transition: transform 0.3s;
}
.collapsible.collapsed::before {
transform: rotate(-90deg);
}
.collapsible-content {
max-height: 2000px;
overflow: hidden;
transition: max-height 0.3s ease;
}
.collapsible-content.collapsed {
max-height: 0;
}
/* Alerts */
.alert {
padding: 15px 20px;
border-radius: 8px;
margin: 20px 0;
display: flex;
align-items: center;
gap: 15px;
}
.alert-success {
background: #10b98120;
border-left: 4px solid var(--success-color);
color: #047857;
}
.alert-warning {
background: #f59e0b20;
border-left: 4px solid var(--warning-color);
color: #b45309;
}
.alert-danger {
background: #ef444420;
border-left: 4px solid var(--danger-color);
color: #b91c1c;
}
.alert-info {
background: #06b6d420;
border-left: 4px solid var(--info-color);
color: #0e7490;
}
/* Footer */
.footer {
text-align: center;
padding: 30px;
color: var(--text-secondary);
border-top: 1px solid var(--border);
margin-top: 50px;
}
/* Responsive */
@media (max-width: 768px) {
.container {
padding: 10px;
}
.header {
padding: 20px;
}
.header h1 {
font-size: 1.8rem;
}
.section {
padding: 20px;
}
.metrics-grid {
grid-template-columns: 1fr;
}
.nav ul {
flex-direction: column;
gap: 10px;
}
}
/* Dark Theme */
@media (prefers-color-scheme: dark) {
:root {
--background: #0f172a;
--surface: #1e293b;
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--border: #334155;
--shadow: rgba(0, 0, 0, 0.3);
}
.code-snippet {
background: #0f172a;
}
}
/* Print Styles */
@media print {
.nav {
display: none;
}
.section {
page-break-inside: avoid;
box-shadow: none;
border: 1px solid var(--border);
}
.chart-container {
page-break-inside: avoid;
}
}
</style>
"""
# Add custom CSS if provided
if self.custom_css:
base_styles += f"\n<style>\n{self.custom_css}\n</style>"
# Add theme-specific styles
if self.theme == "dark":
base_styles += self._get_dark_theme_styles()
elif self.theme == "corporate":
base_styles += self._get_corporate_theme_styles()
return base_styles
get_scripts¶
Get JavaScript libraries and scripts.
RETURNS | DESCRIPTION |
---|---|
str | Script tags TYPE: |
Source code in tenets/core/reporting/html_reporter.py
def get_scripts(self) -> str:
"""Get JavaScript libraries and scripts.
Returns:
str: Script tags
"""
scripts = []
if self.include_charts:
# Include Chart.js for charts
scripts.append(
'<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>'
)
# Include Prism.js for code highlighting
scripts.append(
'<link href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet">'
)
scripts.append(
'<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>'
)
scripts.append(
'<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>'
)
return "\n ".join(scripts)
get_navigation¶
Generate navigation menu.
PARAMETER | DESCRIPTION |
---|---|
sections | Report sections TYPE: |
RETURNS | DESCRIPTION |
---|---|
str | Navigation HTML TYPE: |
Source code in tenets/core/reporting/html_reporter.py
def get_navigation(self, sections: List[ReportSection]) -> str:
"""Generate navigation menu.
Args:
sections: Report sections
Returns:
str: Navigation HTML
"""
nav_items = []
for section in sections:
if section.visible:
icon = section.icon if section.icon else ""
# Preserve emoji/icons as-is; HTML is written as UTF-8
nav_items.append(f'<li><a href="#{section.id}">{icon} {section.title}</a></li>')
return f"""
<nav class="nav">
<ul>
{" ".join(nav_items)}
</ul>
</nav>
"""
HTMLReporter¶
HTML report generator.
Generates standalone HTML reports with rich visualizations and interactive elements from analysis results.
ATTRIBUTE | DESCRIPTION |
---|---|
config | Configuration object |
logger | Logger instance |
template | HTML template generator |
Initialize HTML reporter.
PARAMETER | DESCRIPTION |
---|---|
config | TenetsConfig instance TYPE: |
Source code in tenets/core/reporting/html_reporter.py
Functions¶
generate¶
generate(sections: List[ReportSection], metadata: Dict[str, Any], output_path: Path, report_config: ReportConfig) -> Path
Generate HTML report.
PARAMETER | DESCRIPTION |
---|---|
sections | Report sections TYPE: |
metadata | Report metadata |
output_path | Output file path TYPE: |
report_config | Report configuration TYPE: |
RETURNS | DESCRIPTION |
---|---|
Path | Path to generated report TYPE: |
Source code in tenets/core/reporting/html_reporter.py
def generate(
self,
sections: List[ReportSection],
metadata: Dict[str, Any],
output_path: Path,
report_config: ReportConfig,
) -> Path:
"""Generate HTML report.
Args:
sections: Report sections
metadata: Report metadata
output_path: Output file path
report_config: Report configuration
Returns:
Path: Path to generated report
"""
self.logger.debug(f"Generating HTML report to {output_path}")
# Set template configuration
self.template = HTMLTemplate(
theme=report_config.theme,
custom_css=self._load_custom_css(report_config.custom_css),
include_charts=report_config.include_charts,
)
# Generate HTML content
html_content = self._generate_html(sections, metadata, report_config)
# Ensure output is ASCII-safe for environments that read with
# platform default encodings (e.g., cp1252 on Windows). Convert
# non-ASCII characters to HTML entities to avoid decode errors
# when tests read the file without specifying encoding.
try:
safe_content = html_content.encode("ascii", "xmlcharrefreplace").decode("ascii")
except Exception:
safe_content = html_content # Fallback; still write as-is
# Write to file
with open(output_path, "w", encoding="utf-8") as f:
f.write(safe_content)
self.logger.info(f"HTML report generated: {output_path}")
return output_path
Functions¶
create_html_report¶
create_html_report(sections: List[ReportSection], output_path: Path, title: str = 'Code Analysis Report', config: Optional[TenetsConfig] = None) -> Path
Convenience function to create HTML report.
PARAMETER | DESCRIPTION |
---|---|
sections | Report sections TYPE: |
output_path | Output path TYPE: |
title | Report title TYPE: |
config | Optional configuration TYPE: |
RETURNS | DESCRIPTION |
---|---|
Path | Path to generated report TYPE: |
Source code in tenets/core/reporting/html_reporter.py
def create_html_report(
sections: List[ReportSection],
output_path: Path,
title: str = "Code Analysis Report",
config: Optional[TenetsConfig] = None,
) -> Path:
"""Convenience function to create HTML report.
Args:
sections: Report sections
output_path: Output path
title: Report title
config: Optional configuration
Returns:
Path: Path to generated report
"""
if config is None:
config = TenetsConfig()
reporter = HTMLReporter(config)
report_config = ReportConfig(title=title, format="html")
metadata = {"title": title, "generated_at": datetime.now().isoformat()}
return reporter.generate(sections, metadata, output_path, report_config)
create_dashboard¶
create_dashboard(analysis_results: Dict[str, Any], output_path: Path, config: Optional[TenetsConfig] = None) -> Path
Create an interactive dashboard.
PARAMETER | DESCRIPTION |
---|---|
analysis_results | Analysis results |
output_path | Output path TYPE: |
config | Optional configuration TYPE: |
RETURNS | DESCRIPTION |
---|---|
Path | Path to dashboard TYPE: |
Source code in tenets/core/reporting/html_reporter.py
def create_dashboard(
analysis_results: Dict[str, Any], output_path: Path, config: Optional[TenetsConfig] = None
) -> Path:
"""Create an interactive dashboard.
Args:
analysis_results: Analysis results
output_path: Output path
config: Optional configuration
Returns:
Path: Path to dashboard
"""
# Dashboard is a specialized HTML report
if config is None:
config = TenetsConfig()
# Use module-level ReportGenerator symbol so tests can patch it via this module
generator = ReportGenerator(config) if ReportGenerator is not None else None
if generator is None:
# Fallback import if re-export failed for any reason
from .generator import ReportGenerator as _RG # type: ignore
generator = _RG(config)
report_config = ReportConfig(
title="Code Analysis Dashboard",
format="html",
include_charts=True,
include_toc=False,
)
return generator.generate(analysis_results, output_path, report_config)