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

1

My first footnote.

2(1,2)

My second footnote.

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

A List Table

Column 1

Column 2

Item 1

Item 2

50% width list table

One fifth width column

Four fifths width column

Item 1

Item 2

Alignment

Left Aligned

Column 1

Column 2

Item 1

Item 2

Center Aligned

Column 1

Column 2

Item 1

Item 2

Right Aligned

Treat

Quantity

Albatross

2.99

Crunchy Frog

1.49

Gannet Ripple

1.99

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

\[ \begin{align}\begin{aligned}(a + b)^2 = a^2 + 2ab + b^2\\(a - b)^2 = a^2 - 2ab + b^2\end{aligned}\end{align} \]
\[\begin{split}(a + b)^2 &= (a + b)(a + b) \\ &= a^2 + 2ab + b^2\end{split}\]
\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