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
129 from css_html_js_minify.css_minifier import css_minify
130
131 css_files = glob.glob(os.path.join(app.outdir, "**", "*.css"), recursive=True)
132 print("Minifying {0} css files".format(len(css_files)))
133 for css_file in css_files:
134 colorized = console.colorize("blue", css_file)
135 msg = "minifying css file {0}".format(colorized)
136 sys.stdout.write("\033[K" + msg + "\r")
137 with open(css_file, "r", encoding="utf-8") as content:
138 css = css_minify(content.read())
139 with open(css_file, "w", encoding="utf-8") as content:
140 content.write(css)
141 print()
142 app.multiprocess_manager.shutdown()
143
144
145def update_html_context(app):
146 config = app.config
147 config.html_context = {**get_html_context(), **config.html_context}
148
149
150def update_table_classes(app, config):
151 table_classes = config.html_theme_options.get("table_classes")
152 if table_classes:
153 USER_TABLE_CLASSES.extend(table_classes)
154
155 table_no_strip_classes = config.html_theme_options.get("table_no_strip")
156 if table_no_strip_classes:
157 USER_TABLE_NO_STRIP_CLASSES.extend(table_no_strip_classes)
158
159
160def html_theme_path():
161 return [os.path.dirname(os.path.abspath(__file__))]
162
163
164def ul_to_list(node: bs4.element.Tag, fix_root: bool, page_name: str) -> List[dict]:
165 out = []
166 for child in node.find_all("li", recursive=False):
167 if callable(child.isspace) and child.isspace():
168 continue
169 formatted = {}
170 if child.a is not None:
171 formatted["href"] = child.a["href"]
172 formatted["contents"] = "".join(map(str, child.a.contents))
173 if fix_root and formatted["href"] == "#" and child.a.contents:
174 slug = slugify.slugify(page_name) + ROOT_SUFFIX
175 formatted["href"] = "#" + slug
176 formatted["current"] = "current" in child.a.get("class", [])
177 if child.ul is not None:
178 formatted["children"] = ul_to_list(child.ul, fix_root, page_name)
179 else:
180 formatted["children"] = []
181 out.append(formatted)
182 return out
183
184
185class CaptionList(list):
186 _caption = ""
187
188 def __init__(self, caption=""):
189 super().__init__()
190 self._caption = caption
191
192 @property
193 def caption(self):
194 return self._caption
195
196 @caption.setter
197 def caption(self, value):
198 self._caption = value
199
200
201def derender_toc(
202 toc_text, fix_root=True, page_name: str = "md-page-root--link"
203) -> List[dict]:
204 nodes = []
205 try:
206 toc = BeautifulSoup(toc_text, features="html.parser")
207 for child in toc.children:
208 if callable(child.isspace) and child.isspace():
209 continue
210 if child.name == "p":
211 nodes.append({"caption": "".join(map(str, child.contents))})
212 elif child.name == "ul":
213 nodes.extend(ul_to_list(child, fix_root, page_name))
214 else:
215 raise NotImplementedError
216 except Exception as exc:
217 logger = logging.getLogger(__name__)
218 logger.warning(
219 "Failed to process toctree_text\n" + str(exc) + "\n" + str(toc_text)
220 )
221
222 return nodes
223
224
225def walk_contents(tags):
226 out = []
227 for tag in tags.contents:
228 if hasattr(tag, "contents"):
229 out.append(walk_contents(tag))
230 else:
231 out.append(str(tag))
232 return "".join(out)
233
234
235def table_fix(body_text, page_name="md-page-root--link"):
236 # This is a hack to skip certain classes of tables
237 ignore_table_classes = {"highlighttable", "longtable", "dataframe"} | set(
238 USER_TABLE_NO_STRIP_CLASSES
239 )
240 try:
241 body = BeautifulSoup(body_text, features="html.parser")
242 for table in body.select("table"):
243 classes = set(table.get("class", tuple()))
244 if classes.intersection(ignore_table_classes):
245 continue
246 classes = [tc for tc in classes if tc in USER_TABLE_CLASSES]
247 if classes:
248 table["class"] = classes
249 else:
250 del table["class"]
251 first_h1: Optional[bs4.element.Tag] = body.find("h1")
252 headers = body.find_all(re.compile("^h[1-6]$"))
253 for i, header in enumerate(headers):
254 for a in header.select("a"):
255 if "headerlink" in a.get("class", ""):
256 header["id"] = a["href"][1:]
257 if first_h1 is not None:
258 slug = slugify.slugify(page_name) + ROOT_SUFFIX
259 first_h1["id"] = slug
260 for a in first_h1.select("a"):
261 a["href"] = "#" + slug
262
263 divs = body.find_all("div", {"class": "section"})
264 for div in divs:
265 div.unwrap()
266
267 return str(body)
268 except Exception as exc:
269 logger = logging.getLogger(__name__)
270 logger.warning("Failed to process body_text\n" + str(exc))
271 return body_text
272
273
274# These final lines exist to give sphinx a stable str representation of
275# these two functions across runs, and to ensure that the str changes
276# if the source does.
277#
278# Note that this would be better down with a metaclass factory
279table_fix_src = inspect.getsource(table_fix)
280table_fix_hash = hashlib.sha512(table_fix_src.encode()).hexdigest()
281derender_toc_src = inspect.getsource(derender_toc)
282derender_toc_hash = hashlib.sha512(derender_toc_src.encode()).hexdigest()
283
284
285class TableFixMeta(type):
286 def __repr__(self):
287 return f"table_fix, hash: {table_fix_hash}"
288
289 def __str__(self):
290 return f"table_fix, hash: {table_fix_hash}"
291
292
293class TableFix(object, metaclass=TableFixMeta):
294 def __new__(cls, *args, **kwargs):
295 return table_fix(*args, **kwargs)
296
297
298class DerenderTocMeta(type):
299 def __repr__(self):
300 return f"derender_toc, hash: {derender_toc_hash}"
301
302 def __str__(self):
303 return f"derender_toc, hash: {derender_toc_hash}"
304
305
306class DerenderToc(object, metaclass=DerenderTocMeta):
307 def __new__(cls, *args, **kwargs):
308 return derender_toc(*args, **kwargs)
309
310
311def get_html_context():
312 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