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
This commit is contained in:
Adam Turner 2021-06-30 20:19:44 +01:00 committed by GitHub
parent 7d727005fd
commit 0d93abf9bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 585 additions and 123 deletions

View File

@ -36,6 +36,10 @@ jobs:
- name: 🔧 Build PEPs - name: 🔧 Build PEPs
run: make pages -j$(nproc) 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 - name: 🚀 Deploy to GitHub pages
uses: JamesIves/github-pages-deploy-action@4.1.1 uses: JamesIves/github-pages-deploy-action@4.1.1
with: with:

View File

@ -2,7 +2,6 @@
import argparse import argparse
from pathlib import Path from pathlib import Path
import shutil
from sphinx.application import Sphinx from sphinx.application import Sphinx
@ -25,11 +24,16 @@ def create_parser():
return parser.parse_args() 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.""" """Copies PEP 0 to the root index.html so that /peps/ works."""
pep_zero_path = html_root / "pep-0000" / "index.html" pep_zero_file = "pep-0000.html" if builder == "html" else "pep-0000/index.html"
if pep_zero_path.is_file(): try:
shutil.copy(pep_zero_path, html_root / "index.html") 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__": if __name__ == "__main__":
@ -67,7 +71,8 @@ if __name__ == "__main__":
parallel=args.jobs, parallel=args.jobs,
) )
app.builder.copysource = False # Prevent unneeded source copying - we link direct to GitHub app.builder.copysource = False # Prevent unneeded source copying - we link direct to GitHub
app.builder.search = False # Disable search
app.build() app.build()
if args.index_file: if args.index_file:
create_index_file(build_directory) create_index_file(build_directory, sphinx_builder)

12
conf.py
View File

@ -1,7 +1,7 @@
"""Configuration for building PEPs using Sphinx.""" """Configuration for building PEPs using Sphinx."""
import sys
from pathlib import Path from pathlib import Path
import sys
sys.path.append(str(Path("pep_sphinx_extensions").absolute())) sys.path.append(str(Path("pep_sphinx_extensions").absolute()))
@ -44,3 +44,13 @@ html_math_renderer = "maths_to_html" # Maths rendering
html_show_copyright = False # Turn off miscellany html_show_copyright = False # Turn off miscellany
html_show_sphinx = False html_show_sphinx = False
html_title = "peps.python.org" # Set <title/> html_title = "peps.python.org" # Set <title/>
# 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`

View File

@ -238,11 +238,8 @@ could be mentioned here.
https://mail.python.org/pipermail/python-dev/2003-August/037795.html https://mail.python.org/pipermail/python-dev/2003-August/037795.html
.. [3] Thread on python-dev with subject .. [3] Thread on python-dev with subject
`[Python-Dev] pre-PEP: Resource-Release Support for Generators`
.. [Python-Dev] pre-PEP: Resource-Release Support for Generators
starting at starting at
https://mail.python.org/pipermail/python-dev/2003-August/037803.html https://mail.python.org/pipermail/python-dev/2003-August/037803.html
Copyright Copyright

View File

@ -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 in the boostrapping, and otherwise ignored. They control where the pip
implementation is installed: implementation is installed:
--bootstrap ``--bootstrap``
Install to the user's packages directory. The name of this option is chosen Install to the user's packages directory. The name of this option is chosen
to promote it as the preferred installation option. to promote it as the preferred installation option.
--bootstrap-to-system ``--bootstrap-to-system``
Install to the system site-packages directory. Install to the system site-packages directory.
These command-line options will also need to be implemented, but otherwise These command-line options will also need to be implemented, but otherwise

View File

@ -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 This PEP introduces a package to the Python standard library that
provides a simple interface to the task of becoming a daemon process. 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 PEP Deferral
============ ============

View File

@ -5,8 +5,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from docutils.writers.html5_polyglot import HTMLTranslator from docutils.writers.html5_polyglot import HTMLTranslator
from sphinx.environment import BuildEnvironment
from sphinx.environment import default_settings 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.html import pep_html_translator
from pep_sphinx_extensions.pep_processor.parsing import pep_parser from pep_sphinx_extensions.pep_processor.parsing import pep_parser
from pep_sphinx_extensions.pep_processor.parsing import pep_role from pep_sphinx_extensions.pep_processor.parsing import pep_role
@ -26,19 +28,31 @@ default_settings |= {
"_disable_config": True, # disable using docutils.conf whilst running both PEP generators "_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(): def _depart_maths():
pass # No-op callable for the type checker 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]: def setup(app: Sphinx) -> dict[str, bool]:
"""Initialize Sphinx extension.""" """Initialize Sphinx extension."""
# Register plugin logic # Register plugin logic
app.add_source_parser(pep_parser.PEPParser) # Add PEP transforms 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.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("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 # Mathematics rendering
inline_maths = HTMLTranslator.visit_math, _depart_maths inline_maths = HTMLTranslator.visit_math, _depart_maths

View File

@ -1,6 +1,6 @@
from sphinx import roles from sphinx import roles
from pep_sphinx_extensions.config import pep_url from pep_sphinx_extensions import config
class PEPRole(roles.PEP): class PEPRole(roles.PEP):
@ -8,9 +8,8 @@ class PEPRole(roles.PEP):
def build_uri(self) -> str: def build_uri(self) -> str:
"""Get PEP URI from role text.""" """Get PEP URI from role text."""
base_url = self.inliner.document.settings.pep_base_url pep_str, _, fragment = self.target.partition("#")
pep_num, _, fragment = self.target.partition("#") pep_base = config.pep_url.format(int(pep_str))
pep_base = base_url + pep_url.format(int(pep_num))
if fragment: if fragment:
return f"{pep_base}#{fragment}" return f"{pep_base}#{fragment}"
return pep_base return pep_base

View File

@ -1,3 +1,5 @@
from __future__ import annotations
from pathlib import Path from pathlib import Path
from docutils import nodes from docutils import nodes
@ -14,21 +16,20 @@ class PEPContents(transforms.Transform):
def apply(self) -> None: def apply(self) -> None:
if not Path(self.document["source"]).match("pep-*"): if not Path(self.document["source"]).match("pep-*"):
return # not a PEP file, exit early return # not a PEP file, exit early
# Create the contents placeholder section # Create the contents placeholder section
title = nodes.title("", "Contents") title = nodes.title("", "", nodes.Text("Contents"))
contents_topic = nodes.topic("", title, classes=["contents"]) contents_section = nodes.section("", title)
if not self.document.has_name("contents"): if not self.document.has_name("contents"):
contents_topic["names"].append("contents") contents_section["names"].append("contents")
self.document.note_implicit_target(contents_topic) self.document.note_implicit_target(contents_section)
# Add a table of contents builder # Add a table of contents builder
pending = nodes.pending(Contents) pending = nodes.pending(Contents)
contents_topic += pending contents_section += pending
self.document.note_pending(pending) self.document.note_pending(pending)
# Insert the toc after title and PEP headers # 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 # Add a horizontal rule before contents
transition = nodes.transition() transition = nodes.transition()
@ -37,7 +38,7 @@ class PEPContents(transforms.Transform):
class Contents(parts.Contents): class Contents(parts.Contents):
"""Build Table of Contents from document.""" """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) super().__init__(document, startnode)
# used in parts.Contents.build_contents # used in parts.Contents.build_contents
@ -45,19 +46,34 @@ class Contents(parts.Contents):
self.backlinks = None self.backlinks = None
def apply(self) -> None: def apply(self) -> None:
# used in parts.Contents.build_contents contents = self.build_contents(self.document[0][4:]) # skip PEP title, headers, <hr/>, and 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)
else:
contents = self.build_contents(self.document[0])
if contents: if contents:
self.startnode.replace_self(contents) self.startnode.replace_self(contents)
else: else:
# if no contents, remove the empty placeholder # if no contents, remove the empty placeholder
self.startnode.parent.parent.remove(self.startnode.parent) 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 []

