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