import os
import types
from pathlib import Path
from typing import Any, Dict
import minify_html
from cssmin import cssmin
from jinja2 import Template
from jsmin import jsmin
from sphinx.application import Sphinx
from sphinx.util.fileutil import copy_asset_file
from .config import active_class, icp_no, sitemap
[docs]
def render_banner(current_site="Docs") -> str:
"""Use jinja2 to render banner.
Returns
-------
str
HTML content of banner.
"""
source = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
"banner.html",
)
with open(source) as f:
template = Template(f.read())
for item in sitemap:
if item["title"] == current_site:
item["class"] = active_class
return template.render(
items=sitemap,
)
[docs]
def copy_custom_files(app):
if not app.config.enable_deepmodeling:
return
if app.builder.format == "html":
staticdir = os.path.join(app.builder.outdir, "_static")
cwd = Path(__file__).parent.absolute()
banner_css = cwd / "banner.css"
banner_js = cwd / "banner.js"
dark_css = cwd / "dark_rtd.css"
os.makedirs(staticdir, exist_ok=True)
staticdir = os.path.join(app.builder.outdir, "_static")
copy_asset_file(str(banner_css), staticdir)
copy_asset_file(str(banner_js), staticdir)
copy_asset_file(str(dark_css), staticdir)
[docs]
def insert_icp(app, pagename, templatename, context, doctree):
if not app.config.enable_deepmodeling:
return
if app.config.html_theme == "sphinx_book_theme":
# sphinx_book_theme has provided the option, so there is no need to hack
return
if not hasattr(app.builder.templates.render, "_deepmodeling_icp_patched"):
old_render = app.builder.templates.render
def render(self, template, render_context):
content = old_render(template, render_context)
comment_begin = r"<!--deepmodeling icp begin-->"
comment_end = r"<!--deepmodeling icp end-->"
if comment_begin in content:
return content
footer = content.lower().rfind("</footer>")
icp_footer = (
'<p><a href="https://beian.miit.gov.cn" target="_blank">%s</a></p>'
% icp_no
)
if footer != -1:
content = (
content[:footer]
+ comment_begin
+ icp_footer
+ comment_end
+ content[footer:]
)
return content
render.__dict__.update(old_render.__dict__)
render._deepmodeling_icp_patched = True
app.builder.templates.render = types.MethodType(render, app.builder.templates)
[docs]
def minify_html_files(app, pagename, templatename, context, doctree):
if not hasattr(app.builder.templates.render, "_deepmodeling_minified"):
old_render = app.builder.templates.render
def render(self, template, render_context):
content = old_render(template, render_context)
try:
return minify_html.minify(
content,
minify_js=True,
minify_css=True,
keep_html_and_head_opening_tags=True,
keep_closing_tags=True,
)
except SyntaxError:
return content
render.__dict__.update(old_render.__dict__)
render._deepmodeling_minified = True
app.builder.templates.render = types.MethodType(render, app.builder.templates)
[docs]
def minify_js_files(app, exception):
if not hasattr(app.builder, "script_files"):
# not html builder
return
for js in app.builder.script_files:
if js.filename is None:
continue
fn = os.path.join(app.builder.outdir, js.filename)
if os.path.isfile(fn):
with open(fn, "r+") as f:
minified_js = jsmin(f.read())
f.seek(0)
f.write(minified_js)
f.truncate()
[docs]
def minify_css_files(app, exception):
if not hasattr(app.builder, "css_files"):
# not html builder
return
for css in app.builder.css_files:
if css.filename is None:
continue
fn = os.path.join(app.builder.outdir, css.filename)
if os.path.isfile(fn):
with open(fn, "r+") as f:
minified_css = cssmin(f.read())
f.seek(0)
f.write(minified_css)
f.truncate()
[docs]
def enable_dark_mode(app, config):
"""Enable dark mode if the theme is sphinx_rtd_theme."""
if config.html_theme == "sphinx_rtd_theme":
app.add_css_file("dark_rtd.css")
[docs]
def rtd_config(app, config):
"""Set RTD configurations.
See https://about.readthedocs.com/blog/2024/07/addons-by-default/
"""
config.html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "")
# Tell Jinja2 templates the build is running on Read the Docs
if os.environ.get("READTHEDOCS", "") == "True":
if "html_context" not in config:
config.html_context = {}
config.html_context["READTHEDOCS"] = True
[docs]
def sphinx_book_theme(app, config):
"""Set configurations for sphinx_book_theme."""
if not config.enable_deepmodeling:
return
if config.html_theme != "sphinx_book_theme":
return
icp_footer = (
'<p><a href="https://beian.miit.gov.cn" target="_blank">%s</a></p>' % icp_no
)
config.html_theme_options["extra_footer"] = (
config.html_theme_options.get("extra_footer", "") + icp_footer
)
[docs]
def setup(app: Sphinx) -> Dict[str, Any]:
# enable deepmodeling sidebar and icp
# if the repo is outside the deepmodeling, disable it
app.add_config_value("enable_deepmodeling", True, "html")
app.add_config_value("deepmodeling_current_site", "Docs", "html")
app.connect("builder-inited", copy_custom_files)
app.connect("html-page-context", insert_sidebar)
app.connect("html-page-context", insert_icp)
app.connect("html-page-context", minify_html_files)
app.connect("build-finished", minify_js_files)
app.connect("build-finished", minify_css_files)
# dark mode for rtd theme
app.connect("config-inited", enable_dark_mode)
app.connect("config-inited", rtd_config)
app.connect("config-inited", sphinx_book_theme)
return {"parallel_read_safe": True}