View File

@ -69,16 +69,17 @@ class PEPFooter(transforms.Transform):
# If there are no references after TargetNotes has finished, remove the # If there are no references after TargetNotes has finished, remove the
# references section # 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) reference_section.append(pending)
self.document.note_pending(pending, priority=1) self.document.note_pending(pending, priority=1)
# Add link to source text and last modified date # Add link to source text and last modified date
self.add_source_link(pep_source_path) if pep_source_path.stem != "pep-0000":
self.add_commit_history_info(pep_source_path) self.document += _add_source_link(pep_source_path)
self.document += _add_commit_history_info(pep_source_path)
@staticmethod
def cleanup_callback(pending: nodes.pending) -> None: def _cleanup_callback(pending: nodes.pending) -> None:
"""Remove an empty "References" section. """Remove an empty "References" section.
Called after the `references.TargetNotes` transform is complete. Called after the `references.TargetNotes` transform is complete.
@ -87,14 +88,15 @@ class PEPFooter(transforms.Transform):
if len(pending.parent) == 2: # <title> and <pending> if len(pending.parent) == 2: # <title> and <pending>
pending.parent.parent.remove(pending.parent) pending.parent.parent.remove(pending.parent)
def add_source_link(self, pep_source_path: Path) -> None:
def _add_source_link(pep_source_path: Path) -> nodes.paragraph:
"""Add link to source text on VCS (GitHub)""" """Add link to source text on VCS (GitHub)"""
source_link = config.pep_vcs_url + pep_source_path.name source_link = config.pep_vcs_url + pep_source_path.name
link_node = nodes.reference("", source_link, refuri=source_link) link_node = nodes.reference("", source_link, refuri=source_link)
span_node = nodes.inline("", "Source: ", link_node) return nodes.paragraph("", "Source: ", link_node)
self.document.append(span_node)
def add_commit_history_info(self, pep_source_path: Path) -> None:
def _add_commit_history_info(pep_source_path: Path) -> nodes.paragraph:
"""Use local git history to find last modified date.""" """Use local git history to find last modified date."""
args = ["git", "--no-pager", "log", "-1", "--format=%at", pep_source_path.name] args = ["git", "--no-pager", "log", "-1", "--format=%at", pep_source_path.name]
try: try:
@ -102,10 +104,8 @@ class PEPFooter(transforms.Transform):
since_epoch = file_modified.decode("utf-8").strip() since_epoch = file_modified.decode("utf-8").strip()
dt = datetime.datetime.utcfromtimestamp(float(since_epoch)) dt = datetime.datetime.utcfromtimestamp(float(since_epoch))
except (subprocess.CalledProcessError, ValueError): except (subprocess.CalledProcessError, ValueError):
return None return nodes.paragraph()
commit_link = config.pep_commits_url + pep_source_path.name commit_link = config.pep_commits_url + pep_source_path.name
link_node = nodes.reference("", f"{dt.isoformat()}Z", refuri=commit_link) link_node = nodes.reference("", f"{dt.isoformat(sep=' ')} GMT", refuri=commit_link)
span_node = nodes.inline("", "Last modified: ", link_node) return nodes.paragraph("", "Last modified: ", link_node)
self.document.append(nodes.line("", "", classes=["zero-height"]))
self.document.append(span_node)

