Infra: Use Docutils' `list-table` syntax for PEP 0 (#2616)

This commit is contained in:
Adam Turner 2022-06-08 18:11:14 +01:00 committed by GitHub
parent ab4b55cebb
commit f7c9e62c9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 35 additions and 118 deletions

View File

@ -7,83 +7,17 @@ from docutils import transforms
class PEPZero(transforms.Transform):
"""Schedule PEP 0 processing."""
# Run during sphinx post processing
# Run during sphinx post-processing
default_priority = 760
def apply(self) -> None:
# Walk document and then remove this node
visitor = PEPZeroSpecial(self.document)
self.document.walk(visitor)
# Walk document and mask email addresses if present.
for reference_node in self.document.findall(nodes.reference):
reference_node.replace_self(_mask_email(reference_node))
# Remove this node
self.startnode.parent.remove(self.startnode)
class PEPZeroSpecial(nodes.SparseNodeVisitor):
"""Perform the special processing needed by PEP 0.
- Mask email addresses.
- Link PEP numbers and PEP titles in the table to the PEPs themselves.
"""
def __init__(self, document: nodes.document):
super().__init__(document)
self.pep_table: int = 0
self.entry: int = 0
self.ref: str | None = None
def unknown_visit(self, node: nodes.Node) -> None:
"""No processing for undefined node types."""
pass
@staticmethod
def visit_reference(node: nodes.reference) -> None:
"""Mask email addresses if present."""
node.replace_self(_mask_email(node))
@staticmethod
def visit_field_list(node: nodes.field_list) -> None:
"""Skip PEP headers."""
if "rfc2822" in node["classes"]:
raise nodes.SkipNode
def visit_tgroup(self, node: nodes.tgroup) -> None:
"""Set column counter and PEP table marker."""
self.pep_table = node["cols"] == 4
self.entry = 0 # reset column number
def visit_colspec(self, node: nodes.colspec) -> None:
self.entry += 1
if self.pep_table and self.entry == 2:
node["classes"].append("num")
def visit_row(self, _node: nodes.row) -> None:
self.entry = 0 # reset column number
self.ref = None # Reset PEP URL
def visit_entry(self, node: nodes.entry) -> None:
self.entry += 1
if not self.pep_table:
return
if self.entry == 2 and len(node) == 1:
node["classes"].append("num")
# if this is the PEP number column, replace the number with a link to the PEP
para = node[0]
if isinstance(para, nodes.paragraph) and len(para) == 1:
pep_str = para.astext()
try:
pep_num = int(pep_str)
except ValueError:
return
self.ref = self.document.settings.pep_url.format(pep_num)
para[0] = nodes.reference("", pep_str, refuri=self.ref)
elif self.entry == 3 and len(node) == 1 and self.ref:
# If this is the PEP title column, add a link to the PEP
para = node[0]
if isinstance(para, nodes.paragraph) and len(para) == 1:
pep_title = para.astext()
para[0] = nodes.reference("", pep_title, refuri=self.ref)
def _mask_email(ref: nodes.reference) -> nodes.reference:
"""Mask the email address in `ref` and return a replacement node.

View File

@ -233,7 +233,7 @@ table td {
text-align: left;
padding: 0.25rem 0.5rem 0.2rem;
}
table tr td.num {
table.pep-zero-table tr td:nth-child(2) {
white-space: nowrap;
}
table td + td {

View File

@ -5,7 +5,6 @@ from __future__ import annotations
from email.parser import HeaderParser
from pathlib import Path
import re
import textwrap
from typing import TYPE_CHECKING
from pep_sphinx_extensions.pep_zero_generator.author import parse_author_email
@ -114,13 +113,14 @@ class PEP:
def __eq__(self, other):
return self.number == other.number
def details(self, *, title_length) -> dict[str, str | int]:
@property
def details(self) -> dict[str, str | int]:
"""Return the line entry for the PEP."""
return {
# how the type is to be represented in the index
"type": self.pep_type[0].upper(),
"number": self.number,
"title": _title_abbr(self.title, title_length),
"title": self.title,
# how the status should be represented in the index
"status": " " if self.status in HIDE_STATUS else self.status[0].upper(),
# the author list as a comma-separated with only last names
@ -172,11 +172,3 @@ def _parse_author(data: str) -> list[tuple[str, str]]:
if author_list:
break
return author_list
def _title_abbr(title, title_length) -> str:
"""Shorten the title to be no longer than the max title length."""
if len(title) <= title_length:
return title
wrapped_title, *_excess = textwrap.wrap(title, title_length - 4)
return f"{wrapped_title} ..."

View File

@ -3,7 +3,6 @@
from __future__ import annotations
import datetime
import functools
from typing import TYPE_CHECKING
import unicodedata
@ -26,16 +25,6 @@ from pep_sphinx_extensions.pep_zero_generator.errors import PEPError
if TYPE_CHECKING:
from pep_sphinx_extensions.pep_zero_generator.parser import PEP
title_length = 55
author_length = 40
table_separator = "== ==== " + "="*title_length + " " + "="*author_length
# column format is called as a function with a mapping containing field values
column_format = functools.partial(
"{type}{status}{number: >5} {title: <{title_length}} {authors}".format,
title_length=title_length
)
header = f"""\
PEP: 0
Title: Index of Python Enhancement Proposals (PEPs)
@ -80,21 +69,27 @@ class PEPZeroWriter:
def emit_newline(self) -> None:
self.output.append("")
def emit_table_separator(self) -> None:
self.output.append(table_separator)
def emit_author_table_separator(self, max_name_len: int) -> None:
author_table_separator = "=" * max_name_len + " " + "=" * len("email address")
self.output.append(author_table_separator)
def emit_pep_row(self, pep_details: dict[str, int | str]) -> None:
self.emit_text(column_format(**pep_details))
def emit_pep_row(self, *, type: str, status: str, number: int, title: str, authors: str) -> None:
self.emit_text(f" * - {type}{status}")
self.emit_text(f" - :pep:`{number} <{number}>`")
self.emit_text(f" - :pep:`{title.replace('`', '')} <{number}>`")
self.emit_text(f" - {authors}")
def emit_column_headers(self) -> None:
"""Output the column headers for the PEP indices."""
self.emit_table_separator()
self.emit_pep_row({"status": ".", "type": ".", "number": "PEP", "title": "PEP Title", "authors": "PEP Author(s)"})
self.emit_table_separator()
self.emit_text(".. list-table::")
self.emit_text(" :header-rows: 1")
self.emit_text(" :widths: auto")
self.emit_text(" :class: pep-zero-table")
self.emit_newline()
self.emit_text(" * - ")
self.emit_text(" - PEP")
self.emit_text(" - PEP Title")
self.emit_text(" - PEP Author(s)")
def emit_title(self, text: str, *, symbol: str = "=") -> None:
self.output.append(text)
@ -108,8 +103,13 @@ class PEPZeroWriter:
self.emit_subtitle(category)
self.emit_column_headers()
for pep in peps:
self.output.append(column_format(**pep.details(title_length=title_length)))
self.emit_table_separator()
self.emit_pep_row(**pep.details)
# list-table must have at least one body row
if len(peps) == 0:
self.emit_text(" * -")
self.emit_text(" -")
self.emit_text(" -")
self.emit_text(" -")
self.emit_newline()
def write_pep0(self, peps: list[PEP]):
@ -146,18 +146,16 @@ class PEPZeroWriter:
self.emit_title("Numerical Index")
self.emit_column_headers()
for pep in peps:
self.emit_pep_row(pep.details(title_length=title_length))
self.emit_pep_row(**pep.details)
self.emit_table_separator()
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})
self.emit_pep_row(type="", status="", number=number, title="RESERVED", authors=claimants)
self.emit_table_separator()
self.emit_newline()
# PEP types key

View File

@ -28,21 +28,14 @@ def test_pep_equal():
assert pep_a == pep_b
@pytest.mark.parametrize(
"test_input, expected",
[
(80, "Style Guide for Python Code"),
(10, "Style ..."),
],
)
def test_pep_details(test_input, expected):
def test_pep_details():
pep8 = parser.PEP(Path("pep-0008.txt"), AUTHORS_OVERRIDES)
assert pep8.details(title_length=test_input) == {
assert pep8.details == {
"authors": "GvR, Warsaw, Coghlan",
"number": 8,
"status": " ",
"title": expected,
"title": "Style Guide for Python Code",
"type": "P",
}