Additional
Samples
¶
Various examples of styling applied to Sphinx constructs. You can view the source of this page to see the specific reStructuredText used to create these examples.
Subpages¶
Suppages get bread crumbs when they are not at the top level.
Headings¶
This is a first level heading (h1
).
Code¶
The theme uses pygments for inline code text
and
multiline
code text
Here’s an included example with line numbers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 | """Sphinx Material theme."""
import hashlib
import inspect
import os
import re
import sys
from multiprocessing import Manager
from typing import List, Optional
from xml.etree import ElementTree
import bs4
import slugify
from bs4 import BeautifulSoup
from sphinx.util import console, logging
from ._version import get_versions
__version__ = get_versions()["version"]
del get_versions
ROOT_SUFFIX = "--page-root"
USER_TABLE_CLASSES = []
def setup(app):
"""Setup connects events to the sitemap builder"""
app.connect("html-page-context", add_html_link)
app.connect("build-finished", create_sitemap)
app.connect("build-finished", reformat_pages)
app.connect("build-finished", minify_css)
app.connect("builder-inited", update_html_context)
app.connect("config-inited", update_table_classes)
manager = Manager()
site_pages = manager.list()
sitemap_links = manager.list()
app.multiprocess_manager = manager
app.sitemap_links = sitemap_links
app.site_pages = site_pages
app.add_html_theme(
"sphinx_symbiflow_theme", os.path.join(html_theme_path()[0], "sphinx_symbiflow_theme")
)
return {
"version": __version__,
"parallel_read_safe": True,
"parallel_write_safe": True,
}
def add_html_link(app, pagename, templatename, context, doctree):
"""As each page is built, collect page names for the sitemap"""
base_url = app.config["html_theme_options"].get("base_url", "")
if base_url:
app.sitemap_links.append(base_url + pagename + ".html")
minify = app.config["html_theme_options"].get("html_minify", False)
prettify = app.config["html_theme_options"].get("html_prettify", False)
if minify and prettify:
raise ValueError("html_minify and html_prettify cannot both be True")
if minify or prettify:
app.site_pages.append(os.path.join(app.outdir, pagename + ".html"))
def create_sitemap(app, exception):
"""Generates the sitemap.xml from the collected HTML page links"""
if (
not app.config["html_theme_options"].get("base_url", "")
or exception is not None
or not app.sitemap_links
):
return
filename = app.outdir + "/sitemap.xml"
print(
"Generating sitemap for {0} pages in "
"{1}".format(len(app.sitemap_links), console.colorize("blue", filename))
)
root = ElementTree.Element("urlset")
root.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
for link in app.sitemap_links:
url = ElementTree.SubElement(root, "url")
ElementTree.SubElement(url, "loc").text = link
app.sitemap_links[:] = []
ElementTree.ElementTree(root).write(filename)
def reformat_pages(app, exception):
if exception is not None or not app.site_pages:
return
minify = app.config["html_theme_options"].get("html_minify", False)
last = -1
npages = len(app.site_pages)
transform = "Minifying" if minify else "Prettifying"
print("{0} {1} files".format(transform, npages))
transform = transform.lower()
# TODO: Consider using parallel execution
for i, page in enumerate(app.site_pages):
if int(100 * (i / npages)) - last >= 1:
last = int(100 * (i / npages))
color_page = console.colorize("blue", page)
msg = "{0} files... [{1}%] {2}".format(transform, last, color_page)
sys.stdout.write("\033[K" + msg + "\r")
with open(page, "r", encoding="utf-8") as content:
if minify:
from css_html_js_minify.html_minifier import html_minify
html = html_minify(content.read())
else:
soup = BeautifulSoup(content.read(), features="lxml")
html = soup.prettify()
with open(page, "w", encoding="utf-8") as content:
content.write(html)
app.site_pages[:] = []
print()
def minify_css(app, exception):
if exception is not None or not app.config["html_theme_options"].get(
"css_minify", False
):
app.multiprocess_manager.shutdown()
return
import glob
from css_html_js_minify.css_minifier import css_minify
css_files = glob.glob(os.path.join(app.outdir, "**", "*.css"), recursive=True)
print("Minifying {0} css files".format(len(css_files)))
for css_file in css_files:
colorized = console.colorize("blue", css_file)
msg = "minifying css file {0}".format(colorized)
sys.stdout.write("\033[K" + msg + "\r")
with open(css_file, "r", encoding="utf-8") as content:
css = css_minify(content.read())
with open(css_file, "w", encoding="utf-8") as content:
content.write(css)
print()
app.multiprocess_manager.shutdown()
def update_html_context(app):
config = app.config
config.html_context = {**get_html_context(), **config.html_context}
def update_table_classes(app, config):
table_classes = config.html_theme_options.get("table_classes")
if table_classes:
USER_TABLE_CLASSES.extend(table_classes)
def html_theme_path():
return [os.path.dirname(os.path.abspath(__file__))]
def ul_to_list(node: bs4.element.Tag, fix_root: bool, page_name: str) -> List[dict]:
out = []
for child in node.find_all("li", recursive=False):
if callable(child.isspace) and child.isspace():
continue
formatted = {}
if child.a is not None:
formatted["href"] = child.a["href"]
formatted["contents"] = "".join(map(str, child.a.contents))
if fix_root and formatted["href"] == "#" and child.a.contents:
slug = slugify.slugify(page_name) + ROOT_SUFFIX
formatted["href"] = "#" + slug
formatted["current"] = "current" in child.a.get("class", [])
if child.ul is not None:
formatted["children"] = ul_to_list(child.ul, fix_root, page_name)
else:
formatted["children"] = []
out.append(formatted)
return out
class CaptionList(list):
_caption = ""
def __init__(self, caption=""):
super().__init__()
self._caption = caption
@property
def caption(self):
return self._caption
@caption.setter
def caption(self, value):
self._caption = value
def derender_toc(
toc_text, fix_root=True, page_name: str = "md-page-root--link"
) -> List[dict]:
nodes = []
try:
toc = BeautifulSoup(toc_text, features="html.parser")
for child in toc.children:
if callable(child.isspace) and child.isspace():
continue
if child.name == "p":
nodes.append({"caption": "".join(map(str, child.contents))})
elif child.name == "ul":
nodes.extend(ul_to_list(child, fix_root, page_name))
else:
raise NotImplementedError
except Exception as exc:
logger = logging.getLogger(__name__)
logger.warning(
"Failed to process toctree_text\n" + str(exc) + "\n" + str(toc_text)
)
return nodes
def walk_contents(tags):
out = []
for tag in tags.contents:
if hasattr(tag, "contents"):
out.append(walk_contents(tag))
else:
out.append(str(tag))
return "".join(out)
def table_fix(body_text, page_name="md-page-root--link"):
# This is a hack to skip certain classes of tables
ignore_table_classes = {"highlighttable", "longtable", "dataframe"}
try:
body = BeautifulSoup(body_text, features="html.parser")
for table in body.select("table"):
classes = set(table.get("class", tuple()))
if classes.intersection(ignore_table_classes):
continue
classes = [tc for tc in classes if tc in USER_TABLE_CLASSES]
if classes:
table["class"] = classes
else:
del table["class"]
first_h1: Optional[bs4.element.Tag] = body.find("h1")
headers = body.find_all(re.compile("^h[1-6]$"))
for i, header in enumerate(headers):
for a in header.select("a"):
if "headerlink" in a.get("class", ""):
header["id"] = a["href"][1:]
if first_h1 is not None:
slug = slugify.slugify(page_name) + ROOT_SUFFIX
first_h1["id"] = slug
for a in first_h1.select("a"):
a["href"] = "#" + slug
divs = body.find_all("div", {"class": "section"})
for div in divs:
div.unwrap()
return str(body)
except Exception as exc:
logger = logging.getLogger(__name__)
logger.warning("Failed to process body_text\n" + str(exc))
return body_text
# These final lines exist to give sphinx a stable str representation of
# these two functions across runs, and to ensure that the str changes
# if the source does.
#
# Note that this would be better down with a metaclass factory
table_fix_src = inspect.getsource(table_fix)
table_fix_hash = hashlib.sha512(table_fix_src.encode()).hexdigest()
derender_toc_src = inspect.getsource(derender_toc)
derender_toc_hash = hashlib.sha512(derender_toc_src.encode()).hexdigest()
class TableFixMeta(type):
def __repr__(self):
return f"table_fix, hash: {table_fix_hash}"
def __str__(self):
return f"table_fix, hash: {table_fix_hash}"
class TableFix(object, metaclass=TableFixMeta):
def __new__(cls, *args, **kwargs):
return table_fix(*args, **kwargs)
class DerenderTocMeta(type):
def __repr__(self):
return f"derender_toc, hash: {derender_toc_hash}"
def __str__(self):
return f"derender_toc, hash: {derender_toc_hash}"
class DerenderToc(object, metaclass=DerenderTocMeta):
def __new__(cls, *args, **kwargs):
return derender_toc(*args, **kwargs)
def get_html_context():
return {"table_fix": TableFix, "derender_toc": DerenderToc}
|
It also works with existing Sphinx highlighting:
<html>
<body>Hello World</body>
</html>
def hello():
"""Greet."""
return "Hello World"
/**
* Greet.
*/
function hello(): {
return "Hello World";
}
Admonitions¶
The theme uses the admonition
classes for Sphinx admonitions.
Note¶
Note
This is a note.
Todo¶
Todo
It is essential to complete todo items.
Warning¶
Warning
This is a warning.
Danger¶
Danger
This is danger-ous.
Attention¶
Attention
Do I have your attention?
Caution¶
Caution
Use caution!
Error¶
Error
You have made a grave error.
Hint¶
Hint
Can you take a hint?
Important¶
Important
It is important to correctly use admonitions.
Tip¶
Tip
Please tip your waiter.
Custom Admonitions¶
Custom
You can create your own admonitions with the default style.
Footnotes¶
I have footnoted a first item 1 and second item 2. This also references the second item 2.
Footnotes
Icons¶
The following template HTML:
<span style="font-size: 2rem;" class="md-icon"></span>
translates to a the site’s icon:
The material icon font provides hundreds to choose from. You can use the <i>
tag or the
<span>
tag.
Tables¶
Here are some examples of Sphinx
tables. The Sphinx Material
all classes and only applies the default style to classless tables. If you want
to use a custom table class, you will need to do two thing. First, apply it
using .. cssclass:: custom-class
and then add it to your configuration’s
table_classes
variable.
User-styled Table¶
Note
table_classes is set to [“plain”] in the site’s configuration. Only plain remains as the class of the table. Other standard classes applied by Sphinx are removed.
This is feature demonstration. There is no css for the plain class, and so this is completely unstyled.
User |
Styled |
Table |
---|---|---|
cell1 |
cell2 |
cell3 |
… |
… |
… |
… |
… |
… |
Code Documentation¶
An example Python function.
-
format_exception
(etype, value, tb[, limit=None])¶ Format the exception with a traceback.
- Parameters
etype – exception type
value – exception value
tb – traceback object
limit (integer or None) – maximum number of stack frames to show
- Return type
list of strings
An example JavaScript function.
-
class
MyAnimal
(name[, age])¶ - Arguments
name (string) – The name of the animal
age (number) – an optional age for the animal
Glossaries¶
- environment
A structure where information about all documents under the root is saved, and used for cross-referencing. The environment is pickled after the parsing stage, so that successive runs only need to read and parse new and changed documents.
- source directory
The directory which, including its subdirectories, contains all source files for one Sphinx project.
Math¶
(a + b)^2 = a^2 + 2ab + b^2 (a - b)^2 = a^2 - 2ab + b^2
(a + b)^2 &= (a + b)(a + b) \\ &= a^2 + 2ab + b^2
\begin{eqnarray} y & = & ax^2 + bx + c \\ f(x) & = & x^2 + 2xy + y^2 \end{eqnarray}
Production Lists¶
try_stmt ::= try1_stmt | try2_stmt try1_stmt ::= "try" ":"suite
("except" [expression
[","target
]] ":"suite
)+ ["else" ":"suite
] ["finally" ":"suite
] try2_stmt ::= "try" ":"suite
"finally" ":"suite