Skip to content

Commit 3d60b84

Browse files
authored
PEP 676: Implementation updates (#2208)
1 parent 19684a0 commit 3d60b84

File tree

18 files changed

+217
-182
lines changed

18 files changed

+217
-182
lines changed

.github/workflows/deploy-gh-pages.yaml

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,16 @@ jobs:
1414
with:
1515
fetch-depth: 0 # fetch all history so that last modified date-times are accurate
1616

17-
- name: 🐍 Set up Python 3.9
17+
- name: 🐍 Set up Python 3
1818
uses: actions/setup-python@v2
1919
with:
20-
python-version: 3.9
21-
22-
- name: 🧳 Cache pip
23-
uses: actions/cache@v2
24-
with:
25-
# This path is specific to Ubuntu
26-
path: ~/.cache/pip
27-
# Look to see if there is a cache hit for the corresponding requirements file
28-
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
29-
restore-keys: |
30-
${{ runner.os }}-pip-
31-
${{ runner.os }}-
20+
python-version: 3
21+
cache: "pip"
3222

3323
- name: 👷‍ Install dependencies
3424
run: |
3525
python -m pip install --upgrade pip
36-
pip install -r requirements.txt
26+
python -m pip install -r requirements.txt
3727
3828
- name: 🔧 Build PEPs
3929
run: make pages -j$(nproc)

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ SPHINX_BUILD=$(PYTHON) build.py -j $(SPHINX_JOBS)
6262

6363
# TODO replace `rss:` with this when merged & tested
6464
pep_rss:
65-
$(PYTHON) pep_rss_gen.py
65+
$(PYTHON) generate_rss.py
6666

6767
pages: pep_rss
6868
$(SPHINX_BUILD) --index-file

build.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,6 @@ def create_index_file(html_root: Path, builder: str) -> None:
7070
warningiserror=args.fail_on_warning,
7171
parallel=args.jobs,
7272
)
73-
app.builder.copysource = False # Prevent unneeded source copying - we link direct to GitHub
74-
app.builder.search = False # Disable search
7573
app.build()
7674

7775
if args.index_file:

conf.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,14 @@
4343

4444
# HTML output settings
4545
html_math_renderer = "maths_to_html" # Maths rendering
46-
html_show_copyright = False # Turn off miscellany
47-
html_show_sphinx = False
4846
html_title = "peps.python.org" # Set <title/>
4947

5048
# Theme settings
5149
html_theme_path = ["pep_sphinx_extensions"]
5250
html_theme = "pep_theme" # The actual theme directory (child of html_theme_path)
5351
html_use_index = False # Disable index (we use PEP 0)
54-
html_sourcelink_suffix = "" # Fix links to GitHub (don't append .txt)
5552
html_style = "" # must be defined here or in theme.conf, but is unused
5653
html_permalinks = False # handled in the PEPContents transform
54+
gettext_auto_build = False # speed-ups
5755

5856
templates_path = ['pep_sphinx_extensions/pep_theme/templates'] # Theme template relative paths from `confdir`

pep_rss_gen.py renamed to generate_rss.py

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import datetime
22
import email.utils
33
from pathlib import Path
4-
import re
54

6-
from dateutil import parser
7-
import docutils.frontend
8-
import docutils.nodes
9-
import docutils.parsers.rst
10-
import docutils.utils
5+
from docutils import frontend
6+
from docutils import nodes
7+
from docutils import utils
8+
from docutils.parsers import rst
119
from feedgen import entry
1210
from feedgen import feed
1311

@@ -44,37 +42,26 @@ def first_line_starting_with(full_path: Path, text: str) -> str:
4442