View File

@ -1,12 +1,13 @@
from __future__ import annotations
from pathlib import Path from pathlib import Path
import re import re
from docutils import nodes from docutils import nodes
from docutils import transforms from docutils import transforms
from docutils.transforms import peps
from sphinx import errors 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 from pep_sphinx_extensions.pep_processor.transforms import pep_zero
@ -69,21 +70,18 @@ class PEPHeaders(transforms.Transform):
raise PEPParsingError(msg) raise PEPParsingError(msg)
para = body[0] 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 # mask emails
for node in para: for node in para:
if isinstance(node, nodes.reference): if isinstance(node, nodes.reference):
pep_num = pep if name == "discussions-to" else -1 pep_num = pep if name == "discussions-to" else None
node.replace_self(peps.mask_email(node, pep_num)) node.replace_self(_mask_email(node, pep_num))
elif name in {"replaces", "superseded-by", "requires"}: elif name in {"replaces", "superseded-by", "requires"}:
# replace PEP numbers with normalised list of links to PEPs # replace PEP numbers with normalised list of links to PEPs
new_body = [] new_body = []
space = nodes.Text(" ")
for ref_pep in re.split(r",?\s+", body.astext()): for ref_pep in re.split(r",?\s+", body.astext()):
new_body.append(nodes.reference( new_body += [nodes.reference("", ref_pep, refuri=config.pep_url.format(int(ref_pep)))]
ref_pep, ref_pep, new_body += [nodes.Text(", ")]
refuri=(self.document.settings.pep_base_url + pep_url.format(int(ref_pep)))))
new_body.append(space)
para[:] = new_body[:-1] # drop trailing space para[:] = new_body[:-1] # drop trailing space
elif name in {"last-modified", "content-type", "version"}: elif name in {"last-modified", "content-type", "version"}:
# Mark unneeded fields # Mark unneeded fields
@ -94,7 +92,7 @@ class PEPHeaders(transforms.Transform):
field.parent.remove(field) 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. """Mask the email address in `ref` and return a replacement node.
`ref` is returned unchanged if it contains no email address. `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 given a PEP number `pep_num`, add a default email subject.
""" """
if "refuri" in ref and ref["refuri"].startswith("mailto:"): if "refuri" not in ref or not ref["refuri"].startswith("mailto:"):
non_masked_addresses = {"peps@python.org", "python-list@python.org", "python-dev@python.org"} return ref
if ref['refuri'].removeprefix("mailto:").strip() in non_masked_addresses: non_masked_addresses = {"peps@python.org", "python-list@python.org", "python-dev@python.org"}
replacement = ref[0] if ref["refuri"].removeprefix("mailto:").strip() not in non_masked_addresses:
else: ref[0] = nodes.raw("", ref[0].replace("@", "&#32;&#97;t&#32;"), format="html")
replacement_text = ref.astext().replace("@", "&#32;&#97;t&#32;") if pep_num is None:
replacement = nodes.raw('', replacement_text, format="html") return ref[0] # return email text without mailto link
ref["refuri"] += f"?subject=PEP%20{pep_num}"
if pep_num != -1:
replacement['refuri'] += f"?subject=PEP%20{pep_num}"
return replacement
return ref return ref

View File

@ -1,7 +1,10 @@
from pathlib import Path from pathlib import Path
from docutils import nodes 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): class PEPTitle(transforms.Transform):
@ -34,16 +37,20 @@ class PEPTitle(transforms.Transform):
pep_title_string = f"PEP {pep_number} -- {pep_title}" # double hyphen for en dash pep_title_string = f"PEP {pep_number} -- {pep_title}" # double hyphen for en dash
# Generate the title section node and its properties # Generate the title section node and its properties
pep_title_node = nodes.section() title_nodes = _line_to_nodes(pep_title_string)
text_node = nodes.Text(pep_title_string, pep_title_string) pep_title_node = nodes.section("", nodes.title("", "", *title_nodes, classes=["page-title"]), names=["pep-content"])
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
# Insert the title node as the root element, move children down # Insert the title node as the root element, move children down
document_children = self.document.children document_children = self.document.children
self.document.children = [pep_title_node] self.document.children = [pep_title_node]
pep_title_node.extend(document_children) pep_title_node.extend(document_children)
self.document.note_implicit_target(pep_title_node, pep_title_node) 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

View File

@ -2,7 +2,7 @@ from docutils import nodes
from docutils import transforms from docutils import transforms
from docutils.transforms import peps from docutils.transforms import peps
from pep_sphinx_extensions.config import pep_url from pep_sphinx_extensions import config
class PEPZero(transforms.Transform): class PEPZero(transforms.Transform):
@ -68,7 +68,7 @@ class PEPZeroSpecial(nodes.SparseNodeVisitor):
if isinstance(para, nodes.paragraph) and len(para) == 1: if isinstance(para, nodes.paragraph) and len(para) == 1:
pep_str = para.astext() pep_str = para.astext()
try: 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) para[0] = nodes.reference(pep_str, pep_str, refuri=ref)
except ValueError: except ValueError:
pass pass

View File

@ -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 + "]")

View File

@ -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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

View File

@ -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;
}

View File

@ -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 }}</title>
<link rel="shortcut icon" href="{{ pathto('_static/py.png', resource=True) }}"/>
<link rel="stylesheet" href="{{ pathto('_static/style.css', resource=True) }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/mq.css', resource=True) }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', resource=True) }}" type="text/css" />
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
<meta name="description" content="Python Enhancement Proposals (PEPs)"/>
</head>
<body>
<section id="pep-page-section">
<header>
<h1>Python Enhancement Proposals</h1>
<ul class="breadcrumbs">
<li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li>
<li><a href="{{ pathto("pep-0000") }}">PEP Index</a> &raquo; </li>
<li>{{ title }}</li>
</ul>
</header>
<article>
{{ body }}
</article>
<nav id="pep-sidebar">
<h2>Contents</h2>
{{ toc }}
<br />
<strong><a href="https://github.com/python/peps/blob/master/{{sourcename}}">Page Source (GitHub)</a></strong>
</nav>
</section>
<script src="{{ pathto('_static/doctools.js', resource=True) }}"></script>
</body>
</html>

View File

@ -0,0 +1,4 @@
[theme]
# Theme options
inherit = none
pygments_style = tango