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
).
Sub-Heading ¶
This is a second level heading (
h2
).
Sub-Sub-Heading ¶
This is a third level heading (
h3
).
Code ¶
The theme uses pygments for
inline
code
text
and
multiline
code text
Here’s an included example with line numbers.
1"""Sphinx Material theme."""
2
3import hashlib
4import inspect
5import os
6import re
7import sys
8from multiprocessing import Manager
9from pathlib import Path
10from typing import List, Optional
11from xml.etree import ElementTree
12
13import bs4
14import slugify
15from bs4 import BeautifulSoup
16from sphinx.util import console, logging
17
18from ._version import get_versions
19
20__version__ = get_versions()["version"]
21del get_versions
22
23ROOT_SUFFIX = "--page-root"
24
25USER_TABLE_CLASSES = []
26USER_TABLE_NO_STRIP_CLASSES = ["no-sphinx-material-strip"]
27
28
29def setup(app):
30 """Setup connects events to the sitemap builder"""
31 app.connect("html-page-context", add_html_link)
32 app.connect("build-finished", create_sitemap)
33 app.connect("build-finished", reformat_pages)
34 app.connect("build-finished", minify_css)
35 app.connect("builder-inited", update_html_context)
36 app.connect("config-inited", update_table_classes)
37 manager = Manager()
38 site_pages = manager.list()
39 sitemap_links = manager.list()
40 app.multiprocess_manager = manager
41 app.sitemap_links = sitemap_links
42 app.site_pages = site_pages
43 app.add_html_theme(
44 "sphinx_material", os.path.join(html_theme_path()[0], "sphinx_material")
45 )
46 return {
47 "version": __version__,
48 "parallel_read_safe": True,
49 "parallel_write_safe": True,
50 }
51
52
53def add_html_link(app, pagename, templatename, context, doctree):
54 """As each page is built, collect page names for the sitemap"""
55 base_url = app.config["html_theme_options"].get("base_url", "")
56 if base_url:
57 app.sitemap_links.append(base_url + pagename + ".html")
58 minify = app.config["html_theme_options"].get("html_minify", False)
59 prettify = app.config["html_theme_options"].get("html_prettify", False)
60 if minify and prettify:
61 raise ValueError("html_minify and html_prettify cannot both be True")
62 if minify or prettify:
63 app.site_pages.append(os.path.join(app.outdir, pagename + ".html"))
64
65
66def create_sitemap(app, exception):
67 """Generates the sitemap.xml from the collected HTML page links"""
68 if (
69 not app.config["html_theme_options"].get("base_url", "")
70 or exception is not None
71 or not app.sitemap_links
72 ):
73 return
74
75 outdir = app.outdir if isinstance(app.outdir, Path) else str(app.outdir)
76 filename = str(Path(outdir) / "sitemap.xml")
77
78 print(
79 "Generating sitemap for {0} pages in "
80 "{1}".format(len(app.sitemap_links), console.colorize("blue", filename))
81 )
82
83 root = ElementTree.Element("urlset")
84 root.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
85
86 for link in app.sitemap_links:
87 url = ElementTree.SubElement(root, "url")
88 ElementTree.SubElement(url, "loc").text = link
89 app.sitemap_links[:] = []
90
91 ElementTree.ElementTree(root).write(filename)
92
93
94def reformat_pages(app, exception):
95 if exception is not None or not app.site_pages:
96 return
97 minify = app.config["html_theme_options"].get("html_minify", False)
98 last = -1
99 npages = len(app.site_pages)
100 transform = "Minifying" if minify else "Prettifying"
101 print("{0} {1} files".format(transform, npages))
102 transform = transform.lower()
103 # TODO: Consider using parallel execution
104 for i, page in enumerate(app.site_pages):
105 if int(100 * (i / npages)) - last >= 1:
106 last = int(100 * (i / npages))
107 color_page = console.colorize("blue", page)
108 msg = "{0} files... [{1}%] {2}".format(transform, last, color_page)
109 sys.stdout.write("\033[K" + msg + "\r")
110 with open(page, "r", encoding="utf-8") as content:
111 if minify:
112 from css_html_js_minify.html_minifier import html_minify
113
114 html = html_minify(content.read())
115 else:
116 soup = BeautifulSoup(content.read(), features="lxml")
117 html = soup.prettify()
118 with open(page, "w", encoding="utf-8") as content:
119 content.write(html)
120 app.site_pages[:] = []
121 print()
122
123
124def minify_css(app, exception):
125 if exception is not None or not app.config["html_theme_options"].get(
126 "css_minify", False
127 ):
128 app.multiprocess_manager.shutdown()
129 return
130 import glob
131
132 from css_html_js_minify.css_minifier import css_minify
133
134 css_files = glob.glob(os.path.join(app.outdir, "**", "*.css"), recursive=True)
135 print("Minifying {0} css files".format(len(css_files)))
136 for css_file in css_files:
137 colorized = console.colorize("blue", css_file)
138 msg = "minifying css file {0}".format(colorized)
139 sys.stdout.write("\033[K" + msg + "\r")
140 with open(css_file, "r", encoding="utf-8") as content:
141 css = css_minify(content.read())
142 with open(css_file, "w", encoding="utf-8") as content:
143 content.write(css)
144 print()
145 app.multiprocess_manager.shutdown()
146
147
148def update_html_context(app):
149 config = app.config
150 config.html_context = {**get_html_context(), **config.html_context}
151
152
153def update_table_classes(app, config):
154 table_classes = config.html_theme_options.get("table_classes")
155 if table_classes:
156 USER_TABLE_CLASSES.extend(table_classes)
157
158 table_no_strip_classes = config.html_theme_options.get("table_no_strip")
159 if table_no_strip_classes:
160 USER_TABLE_NO_STRIP_CLASSES.extend(table_no_strip_classes)
161
162
163def html_theme_path():
164 return [os.path.dirname(os.path.abspath(__file__))]
165
166
167def ul_to_list(node: bs4.element.Tag, fix_root: bool, page_name: str) -> List[dict]:
168 out = []
169 for child in node.find_all("li", recursive=False):
170 if callable(child.isspace) and child.isspace():
171 continue
172 formatted = {}
173 if child.a is not None:
174 formatted["href"] = child.a["href"]
175 formatted["contents"] = "".join(map(str, child.a.contents))
176 if fix_root and formatted["href"] == "#" and child.a.contents:
177 slug = slugify.slugify(page_name) + ROOT_SUFFIX
178 formatted["href"] = "#" + slug
179 formatted["current"] = "current" in child.a.get("class", [])
180 if child.ul is not None:
181 formatted["children"] = ul_to_list(child.ul, fix_root, page_name)
182 else:
183 formatted["children"] = []
184 out.append(formatted)
185 return out
186
187
188class CaptionList(list):
189 _caption = ""
190
191 def __init__(self, caption=""):
192 super().__init__()
193 self._caption = caption
194
195 @property
196 def caption(self):
197 return self._caption
198
199 @caption.setter
200 def caption(self, value):
201 self._caption = value
202
203
204def derender_toc(
205 toc_text, fix_root=True, page_name: str = "md-page-root--link"
206) -> List[dict]:
207 nodes = []
208 try:
209 toc = BeautifulSoup(toc_text, features="html.parser")
210 for child in toc.children:
211 if callable(child.isspace) and child.isspace():
212 continue
213 if child.name == "p":
214 nodes.append({"caption": "".join(map(str, child.contents))})
215 elif child.name == "ul":
216 nodes.extend(ul_to_list(child, fix_root, page_name))
217 else:
218 raise NotImplementedError
219 except Exception as exc:
220 logger = logging.getLogger(__name__)
221 logger.warning(
222 "Failed to process toctree_text\n" + str(exc) + "\n" + str(toc_text)
223 )
224
225 return nodes
226
227
228def walk_contents(tags):
229 out = []
230 for tag in tags.contents:
231 if hasattr(tag, "contents"):
232 out.append(walk_contents(tag))
233 else:
234 out.append(str(tag))
235 return "".join(out)
236
237
238def table_fix(body_text, page_name="md-page-root--link"):
239 # This is a hack to skip certain classes of tables
240 ignore_table_classes = {"highlighttable", "longtable", "dataframe"} | set(
241 USER_TABLE_NO_STRIP_CLASSES
242 )
243 try:
244 body = BeautifulSoup(body_text, features="html.parser")
245 for table in body.select("table"):
246 classes = set(table.get("class", tuple()))
247 if classes.intersection(ignore_table_classes):
248 continue
249 classes = [tc for tc in classes if tc in USER_TABLE_CLASSES]
250 if classes:
251 table["class"] = classes
252 else:
253 del table["class"]
254 first_h1: Optional[bs4.element.Tag] = body.find("h1")
255 headers = body.find_all(re.compile("^h[1-6]$"))
256 for i, header in enumerate(headers):
257 for a in header.select("a"):
258 if "headerlink" in a.get("class", ""):
259 header["id"] = a["href"][1:]
260 if first_h1 is not None:
261 slug = slugify.slugify(page_name) + ROOT_SUFFIX
262 first_h1["id"] = slug
263 for a in first_h1.select("a"):
264 a["href"] = "#" + slug
265
266 divs = body.find_all("div", {"class": "section"})
267 for div in divs:
268 div.unwrap()
269
270 return str(body)
271 except Exception as exc:
272 logger = logging.getLogger(__name__)
273 logger.warning("Failed to process body_text\n" + str(exc))
274 return body_text
275
276
277# These final lines exist to give sphinx a stable str representation of
278# these two functions across runs, and to ensure that the str changes
279# if the source does.
280#
281# Note that this would be better down with a metaclass factory
282table_fix_src = inspect.getsource(table_fix)
283table_fix_hash = hashlib.sha512(table_fix_src.encode()).hexdigest()
284derender_toc_src = inspect.getsource(derender_toc)
285derender_toc_hash = hashlib.sha512(derender_toc_src.encode()).hexdigest()
286
287
288class TableFixMeta(type):
289 def __repr__(self):
290 return f"table_fix, hash: {table_fix_hash}"
291
292 def __str__(self):
293 return f"table_fix, hash: {table_fix_hash}"
294
295
296class TableFix(object, metaclass=TableFixMeta):
297 def __new__(cls, *args, **kwargs):
298 return table_fix(*args, **kwargs)
299
300
301class DerenderTocMeta(type):
302 def __repr__(self):
303 return f"derender_toc, hash: {derender_toc_hash}"
304
305 def __str__(self):
306 return f"derender_toc, hash: {derender_toc_hash}"
307
308
309class DerenderToc(object, metaclass=DerenderTocMeta):
310 def __new__(cls, *args, **kwargs):
311 return derender_toc(*args, **kwargs)
312
313
314def get_html_context():
315 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.
Grid ¶
A grid table:
Header1 |
Header2 |
Header3 |
Header4 |
---|---|---|---|
row1, cell1 |
cell2 |
cell3 |
cell4 |
row2 … |
… |
… |
|
… |
… |
… |
Simple ¶
A simple table:
H1 |
H2 |
H3 |
---|---|---|
cell1 |
cell2 |
cell3 |
… |
… |
… |
… |
… |
… |
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 |
… |
… |
… |
… |
… |
… |
List Tables ¶
Column 1 |
Column 2 |
---|---|
Item 1 |
Item 2 |
Alignment ¶
Warning
Alignment is not currently working as expected.
Column 1 |
Column 2 |
---|---|
Item 1 |
Item 2 |
Treat |
Quantity |
Description |
---|---|---|
Albatross |
2.99 |
On a stick! |
Crunchy Frog |
1.49 |
If we took the bones out, it wouldn’t be crunchy, now would it? |
Gannet Ripple |
1.99 |
On a stick! |
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 ¶
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