From 0d93abf9bf53e127202299a56a242bfc3a69f900 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Wed, 30 Jun 2021 20:19:44 +0100 Subject: [PATCH] Sphinx support: theming (#1933) See #2, #1385 for context. Superseeds #1568. This is the Sphinx-theming part, building on PR #1930. ### Stylesheets: - `style.css` - styles - `mq.css` - media queries ### Jinja2 Templates: - `page.html` - overarching template ### Javascript: - `doctools.js` - fixes footnote brackets ### Theme miscellany - `theme.conf` - sets pygments styles, theme internals --- .github/workflows/deploy-gh-pages.yaml | 4 + build.py | 17 +- conf.py | 12 +- pep-0310.txt | 9 +- pep-0439.txt | 4 +- pep-3143.txt | 18 -- pep_sphinx_extensions/__init__.py | 16 +- .../pep_processor/parsing/pep_role.py | 7 +- .../pep_processor/transforms/pep_contents.py | 62 ++-- .../pep_processor/transforms/pep_footer.py | 72 ++--- .../pep_processor/transforms/pep_headers.py | 39 +-- .../pep_processor/transforms/pep_title.py | 23 +- .../pep_processor/transforms/pep_zero.py | 4 +- .../pep_theme/static/doctools.js | 5 + pep_sphinx_extensions/pep_theme/static/mq.css | 95 ++++++ pep_sphinx_extensions/pep_theme/static/py.png | Bin 0 -> 695 bytes .../pep_theme/static/style.css | 292 ++++++++++++++++++ .../pep_theme/templates/page.html | 37 +++ pep_sphinx_extensions/pep_theme/theme.conf | 4 + 19 files changed, 591 insertions(+), 129 deletions(-) create mode 100644 pep_sphinx_extensions/pep_theme/static/doctools.js create mode 100644 pep_sphinx_extensions/pep_theme/static/mq.css create mode 100644 pep_sphinx_extensions/pep_theme/static/py.png create mode 100644 pep_sphinx_extensions/pep_theme/static/style.css create mode 100644 pep_sphinx_extensions/pep_theme/templates/page.html create mode 100644 pep_sphinx_extensions/pep_theme/theme.conf diff --git a/.github/workflows/deploy-gh-pages.yaml b/.github/workflows/deploy-gh-pages.yaml index 5fbff052a59..64d2732c57e 100644 --- a/.github/workflows/deploy-gh-pages.yaml +++ b/.github/workflows/deploy-gh-pages.yaml @@ -36,6 +36,10 @@ jobs: - name: 🔧 Build PEPs run: make pages -j$(nproc) + # remove the .doctrees folder when building for deployment as it takes two thirds of disk space + - name: 🔥 Clean up files + run: rm -r build/.doctrees/ + - name: 🚀 Deploy to GitHub pages uses: JamesIves/github-pages-deploy-action@4.1.1 with: diff --git a/build.py b/build.py index 901b00d6c5f..97c01453605 100644 --- a/build.py +++ b/build.py @@ -2,7 +2,6 @@ import argparse from pathlib import Path -import shutil from sphinx.application import Sphinx @@ -25,11 +24,16 @@ def create_parser(): return parser.parse_args() -def create_index_file(html_root: Path): +def create_index_file(html_root: Path, builder: str) -> None: """Copies PEP 0 to the root index.html so that /peps/ works.""" - pep_zero_path = html_root / "pep-0000" / "index.html" - if pep_zero_path.is_file(): - shutil.copy(pep_zero_path, html_root / "index.html") + pep_zero_file = "pep-0000.html" if builder == "html" else "pep-0000/index.html" + try: + pep_zero_text = html_root.joinpath(pep_zero_file).read_text(encoding="utf-8") + except FileNotFoundError: + return None + if builder == "dirhtml": + pep_zero_text = pep_zero_text.replace('="../', '="') # remove relative directory links + html_root.joinpath("index.html").write_text(pep_zero_text, encoding="utf-8") if __name__ == "__main__": @@ -67,7 +71,8 @@ def create_index_file(html_root: Path): parallel=args.jobs, ) app.builder.copysource = False # Prevent unneeded source copying - we link direct to GitHub + app.builder.search = False # Disable search app.build() if args.index_file: - create_index_file(build_directory) + create_index_file(build_directory, sphinx_builder) diff --git a/conf.py b/conf.py index 91e9dcdfb22..422a65fd5bb 100644 --- a/conf.py +++ b/conf.py @@ -1,7 +1,7 @@ """Configuration for building PEPs using Sphinx.""" -import sys from pathlib import Path +import sys sys.path.append(str(Path("pep_sphinx_extensions").absolute())) @@ -44,3 +44,13 @@ html_show_copyright = False # Turn off miscellany html_show_sphinx = False html_title = "peps.python.org" # Set + +# Theme settings +html_theme_path = ["pep_sphinx_extensions"] +html_theme = "pep_theme" # The actual theme directory (child of html_theme_path) +html_use_index = False # Disable index (we use PEP 0) +html_sourcelink_suffix = "" # Fix links to GitHub (don't append .txt) +html_style = "" # must be defined here or in theme.conf, but is unused +html_permalinks = False # handled in the PEPContents transform + +templates_path = ['pep_sphinx_extensions/pep_theme/templates'] # Theme template relative paths from `confdir` diff --git a/pep-0310.txt b/pep-0310.txt index 61d72d0fe57..29fcf705d36 100644 --- a/pep-0310.txt +++ b/pep-0310.txt @@ -238,12 +238,9 @@ could be mentioned here. https://mail.python.org/pipermail/python-dev/2003-August/037795.html .. [3] Thread on python-dev with subject - -.. [Python-Dev] pre-PEP: Resource-Release Support for Generators - - starting at - - https://mail.python.org/pipermail/python-dev/2003-August/037803.html + `[Python-Dev] pre-PEP: Resource-Release Support for Generators` + starting at + https://mail.python.org/pipermail/python-dev/2003-August/037803.html Copyright ========= diff --git a/pep-0439.txt b/pep-0439.txt index 13e909ed8a8..d1a2be14576 100644 --- a/pep-0439.txt +++ b/pep-0439.txt @@ -147,11 +147,11 @@ The "pip3" command will support two new command-line options that are used in the boostrapping, and otherwise ignored. They control where the pip implementation is installed: ---bootstrap +``--bootstrap`` Install to the user's packages directory. The name of this option is chosen to promote it as the preferred installation option. ---bootstrap-to-system +``--bootstrap-to-system`` Install to the system site-packages directory. These command-line options will also need to be implemented, but otherwise diff --git a/pep-3143.txt b/pep-3143.txt index d7e76518dc5..fa0858d251f 100644 --- a/pep-3143.txt +++ b/pep-3143.txt @@ -22,24 +22,6 @@ any daemon regardless of what else the program may need to do. This PEP introduces a package to the Python standard library that provides a simple interface to the task of becoming a daemon process. - -.. contents:: -.. - Table of Contents: - Abstract - Specification - Example usage - Interface - ``DaemonContext`` objects - Motivation - Rationale - Correct daemon behaviour - A daemon is not a service - Reference Implementation - Other daemon implementations - References - Copyright - ============ PEP Deferral ============ diff --git a/pep_sphinx_extensions/__init__.py b/pep_sphinx_extensions/__init__.py index 326521eabea..37ffde4081c 100644 --- a/pep_sphinx_extensions/__init__.py +++ b/pep_sphinx_extensions/__init__.py @@ -5,8 +5,10 @@ from typing import TYPE_CHECKING from docutils.writers.html5_polyglot import HTMLTranslator +from sphinx.environment import BuildEnvironment from sphinx.environment import default_settings +from pep_sphinx_extensions import config from pep_sphinx_extensions.pep_processor.html import pep_html_translator from pep_sphinx_extensions.pep_processor.parsing import pep_parser from pep_sphinx_extensions.pep_processor.parsing import pep_role @@ -26,19 +28,31 @@ "_disable_config": True, # disable using docutils.conf whilst running both PEP generators } +# Monkeypatch sphinx.environment.BuildEnvironment.collect_relations, as it takes a long time +# and we don't use the parent/next/prev functionality +BuildEnvironment.collect_relations = lambda self: {} + def _depart_maths(): pass # No-op callable for the type checker +def _update_config_for_builder(app: Sphinx): + if app.builder.name == "dirhtml": + config.pep_url = f"../{config.pep_stem}" + app.env.settings["pep_file_url_template"] = "../pep-%04d" + + def setup(app: Sphinx) -> dict[str, bool]: """Initialize Sphinx extension.""" # Register plugin logic app.add_source_parser(pep_parser.PEPParser) # Add PEP transforms app.add_role("pep", pep_role.PEPRole(), override=True) # Transform PEP references to links - app.set_translator("html", pep_html_translator.PEPTranslator) # Docutils Node Visitor overrides + app.set_translator("html", pep_html_translator.PEPTranslator) # Docutils Node Visitor overrides (html builder) + app.set_translator("dirhtml", pep_html_translator.PEPTranslator) # Docutils Node Visitor overrides (dirhtml builder) app.connect("env-before-read-docs", create_pep_zero) # PEP 0 hook + app.connect("builder-inited", _update_config_for_builder) # Update configuration values for builder used # Mathematics rendering inline_maths = HTMLTranslator.visit_math, _depart_maths diff --git a/pep_sphinx_extensions/pep_processor/parsing/pep_role.py b/pep_sphinx_extensions/pep_processor/parsing/pep_role.py index 5df21a731b6..6260c342261 100644 --- a/pep_sphinx_extensions/pep_processor/parsing/pep_role.py +++ b/pep_sphinx_extensions/pep_processor/parsing/pep_role.py @@ -1,6 +1,6 @@ from sphinx import roles -from pep_sphinx_extensions.config import pep_url +from pep_sphinx_extensions import config class PEPRole(roles.PEP): @@ -8,9 +8,8 @@ class PEPRole(roles.PEP): def build_uri(self) -> str: """Get PEP URI from role text.""" - base_url = self.inliner.document.settings.pep_base_url - pep_num, _, fragment = self.target.partition("#") - pep_base = base_url + pep_url.format(int(pep_num)) + pep_str, _, fragment = self.target.partition("#") + pep_base = config.pep_url.format(int(pep_str)) if fragment: return f"{pep_base}#{fragment}" return pep_base diff --git a/pep_sphinx_extensions/pep_processor/transforms/pep_contents.py b/pep_sphinx_extensions/pep_processor/transforms/pep_contents.py index 94caf3ba832..db975549f7f 100644 --- a/pep_sphinx_extensions/pep_processor/transforms/pep_contents.py +++ b/pep_sphinx_extensions/pep_processor/transforms/pep_contents.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from pathlib import Path from docutils import nodes @@ -14,21 +16,20 @@ class PEPContents(transforms.Transform): def apply(self) -> None: if not Path(self.document["source"]).match("pep-*"): return # not a PEP file, exit early - # Create the contents placeholder section - title = nodes.title("", "Contents") - contents_topic = nodes.topic("", title, classes=["contents"]) + title = nodes.title("", "", nodes.Text("Contents")) + contents_section = nodes.section("", title) if not self.document.has_name("contents"): - contents_topic["names"].append("contents") - self.document.note_implicit_target(contents_topic) + contents_section["names"].append("contents") + self.document.note_implicit_target(contents_section) # Add a table of contents builder pending = nodes.pending(Contents) - contents_topic += pending + contents_section += pending self.document.note_pending(pending) # Insert the toc after title and PEP headers - self.document.children[0].insert(2, contents_topic) + self.document.children[0].insert(2, contents_section) # Add a horizontal rule before contents transition = nodes.transition() @@ -37,7 +38,7 @@ def apply(self) -> None: class Contents(parts.Contents): """Build Table of Contents from document.""" - def __init__(self, document, startnode=None): + def __init__(self, document: nodes.document, startnode: nodes.Node | None = None): super().__init__(document, startnode) # used in parts.Contents.build_contents @@ -45,19 +46,34 @@ def __init__(self, document, startnode=None): self.backlinks = None def apply(self) -> None: - # used in parts.Contents.build_contents - self.toc_id = self.startnode.parent["ids"][0] - self.backlinks = self.document.settings.toc_backlinks - - # let the writer (or output software) build the contents list? - if getattr(self.document.settings, "use_latex_toc", False): - # move customisation settings to the parent node - self.startnode.parent.attributes.update(self.startnode.details) - self.startnode.parent.remove(self.startnode) + contents = self.build_contents(self.document[0][4:]) # skip PEP title, headers, <hr/>, and contents + if contents: + self.startnode.replace_self(contents) else: - contents = self.build_contents(self.document[0]) - if contents: - self.startnode.replace_self(contents) - else: - # if no contents, remove the empty placeholder - self.startnode.parent.parent.remove(self.startnode.parent) + # if no contents, remove the empty placeholder + self.startnode.parent.parent.remove(self.startnode.parent) + + def build_contents(self, node: nodes.Node | list[nodes.Node], _level: None = None): + entries = [] + children = getattr(node, "children", node) + + for section in children: + if not isinstance(section, nodes.section): + continue + + title = section[0] + + # remove all pre-existing hyperlinks in the title (e.g. PEP references) + while (link_node := title.next_node(nodes.reference)) is not None: + link_node.replace_self(link_node[0]) + ref_id = section['ids'][0] + title["refid"] = ref_id # Add a link to self + entry_text = self.copy_and_filter(title) + reference = nodes.reference("", "", refid=ref_id, *entry_text) + item = nodes.list_item("", nodes.paragraph("", "", reference)) + + item += self.build_contents(section) # recurse to add sub-sections + entries.append(item) + if entries: + return nodes.bullet_list('', *entries) + return [] diff --git a/pep_sphinx_extensions/pep_processor/transforms/pep_footer.py b/pep_sphinx_extensions/pep_processor/transforms/pep_footer.py index 9f25df2ae5a..5fa1b3844d4 100644 --- a/pep_sphinx_extensions/pep_processor/transforms/pep_footer.py +++ b/pep_sphinx_extensions/pep_processor/transforms/pep_footer.py @@ -69,43 +69,43 @@ def apply(self) -> None: # If there are no references after TargetNotes has finished, remove the # references section - pending = nodes.pending(misc.CallBack, details={"callback": self.cleanup_callback}) + pending = nodes.pending(misc.CallBack, details={"callback": _cleanup_callback}) reference_section.append(pending) self.document.note_pending(pending, priority=1) # Add link to source text and last modified date - self.add_source_link(pep_source_path) - self.add_commit_history_info(pep_source_path) - - @staticmethod - def cleanup_callback(pending: nodes.pending) -> None: - """Remove an empty "References" section. - - Called after the `references.TargetNotes` transform is complete. - - """ - if len(pending.parent) == 2: # <title> and <pending> - pending.parent.parent.remove(pending.parent) - - def add_source_link(self, pep_source_path: Path) -> None: - """Add link to source text on VCS (GitHub)""" - source_link = config.pep_vcs_url + pep_source_path.name - link_node = nodes.reference("", source_link, refuri=source_link) - span_node = nodes.inline("", "Source: ", link_node) - self.document.append(span_node) - - def add_commit_history_info(self, pep_source_path: Path) -> None: - """Use local git history to find last modified date.""" - args = ["git", "--no-pager", "log", "-1", "--format=%at", pep_source_path.name] - try: - file_modified = subprocess.check_output(args) - since_epoch = file_modified.decode("utf-8").strip() - dt = datetime.datetime.utcfromtimestamp(float(since_epoch)) - except (subprocess.CalledProcessError, ValueError): - return None - - commit_link = config.pep_commits_url + pep_source_path.name - link_node = nodes.reference("", f"{dt.isoformat()}Z", refuri=commit_link) - span_node = nodes.inline("", "Last modified: ", link_node) - self.document.append(nodes.line("", "", classes=["zero-height"])) - self.document.append(span_node) + if pep_source_path.stem != "pep-0000": + self.document += _add_source_link(pep_source_path) + self.document += _add_commit_history_info(pep_source_path) + + +def _cleanup_callback(pending: nodes.pending) -> None: + """Remove an empty "References" section. + + Called after the `references.TargetNotes` transform is complete. + + """ + if len(pending.parent) == 2: # <title> and <pending> + pending.parent.parent.remove(pending.parent) + + +def _add_source_link(pep_source_path: Path) -> nodes.paragraph: + """Add link to source text on VCS (GitHub)""" + source_link = config.pep_vcs_url + pep_source_path.name + link_node = nodes.reference("", source_link, refuri=source_link) + return nodes.paragraph("", "Source: ", link_node) + + +def _add_commit_history_info(pep_source_path: Path) -> nodes.paragraph: + """Use local git history to find last modified date.""" + args = ["git", "--no-pager", "log", "-1", "--format=%at", pep_source_path.name] + try: + file_modified = subprocess.check_output(args) + since_epoch = file_modified.decode("utf-8").strip() + dt = datetime.datetime.utcfromtimestamp(float(since_epoch)) + except (subprocess.CalledProcessError, ValueError): + return nodes.paragraph() + + commit_link = config.pep_commits_url + pep_source_path.name + link_node = nodes.reference("", f"{dt.isoformat(sep=' ')} GMT", refuri=commit_link) + return nodes.paragraph("", "Last modified: ", link_node) diff --git a/pep_sphinx_extensions/pep_processor/transforms/pep_headers.py b/pep_sphinx_extensions/pep_processor/transforms/pep_headers.py index 5e5a4bc18b2..12805db4981 100644 --- a/pep_sphinx_extensions/pep_processor/transforms/pep_headers.py +++ b/pep_sphinx_extensions/pep_processor/transforms/pep_headers.py @@ -1,12 +1,13 @@ +from __future__ import annotations + from pathlib import Path import re from docutils import nodes from docutils import transforms -from docutils.transforms import peps from sphinx import errors -from pep_sphinx_extensions.config import pep_url +from pep_sphinx_extensions import config from pep_sphinx_extensions.pep_processor.transforms import pep_zero @@ -69,21 +70,18 @@ def apply(self) -> None: raise PEPParsingError(msg) para = body[0] - if name in {"author", "bdfl-delegate", "pep-delegate", "sponsor"}: + if name in {"author", "bdfl-delegate", "pep-delegate", "discussions-to", "sponsor"}: # mask emails for node in para: if isinstance(node, nodes.reference): - pep_num = pep if name == "discussions-to" else -1 - node.replace_self(peps.mask_email(node, pep_num)) + pep_num = pep if name == "discussions-to" else None + node.replace_self(_mask_email(node, pep_num)) elif name in {"replaces", "superseded-by", "requires"}: # replace PEP numbers with normalised list of links to PEPs new_body = [] - space = nodes.Text(" ") for ref_pep in re.split(r",?\s+", body.astext()): - new_body.append(nodes.reference( - ref_pep, ref_pep, - refuri=(self.document.settings.pep_base_url + pep_url.format(int(ref_pep))))) - new_body.append(space) + new_body += [nodes.reference("", ref_pep, refuri=config.pep_url.format(int(ref_pep)))] + new_body += [nodes.Text(", ")] para[:] = new_body[:-1] # drop trailing space elif name in {"last-modified", "content-type", "version"}: # Mark unneeded fields @@ -94,7 +92,7 @@ def apply(self) -> None: field.parent.remove(field) -def _mask_email(ref: nodes.reference, pep_num: int = -1) -> nodes.reference: +def _mask_email(ref: nodes.reference, pep_num: int | None = None) -> nodes.reference: """Mask the email address in `ref` and return a replacement node. `ref` is returned unchanged if it contains no email address. @@ -105,15 +103,12 @@ def _mask_email(ref: nodes.reference, pep_num: int = -1) -> nodes.reference: If given a PEP number `pep_num`, add a default email subject. """ - if "refuri" in ref and ref["refuri"].startswith("mailto:"): - non_masked_addresses = {"peps@python.org", "python-list@python.org", "python-dev@python.org"} - if ref['refuri'].removeprefix("mailto:").strip() in non_masked_addresses: - replacement = ref[0] - else: - replacement_text = ref.astext().replace("@", " at ") - replacement = nodes.raw('', replacement_text, format="html") - - if pep_num != -1: - replacement['refuri'] += f"?subject=PEP%20{pep_num}" - return replacement + if "refuri" not in ref or not ref["refuri"].startswith("mailto:"): + return ref + non_masked_addresses = {"peps@python.org", "python-list@python.org", "python-dev@python.org"} + if ref["refuri"].removeprefix("mailto:").strip() not in non_masked_addresses: + ref[0] = nodes.raw("", ref[0].replace("@", " at "), format="html") + if pep_num is None: + return ref[0] # return email text without mailto link + ref["refuri"] += f"?subject=PEP%20{pep_num}" return ref diff --git a/pep_sphinx_extensions/pep_processor/transforms/pep_title.py b/pep_sphinx_extensions/pep_processor/transforms/pep_title.py index 84657fbadd0..14e1190aabc 100644 --- a/pep_sphinx_extensions/pep_processor/transforms/pep_title.py +++ b/pep_sphinx_extensions/pep_processor/transforms/pep_title.py @@ -1,7 +1,10 @@ from pathlib import Path from docutils import nodes -import docutils.transforms as transforms +from docutils import transforms +from docutils import utils +from docutils.parsers.rst import roles +from docutils.parsers.rst import states class PEPTitle(transforms.Transform): @@ -34,16 +37,20 @@ def apply(self) -> None: pep_title_string = f"PEP {pep_number} -- {pep_title}" # double hyphen for en dash # Generate the title section node and its properties - pep_title_node = nodes.section() - text_node = nodes.Text(pep_title_string, pep_title_string) - title_node = nodes.title(pep_title_string, "", text_node) - title_node["classes"].append("page-title") - name = " ".join(title_node.astext().lower().split()) # normalise name - pep_title_node["names"].append(name) - pep_title_node += title_node + title_nodes = _line_to_nodes(pep_title_string) + pep_title_node = nodes.section("", nodes.title("", "", *title_nodes, classes=["page-title"]), names=["pep-content"]) # Insert the title node as the root element, move children down document_children = self.document.children self.document.children = [pep_title_node] pep_title_node.extend(document_children) self.document.note_implicit_target(pep_title_node, pep_title_node) + + +def _line_to_nodes(text: str) -> list[nodes.Node]: + """Parse RST string to nodes.""" + document = utils.new_document("<inline-rst>") + document.settings.pep_references = document.settings.rfc_references = False # patch settings + states.RSTStateMachine(state_classes=states.state_classes, initial_state="Body").run([text], document) # do parsing + roles._roles.pop("", None) # restore the "default" default role after parsing a document + return document[0].children diff --git a/pep_sphinx_extensions/pep_processor/transforms/pep_zero.py b/pep_sphinx_extensions/pep_processor/transforms/pep_zero.py index bfaa82a4184..b638dbbb87a 100644 --- a/pep_sphinx_extensions/pep_processor/transforms/pep_zero.py +++ b/pep_sphinx_extensions/pep_processor/transforms/pep_zero.py @@ -2,7 +2,7 @@ from docutils import transforms from docutils.transforms import peps -from pep_sphinx_extensions.config import pep_url +from pep_sphinx_extensions import config class PEPZero(transforms.Transform): @@ -68,7 +68,7 @@ def visit_entry(self, node: nodes.entry) -> None: if isinstance(para, nodes.paragraph) and len(para) == 1: pep_str = para.astext() try: - ref = self.document.settings.pep_base_url + pep_url.format(int(pep_str)) + ref = config.pep_url.format(int(pep_str)) para[0] = nodes.reference(pep_str, pep_str, refuri=ref) except ValueError: pass diff --git a/pep_sphinx_extensions/pep_theme/static/doctools.js b/pep_sphinx_extensions/pep_theme/static/doctools.js new file mode 100644 index 00000000000..5676a8ff3a2 --- /dev/null +++ b/pep_sphinx_extensions/pep_theme/static/doctools.js @@ -0,0 +1,5 @@ +/* JavaScript utilities for all documentation. */ + +// Footnote fixer +document.querySelectorAll("span.brackets").forEach(el => el.innerHTML = "[" + el.innerHTML + "]") +document.querySelectorAll("a.brackets").forEach(el => el.innerHTML = "[" + el.innerHTML + "]") diff --git a/pep_sphinx_extensions/pep_theme/static/mq.css b/pep_sphinx_extensions/pep_theme/static/mq.css new file mode 100644 index 00000000000..c800e5d925b --- /dev/null +++ b/pep_sphinx_extensions/pep_theme/static/mq.css @@ -0,0 +1,95 @@ +@charset "UTF-8"; +/* Media Queries */ +@media (max-width: 32.5em) { + /* Reduce padding & margins for the smallest screens */ + section#pep-page-section > header > h1 { + padding-right: 0; + border-right: none; + } + ul.breadcrumbs { + padding: 0 0 .5rem; + } + nav#pep-sidebar { + display: none; + } + table th, + table td { + padding: 0 0.1rem; + } +} +@media (min-width: 32.5em) { + section#pep-page-section { + max-width: 40em; + width: 100%; + margin: 0 auto; + padding: .5rem 1rem 0; + } +} +@media (min-width: 54em) { + section#pep-page-section { + max-width: 75em; + } + section#pep-page-section > article { + max-width: 40em; + width: 74%; + float: right; + margin-right: 0; + font-size: 1rem; + } + nav#pep-sidebar { + width: 24%; + float: left; + margin-right: 2%; + } +} +@media (min-width: 60em) { + section#pep-page-section > article { + max-width: none; + padding-left: 3.2%; + padding-right: 3.2%; + } +} + +@media print { + *, + *:before, + *:after { + color: #000 !important; + } + body { + font-size: 10pt; + line-height: 1.67; + } + *[role="main"] a[href]:after { + content: " (" attr(href) ")"; + font-size: .75rem; + } + pre, + blockquote { + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + @page { + margin: 0.5cm; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h1, + h2, + h3 { + page-break-after: avoid; + } +} diff --git a/pep_sphinx_extensions/pep_theme/static/py.png b/pep_sphinx_extensions/pep_theme/static/py.png new file mode 100644 index 0000000000000000000000000000000000000000..93e4a02c3d321c545898a2ebb8873c26dd8a9e5b GIT binary patch literal 695 zcmV;o0!aOdP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00001b5ch_0Itp) z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOD~ z5jY_RGbdgE00K8jL_t(I%Vm>KNL4`;hrfH@vv+$Uln|xSCTLTOHmzE;5FTwpDtJ)a zz)exE-2_34AR^i+ZCXeh7m*-F5k*FrmbSHs%rX_!{PXUec{69`w(#D(>Vd<+%=bIz zH_V)XUD5sE?K5ebbKaSDu}#Dnq^Y!3`tis9>wSO*T+WBP3wS3NNBU~*PAtw^yjZ+< zQ5yK0z%d{y@_Pz6YdL>s7$<f+t<8@+fNGI119U%pf7@GoT&xO=P6mx?ltsrSXAI%K zN<B?|&R2lajslk-eR%D}cBNjxdjg<TZh=Y_f)Y;b4u|PneZkvUi*DSnoO*XJ^xk!C zSv5OtXPJkn%~8N2387O_8GQrjEa#vvh<xGRY3jAp@tdh9eFMu=wHl&85<;)qfsXPH zv9;*N+UWfF@YDI}S8IR(DPZQtY9|l2N-s`<OF$BU{beyt?Zrk;ENXpuXkwsuaO3B# zE3MIqOQBQIe^_Gj?uf#R)<6(Ck=fk(pUIcc<6pf!ZB>6Y^hI$}5GUX?Ns>+iCUiRP z7h|*&%kQa?wQA%pYzqpaf*4?WM!x_ygY0K@07rplx^QhGhnE)r4wbx0x11<&3V?63 z=F8ch)p6iQ88E;iumPmSZif%JIwIgKh$T3)*ab8ImGLVf;IoYtVAb11Z&@wm>96=S z)!B<I0N=AU*)t1`jrF?K@n>&hojbAPoik&f)Lp;=N!HK;X~5oyfJ49$pbcorTyADv dm$H!t;2#0MumAjv0Ga>*002ovPDHLkV1gIiH5UK? literal 0 HcmV?d00001 diff --git a/pep_sphinx_extensions/pep_theme/static/style.css b/pep_sphinx_extensions/pep_theme/static/style.css new file mode 100644 index 00000000000..4172861e39c --- /dev/null +++ b/pep_sphinx_extensions/pep_theme/static/style.css @@ -0,0 +1,292 @@ +@charset "UTF-8"; +/* Styles for PEPs + +Colours: +white: + background + footnotes/references vertical border +#333 + body text +#888 + blockquote left line + header breadcrumbs separator + link underline (hovered/focussed) +#ccc: + scrollbar +#ddd + header bottom border + horizontal rule + table vertical border +#eee: + link underline + table rows & top/bottom border + PEP header rows + footnotes/references rows + admonition note background +#f8f8f8: + inline code background + +#0072aa: + links +# fee: + admonition warning background + +*/ + +/* Set master rules */ +* {box-sizing: border-box} +html { + overflow-y: scroll; + -webkit-font-smoothing: antialiased; + margin: 0; + line-height: 1.4rem; + font-weight: normal; + font-size: 1rem; + font-family: "Source Sans Pro", Arial, sans-serif; +} +body { + margin: 0; + color: #333; + background-color: white; +} +section#pep-page-section { + padding: 0.25rem 0.25rem 0; + display: table; +} + +/* Reduce margin sizes for body text */ +p {margin: .5rem 0} + +/* Header rules */ +h1.page-title { + line-height: 2.3rem; + font-size: 2rem; + font-weight: bold; + margin-top: 2rem; + margin-bottom: 1.5rem; +} +h2 { + font-size: 1.6rem; + font-weight: bold; + margin-top: 1rem; + margin-bottom: .5rem; +} +h3 { + font-size: 1.4rem; + font-weight: normal; + margin-top: 1rem; + margin-bottom: 0.5rem; +} +h4 { + font-size: 1.2rem; + font-weight: normal; + margin-top: .5rem; + margin-bottom: 0; +} +h5, +h6 { + font-size: 1rem; + font-weight: bold; + margin-top: 0; + margin-bottom: 0; +} + +/* Anchor link rules */ +a, +a:active, +a:visited { + color: #0072aa; + text-decoration-color: #eee; + display: inline; +} +a:hover, +a:focus { + text-decoration-color: #888; +} + +/* Blockquote rules */ +blockquote { + font-style: italic; + border-left: 1px solid #888; + margin: .5rem; + padding: .5rem 1rem; +} +blockquote em { + font-style: normal; +} + +cite { + font-style: italic; +} + +/* Code rules (code literals and Pygments highlighting blocks) */ +pre, +code { + font-family: ui-monospace, "Cascadia Mono", "Segoe UI Mono", "DejaVu Sans Mono", Consolas, monospace; + white-space: pre-wrap; + word-wrap: break-word; + -webkit-hyphens: none; + hyphens: none; +} +code.literal { + font-size: .8em; + background-color: #f8f8f8; +} +pre { + padding: .5rem .75rem; +} + +/* Definition list rules */ +dl dt { + font-weight: bold; +} +dl dd { + margin: 0; +} + +/* Horizontal rule rule */ +hr { + border: 0; + border-top: 1px solid #ddd; + margin: 1.75rem 0; +} +/*Image rules */ +img { + max-width: 100%; +} +a img { + display: block; + margin: 0 auto; +} + +/* List rules */ +ul, +ol { + padding: 0; + margin: 0 0 0 1.5rem; +} +ul {list-style: square} +ol.arabic {list-style: decimal} +ol.loweralpha {list-style: lower-alpha} +ol.upperalpha {list-style: upper-alpha} +ol.lowerroman {list-style: lower-roman} +ol.upperroman {list-style: upper-roman} + +/* Maths rules */ +sub, +sup { + font-size: .75em; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup {top: -0.5em} +sub {bottom: -0.25em} + +/* Table rules */ +table { + width: 100%; + border-collapse: collapse; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; +} +table caption { + margin: 1rem 0 .75rem; +} +table tbody tr:nth-of-type(odd) { + background-color: #eee; +} +table th, +table td { + text-align: left; + padding: 0.25rem 0.5rem 0.2rem; +} +table td + td { + border-left: 1px solid #ddd; +} + +/* Breadcrumbs rules */ +section#pep-page-section > header { + border-bottom: 1px solid #ddd; +} +section#pep-page-section > header > h1 { + font-size: 1.1rem; + margin: 0; + display: inline-block; + padding-right: .6rem; + border-right: 1px solid #888; +} +ul.breadcrumbs { + margin: 0; + padding: .5rem 0 .5rem .4rem; + list-style: none; + display: inline-block; +} +ul.breadcrumbs li { + display: inline; +} +ul.breadcrumbs a { + text-decoration: none; +} + +/* Admonitions rules */ +div.note, +div.warning { + padding: 0.5rem 0.75rem; + margin-top: 1rem; + margin-bottom: 1rem; +} +div.note { + background-color: #eee; +} +div.warning { + background-color: #fee; +} +p.admonition-title { + font-weight: bold; +} + +/* PEP Header / references rules */ +dl.rfc2822, +dl.footnote { + display: grid; + grid-template-columns: fit-content(30%) auto; + line-height: 1.875; + width: 100%; + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; +} +dl.rfc2822 > dt, dl.rfc2822 > dd, +dl.footnote > dt, dl.footnote > dd { + padding: .25rem .5rem .2rem; +} +dl.rfc2822 > dt:nth-of-type(even), dl.rfc2822 > dd:nth-of-type(even), +dl.footnote > dt:nth-of-type(even), dl.footnote > dd:nth-of-type(even) { + background-color: #eee; +} +dl.footnote > dt { + font-weight: normal; + border-right: 1px solid white; +} + +/* Sidebar formatting */ +nav#pep-sidebar { + overflow-y: scroll; + position: sticky; + top: 0; + height: 100vh; + scrollbar-width: thin; /* CSS Standards, not *yet* widely supported */ + scrollbar-color: #ccc transparent; +} +nav#pep-sidebar::-webkit-scrollbar {width: 6px} +nav#pep-sidebar::-webkit-scrollbar-track {background: transparent} +nav#pep-sidebar::-webkit-scrollbar-thumb {background: #ccc} +nav#pep-sidebar > h2 { + font-size: 1.4rem; +} +nav#pep-sidebar ul { + margin-left: 1rem; +} +nav#pep-sidebar ul a { + text-decoration: none; +} diff --git a/pep_sphinx_extensions/pep_theme/templates/page.html b/pep_sphinx_extensions/pep_theme/templates/page.html new file mode 100644 index 00000000000..38fb3545118 --- /dev/null +++ b/pep_sphinx_extensions/pep_theme/templates/page.html @@ -0,0 +1,37 @@ +{# Master template for simple pages (e.g. RST files) #} +<!DOCTYPE html> +<html lang="en-GB"> +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <title>{{ title + " | "|safe + docstitle }} + + + + + + + + +
+
+

Python Enhancement Proposals

+ +
+
+ {{ body }} +
+ +
+ + + diff --git a/pep_sphinx_extensions/pep_theme/theme.conf b/pep_sphinx_extensions/pep_theme/theme.conf new file mode 100644 index 00000000000..8150ef720fa --- /dev/null +++ b/pep_sphinx_extensions/pep_theme/theme.conf @@ -0,0 +1,4 @@ +[theme] +# Theme options +inherit = none +pygments_style = tango