PEP 0: Refactoring (#3340)
This commit is contained in:
parent
32a92bd50b
commit
3550731898
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
from email.parser import HeaderParser
|
from email.parser import HeaderParser
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
|
@ -17,6 +18,13 @@ from pep_sphinx_extensions.pep_zero_generator.constants import TYPE_VALUES
|
||||||
from pep_sphinx_extensions.pep_zero_generator.errors import PEPError
|
from pep_sphinx_extensions.pep_zero_generator.errors import PEPError
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(order=True, frozen=True)
|
||||||
|
class _Author:
|
||||||
|
"""Represent PEP authors."""
|
||||||
|
full_name: str # The author's name.
|
||||||
|
email: str # The author's email address.
|
||||||
|
|
||||||
|
|
||||||
class PEP:
|
class PEP:
|
||||||
"""Representation of PEPs.
|
"""Representation of PEPs.
|
||||||
|
|
||||||
|
@ -83,7 +91,9 @@ class PEP:
|
||||||
self.status: str = status
|
self.status: str = status
|
||||||
|
|
||||||
# Parse PEP authors
|
# Parse PEP authors
|
||||||
self.authors: dict[str, str] = _parse_authors(self, metadata["Author"])
|
self.authors: list[_Author] = _parse_author(metadata["Author"])
|
||||||
|
if not self.authors:
|
||||||
|
raise _raise_pep_error(self, "no authors found", pep_num=True)
|
||||||
|
|
||||||
# Topic (for sub-indices)
|
# Topic (for sub-indices)
|
||||||
_topic = metadata.get("Topic", "").lower().split(",")
|
_topic = metadata.get("Topic", "").lower().split(",")
|
||||||
|
@ -130,7 +140,7 @@ class PEP:
|
||||||
# a tooltip representing the type and status
|
# a tooltip representing the type and status
|
||||||
"shorthand": self.shorthand,
|
"shorthand": self.shorthand,
|
||||||
# the author list as a comma-separated with only last names
|
# the author list as a comma-separated with only last names
|
||||||
"authors": ", ".join(self.authors),
|
"authors": ", ".join(author.full_name for author in self.authors),
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -139,7 +149,7 @@ class PEP:
|
||||||
return {
|
return {
|
||||||
"number": self.number,
|
"number": self.number,
|
||||||
"title": self.title,
|
"title": self.title,
|
||||||
"authors": ", ".join(self.authors),
|
"authors": ", ".join(author.full_name for author in self.authors),
|
||||||
"discussions_to": self.discussions_to,
|
"discussions_to": self.discussions_to,
|
||||||
"status": self.status,
|
"status": self.status,
|
||||||
"type": self.pep_type,
|
"type": self.pep_type,
|
||||||
|
@ -161,46 +171,27 @@ def _raise_pep_error(pep: PEP, msg: str, pep_num: bool = False) -> None:
|
||||||
raise PEPError(msg, pep.filename)
|
raise PEPError(msg, pep.filename)
|
||||||
|
|
||||||
|
|
||||||
def _parse_authors(pep: PEP, author_header: str) -> dict[str, str]:
|
jr_placeholder = ",Jr"
|
||||||
"""Parse Author header line"""
|
|
||||||
authors_to_emails = _parse_author(author_header)
|
|
||||||
if not authors_to_emails:
|
|
||||||
raise _raise_pep_error(pep, "no authors found", pep_num=True)
|
|
||||||
return authors_to_emails
|
|
||||||
|
|
||||||
|
|
||||||
author_angled = re.compile(r"(?P<author>.+?) <(?P<email>.+?)>(,\s*)?")
|
def _parse_author(data: str) -> list[_Author]:
|
||||||
author_paren = re.compile(r"(?P<email>.+?) \((?P<author>.+?)\)(,\s*)?")
|
"""Return a list of author names and emails."""
|
||||||
author_simple = re.compile(r"(?P<author>[^,]+)(,\s*)?")
|
|
||||||
|
|
||||||
|
author_list = []
|
||||||
def _parse_author(data: str) -> dict[str, str]:
|
data = (data.replace("\n", " ")
|
||||||
"""Return a mapping of author names to emails."""
|
.replace(", Jr", jr_placeholder)
|
||||||
|
.rstrip().removesuffix(","))
|
||||||
author_items = []
|
for author_email in data.split(", "):
|
||||||
for regex in (author_angled, author_paren, author_simple):
|
if ' <' in author_email:
|
||||||
for match in regex.finditer(data):
|
author, email = author_email.removesuffix(">").split(" <")
|
||||||
# Watch out for suffixes like 'Jr.' when they are comma-separated
|
|
||||||
# from the name and thus cause issues when *all* names are only
|
|
||||||
# separated by commas.
|
|
||||||
match_dict = match.groupdict()
|
|
||||||
author = match_dict["author"]
|
|
||||||
if not author.partition(" ")[1] and author.endswith("."):
|
|
||||||
prev_author = author_items.pop()
|
|
||||||
author = ", ".join([prev_author, author])
|
|
||||||
if "email" not in match_dict:
|
|
||||||
email = ""
|
|
||||||
else:
|
else:
|
||||||
email = match_dict["email"]
|
author, email = author_email, ""
|
||||||
|
|
||||||
author = author.strip()
|
author = author.strip()
|
||||||
if not author:
|
if author == "":
|
||||||
raise ValueError("Name is empty!")
|
raise ValueError("Name is empty!")
|
||||||
|
|
||||||
author_items.append((author, email.lower().strip()))
|
author = author.replace(jr_placeholder, ", Jr")
|
||||||
|
email = email.lower()
|
||||||
# If authors were found then stop searching as only expect one
|
author_list.append(_Author(author, email))
|
||||||
# style of author citation.
|
return author_list
|
||||||
if author_items:
|
|
||||||
break
|
|
||||||
return dict(author_items)
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import datetime as dt
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
import unicodedata
|
import unicodedata
|
||||||
|
|
||||||
|
@ -29,11 +28,10 @@ from pep_sphinx_extensions.pep_zero_generator.errors import PEPError
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pep_sphinx_extensions.pep_zero_generator.parser import PEP
|
from pep_sphinx_extensions.pep_zero_generator.parser import PEP
|
||||||
|
|
||||||
HEADER = f"""\
|
HEADER = """\
|
||||||
PEP: 0
|
PEP: 0
|
||||||
Title: Index of Python Enhancement Proposals (PEPs)
|
Title: Index of Python Enhancement Proposals (PEPs)
|
||||||
Last-Modified: {dt.date.today()}
|
Author: The PEP Editors
|
||||||
Author: python-dev <python-dev@python.org>
|
|
||||||
Status: Active
|
Status: Active
|
||||||
Type: Informational
|
Type: Informational
|
||||||
Content-Type: text/x-rst
|
Content-Type: text/x-rst
|
||||||
|
@ -241,7 +239,7 @@ class PEPZeroWriter:
|
||||||
self.emit_newline()
|
self.emit_newline()
|
||||||
self.emit_newline()
|
self.emit_newline()
|
||||||
|
|
||||||
pep0_string = "\n".join([str(s) for s in self.output])
|
pep0_string = "\n".join(map(str, self.output))
|
||||||
return pep0_string
|
return pep0_string
|
||||||
|
|
||||||
|
|
||||||
|
@ -295,24 +293,24 @@ def _classify_peps(peps: list[PEP]) -> tuple[list[PEP], ...]:
|
||||||
def _verify_email_addresses(peps: list[PEP]) -> dict[str, str]:
|
def _verify_email_addresses(peps: list[PEP]) -> dict[str, str]:
|
||||||
authors_dict: dict[str, set[str]] = {}
|
authors_dict: dict[str, set[str]] = {}
|
||||||
for pep in peps:
|
for pep in peps:
|
||||||
for author, email in pep.authors.items():
|
for author in pep.authors:
|
||||||
# If this is the first time we have come across an author, add them.
|
# If this is the first time we have come across an author, add them.
|
||||||
if author not in authors_dict:
|
if author.full_name not in authors_dict:
|
||||||
authors_dict[author] = set()
|
authors_dict[author.full_name] = set()
|
||||||
|
|
||||||
# If the new email is an empty string, move on.
|
# If the new email is an empty string, move on.
|
||||||
if not email:
|
if not author.email:
|
||||||
continue
|
continue
|
||||||
# If the email has not been seen, add it to the list.
|
# If the email has not been seen, add it to the list.
|
||||||
authors_dict[author].add(email)
|
authors_dict[author.full_name].add(author.email)
|
||||||
|
|
||||||
valid_authors_dict: dict[str, str] = {}
|
valid_authors_dict: dict[str, str] = {}
|
||||||
too_many_emails: list[tuple[str, set[str]]] = []
|
too_many_emails: list[tuple[str, set[str]]] = []
|
||||||
for name, emails in authors_dict.items():
|
for full_name, emails in authors_dict.items():
|
||||||
if len(emails) > 1:
|
if len(emails) > 1:
|
||||||
too_many_emails.append((name, emails))
|
too_many_emails.append((full_name, emails))
|
||||||
else:
|
else:
|
||||||
valid_authors_dict[name] = next(iter(emails), "")
|
valid_authors_dict[full_name] = next(iter(emails), "")
|
||||||
if too_many_emails:
|
if too_many_emails:
|
||||||
err_output = []
|
err_output = []
|
||||||
for author, emails in too_many_emails:
|
for author, emails in too_many_emails:
|
||||||
|
|
|
@ -17,7 +17,7 @@ from pep_sphinx_extensions.pep_zero_generator.constants import (
|
||||||
TYPE_PROCESS,
|
TYPE_PROCESS,
|
||||||
TYPE_STANDARDS,
|
TYPE_STANDARDS,
|
||||||
)
|
)
|
||||||
from pep_sphinx_extensions.pep_zero_generator.errors import PEPError
|
from pep_sphinx_extensions.pep_zero_generator.parser import _Author
|
||||||
|
|
||||||
|
|
||||||
def test_pep_repr():
|
def test_pep_repr():
|
||||||
|
@ -56,27 +56,15 @@ def test_pep_details(monkeypatch):
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"First Last <user@example.com>",
|
"First Last <user@example.com>",
|
||||||
{"First Last": "user@example.com"},
|
[_Author(full_name="First Last", email="user@example.com")],
|
||||||
),
|
|
||||||
(
|
|
||||||
"First Last < user@example.com >",
|
|
||||||
{"First Last": "user@example.com"},
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"First Last",
|
"First Last",
|
||||||
{"First Last": ""},
|
[_Author(full_name="First Last", email="")],
|
||||||
),
|
|
||||||
(
|
|
||||||
"user@example.com (First Last)",
|
|
||||||
{"First Last": "user@example.com"},
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"user@example.com ( First Last )",
|
|
||||||
{"First Last": "user@example.com"},
|
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
"First Last <user at example.com>",
|
"First Last <user at example.com>",
|
||||||
{"First Last": "user@example.com"},
|
[_Author(full_name="First Last", email="user@example.com")],
|
||||||
marks=pytest.mark.xfail,
|
marks=pytest.mark.xfail,
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
|
@ -87,21 +75,16 @@ def test_pep_details(monkeypatch):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_parse_authors(test_input, expected):
|
def test_parse_authors(test_input, expected):
|
||||||
# Arrange
|
|
||||||
dummy_object = parser.PEP(Path("pep-0160.txt"))
|
|
||||||
|
|
||||||
# Act
|
# Act
|
||||||
out = parser._parse_authors(dummy_object, test_input)
|
out = parser._parse_author(test_input)
|
||||||
|
|
||||||
# Assert
|
# Assert
|
||||||
assert out == expected
|
assert out == expected
|
||||||
|
|
||||||
|
|
||||||
def test_parse_authors_invalid():
|
def test_parse_authors_invalid():
|
||||||
pep = parser.PEP(Path("pep-0008.txt"))
|
with pytest.raises(ValueError, match="Name is empty!"):
|
||||||
|
assert parser._parse_author("")
|
||||||
with pytest.raises(PEPError, match="no authors found"):
|
|
||||||
parser._parse_authors(pep, "")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|
Loading…
Reference in New Issue