additional_samples_with_very_long_name
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 second level heading (h2).
Sub-Heading
This is a third level heading (h3).
Sub-Sub-Heading
This is a fourth level heading (h4).
Code
The theme uses pygments for inline code text and
multiline
code text
Here’s an included example with line numbers.
1"""OpenFF Sphinx theme."""
2
3import hashlib
4import inspect
5import os
6import sys
7from pathlib import Path
8from typing import List
9
10import bs4
11import sass
12import slugify
13from bs4 import BeautifulSoup
14from css_html_js_minify.html_minifier import html_minify
15from sass import SassColor
16from sphinx.util import console, logging
17import sphinx
18
19from ._version import get_versions
20
21__version__ = get_versions()["version"]
22del get_versions
23
24ROOT_SUFFIX = "--page-root"
25
26
27def setup(app):
28 """Setup connects events to the sitemap builder"""
29 app.connect("builder-inited", register_template_functions)
30 app.connect("config-inited", set_default_settings)
31 app.connect("env-get-outdated", register_outdated)
32 app.connect("build-finished", postproc_html)
33 app.connect("build-finished", compile_css)
34 app.site_pages = []
35 app.add_html_theme(
36 "openff_sphinx_theme", os.path.join(html_theme_path()[0], "openff_sphinx_theme")
37 )
38 return {
39 "version": __version__,
40 "parallel_read_safe": True,
41 "parallel_write_safe": True,
42 }
43
44
45def set_default_settings(app, config):
46 if not config.html_sidebars:
47 config.html_sidebars["**"] = [
48 "globaltoc.html",
49 "localtoc.html",
50 "searchbox.html",
51 ]
52 if config.html_permalinks_icon == "¶":
53 config.html_permalinks_icon = "<i class='fas fa-link'></i>"
54
55
56def compile_css(app, exception):
57 """Compile Bulma SASS into CSS"""
58 if exception is not None:
59 return
60
61 theme_path = Path(html_theme_path()[0]) / "openff_sphinx_theme"
62 src = theme_path / "sass/site.sass"
63 dest = Path(app.outdir) / "_static/site.css"
64
65 if not dest.parent.exists():
66 return
67
68 accent_color = app.config["html_theme_options"].get(
69 "color_accent", "openff-toolkit-blue"
70 )
71 accent_color = {
72 "openff-blue": (1, 84, 128),
73 "openff-toolkit-blue": (47, 158, 210),
74 "openff-dataset-yellow": (240, 133, 33),
75 "openff-evaluator-orange": (240, 58, 33),
76 "aquamarine": (44, 218, 157),
77 "lilac": (228, 183, 229),
78 "amaranth": (164, 14, 76),
79 "grape": (171, 146, 191),
80 "violet": (141, 107, 148),
81 "pink": (238, 66, 102),
82 "pale-green": (238, 66, 102),
83 "green": (4, 231, 98),
84 "crimson": (214, 40, 57),
85 "eggplant": (117, 79, 91),
86 "turquoise": (45, 225, 194),
87 }.get(accent_color, accent_color)
88
89 if app.config["html_theme_options"].get("css_minify", False):
90 output_style = "compressed"
91 source_comments = False
92 else:
93 output_style = "expanded"
94 source_comments = True
95
96 css = sass.compile(
97 filename=str(src),
98 output_style=output_style,
99 custom_functions={"accent_color": lambda: SassColor(*accent_color, 1)},
100 )
101
102 print(f"Writing compiled SASS to {console.colorize('blue', str(dest))}")
103
104 with open(dest, "w") as f:
105 print(css, file=f)
106
107
108def register_outdated(app, env, added, changed, removed):
109 if isinstance(app.builder, sphinx.builders.html.StandaloneHTMLBuilder):
110 env.openff_docs_to_postproc = added | changed
111 else:
112 env.openff_docs_to_postproc = None
113 return ()
114
115
116def wrap_tables(soup: BeautifulSoup):
117 for table in soup.find_all("table"):
118 container_attributes = {
119 "class": ["table-container"] + table.get("class", []),
120 }
121 if "table-container" not in table.parent.get("class", ()):
122 table.wrap(soup.new_tag("div", **container_attributes))
123
124
125def api_dl_to_details(soup: BeautifulSoup):
126 for dl in soup.select(
127 "dl:not(.docutils):not(.field-list):not(.simple):not(.citation):not(.option-list):not(.footnote)[class]"
128 ):
129 dl.name = "details"
130 if "section" in dl.parent.get("class", []):
131 dl["open"] = ""
132 try:
133 dl["class"].append("autodoc")
134 except KeyError:
135 dl["class"] = ["autodoc"]
136 for child in dl.children:
137 if child.name == "dd":
138 child.name = "div"
139 elif child.name == "dt":
140 child.name = "summary"
141 else:
142 continue
143
144 try:
145 child["class"].append("autodoc")
146 except KeyError:
147 child["class"] = ["autodoc"]
148
149
150def postproc_html(app, exception):
151 """Prettify or minify the HTML, as well as wrap tables with .table-container"""
152 if exception is not None:
153 return
154
155 target_files = app.env.openff_docs_to_postproc
156
157 if target_files is None:
158 return
159
160 outdir = Path(app.outdir)
161
162 minify = app.config["html_theme_options"].get("html_minify", False)
163 prettify = app.config["html_theme_options"].get("html_prettify", False)
164 collapsible_api = app.config["html_theme_options"].get(
165 "html_collapsible_autodoc", False
166 )
167 last = -1
168 npages = len(target_files)
169 print(f"Post-processing {npages} HTML files")
170
171 # TODO: Consider using parallel execution
172 for i, doc in enumerate(target_files):
173 try:
174 page = outdir / app.builder.get_target_uri(doc)
175 except sphinx.errors.NoUri as e:
176 print(doc, "has no URI; skipping")
177 continue
178
179 if int(100 * (i / npages)) - last >= 25:
180 last = int(100 * (i / npages))
181 color_page = console.colorize("blue", str(page))
182 msg = f"Post-processing files... [{last}%] {color_page}"
183 print("\033[K", msg, sep="", end="\r")
184
185 if not page.exists():
186 print(page, "does not exist; skipping")
187 continue
188
189 with open(page, "r", encoding="utf-8") as content:
190 soup = BeautifulSoup(content, "lxml")
191 wrap_tables(soup)
192
193 if collapsible_api:
194 api_dl_to_details(soup)
195
196 if minify:
197 html = html_minify(str(soup))
198 elif prettify:
199 html = soup.prettify()
200 else:
201 html = str(soup)
202
203 with open(page, "w", encoding="utf-8") as content:
204 content.write(html)
205
206 msg = f"Post-processing files... [100%]"
207 sys.stdout.write("\033[K" + msg + "\r")
208
209
210def register_template_functions(app):
211 config = app.config
212 config.html_context = {**get_html_context(), **config.html_context}
213
214
215def html_theme_path():
216 return [os.path.dirname(os.path.abspath(__file__))]
217
218
219def ul_to_list(node: bs4.element.Tag, fix_root: bool, page_name: str) -> List[dict]:
220 out = []
221 for child in node.find_all("li", recursive=False):
222 if callable(child.isspace) and child.isspace():
223 continue
224 formatted = {}
225 if child.a is not None:
226 formatted["href"] = child.a["href"]
227 formatted["contents"] = "".join(map(str, child.a.contents))
228 if fix_root and formatted["href"] == "#" and child.a.contents:
229 slug = slugify.slugify(page_name) + ROOT_SUFFIX
230 formatted["href"] = "#" + slug
231 formatted["current"] = "current" in child.a.get("class", [])
232 if child.ul is not None:
233 formatted["children"] = ul_to_list(child.ul, fix_root, page_name)
234 else:
235 formatted["children"] = []
236 out.append(formatted)
237 return out
238
239
240def derender_toc(
241 toc_text, fix_root=True, page_name: str = "md-page-root--link"
242) -> List[dict]:
243 nodes = []
244 try:
245 toc = BeautifulSoup(toc_text, features="html.parser")
246 for child in toc.children:
247 if callable(child.isspace) and child.isspace():
248 continue
249 if child.name == "p":
250 nodes.append({"caption": "".join(map(str, child.contents))})
251 elif child.name == "ul":
252 nodes.extend(ul_to_list(child, fix_root, page_name))
253 else:
254 raise NotImplementedError
255 except Exception as exc:
256 logger = logging.getLogger(__name__)
257 logger.warning(
258 "Failed to process toctree_text\n" + str(exc) + "\n" + str(toc_text)
259 )
260
261 return nodes
262
263
264# These final lines exist to give sphinx a stable str representation of
265# this function across runs, and to ensure that the str changes
266# if the source does.
267derender_toc_src = inspect.getsource(derender_toc)
268derender_toc_hash = hashlib.sha512(derender_toc_src.encode()).hexdigest()
269
270
271class DerenderTocMeta(type):
272 def __repr__(self):
273 return f"derender_toc, hash: {derender_toc_hash}"
274
275 def __str__(self):
276 return f"derender_toc, hash: {derender_toc_hash}"
277
278
279class DerenderToc(object, metaclass=DerenderTocMeta):
280 def __new__(cls, *args, **kwargs):
281 return derender_toc(*args, **kwargs)
282
283
284def get_html_context():
285 return {"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 the standard Sphinx admonitions.
Warning
Warning
This is a warning.
Attention
Attention
Do I have your attention?
Caution
Caution
Use caution!
Danger
Danger
This is danger-ous.
Error
Error
You have made a grave error.
Hint
Hint
Can you take a hint?
Important
Important
It is important to correctly use admonitions.
Note
Note
This is a note.
Tip
Tip
Please tip your waiter.
Custom Admonitions
An admonition of my own making
You can create your own admonitions with the accent color.
Example
But lots of custom admonition styles are also defined.
Quote
The needs of the many outweigh the needs of the few
Bug
Bugs weren’t always a metaphor
Success
Woohoo!
Footnotes
I have footnoted a first item 1 and second item 2. This also references the second item 2.
Footnotes
Icons
Font Awesome and Academicons are both available:
<i class="fa fa-camera-retro fa-lg"></i>
<i class="fa fa-camera-retro fa-2x"></i>
<i class="fa fa-camera-retro fa-3x"></i>
<i class="fa fa-camera-retro fa-4x"></i>
<i class="fa fa-camera-retro fa-5x"></i>
<i class="ai ai-google-scholar-square ai-3x"></i>
<i class="ai ai-zenodo ai-3x" style="color: red"></i>
Tables
Here are some examples of Sphinx tables.
Grid
A grid table:
Header1 |
Header2 |
Header3 |
Header4 |
|---|---|---|---|
row1, cell1 |
cell2 |
cell3 |
cell4 |
row2 … |
… |
… |
|
… |
… |
… |
Simple
A simple table:
H1 |
H2 |
H3 |
|---|---|---|
cell1 |
cell2 |
cell3 |
… |
… |
… |
… |
… |
… |
List Tables
Alignment
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.
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