4543
def pep_creation(full_path: Path) -> datetime.datetime:
4644
created_str = first_line_starting_with(full_path, "Created:")
47-
# bleh, I was hoping to avoid re but some PEPs editorialize on the Created line
48-
# (note as of Aug 2020 only PEP 102 has additional content on the Created line)
49-
m = re.search(r"(\d+[- ][\w\d]+[- ]\d{2,4})", created_str)
50-
if not m:
51-
# some older ones have an empty line, that's okay, if it's old we ipso facto don't care about it.
52-
# "return None" would make the most sense but datetime objects refuse to compare with that. :-|
53-
return datetime.datetime(1900, 1, 1)
54-
created_str = m.group(1)
55-
try:
56-
return parser.parse(created_str, dayfirst=True)
57-
except (ValueError, OverflowError):
58-
return datetime.datetime(1900, 1, 1)
59-
60-
61-
def parse_rst(text: str) -> docutils.nodes.document:
62-
rst_parser = docutils.parsers.rst.Parser()
63-
components = (docutils.parsers.rst.Parser,)
64-
settings = docutils.frontend.OptionParser(components=components).get_default_values()
65-
document = docutils.utils.new_document('<rst-doc>', settings=settings)
66-
rst_parser.parse(text, document)
45+
if full_path.stem == "pep-0102":
46+
# remove additional content on the Created line
47+
created_str = created_str.split(" ", 1)[0]
48+
return datetime.datetime.strptime(created_str, "%d-%b-%Y")
49+
50+
51+
def parse_rst(text: str) -> nodes.document:
52+
settings = frontend.OptionParser((rst.Parser,)).get_default_values()
53+
document = utils.new_document('<rst-doc>', settings=settings)
54+
rst.Parser().parse(text, document)
6755
return document
6856

6957

7058
def pep_abstract(full_path: Path) -> str:
7159
"""Return the first paragraph of the PEP abstract"""
7260
text = full_path.read_text(encoding="utf-8")
73-
for node in parse_rst(text):
74-
if "<title>Abstract</title>" in str(node):
75-
for child in node:
76-
if child.tagname == "paragraph":
77-
return child.astext().strip().replace("\n", " ")
61+
# TODO replace .traverse with .findall when Sphinx updates to docutils>=0.18.1
62+
for node in parse_rst(text).traverse(nodes.section):
63+
if node.next_node(nodes.title).astext() == "Abstract":
64+
return node.next_node(nodes.paragraph).astext().strip().replace("\n", " ")
7865
return ""
7966

8067

@@ -119,7 +106,7 @@ def main():
119106
Newest Python Enhancement Proposals (PEPs) - Information on new
120107
language features, and some meta-information like release
121108
procedure and schedules.
122-
""".replace("\n ", " ").strip()
109+
"""
123110

124111
# Setup feed generator
125112
fg = feed.FeedGenerator()
@@ -131,7 +118,7 @@ def main():
131118
fg.title("Newest Python PEPs")
132119
fg.link(href="https://www.python.org/dev/peps")
133120
fg.link(href="https://www.python.org/dev/peps/peps.rss", rel="self")
134-
fg.description(desc)
121+
fg.description(" ".join(desc.split()))
135122
fg.lastBuildDate(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc))
136123

137124
# Add PEP information (ordered by newest first)

pep-0456.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ not affect any application code.
529529

530530
The benchmarks were conducted on CPython default branch revision b08868fd5994
531531
and the PEP repository [pep-456-repos]_. All upstream changes were merged
532-
into the pep-456 branch. The "performance" CPU governor was configured and
532+
into the ``pep-456`` branch. The "performance" CPU governor was configured and
533533
almost all programs were stopped so the benchmarks were able to utilize
534534
TurboBoost and the CPU caches as much as possible. The raw benchmark results
535535
of multiple machines and platforms are made available at [benchmarks]_.

pep_sphinx_extensions/__init__.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
from typing import TYPE_CHECKING
66

7+
from docutils import nodes
8+
from docutils.parsers.rst import states
79
from docutils.writers.html5_polyglot import HTMLTranslator
8-
from sphinx.environment import BuildEnvironment
9-
from sphinx.environment import default_settings
10+
from sphinx import environment
1011

