Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sphinx - PEP 0 generation #1932

Merged
merged 37 commits into from
Jun 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4a52b3c
Add PEP 0 parser
AA-Turner Apr 20, 2021
d3771d4
Add PEP 0 writer
AA-Turner Apr 20, 2021
c8268fb
Add PEP 0 generator and authors override
AA-Turner Apr 20, 2021
85ae140
Add/update build and run
AA-Turner Apr 20, 2021
835adfc
Simplify `create_index_file`
AA-Turner May 7, 2021
530ca9a
Special status handling
AA-Turner Jun 9, 2021
2578fe2
Add constants for PEP related magic strings
AA-Turner Jun 9, 2021
c839d51
Prefer checking on class
AA-Turner Jun 9, 2021
a9b0559
Add PEP.hide_status, use constants
AA-Turner Jun 9, 2021
77c5492
Remove comment from 2008 (current method works fine)
AA-Turner Jun 9, 2021
f6f7b65
Clarify intent of for-else loop
AA-Turner Jun 9, 2021
d0513e2
Hook in to Sphinx (oops, missed when splitting out this PR)
AA-Turner Jun 9, 2021
b8d9eff
Rename AUTHORS.csv for clarity
AA-Turner Jun 9, 2021
4b0d042
Sort and strip spaces
AA-Turner Jun 9, 2021
a993eed
Prefer `authors_overrides` name
AA-Turner Jun 9, 2021
92fe1fb
Add pep_0_errors.py
AA-Turner Jun 9, 2021
3f695ab
Move author_sort_by to writer
AA-Turner Jun 9, 2021
327fd1b
PEP init misc
AA-Turner Jun 9, 2021
403bff3
Split out Author
AA-Turner Jun 9, 2021
0d9bf61
Drop pep_0 prefix
AA-Turner Jun 9, 2021
dedb043
Pass title length as an argument
AA-Turner Jun 9, 2021
84518a3
Add constants.py to hold global type / status values
AA-Turner Jun 9, 2021
5164571
Capitalise constants
AA-Turner Jun 9, 2021
29738c5
Capitalise constants
AA-Turner Jun 9, 2021
918a4b9
Update PEP classification algorithm
AA-Turner Jun 9, 2021
70011e0
Extract static methods to module level
AA-Turner Jun 9, 2021
e72bed1
Add emit_text, emit_pep_row
AA-Turner Jun 9, 2021
32454c8
Use constants in writer.py
AA-Turner Jun 9, 2021
e42938a
Sort imports
AA-Turner Jun 9, 2021
d4447ab
Sort constants
AA-Turner Jun 9, 2021
5ebcb9d
Fix sorting in historical and dead PEPs
AA-Turner Jun 9, 2021
a4a4f50
Extract static methods to module level
AA-Turner Jun 9, 2021
1ec8438
Extract static methods to module level (parser.py
AA-Turner Jun 9, 2021
de9ab25
Make Author a NamedTuple
AA-Turner Jun 9, 2021
4cb6e8c
Fix author duplication bug with NamedTuples
AA-Turner Jun 9, 2021
1e62868
Revert to old PEP classification algorithm
AA-Turner Jun 10, 2021
48b72c2
Define PEP equality
AA-Turner Jun 10, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AUTHOR_OVERRIDES.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Overridden Name,Surname First,Name Reference
The Python core team and community,The Python core team and community,python-dev
Ernest W. Durbin III,"Durbin, Ernest W., III",Durbin
Greg Ewing,"Ewing, Gregory",Ewing
Guido van Rossum,"van Rossum, Guido (GvR)",GvR
Inada Naoki,"Inada, Naoki",Inada
Jim Jewett,"Jewett, Jim J.",Jewett
Just van Rossum,"van Rossum, Just (JvR)",JvR
Martin v. Löwis,"von Löwis, Martin",von Löwis
Nathaniel Smith,"Smith, Nathaniel J.",Smith
P.J. Eby,"Eby, Phillip J.",Eby
11 changes: 11 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import argparse
from pathlib import Path
import shutil

from sphinx.application import Sphinx

Expand All @@ -22,6 +23,13 @@ def create_parser():
return parser.parse_args()


def create_index_file(html_root: Path):
"""Copies PEP 0 to the root index.html so that /peps/ works."""
pep_zero_path = html_root / "pep-0000" / "index.html"
if pep_zero_path.is_file():
shutil.copy(pep_zero_path, html_root / "index.html")


if __name__ == "__main__":
args = create_parser()

Expand Down Expand Up @@ -52,3 +60,6 @@ def create_parser():
)
app.builder.copysource = False # Prevent unneeded source copying - we link direct to GitHub
app.build()

