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

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

Merged
merged 5 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 5 additions & 71 deletions pep_sphinx_extensions/pep_processor/transforms/pep_zero.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,83 +7,17 @@
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.

Expand Down
2 changes: 1 addition & 1 deletion pep_sphinx_extensions/pep_theme/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 3 additions & 11 deletions pep_sphinx_extensions/pep_zero_generator/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
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
Expand Down Expand Up @@ -114,13 +113,14 @@ def __lt__(self, other: PEP) -> bool:
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
Expand Down Expand Up @@ -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} ..."
48 changes: 23 additions & 25 deletions pep_sphinx_extensions/pep_zero_generator/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import datetime
import functools
from typing import TYPE_CHECKING
import unicodedata

Expand All @@ -26,16 +25,6 @@
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)
Expand Down Expand Up @@ -80,21 +69,27 @@ def emit_text(self, content: str) -> None:
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)")
Comment on lines +90 to +92
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Author(s)" is a bit ugly, for a table heading can we just put "Authors" or "Author"?

Also I think it's clear that the title is the PEP title, and the authors are the PEP authors, in a list of PEPs on the PEP index page on https://peps.python.org/ :)

Suggested change
self.emit_text(" - PEP")
self.emit_text(" - PEP Title")
self.emit_text(" - PEP Author(s)")
self.emit_text(" - PEP")
self.emit_text(" - Title")
self.emit_text(" - Authors")


def emit_title(self, text: str, *, symbol: str = "=") -> None:
self.output.append(text)
Expand All @@ -108,8 +103,13 @@ def emit_pep_category(self, category: str, peps: list[PEP]) -> None:
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]):
Expand Down Expand Up @@ -146,18 +146,16 @@ def write_pep0(self, peps: list[PEP]):
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
Expand Down
13 changes: 3 additions & 10 deletions pep_sphinx_extensions/tests/pep_zero_generator/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}

Expand Down