11-
from pep_sphinx_extensions import config
12+
from pep_sphinx_extensions.pep_processor.html import pep_html_builder
1213
from pep_sphinx_extensions.pep_processor.html import pep_html_translator
1314
from pep_sphinx_extensions.pep_processor.parsing import pep_parser
1415
from pep_sphinx_extensions.pep_processor.parsing import pep_role
@@ -20,17 +21,16 @@
2021
# Monkeypatch sphinx.environment.default_settings as Sphinx doesn't allow custom settings or Readers
2122
# These settings should go in docutils.conf, but are overridden here for now so as not to affect
2223
# pep2html.py
23-
default_settings |= {
24+
environment.default_settings |= {
2425
"pep_references": True,
2526
"rfc_references": True,
2627
"pep_base_url": "",
27-
"pep_file_url_template": "pep-%04d.html",
28+
"pep_file_url_template": "",
2829
"_disable_config": True, # disable using docutils.conf whilst running both PEP generators
2930
}
3031

31-
# Monkeypatch sphinx.environment.BuildEnvironment.collect_relations, as it takes a long time
32-
# and we don't use the parent/next/prev functionality
33-
BuildEnvironment.collect_relations = lambda self: {}
32+
# TODO replace all inlined PEP and RFC strings with marked-up roles, disable pep_references and rfc_references and remove this monkey-patch
33+
states.Inliner.pep_reference = lambda s, m, _l: [nodes.reference("", m.group(0), refuri=s.document.settings.pep_url.format(int(m.group("pepnum2"))))]
3434

3535

3636
def _depart_maths():
@@ -39,14 +39,17 @@ def _depart_maths():
3939

4040
def _update_config_for_builder(app: Sphinx):
4141
if app.builder.name == "dirhtml":
42-
config.pep_url = f"../{config.pep_stem}"
43-
app.env.settings["pep_file_url_template"] = "../pep-%04d"
42+
environment.default_settings["pep_url"] = "../pep-{:0>4}"
4443

4544

4645
def setup(app: Sphinx) -> dict[str, bool]:
4746
"""Initialize Sphinx extension."""
4847

48+
environment.default_settings["pep_url"] = "pep-{:0>4}.html"
49+
4950
# Register plugin logic
51+
app.add_builder(pep_html_builder.FileBuilder, override=True)
52+
app.add_builder(pep_html_builder.DirectoryBuilder, override=True)
5053
app.add_source_parser(pep_parser.PEPParser) # Add PEP transforms
5154
app.add_role("pep", pep_role.PEPRole(), override=True) # Transform PEP references to links
5255
app.set_translator("html", pep_html_translator.PEPTranslator) # Docutils Node Visitor overrides (html builder)

pep_sphinx_extensions/config.py

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from pathlib import Path
2+
3+
from docutils import nodes
4+
from docutils.frontend import OptionParser
5+
from sphinx.builders.html import StandaloneHTMLBuilder
6+
from sphinx.writers.html import HTMLWriter
7+
8+
from sphinx.builders.dirhtml import DirectoryHTMLBuilder
9+
10+
11+
class FileBuilder(StandaloneHTMLBuilder):
12+
copysource = False # Prevent unneeded source copying - we link direct to GitHub
13+
search = False # Disable search
14+
15+
# Things we don't use but that need to exist:
16+
indexer = None
17+
relations = {}
18+
_script_files = _css_files = []
19+
20+
def prepare_writing(self, _doc_names: set[str]) -> None:
21+
self.docwriter = HTMLWriter(self)
22+
_opt_parser = OptionParser([self.docwriter], defaults=self.env.settings, read_config_files=True)
23+
self.docsettings = _opt_parser.get_default_values()
24+
self.globalcontext = {"docstitle": self.config.html_title, "script_files": [], "css_files": []}
25+
26+
def get_doc_context(self, docname: str, body: str, _metatags: str) -> dict:
27+
"""Collect items for the template context of a page."""
28+
try:
29+
title = self.env.longtitles[docname].astext()
30+
except KeyError:
31+
title = ""
32+
33+
# source filename
34+
file_is_rst = Path(self.env.srcdir, docname + ".rst").exists()
35+
source_name = f"{docname}.rst" if file_is_rst else f"{docname}.txt"
36+
37+
# local table of contents
38+
toc_tree = self.env.tocs[docname].deepcopy()
39+
for node in toc_tree.traverse(nodes.reference):
40+
node["refuri"] = node["anchorname"] or '#' # fix targets
41+
toc = self.render_partial(toc_tree)["fragment"]
42+
43+
return {"title": title, "sourcename": source_name, "toc": toc, "body": body}
44+
45+
46+
class DirectoryBuilder(FileBuilder):
47+
# sync all overwritten things from DirectoryHTMLBuilder
48+
name = DirectoryHTMLBuilder.name
49+
get_target_uri = DirectoryHTMLBuilder.get_target_uri
50+
get_outfilename = DirectoryHTMLBuilder.get_outfilename

pep_sphinx_extensions/pep_processor/html/pep_html_translator.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,26 +57,34 @@ def depart_paragraph(self, _: nodes.paragraph) -> None:
5757
"""Add corresponding end tag from `visit_paragraph`."""
5858
self.body.append(self.context.pop())
5959

60+
def visit_footnote_reference(self, node):
61+
self.body.append(self.starttag(node, "a", suffix="[",
62+
CLASS=f"footnote-reference {self.settings.footnote_references}",
63+
href=f"#{node['refid']}"
64+
))
65+
66+
def depart_footnote_reference(self, node):
67+
self.body.append(']</a>')
68+
69+
def visit_label(self, node):
70+
# pass parent node to get id into starttag:
71+
self.body.append(self.starttag(node.parent, "dt", suffix="[", CLASS="label"))
72+
73+
# footnote/citation backrefs:
74+
back_refs = node.parent["backrefs"]
75+
if self.settings.footnote_backlinks and len(back_refs) == 1:
76+
self.body.append(f'<a href="#{back_refs[0]}">')
77+
self.context.append(f"</a>]")
78+
else:
79+
self.context.append("]")
80+
6081
def depart_label(self, node) -> None:
6182
"""PEP link/citation block cleanup with italicised backlinks."""
62-
if not self.settings.footnote_backlinks:
63-
self.body.append("</span>")
64-
self.body.append("</dt>\n<dd>")
65-
return
66-
67-
# If only one reference to this footnote
68-
back_references = node.parent["backrefs"]
69-
if len(back_references) == 1:
70-
self.body.append("</a>")
71-
72-
# Close the tag
73-
self.body.append("</span>")
74-
75-
# If more than one reference
76-
if len(back_references) > 1:
77-
back_links = [f"<a href='#{ref}'>{i}</a>" for i, ref in enumerate(back_references, start=1)]
78-
back_links_str = ", ".join(back_links)
79-
self.body.append(f"<span class='fn-backref''><em> ({back_links_str}) </em></span>")
83+
self.body.append(self.context.pop())
84+
back_refs = node.parent["backrefs"]
85+
if self.settings.footnote_backlinks and len(back_refs) > 1:
86+
back_links = ", ".join(f"<a href='#{ref}'>{i}</a>" for i, ref in enumerate(back_refs, start=1))
87+
self.body.append(f"<em> ({back_links}) </em>")
8088

8189
# Close the def tags
8290
self.body.append("</dt>\n<dd>")
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
from sphinx import roles
22

3-
from pep_sphinx_extensions import config
4-
53

64
class PEPRole(roles.PEP):
75
"""Override the :pep: role"""
6+
# TODO override the entire thing (internal should be True)
87

98
def build_uri(self) -> str:
109
"""Get PEP URI from role text."""
1110
pep_str, _, fragment = self.target.partition("#")
12-
pep_base = config.pep_url.format(int(pep_str))
11+
pep_base = self.inliner.document.settings.pep_url.format(int(pep_str))
1312
if fragment:
1413
return f"{pep_base}#{fragment}"
1514
return pep_base

0 commit comments

Comments
 (0)