Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ AUTHOR_OVERRIDES.csv @AA-Turner
build.py @AA-Turner
conf.py @AA-Turner
contents.rst @AA-Turner
generate_rss.py @AA-Turner

# Linting infrastructure
.codespell/ @CAM-Gerlach @hugovk
Expand Down
7 changes: 1 addition & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ htmlview: html
## dirhtml to render PEPs to "index.html" files within "pep-NNNN" directories
.PHONY: dirhtml
dirhtml: BUILDER = dirhtml
dirhtml: venv rss
dirhtml: venv
$(SPHINXBUILD) $(ALLSPHINXOPTS)

## fail-warning to render PEPs to "pep-NNNN.html" files and fail the Sphinx build on any warning
Expand All @@ -41,11 +41,6 @@ check-links: BUILDER = linkcheck
check-links: venv
$(SPHINXBUILD) $(ALLSPHINXOPTS)

## rss to generate the peps.rss file
.PHONY: rss
rss: venv
$(VENVDIR)/bin/python3 generate_rss.py -o $(OUTPUT_DIR)

## clean to remove the venv and build files
.PHONY: clean
clean: clean-venv
Expand Down
210 changes: 0 additions & 210 deletions generate_rss.py

This file was deleted.

11 changes: 7 additions & 4 deletions pep_sphinx_extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from docutils.writers.html5_polyglot import HTMLTranslator
from sphinx import environment

from pep_sphinx_extensions.generate_rss import create_rss_feed
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_banner_directive
Expand All @@ -29,9 +30,7 @@ def _update_config_for_builder(app: Sphinx) -> None:
if app.builder.name == "dirhtml":
app.env.settings["pep_url"] = "pep-{:0>4}"

# internal_builder exists if Sphinx is run by build.py
if "internal_builder" not in app.tags:
app.connect("build-finished", _post_build) # Post-build tasks
app.connect("build-finished", _post_build) # Post-build tasks


def _post_build(app: Sphinx, exception: Exception | None) -> None:
Expand All @@ -41,7 +40,11 @@ def _post_build(app: Sphinx, exception: Exception | None) -> None:

if exception is not None:
return
create_index_file(Path(app.outdir), app.builder.name)

# internal_builder exists if Sphinx is run by build.py
if "internal_builder" not in app.tags:
create_index_file(Path(app.outdir), app.builder.name)
create_rss_feed(app.doctreedir, app.outdir)


def setup(app: Sphinx) -> dict[str, bool]:
Expand Down
120 changes: 120 additions & 0 deletions pep_sphinx_extensions/generate_rss.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# This file is placed in the public domain or under the
# CC0-1.0-Universal license, whichever is more permissive.

from __future__ import annotations

import datetime as dt
import pickle
from email.utils import format_datetime, getaddresses
from html import escape
from pathlib import Path

from docutils import nodes

RSS_DESCRIPTION = (
"Newest Python Enhancement Proposals (PEPs): "
"Information on new language features "
"and some meta-information like release procedure and schedules."
)

# get the directory with the PEP sources
PEP_ROOT = Path(__file__).parent


def _format_rfc_2822(datetime: dt.datetime) -> str:
datetime = datetime.replace(tzinfo=dt.timezone.utc)
return format_datetime(datetime, usegmt=True)


document_cache: dict[Path, dict[str, str]] = {}


def get_from_doctree(full_path: Path, text: str) -> str:
# Try and retrieve from cache
if full_path in document_cache:
return document_cache[full_path].get(text, "")

# Else load doctree
document = pickle.loads(full_path.read_bytes())
# Store the headers (populated in the PEPHeaders transform)
document_cache[full_path] = path_cache = document.get("headers", {})
# Store the Abstract
path_cache["Abstract"] = pep_abstract(document)
# Return the requested key
return path_cache.get(text, "")


def pep_creation(full_path: Path) -> dt.datetime:
created_str = get_from_doctree(full_path, "Created")
try:
return dt.datetime.strptime(created_str, "%d-%b-%Y")
except ValueError:
return dt.datetime.min


def pep_abstract(document: nodes.document) -> str:
"""Return the first paragraph of the PEP abstract"""
for node in document.findall(nodes.section):
title_node = node.next_node(nodes.title)
if title_node is None:
continue
if title_node.astext() == "Abstract":
return node.next_node(nodes.paragraph).astext().strip().replace("\n", " ")
return ""


def _generate_items(doctree_dir: Path):
# get list of peps with creation time (from "Created:" string in pep source)
peps_with_dt = sorted((pep_creation(path), path) for path in doctree_dir.glob("pep-????.doctree"))

# generate rss items for 10 most recent peps (in reverse order)
for datetime, full_path in reversed(peps_with_dt[-10:]):
try:
pep_num = int(get_from_doctree(full_path, "PEP"))
except ValueError:
continue

title = get_from_doctree(full_path, "Title")
url = f"https://peps.python.org/pep-{pep_num:0>4}/"
abstract = get_from_doctree(full_path, "Abstract")
author = get_from_doctree(full_path, "Author")
if "@" in author or " at " in author:
parsed_authors = getaddresses([author])
joined_authors = ", ".join(f"{name} ({email_address})" for name, email_address in parsed_authors)
else:
joined_authors = author

item = f"""\
<item>
<title>PEP {pep_num}: {escape(title, quote=False)}</title>
<link>{escape(url, quote=False)}</link>
<description>{escape(abstract, quote=False)}</description>
<author>{escape(joined_authors, quote=False)}</author>
<guid isPermaLink="true">{url}</guid>
<pubDate>{_format_rfc_2822(datetime)}</pubDate>
</item>"""
yield item


def create_rss_feed(doctree_dir: Path, output_dir: Path):
# The rss envelope
last_build_date = _format_rfc_2822(dt.datetime.now(dt.timezone.utc))
items = "\n".join(_generate_items(Path(doctree_dir)))
output = f"""\
<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
<channel>
<title>Newest Python PEPs</title>
<link>https://peps.python.org/peps.rss</link>
<description>{RSS_DESCRIPTION}</description>
<atom:link href="https://peps.python.org/peps.rss" rel="self"/>
<docs>https://cyber.harvard.edu/rss/rss.html</docs>
<language>en</language>
<lastBuildDate>{last_build_date}</lastBuildDate>
{items}
</channel>
</rss>
"""

# output directory for target HTML files
Path(output_dir, "peps.rss").write_text(output, encoding="utf-8")
Loading