From ff4dd8dd1c9371b2750b75682d6a0d8c82276e9a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 6 May 2022 00:02:51 +0100 Subject: [PATCH] Support subindicies --- .../pep_zero_generator/parser.py | 34 ++++++++- .../pep_zero_generator/pep_index_generator.py | 71 ++++++------------- .../pep_zero_generator/subindicies.py | 50 +++++++++++++ .../pep_zero_generator/writer.py | 22 +++--- 4 files changed, 116 insertions(+), 61 deletions(-) create mode 100644 pep_sphinx_extensions/pep_zero_generator/subindicies.py diff --git a/pep_sphinx_extensions/pep_zero_generator/parser.py b/pep_sphinx_extensions/pep_zero_generator/parser.py index e2d630130a8c..5901f97c4227 100644 --- a/pep_sphinx_extensions/pep_zero_generator/parser.py +++ b/pep_sphinx_extensions/pep_zero_generator/parser.py @@ -2,6 +2,7 @@ from __future__ import annotations +import csv from email.parser import HeaderParser from pathlib import Path import re @@ -23,6 +24,14 @@ from pep_sphinx_extensions.pep_zero_generator.author import Author +# AUTHOR_OVERRIDES.csv is an exception file for PEP0 name parsing +AUTHOR_OVERRIDES: dict[str, dict[str, str]] = {} +with open("AUTHOR_OVERRIDES.csv", "r", encoding="utf-8") as f: + for line in csv.DictReader(f): + full_name = line.pop("Overridden Name") + AUTHOR_OVERRIDES[full_name] = line + + class PEP: """Representation of PEPs. @@ -38,7 +47,7 @@ class PEP: # The required RFC 822 headers for all PEPs. required_headers = {"PEP", "Title", "Author", "Status", "Type", "Created"} - def __init__(self, filename: Path, authors_overrides: dict): + def __init__(self, filename: Path): """Init object from an open PEP file object. pep_file is full text of the PEP file, filename is path of the PEP file, author_lookup is author exceptions file @@ -89,7 +98,11 @@ def __init__(self, filename: Path, authors_overrides: dict): self.status: str = status # Parse PEP authors - self.authors: list[Author] = _parse_authors(self, metadata["Author"], authors_overrides) + self.authors: list[Author] = _parse_authors(self, metadata["Author"], AUTHOR_OVERRIDES) + + # Topic (for sub-indicies) + _topic = metadata.get("Topic", "").lower().split(",") + self.topics: set[str] = {topic for topic_raw in _topic if (topic := topic_raw.strip())} # Other headers self.created = metadata["Created"] @@ -127,6 +140,23 @@ def details(self, *, title_length) -> dict[str, str | int]: "authors": ", ".join(author.nick for author in self.authors), } + def json(self) -> dict[str, str]: + return { + "title": self.title, + "authors": ", ".join(author.nick for author in self.authors), + "discussions_to": self.discussions_to, + "status": self.status, + "type": self.pep_type, + "created": self.created, + "python_version": self.python_version, + "post_history": self.post_history, + "resolution": self.resolution, + "requires": self.requires, + "replaces": self.replaces, + "superseded_by": self.superseded_by, + "url": f"https://peps.python.org/pep-{self.number:0>4}/", + } + def _raise_pep_error(pep: PEP, msg: str, pep_num: bool = False) -> None: if pep_num: diff --git a/pep_sphinx_extensions/pep_zero_generator/pep_index_generator.py b/pep_sphinx_extensions/pep_zero_generator/pep_index_generator.py index 257be006d512..727aa35311ee 100644 --- a/pep_sphinx_extensions/pep_zero_generator/pep_index_generator.py +++ b/pep_sphinx_extensions/pep_zero_generator/pep_index_generator.py @@ -17,13 +17,12 @@ """ from __future__ import annotations -import csv import json from pathlib import Path -import re from typing import TYPE_CHECKING from pep_sphinx_extensions.pep_zero_generator import parser +from pep_sphinx_extensions.pep_zero_generator import subindicies from pep_sphinx_extensions.pep_zero_generator import writer if TYPE_CHECKING: @@ -31,65 +30,39 @@ from sphinx.environment import BuildEnvironment -def create_pep_json(peps: list[parser.PEP]) -> str: - pep_dict = { - pep.number: { - "title": pep.title, - "authors": ", ".join(pep.authors.nick for pep.authors in pep.authors), - "discussions_to": pep.discussions_to, - "status": pep.status, - "type": pep.pep_type, - "created": pep.created, - "python_version": pep.python_version, - "post_history": pep.post_history, - "resolution": pep.resolution, - "requires": pep.requires, - "replaces": pep.replaces, - "superseded_by": pep.superseded_by, - "url": f"https://peps.python.org/pep-{pep.number:0>4}/", - } - for pep in sorted(peps) - } - return json.dumps(pep_dict, indent=1) - - -def create_pep_zero(app: Sphinx, env: BuildEnvironment, docnames: list[str]) -> None: +def _parse_peps() -> list[parser.PEP]: # Read from root directory path = Path(".") - - pep_zero_filename = "pep-0000" peps: list[parser.PEP] = [] - pep_pat = re.compile(r"pep-\d{4}") # Path.match() doesn't support regular expressions - - # AUTHOR_OVERRIDES.csv is an exception file for PEP0 name parsing - with open("AUTHOR_OVERRIDES.csv", "r", encoding="utf-8") as f: - authors_overrides = {} - for line in csv.DictReader(f): - full_name = line.pop("Overridden Name") - authors_overrides[full_name] = line for file_path in path.iterdir(): if not file_path.is_file(): continue # Skip directories etc. if file_path.match("pep-0000*"): continue # Skip pre-existing PEP 0 files - if pep_pat.match(str(file_path)) and file_path.suffix in {".txt", ".rst"}: - pep = parser.PEP(path.joinpath(file_path).absolute(), authors_overrides) + if (len(file_path.stem) == 8 + and file_path.stem.startswith("pep-") + and file_path.suffix in {".txt", ".rst"}): + pep = parser.PEP(path.joinpath(file_path).absolute()) peps.append(pep) - pep0_text = writer.PEPZeroWriter().write_pep0(sorted(peps)) - pep0_path = Path(f"{pep_zero_filename}.rst") - pep0_path.write_text(pep0_text, encoding="utf-8") + return sorted(peps) + + +def create_pep_json(peps: list[parser.PEP]) -> str: + return json.dumps({pep.number: pep.json() for pep in peps}, indent=1) + + +def create_pep_zero(app: Sphinx, env: BuildEnvironment, docnames: list[str]) -> None: + peps = _parse_peps() - peps.append(parser.PEP(pep0_path, authors_overrides)) + pep0_text = writer.PEPZeroWriter().write_pep0(peps) + pep0_path = subindicies.update_sphinx("pep-0000", pep0_text, docnames, env) + peps.append(parser.PEP(pep0_path)) - # Add to files for builder - docnames.insert(1, pep_zero_filename) - # Add to files for writer - env.found_docs.add(pep_zero_filename) + # subindicies_to_generate = ("packaging",) + subindicies_to_generate = () + subindicies.generate_subindicies(subindicies_to_generate, peps, docnames, env) # Create peps.json - pep0_json = create_pep_json(peps) - out_dir = Path(app.outdir) / "api" - out_dir.mkdir(exist_ok=True) - Path(out_dir, "peps.json").write_text(pep0_json, encoding="utf-8") + Path(app.outdir, "peps.json").write_text(create_pep_json(peps), encoding="utf-8") diff --git a/pep_sphinx_extensions/pep_zero_generator/subindicies.py b/pep_sphinx_extensions/pep_zero_generator/subindicies.py new file mode 100644 index 000000000000..cbb6ff39fa7b --- /dev/null +++ b/pep_sphinx_extensions/pep_zero_generator/subindicies.py @@ -0,0 +1,50 @@ +"""Utilities to support sub-indices for PEPs.""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from pep_sphinx_extensions.pep_zero_generator import writer + +if TYPE_CHECKING: + from sphinx.environment import BuildEnvironment + + from pep_sphinx_extensions.pep_zero_generator.parser import PEP + + +def update_sphinx(filename: str, text: str, docnames: list[str], env: BuildEnvironment) -> Path: + file_path = Path(f"{filename}.rst").resolve() + file_path.parent.mkdir(parents=True, exist_ok=True) + file_path.write_text(text, encoding="utf-8") + + # Add to files for builder + docnames.insert(1, filename) + # Add to files for writer + env.found_docs.add(filename) + + return file_path + + +def generate_subindicies( + subindicies: tuple[str], + peps: list[PEP], + docnames: list[str], + env: BuildEnvironment +) -> None: + for subindex in subindicies: + header_text = f"{subindex.title()} PEPs" + header_line = "#" * len(header_text) + header = header_text + "\n" + header_line + "\n" + + topic = subindex.lower() + filtered_peps = [pep for pep in peps if topic in pep.topics] + subindex_intro = f"""\ +This is the index of all Python Enhancement Proposals (PEPs) labelled +under the '{subindex.title()}' topic. This is a sub-index of :pep:`0`, +the PEP index. +""" + subindex_text = writer.PEPZeroWriter().write_pep0( + filtered_peps, header, subindex_intro, is_subindex=True, + ) + update_sphinx(f"topic/{subindex}", subindex_text, docnames, env) diff --git a/pep_sphinx_extensions/pep_zero_generator/writer.py b/pep_sphinx_extensions/pep_zero_generator/writer.py index 822bd02fdc44..2db7c9480276 100644 --- a/pep_sphinx_extensions/pep_zero_generator/writer.py +++ b/pep_sphinx_extensions/pep_zero_generator/writer.py @@ -36,7 +36,7 @@ title_length=title_length ) -header = f"""\ +HEADER = f"""\ PEP: 0 Title: Index of Python Enhancement Proposals (PEPs) Last-Modified: {datetime.date.today()} @@ -47,7 +47,7 @@ Created: 13-Jul-2000 """ -intro = """\ +INTRO = """\ This PEP contains the index of all Python Enhancement Proposals, known as PEPs. PEP numbers are :pep:`assigned <1#pep-editors>` by the PEP editors, and once assigned are never changed. The @@ -112,7 +112,7 @@ def emit_pep_category(self, category: str, peps: list[PEP]) -> None: self.emit_table_separator() self.emit_newline() - def write_pep0(self, peps: list[PEP]): + def write_pep0(self, peps: list[PEP], header: str = HEADER, intro: str = INTRO, is_subindex: bool = False): # PEP metadata self.emit_text(header) @@ -138,7 +138,8 @@ def write_pep0(self, peps: list[PEP]): ("Abandoned, Withdrawn, and Rejected PEPs", dead), ] for (category, peps_in_category) in pep_categories: - self.emit_pep_category(category, peps_in_category) + if is_subindex and len(peps_in_category) > 0: + self.emit_pep_category(category, peps_in_category) self.emit_newline() @@ -152,13 +153,14 @@ def write_pep0(self, peps: list[PEP]): self.emit_newline() # Reserved PEP numbers - 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}) + if not is_subindex: + 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}) - self.emit_table_separator() - self.emit_newline() + self.emit_table_separator() + self.emit_newline() # PEP types key self.emit_title("PEP Types Key")