PEP 676: Implementation updates (#2208)
This commit is contained in:
parent
19684a0787
commit
3d60b84e35
|
@ -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)
|
||||
|
|
2
Makefile
2
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
|
||||
|
|
2
build.py
2
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:
|
||||
|
|
4
conf.py
4
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 <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
|
||||
gettext_auto_build = False # speed-ups
|
||||
|
||||
templates_path = ['pep_sphinx_extensions/pep_theme/templates'] # Theme template relative paths from `confdir`
|
||||
|
|
|
@ -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('<rst-doc>', 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('<rst-doc>', 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 "<title>Abstract</title>" 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)
|
|
@ -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]_.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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/"
|
|
@ -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
|
|
@ -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(']</a>')
|
||||
|
||||
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'<a href="#{back_refs[0]}">')
|
||||
self.context.append(f"</a>]")
|
||||
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("</span>")
|
||||
self.body.append("</dt>\n<dd>")
|
||||
return
|
||||
|
||||
# If only one reference to this footnote
|
||||
back_references = node.parent["backrefs"]
|
||||
if len(back_references) == 1:
|
||||
self.body.append("</a>")
|
||||
|
||||
# Close the tag
|
||||
self.body.append("</span>")
|
||||
|
||||
# If more than one reference
|
||||
if len(back_references) > 1:
|
||||
back_links = [f"<a href='#{ref}'>{i}</a>" for i, ref in enumerate(back_references, start=1)]
|
||||
back_links_str = ", ".join(back_links)
|
||||
self.body.append(f"<span class='fn-backref''><em> ({back_links_str}) </em></span>")
|
||||
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"<a href='#{ref}'>{i}</a>" for i, ref in enumerate(back_refs, start=1))
|
||||
self.body.append(f"<em> ({back_links}) </em>")
|
||||
|
||||
# Close the def tags
|
||||
self.body.append("</dt>\n<dd>")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 + "]")
|
|
@ -290,3 +290,6 @@ nav#pep-sidebar ul {
|
|||
nav#pep-sidebar ul a {
|
||||
text-decoration: none;
|
||||
}
|
||||
#source {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
|
|
@ -29,9 +29,8 @@
|
|||
<h2>Contents</h2>
|
||||
{{ toc }}
|
||||
<br />
|
||||
<strong><a href="https://github.com/python/peps/blob/main/{{sourcename}}">Page Source (GitHub)</a></strong>
|
||||
<strong id="source"><a href="https://github.com/python/peps/blob/main/{{sourcename}}">Page Source (GitHub)</a></strong>
|
||||
</nav>
|
||||
</section>
|
||||
<script src="{{ pathto('_static/doctools.js', resource=True) }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -49,16 +49,12 @@ Created: 13-Jul-2000
|
|||
|
||||
intro = """\
|
||||
This PEP contains the index of all Python Enhancement Proposals,
|
||||
known as PEPs. PEP numbers are assigned by the PEP editors, and
|
||||
once assigned are never changed [1_]. The version control history [2_] of
|
||||
known as PEPs. PEP numbers are :pep:`assigned <1#pep-editors>`
|
||||
by the PEP editors, and once assigned are never changed. The
|
||||
`version control history <https://github.com/python/peps>`_ of
|
||||
the PEP texts represent their historical record.
|
||||
"""
|
||||
|
||||
references = """\
|
||||
.. [1] PEP 1: PEP Purpose and Guidelines
|
||||
.. [2] View PEP history online: https://github.com/python/peps
|
||||
"""
|
||||
|
||||
|
||||
class PEPZeroWriter:
|
||||
# This is a list of reserved PEP numbers. Reservations are not to be used for
|
||||
|
@ -100,17 +96,16 @@ class PEPZeroWriter:
|
|||
self.emit_pep_row({"status": ".", "type": ".", "number": "PEP", "title": "PEP Title", "authors": "PEP Author(s)"})
|
||||
self.emit_table_separator()
|
||||
|
||||
def emit_title(self, text: str, anchor: str, *, symbol: str = "=") -> None:
|
||||
self.output.append(f".. _{anchor}:\n")
|
||||
def emit_title(self, text: str, *, symbol: str = "=") -> None:
|
||||
self.output.append(text)
|
||||
self.output.append(symbol * len(text))
|
||||
self.emit_newline()
|
||||
|
||||
def emit_subtitle(self, text: str, anchor: str) -> None:
|
||||
self.emit_title(text, anchor, symbol="-")
|
||||
def emit_subtitle(self, text: str) -> None:
|
||||
self.emit_title(text, symbol="-")
|
||||
|
||||
def emit_pep_category(self, category: str, anchor: str, peps: list[PEP]) -> None:
|
||||
self.emit_subtitle(category, anchor)
|
||||
def emit_pep_category(self, category: str, peps: list[PEP]) -> None:
|
||||
self.emit_subtitle(category)
|
||||
self.emit_column_headers()
|
||||
for pep in peps:
|
||||
self.output.append(column_format(**pep.details(title_length=title_length)))
|
||||
|
@ -124,44 +119,40 @@ class PEPZeroWriter:
|
|||
self.emit_newline()
|
||||
|
||||
# Introduction
|
||||
self.emit_title("Introduction", "intro")
|
||||
self.emit_title("Introduction")
|
||||
self.emit_text(intro)
|
||||
self.emit_newline()
|
||||
|
||||
# PEPs by category
|
||||
self.emit_title("Index by Category", "by-category")
|
||||
self.emit_title("Index by Category")
|
||||
meta, info, provisional, accepted, open_, finished, historical, deferred, dead = _classify_peps(peps)
|
||||
pep_categories = [
|
||||
("Meta-PEPs (PEPs about PEPs or Processes)", "by-category-meta", meta),
|
||||
("Other Informational PEPs", "by-category-other-info", info),
|
||||
("Provisional PEPs (provisionally accepted; interface may still change)", "by-category-provisional", provisional),
|
||||
("Accepted PEPs (accepted; may not be implemented yet)", "by-category-accepted", accepted),
|
||||
("Open PEPs (under consideration)", "by-category-open", open_),
|
||||
("Finished PEPs (done, with a stable interface)", "by-category-finished", finished),
|
||||
("Historical Meta-PEPs and Informational PEPs", "by-category-historical", historical),
|
||||
("Deferred PEPs (postponed pending further research or updates)", "by-category-deferred", deferred),
|
||||
("Abandoned, Withdrawn, and Rejected PEPs", "by-category-abandoned", dead),
|
||||
("Meta-PEPs (PEPs about PEPs or Processes)", meta),
|
||||
("Other Informational PEPs", info),
|
||||
("Provisional PEPs (provisionally accepted; interface may still change)", provisional),
|
||||
("Accepted PEPs (accepted; may not be implemented yet)", accepted),
|
||||
("Open PEPs (under consideration)", open_),
|
||||
("Finished PEPs (done, with a stable interface)", finished),
|
||||
("Historical Meta-PEPs and Informational PEPs", historical),
|
||||
("Deferred PEPs (postponed pending further research or updates)", deferred),
|
||||
("Abandoned, Withdrawn, and Rejected PEPs", dead),
|
||||
]
|
||||
for (category, anchor, peps_in_category) in pep_categories:
|
||||
self.emit_pep_category(category, anchor, peps_in_category)
|
||||
for (category, peps_in_category) in pep_categories:
|
||||
self.emit_pep_category(category, peps_in_category)
|
||||
|
||||
self.emit_newline()
|
||||
|
||||
# PEPs by number
|
||||
self.emit_title("Numerical Index", "by-pep-number")
|
||||
self.emit_title("Numerical Index")
|
||||
self.emit_column_headers()
|
||||
prev_pep = 0
|
||||
for pep in peps:
|
||||
if pep.number - prev_pep > 1:
|
||||
self.emit_newline()
|
||||
self.emit_pep_row(pep.details(title_length=title_length))
|
||||
prev_pep = pep.number
|
||||
|
||||
self.emit_table_separator()
|
||||
self.emit_newline()
|
||||
|
||||
# Reserved PEP numbers
|
||||
self.emit_title("Reserved PEP Numbers", "reserved")
|
||||
self.emit_title("Reserved PEP Numbers")
|
||||
self.emit_column_headers()
|
||||
for number, claimants in sorted(self.RESERVED.items()):
|
||||
self.emit_pep_row({"type": ".", "status": ".", "number": number, "title": "RESERVED", "authors": claimants})
|
||||
|
@ -170,7 +161,7 @@ class PEPZeroWriter:
|
|||
self.emit_newline()
|
||||
|
||||
# PEP types key
|
||||
self.emit_title("PEP Types Key", "type-key")
|
||||
self.emit_title("PEP Types Key")
|
||||
for type_ in sorted(TYPE_VALUES):
|
||||
self.emit_text(f" {type_[0]} - {type_} PEP")
|
||||
self.emit_newline()
|
||||
|
@ -178,7 +169,7 @@ class PEPZeroWriter:
|
|||
self.emit_newline()
|
||||
|
||||
# PEP status key
|
||||
self.emit_title("PEP Status Key", "status-key")
|
||||
self.emit_title("PEP Status Key")
|
||||
for status in sorted(STATUS_VALUES):
|
||||
# Draft PEPs have no status displayed, Active shares a key with Accepted
|
||||
if status in HIDE_STATUS:
|
||||
|
@ -195,7 +186,7 @@ class PEPZeroWriter:
|
|||
# PEP owners
|
||||
authors_dict = _verify_email_addresses(peps)
|
||||
max_name_len = max(len(author_name) for author_name in authors_dict)
|
||||
self.emit_title("Authors/Owners", "authors")
|
||||
self.emit_title("Authors/Owners")
|
||||
self.emit_author_table_separator(max_name_len)
|
||||
self.emit_text(f"{'Name':{max_name_len}} Email Address")
|
||||
self.emit_author_table_separator(max_name_len)
|
||||
|
@ -207,10 +198,6 @@ class PEPZeroWriter:
|
|||
self.emit_newline()
|
||||
self.emit_newline()
|
||||
|
||||
# References for introduction footnotes
|
||||
self.emit_title("References", "references")
|
||||
self.emit_text(references)
|
||||
|
||||
pep0_string = "\n".join([str(s) for s in self.output])
|
||||
return pep0_string
|
||||
|
||||
|
|
Loading…
Reference in New Issue