From 3d60b84e3533e32f845224714649dcb566b9d135 Mon Sep 17 00:00:00 2001
From: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 9 Jan 2022 18:07:03 +0000
Subject: [PATCH] PEP 676: Implementation updates (#2208)
---
.github/workflows/deploy-gh-pages.yaml | 18 ++---
Makefile | 2 +-
build.py | 2 -
conf.py | 4 +-
pep_rss_gen.py => generate_rss.py | 49 +++++---------
pep-0456.txt | 2 +-
pep_sphinx_extensions/__init__.py | 23 ++++---
pep_sphinx_extensions/config.py | 6 --
.../pep_processor/html/pep_html_builder.py | 50 ++++++++++++++
.../pep_processor/html/pep_html_translator.py | 44 ++++++++-----
.../pep_processor/parsing/pep_role.py | 5 +-
.../pep_processor/transforms/pep_footer.py | 46 ++++++++++---
.../pep_processor/transforms/pep_headers.py | 32 ++-------
.../pep_processor/transforms/pep_zero.py | 36 ++++++++--
.../pep_theme/static/doctools.js | 5 --
.../pep_theme/static/style.css | 3 +
.../pep_theme/templates/page.html | 3 +-
.../pep_zero_generator/writer.py | 65 ++++++++-----------
18 files changed, 215 insertions(+), 180 deletions(-)
rename pep_rss_gen.py => generate_rss.py (72%)
delete mode 100644 pep_sphinx_extensions/config.py
create mode 100644 pep_sphinx_extensions/pep_processor/html/pep_html_builder.py
delete mode 100644 pep_sphinx_extensions/pep_theme/static/doctools.js
diff --git a/.github/workflows/deploy-gh-pages.yaml b/.github/workflows/deploy-gh-pages.yaml
index 98720e06c..bb96d063b 100644
--- a/.github/workflows/deploy-gh-pages.yaml
+++ b/.github/workflows/deploy-gh-pages.yaml
@@ -14,26 +14,16 @@ jobs:
with:
fetch-depth: 0 # fetch all history so that last modified date-times are accurate
- - name: 🐍 Set up Python 3.9
+ - name: 🐍 Set up Python 3
uses: actions/setup-python@v2
with:
- python-version: 3.9
-
- - name: 🧳 Cache pip
- uses: actions/cache@v2
- with:
- # This path is specific to Ubuntu
- path: ~/.cache/pip
- # Look to see if there is a cache hit for the corresponding requirements file
- key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
- restore-keys: |
- ${{ runner.os }}-pip-
- ${{ runner.os }}-
+ python-version: 3
+ cache: "pip"
- name: 👷 Install dependencies
run: |
python -m pip install --upgrade pip
- pip install -r requirements.txt
+ python -m pip install -r requirements.txt
- name: 🔧 Build PEPs
run: make pages -j$(nproc)
diff --git a/Makefile b/Makefile
index 0f201b0c0..080be0268 100644
--- a/Makefile
+++ b/Makefile
@@ -62,7 +62,7 @@ SPHINX_BUILD=$(PYTHON) build.py -j $(SPHINX_JOBS)
# TODO replace `rss:` with this when merged & tested
pep_rss:
- $(PYTHON) pep_rss_gen.py
+ $(PYTHON) generate_rss.py
pages: pep_rss
$(SPHINX_BUILD) --index-file
diff --git a/build.py b/build.py
index 97c014536..784fbea54 100644
--- a/build.py
+++ b/build.py
@@ -70,8 +70,6 @@ if __name__ == "__main__":
warningiserror=args.fail_on_warning,
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:
diff --git a/conf.py b/conf.py
index 8eb397038..b7b374055 100644
--- a/conf.py
+++ b/conf.py
@@ -43,16 +43,14 @@ exclude_patterns = [
# HTML output settings
html_math_renderer = "maths_to_html" # Maths rendering
-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
+gettext_auto_build = False # speed-ups
templates_path = ['pep_sphinx_extensions/pep_theme/templates'] # Theme template relative paths from `confdir`
diff --git a/pep_rss_gen.py b/generate_rss.py
similarity index 72%
rename from pep_rss_gen.py
rename to generate_rss.py
index a06ffd20c..88798cb79 100644
--- a/pep_rss_gen.py
+++ b/generate_rss.py
@@ -1,13 +1,11 @@
import datetime
import email.utils
from pathlib import Path
-import re
-from dateutil import parser
-import docutils.frontend
-import docutils.nodes
-import docutils.parsers.rst
-import docutils.utils
+from docutils import frontend
+from docutils import nodes
+from docutils import utils
+from docutils.parsers import rst
from feedgen import entry
from feedgen import feed
@@ -44,37 +42,26 @@ def first_line_starting_with(full_path: Path, text: str) -> str:
def pep_creation(full_path: Path) -> datetime.datetime:
created_str = first_line_starting_with(full_path, "Created:")
- # bleh, I was hoping to avoid re but some PEPs editorialize on the Created line
- # (note as of Aug 2020 only PEP 102 has additional content on the Created line)
- m = re.search(r"(\d+[- ][\w\d]+[- ]\d{2,4})", created_str)
- if not m:
- # some older ones have an empty line, that's okay, if it's old we ipso facto don't care about it.
- # "return None" would make the most sense but datetime objects refuse to compare with that. :-|
- return datetime.datetime(1900, 1, 1)
- created_str = m.group(1)
- try:
- return parser.parse(created_str, dayfirst=True)
- except (ValueError, OverflowError):
- return datetime.datetime(1900, 1, 1)
+ if full_path.stem == "pep-0102":
+ # remove additional content on the Created line
+ created_str = created_str.split(" ", 1)[0]
+ return datetime.datetime.strptime(created_str, "%d-%b-%Y")
-def parse_rst(text: str) -> docutils.nodes.document:
- rst_parser = docutils.parsers.rst.Parser()
- components = (docutils.parsers.rst.Parser,)
- settings = docutils.frontend.OptionParser(components=components).get_default_values()
- document = docutils.utils.new_document('', settings=settings)
- rst_parser.parse(text, document)
+def parse_rst(text: str) -> nodes.document:
+ settings = frontend.OptionParser((rst.Parser,)).get_default_values()
+ document = utils.new_document('', settings=settings)
+ rst.Parser().parse(text, document)
return document
def pep_abstract(full_path: Path) -> str:
"""Return the first paragraph of the PEP abstract"""
text = full_path.read_text(encoding="utf-8")
- for node in parse_rst(text):
- if "Abstract" in str(node):
- for child in node:
- if child.tagname == "paragraph":
- return child.astext().strip().replace("\n", " ")
+ # TODO replace .traverse with .findall when Sphinx updates to docutils>=0.18.1
+ for node in parse_rst(text).traverse(nodes.section):
+ if node.next_node(nodes.title).astext() == "Abstract":
+ return node.next_node(nodes.paragraph).astext().strip().replace("\n", " ")
return ""
@@ -119,7 +106,7 @@ def main():
Newest Python Enhancement Proposals (PEPs) - Information on new
language features, and some meta-information like release
procedure and schedules.
- """.replace("\n ", " ").strip()
+ """
# Setup feed generator
fg = feed.FeedGenerator()
@@ -131,7 +118,7 @@ def main():
fg.title("Newest Python PEPs")
fg.link(href="https://www.python.org/dev/peps")
fg.link(href="https://www.python.org/dev/peps/peps.rss", rel="self")
- fg.description(desc)
+ fg.description(" ".join(desc.split()))
fg.lastBuildDate(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc))
# Add PEP information (ordered by newest first)
diff --git a/pep-0456.txt b/pep-0456.txt
index a9866e780..b7ee2e344 100644
--- a/pep-0456.txt
+++ b/pep-0456.txt
@@ -529,7 +529,7 @@ not affect any application code.
The benchmarks were conducted on CPython default branch revision b08868fd5994
and the PEP repository [pep-456-repos]_. All upstream changes were merged
-into the pep-456 branch. The "performance" CPU governor was configured and
+into the ``pep-456`` branch. The "performance" CPU governor was configured and
almost all programs were stopped so the benchmarks were able to utilize
TurboBoost and the CPU caches as much as possible. The raw benchmark results
of multiple machines and platforms are made available at [benchmarks]_.
diff --git a/pep_sphinx_extensions/__init__.py b/pep_sphinx_extensions/__init__.py
index 37ffde408..f302b14b7 100644
--- a/pep_sphinx_extensions/__init__.py
+++ b/pep_sphinx_extensions/__init__.py
@@ -4,11 +4,12 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from docutils import nodes
+from docutils.parsers.rst import states
from docutils.writers.html5_polyglot import HTMLTranslator
-from sphinx.environment import BuildEnvironment
-from sphinx.environment import default_settings
+from sphinx import environment
-from pep_sphinx_extensions import config
+from pep_sphinx_extensions.pep_processor.html import pep_html_builder
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
@@ -20,17 +21,16 @@ if TYPE_CHECKING:
# Monkeypatch sphinx.environment.default_settings as Sphinx doesn't allow custom settings or Readers
# These settings should go in docutils.conf, but are overridden here for now so as not to affect
# pep2html.py
-default_settings |= {
+environment.default_settings |= {
"pep_references": True,
"rfc_references": True,
"pep_base_url": "",
- "pep_file_url_template": "pep-%04d.html",
+ "pep_file_url_template": "",
"_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: {}
+# TODO replace all inlined PEP and RFC strings with marked-up roles, disable pep_references and rfc_references and remove this monkey-patch
+states.Inliner.pep_reference = lambda s, m, _l: [nodes.reference("", m.group(0), refuri=s.document.settings.pep_url.format(int(m.group("pepnum2"))))]
def _depart_maths():
@@ -39,14 +39,17 @@ def _depart_maths():
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"
+ environment.default_settings["pep_url"] = "../pep-{:0>4}"
def setup(app: Sphinx) -> dict[str, bool]:
"""Initialize Sphinx extension."""
+ environment.default_settings["pep_url"] = "pep-{:0>4}.html"
+
# Register plugin logic
+ app.add_builder(pep_html_builder.FileBuilder, override=True)
+ app.add_builder(pep_html_builder.DirectoryBuilder, override=True)
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 (html builder)
diff --git a/pep_sphinx_extensions/config.py b/pep_sphinx_extensions/config.py
deleted file mode 100644
index 64f3ad283..000000000
--- a/pep_sphinx_extensions/config.py
+++ /dev/null
@@ -1,6 +0,0 @@
-"""Miscellaneous configuration variables for the PEP Sphinx extensions."""
-
-pep_stem = "pep-{:0>4}"
-pep_url = f"{pep_stem}.html"
-pep_vcs_url = "https://github.com/python/peps/blob/main/"
-pep_commits_url = "https://github.com/python/peps/commits/main/"
diff --git a/pep_sphinx_extensions/pep_processor/html/pep_html_builder.py b/pep_sphinx_extensions/pep_processor/html/pep_html_builder.py
new file mode 100644
index 000000000..703aa3af3
--- /dev/null
+++ b/pep_sphinx_extensions/pep_processor/html/pep_html_builder.py
@@ -0,0 +1,50 @@
+from pathlib import Path
+
+from docutils import nodes
+from docutils.frontend import OptionParser
+from sphinx.builders.html import StandaloneHTMLBuilder
+from sphinx.writers.html import HTMLWriter
+
+from sphinx.builders.dirhtml import DirectoryHTMLBuilder
+
+
+class FileBuilder(StandaloneHTMLBuilder):
+ copysource = False # Prevent unneeded source copying - we link direct to GitHub
+ search = False # Disable search
+
+ # Things we don't use but that need to exist:
+ indexer = None
+ relations = {}
+ _script_files = _css_files = []
+
+ def prepare_writing(self, _doc_names: set[str]) -> None:
+ self.docwriter = HTMLWriter(self)
+ _opt_parser = OptionParser([self.docwriter], defaults=self.env.settings, read_config_files=True)
+ self.docsettings = _opt_parser.get_default_values()
+ self.globalcontext = {"docstitle": self.config.html_title, "script_files": [], "css_files": []}
+
+ def get_doc_context(self, docname: str, body: str, _metatags: str) -> dict:
+ """Collect items for the template context of a page."""
+ try:
+ title = self.env.longtitles[docname].astext()
+ except KeyError:
+ title = ""
+
+ # source filename
+ file_is_rst = Path(self.env.srcdir, docname + ".rst").exists()
+ source_name = f"{docname}.rst" if file_is_rst else f"{docname}.txt"
+
+ # local table of contents
+ toc_tree = self.env.tocs[docname].deepcopy()
+ for node in toc_tree.traverse(nodes.reference):
+ node["refuri"] = node["anchorname"] or '#' # fix targets
+ toc = self.render_partial(toc_tree)["fragment"]
+
+ return {"title": title, "sourcename": source_name, "toc": toc, "body": body}
+
+
+class DirectoryBuilder(FileBuilder):
+ # sync all overwritten things from DirectoryHTMLBuilder
+ name = DirectoryHTMLBuilder.name
+ get_target_uri = DirectoryHTMLBuilder.get_target_uri
+ get_outfilename = DirectoryHTMLBuilder.get_outfilename
diff --git a/pep_sphinx_extensions/pep_processor/html/pep_html_translator.py b/pep_sphinx_extensions/pep_processor/html/pep_html_translator.py
index 9e87daf89..d1ad27f2f 100644
--- a/pep_sphinx_extensions/pep_processor/html/pep_html_translator.py
+++ b/pep_sphinx_extensions/pep_processor/html/pep_html_translator.py
@@ -57,26 +57,34 @@ class PEPTranslator(html5.HTML5Translator):
"""Add corresponding end tag from `visit_paragraph`."""
self.body.append(self.context.pop())
+ def visit_footnote_reference(self, node):
+ self.body.append(self.starttag(node, "a", suffix="[",
+ CLASS=f"footnote-reference {self.settings.footnote_references}",
+ href=f"#{node['refid']}"
+ ))
+
+ def depart_footnote_reference(self, node):
+ self.body.append(']')
+
+ def visit_label(self, node):
+ # pass parent node to get id into starttag:
+ self.body.append(self.starttag(node.parent, "dt", suffix="[", CLASS="label"))
+
+ # footnote/citation backrefs:
+ back_refs = node.parent["backrefs"]
+ if self.settings.footnote_backlinks and len(back_refs) == 1:
+ self.body.append(f'')
+ self.context.append(f"]")
+ else:
+ self.context.append("]")
+
def depart_label(self, node) -> None:
"""PEP link/citation block cleanup with italicised backlinks."""
- if not self.settings.footnote_backlinks:
- self.body.append("")
- self.body.append("\n")
- return
-
- # If only one reference to this footnote
- back_references = node.parent["backrefs"]
- if len(back_references) == 1:
- self.body.append("")
-
- # Close the tag
- self.body.append("")
-
- # If more than one reference
- if len(back_references) > 1:
- back_links = [f"{i}" for i, ref in enumerate(back_references, start=1)]
- back_links_str = ", ".join(back_links)
- self.body.append(f" ({back_links_str}) ")
+ self.body.append(self.context.pop())
+ back_refs = node.parent["backrefs"]
+ if self.settings.footnote_backlinks and len(back_refs) > 1:
+ back_links = ", ".join(f"{i}" for i, ref in enumerate(back_refs, start=1))
+ self.body.append(f" ({back_links}) ")
# Close the def tags
self.body.append("\n")
diff --git a/pep_sphinx_extensions/pep_processor/parsing/pep_role.py b/pep_sphinx_extensions/pep_processor/parsing/pep_role.py
index 6260c3422..e74ab0dd3 100644
--- a/pep_sphinx_extensions/pep_processor/parsing/pep_role.py
+++ b/pep_sphinx_extensions/pep_processor/parsing/pep_role.py
@@ -1,15 +1,14 @@
from sphinx import roles
-from pep_sphinx_extensions import config
-
class PEPRole(roles.PEP):
"""Override the :pep: role"""
+ # TODO override the entire thing (internal should be True)
def build_uri(self) -> str:
"""Get PEP URI from role text."""
pep_str, _, fragment = self.target.partition("#")
- pep_base = config.pep_url.format(int(pep_str))
+ pep_base = self.inliner.document.settings.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_footer.py b/pep_sphinx_extensions/pep_processor/transforms/pep_footer.py
index 39dad0404..497811dc4 100644
--- a/pep_sphinx_extensions/pep_processor/transforms/pep_footer.py
+++ b/pep_sphinx_extensions/pep_processor/transforms/pep_footer.py
@@ -5,8 +5,6 @@ import subprocess
from docutils import nodes
from docutils import transforms
-from pep_sphinx_extensions import config
-
class PEPFooter(transforms.Transform):
"""Footer transforms for PEPs.
@@ -49,21 +47,49 @@ class PEPFooter(transforms.Transform):
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
+ source_link = f"https://github.com/python/peps/blob/main/{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):
+ since_epoch = LAST_MODIFIED_TIMES[pep_source_path.name]
+ except KeyError:
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)
+ iso_time = datetime.datetime.utcfromtimestamp(since_epoch).isoformat(sep=" ")
+ commit_link = f"https://github.com/python/peps/commits/main/{pep_source_path.name}"
+ link_node = nodes.reference("", f"{iso_time} GMT", refuri=commit_link)
return nodes.paragraph("", "Last modified: ", link_node)
+
+
+def _get_last_modified_timestamps():
+ # get timestamps and changed files from all commits (without paging results)
+ args = ["git", "--no-pager", "log", "--format=#%at", "--name-only"]
+ with subprocess.Popen(args, stdout=subprocess.PIPE) as process:
+ all_modified = process.stdout.read().decode("utf-8")
+ process.stdout.close()
+ if process.wait(): # non-zero return code
+ return {}
+
+ # set up the dictionary with the *current* files
+ last_modified = {path.name: 0 for path in Path().glob("pep-*") if path.suffix in {".txt", ".rst"}}
+
+ # iterate through newest to oldest, updating per file timestamps
+ change_sets = all_modified.removeprefix("#").split("#")
+ for change_set in change_sets:
+ timestamp, files = change_set.split("\n", 1)
+ for file in files.strip().split("\n"):
+ if file.startswith("pep-") and file[-3:] in {"txt", "rst"}:
+ if last_modified.get(file) == 0:
+ try:
+ last_modified[file] = float(timestamp)
+ except ValueError:
+ pass # if float conversion fails
+
+ return last_modified
+
+
+LAST_MODIFIED_TIMES = _get_last_modified_timestamps()
diff --git a/pep_sphinx_extensions/pep_processor/transforms/pep_headers.py b/pep_sphinx_extensions/pep_processor/transforms/pep_headers.py
index 12805db49..efc1b87c0 100644
--- a/pep_sphinx_extensions/pep_processor/transforms/pep_headers.py
+++ b/pep_sphinx_extensions/pep_processor/transforms/pep_headers.py
@@ -1,5 +1,3 @@
-from __future__ import annotations
-
from pathlib import Path
import re
@@ -7,8 +5,8 @@ from docutils import nodes
from docutils import transforms
from sphinx import errors
-from pep_sphinx_extensions import config
from pep_sphinx_extensions.pep_processor.transforms import pep_zero
+from pep_sphinx_extensions.pep_processor.transforms.pep_zero import _mask_email
class PEPParsingError(errors.SphinxError):
@@ -79,9 +77,9 @@ class PEPHeaders(transforms.Transform):
elif name in {"replaces", "superseded-by", "requires"}:
# replace PEP numbers with normalised list of links to PEPs
new_body = []
- for ref_pep in re.split(r",?\s+", body.astext()):
- new_body += [nodes.reference("", ref_pep, refuri=config.pep_url.format(int(ref_pep)))]
- new_body += [nodes.Text(", ")]
+ for pep_str in re.split(r",?\s+", body.astext()):
+ target = self.document.settings.pep_url.format(int(pep_str))
+ new_body += [nodes.reference("", pep_str, refuri=target), nodes.Text(", ")]
para[:] = new_body[:-1] # drop trailing space
elif name in {"last-modified", "content-type", "version"}:
# Mark unneeded fields
@@ -90,25 +88,3 @@ class PEPHeaders(transforms.Transform):
# Remove unneeded fields
for field in fields_to_remove:
field.parent.remove(field)
-
-
-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.
-
- If given an email not explicitly whitelisted, process it such that
- `user@host` -> `user at host`.
-
- If given a PEP number `pep_num`, add a default email subject.
-
- """
- 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_zero.py b/pep_sphinx_extensions/pep_processor/transforms/pep_zero.py
index b638dbbb8..25a2082ab 100644
--- a/pep_sphinx_extensions/pep_processor/transforms/pep_zero.py
+++ b/pep_sphinx_extensions/pep_processor/transforms/pep_zero.py
@@ -1,8 +1,7 @@
+from __future__ import annotations
+
from docutils import nodes
from docutils import transforms
-from docutils.transforms import peps
-
-from pep_sphinx_extensions import config
class PEPZero(transforms.Transform):
@@ -38,7 +37,7 @@ class PEPZeroSpecial(nodes.SparseNodeVisitor):
@staticmethod
def visit_reference(node: nodes.reference) -> None:
"""Mask email addresses if present."""
- node.replace_self(peps.mask_email(node))
+ node.replace_self(_mask_email(node))
@staticmethod
def visit_field_list(node: nodes.field_list) -> None:
@@ -68,7 +67,30 @@ class PEPZeroSpecial(nodes.SparseNodeVisitor):
if isinstance(para, nodes.paragraph) and len(para) == 1:
pep_str = para.astext()
try:
- ref = config.pep_url.format(int(pep_str))
- para[0] = nodes.reference(pep_str, pep_str, refuri=ref)
+ pep_num = int(pep_str)
except ValueError:
- pass
+ return
+ ref = self.document.settings.pep_url.format(pep_num)
+ para[0] = nodes.reference("", pep_str, refuri=ref)
+
+
+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.
+
+ If given an email not explicitly whitelisted, process it such that
+ `user@host` -> `user at host`.
+
+ If given a PEP number `pep_num`, add a default email subject.
+
+ """
+ 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_theme/static/doctools.js b/pep_sphinx_extensions/pep_theme/static/doctools.js
deleted file mode 100644
index 5676a8ff3..000000000
--- a/pep_sphinx_extensions/pep_theme/static/doctools.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/* 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/style.css b/pep_sphinx_extensions/pep_theme/static/style.css
index 29d90b350..a23a9ae60 100644
--- a/pep_sphinx_extensions/pep_theme/static/style.css
+++ b/pep_sphinx_extensions/pep_theme/static/style.css
@@ -290,3 +290,6 @@ nav#pep-sidebar ul {
nav#pep-sidebar ul a {
text-decoration: none;
}
+#source {
+ padding-bottom: 2rem;
+}
diff --git a/pep_sphinx_extensions/pep_theme/templates/page.html b/pep_sphinx_extensions/pep_theme/templates/page.html
index b1d8725c4..d47644a73 100644
--- a/pep_sphinx_extensions/pep_theme/templates/page.html
+++ b/pep_sphinx_extensions/pep_theme/templates/page.html
@@ -29,9 +29,8 @@
Contents
{{ toc }}
- Page Source (GitHub)
+ Page Source (GitHub)
-