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,
, 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: # and
- 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: # and
+ 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("")
+ 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 00000000000..93e4a02c3d3
Binary files /dev/null and b/pep_sphinx_extensions/pep_theme/static/py.png differ
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) #}
+
+
+
+
+
+ {{ 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