if args.index_file:
create_index_file(build_directory)
4 changes: 3 additions & 1 deletion pep_sphinx_extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@

from typing import TYPE_CHECKING

from sphinx.environment import default_settings
from docutils.writers.html5_polyglot import HTMLTranslator
from sphinx.environment import default_settings

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
from pep_sphinx_extensions.pep_zero_generator.pep_index_generator import create_pep_zero

if TYPE_CHECKING:
from sphinx.application import Sphinx
Expand Down Expand Up @@ -37,6 +38,7 @@ def setup(app: Sphinx) -> dict[str, bool]:
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
app.connect("env-before-read-docs", create_pep_zero) # PEP 0 hook

# Mathematics rendering
inline_maths = HTMLTranslator.visit_math, _depart_maths
Expand Down
4 changes: 2 additions & 2 deletions pep_sphinx_extensions/pep_processor/parsing/pep_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

from sphinx import parsers

from pep_sphinx_extensions.pep_processor.transforms import pep_headers
from pep_sphinx_extensions.pep_processor.transforms import pep_title
from pep_sphinx_extensions.pep_processor.transforms import pep_contents
from pep_sphinx_extensions.pep_processor.transforms import pep_footer
from pep_sphinx_extensions.pep_processor.transforms import pep_headers
from pep_sphinx_extensions.pep_processor.transforms import pep_title

if TYPE_CHECKING:
from docutils import transforms
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import datetime
import subprocess
from pathlib import Path
import subprocess

from docutils import nodes
from docutils import transforms
Expand Down
4 changes: 2 additions & 2 deletions pep_sphinx_extensions/pep_processor/transforms/pep_headers.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import re
from pathlib import Path
import re

from docutils import nodes
from docutils import transforms
from docutils.transforms import peps
from sphinx import errors

from pep_sphinx_extensions.pep_processor.transforms import pep_zero
from pep_sphinx_extensions.config import pep_url
from pep_sphinx_extensions.pep_processor.transforms import pep_zero


class PEPParsingError(errors.SphinxError):
Expand Down
93 changes: 93 additions & 0 deletions pep_sphinx_extensions/pep_zero_generator/author.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from __future__ import annotations

from typing import NamedTuple


class _Name(NamedTuple):
mononym: str = None
forename: str = None
surname: str = None
suffix: str = None


class Author(NamedTuple):
"""Represent PEP authors."""
last_first: str # The author's name in Surname, Forename, Suffix order.
nick: str # Author's nickname for PEP tables. Defaults to surname.
email: str # The author's email address.


def parse_author_email(author_email_tuple: tuple[str, str], authors_overrides: dict[str, dict[str, str]]) -> Author:
"""Parse the name and email address of an author."""
name, email = author_email_tuple
_first_last = name.strip()
email = email.lower()

if _first_last in authors_overrides:
name_dict = authors_overrides[_first_last]
last_first = name_dict["Surname First"]
nick = name_dict["Name Reference"]
return Author(last_first, nick, email)

name_parts = _parse_name(_first_last)
if name_parts.mononym is not None:
return Author(name_parts.mononym, name_parts.mononym, email)

if name_parts.surname[1] == ".":
# Add an escape to avoid docutils turning `v.` into `22.`.
name_parts.surname = f"\\{name_parts.surname}"

if name_parts.suffix:
last_first = f"{name_parts.surname}, {name_parts.forename}, {name_parts.suffix}"
return Author(last_first, name_parts.surname, email)

last_first = f"{name_parts.surname}, {name_parts.forename}"
return Author(last_first, name_parts.surname, email)


