Merge pull request #3079 from aftersomemath:sysid-pr
PiperOrigin-RevId: 868229512 Change-Id: I790bc08fc8b0745583a2f92d9ee2c5a19ba558ea
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
# Copyright 2026 DeepMind Technologies Limited
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
"""HTML report builder using Jinja2 templates."""
|
||||
|
||||
# report/builder.py
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import jinja2
|
||||
from mujoco.sysid.report.sections.base import ReportSection
|
||||
|
||||
# Path to the templates directory
|
||||
TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), "templates")
|
||||
|
||||
|
||||
class ReportBuilder:
|
||||
"""Assembles report sections into a complete HTML report."""
|
||||
|
||||
def __init__(self, title: str, global_context: dict[str, Any] | None = None):
|
||||
self._title = title
|
||||
self._sections: list[ReportSection] = []
|
||||
self._global_context = global_context or {}
|
||||
|
||||
# Setup Jinja to load from files
|
||||
self._env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader(TEMPLATE_DIR),
|
||||
autoescape=jinja2.select_autoescape(["html", "xml"]),
|
||||
)
|
||||
|
||||
def add_section(self, section: ReportSection):
|
||||
self._sections.append(section)
|
||||
|
||||
def build(self) -> str:
|
||||
"""Render all sections into a complete HTML report string."""
|
||||
# Load the main layout
|
||||
layout_template = self._env.get_template("layout.html")
|
||||
|
||||
# Render each section individually
|
||||
rendered_sections = []
|
||||
all_header_includes = set()
|
||||
|
||||
for section in self._sections:
|
||||
# Get the specific template for this section
|
||||
sec_template = self._env.get_template(section.template_filename)
|
||||
|
||||
# Check if this is a GroupSection (has 'sections' attribute)
|
||||
extra_context = {}
|
||||
child_sections: list[ReportSection] = getattr(section, "sections", [])
|
||||
if child_sections:
|
||||
child_sections_content = []
|
||||
for child in child_sections:
|
||||
child_template = self._env.get_template(child.template_filename)
|
||||
child_html = child_template.render(child.get_context())
|
||||
all_header_includes.update(child.header_includes())
|
||||
|
||||
# Fallback anchor for child
|
||||
child_anchor = child.anchor
|
||||
if not child_anchor:
|
||||
child_anchor = (
|
||||
child.title.lower()
|
||||
.replace(" ", "-")
|
||||
.replace("[", "")
|
||||
.replace("]", "")
|
||||
.replace("(", "")
|
||||
.replace(")", "")
|
||||
)
|
||||
|
||||
child_sections_content.append({
|
||||
"title": child.title,
|
||||
"content": child_html,
|
||||
"anchor": child_anchor,
|
||||
})
|
||||
extra_context["child_sections"] = child_sections_content
|
||||
|
||||
# Render the section HTML (main wrapper)
|
||||
html_content = sec_template.render(section.get_context() | extra_context)
|
||||
# Collect header requirements (scripts/css)
|
||||
all_header_includes.update(section.header_includes())
|
||||
|
||||
# Fallback anchor generation
|
||||
anchor = section.anchor
|
||||
if not anchor:
|
||||
anchor = (
|
||||
section.title.lower()
|
||||
.replace(" ", "-")
|
||||
.replace("[", "")
|
||||
.replace("]", "")
|
||||
.replace("(", "")
|
||||
.replace(")", "")
|
||||
)
|
||||
|
||||
rendered_sections.append({
|
||||
"title": section.title,
|
||||
"anchor": anchor,
|
||||
"collapsible": section.collapsible,
|
||||
"is_open": section.is_open,
|
||||
"content": html_content,
|
||||
})
|
||||
|
||||
# Render final report
|
||||
return layout_template.render(
|
||||
report_title=self._title,
|
||||
sections=rendered_sections,
|
||||
header_includes=all_header_includes,
|
||||
**self._global_context,
|
||||
)
|
||||
|
||||
def save(self, path: str):
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(self.build())
|
||||
Reference in New Issue
Block a user