def _parse_name(full_name: str) -> _Name:
"""Decompose a full name into parts.

If a mononym (e.g, 'Aahz') then return the full name. If there are
suffixes in the name (e.g. ', Jr.' or 'II'), then find and extract
them. If there is a middle initial followed by a full stop, then
combine the following words into a surname (e.g. N. Vander Weele). If
there is a leading, lowercase portion to the last name (e.g. 'van' or
'von') then include it in the surname.

"""
possible_suffixes = {"Jr", "Jr.", "II", "III"}

pre_suffix, _, raw_suffix = full_name.partition(",")
name_parts = pre_suffix.strip().split(" ")
num_parts = len(name_parts)
suffix = raw_suffix.strip()

if num_parts == 0:
raise ValueError("Name is empty!")
elif num_parts == 1:
return _Name(mononym=name_parts[0], suffix=suffix)
elif num_parts == 2:
return _Name(forename=name_parts[0].strip(), surname=name_parts[1], suffix=suffix)

# handles rogue uncaught suffixes
if name_parts[-1] in possible_suffixes:
suffix = f"{name_parts.pop(-1)} {suffix}".strip()

# handles von, van, v. etc.
if name_parts[-2].islower():
forename = " ".join(name_parts[:-2]).strip()
surname = " ".join(name_parts[-2:])
return _Name(forename=forename, surname=surname, suffix=suffix)

# handles double surnames after a middle initial (e.g. N. Vander Weele)
elif any(s.endswith(".") for s in name_parts):
split_position = [i for i, x in enumerate(name_parts) if x.endswith(".")][-1] + 1
forename = " ".join(name_parts[:split_position]).strip()
surname = " ".join(name_parts[split_position:])
return _Name(forename=forename, surname=surname, suffix=suffix)

# default to using the last item as the surname
else:
forename = " ".join(name_parts[:-1]).strip()
return _Name(forename=forename, surname=name_parts[-1], suffix=suffix)
34 changes: 34 additions & 0 deletions pep_sphinx_extensions/pep_zero_generator/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Holds type and status constants for PEP 0 generation."""

STATUS_ACCEPTED = "Accepted"
STATUS_ACTIVE = "Active"
STATUS_DEFERRED = "Deferred"
STATUS_DRAFT = "Draft"
STATUS_FINAL = "Final"
STATUS_PROVISIONAL = "Provisional"
STATUS_REJECTED = "Rejected"
STATUS_SUPERSEDED = "Superseded"
STATUS_WITHDRAWN = "Withdrawn"

# Valid values for the Status header.
STATUS_VALUES = {
STATUS_ACCEPTED, STATUS_PROVISIONAL, STATUS_REJECTED, STATUS_WITHDRAWN,
STATUS_DEFERRED, STATUS_FINAL, STATUS_ACTIVE, STATUS_DRAFT, STATUS_SUPERSEDED,
}
# Map of invalid/special statuses to their valid counterparts
SPECIAL_STATUSES = {
"April Fool!": STATUS_REJECTED, # See PEP 401 :)
}
# Draft PEPs have no status displayed, Active shares a key with Accepted
HIDE_STATUS = {STATUS_DRAFT, STATUS_ACTIVE}
# Dead PEP statuses
DEAD_STATUSES = {STATUS_REJECTED, STATUS_WITHDRAWN, STATUS_SUPERSEDED}

TYPE_INFO = "Informational"
TYPE_PROCESS = "Process"
TYPE_STANDARDS = "Standards Track"

# Valid values for the Type header.
TYPE_VALUES = {TYPE_STANDARDS, TYPE_INFO, TYPE_PROCESS}
# Active PEPs can only be for Informational or Process PEPs.
ACTIVE_ALLOWED = {TYPE_PROCESS, TYPE_INFO}
16 changes: 16 additions & 0 deletions pep_sphinx_extensions/pep_zero_generator/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import annotations

from pathlib import Path


class PEPError(Exception):
def __init__(self, error: str, pep_file: Path, pep_number: int | None = None):
super().__init__(error)
self.filename = pep_file
self.number = pep_number

def __str__(self):
error_msg = super(PEPError, self).__str__()
error_msg = f"({self.filename}): {error_msg}"
pep_str = f"PEP {self.number}"
return f"{pep_str} {error_msg}" if self.number is not None else error_msg
Loading