-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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 - docutils #1931
Merged
Merged
Sphinx - docutils #1931
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
f6f4490
Add PEPZero transform
AA-Turner 4203b11
Add PEPHeaders transform
AA-Turner 7a7c040
Add PEPTitle transform
AA-Turner dc6f0ce
Add PEPContents transform
AA-Turner 0decaf3
Add PEPFooter transform
AA-Turner 0f5d44a
Add PEPParser document parser
AA-Turner 61a1bf7
Add PEPRole rst role
AA-Turner 3e2a13f
Add PEPTranslator HTML translator
AA-Turner 9368f99
Add config for pep_processor
AA-Turner 0f51f42
Add extension setup and maths & settings overrides
AA-Turner 99c27a5
Update conf with extension details
AA-Turner e4b7ef4
Add sphinx.ext.githubpages for automatic `.nojekyll`
AA-Turner c692b36
Set default for Sphinx jobs / parallelism
AA-Turner 646e971
Explanatory comment RE sphinx.environment.default_settings
AA-Turner 0ab3a3c
Use any(*) per Pablo
AA-Turner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
"""Sphinx extensions for performant PEP processing""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
from sphinx.environment import default_settings | ||
from docutils.writers.html5_polyglot import HTMLTranslator | ||
|
||
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 | ||
|
||
if TYPE_CHECKING: | ||
from sphinx.application import Sphinx | ||
|
||
# Monkeypatch sphinx.environment.default_settings as Sphinx doesn't allow custom settings or Readers | ||
# These settings should go in docutils.conf, but are overridden here for now so as not to affect | ||
# pep2html.py | ||
default_settings |= { | ||
"pep_references": True, | ||
"rfc_references": True, | ||
"pep_base_url": "", | ||
"pep_file_url_template": "pep-%04d.html", | ||
"_disable_config": True, # disable using docutils.conf whilst running both PEP generators | ||
} | ||
|
||
|
||
def _depart_maths(): | ||
pass # No-op callable for the type checker | ||
|
||
|
||
def setup(app: Sphinx) -> dict[str, bool]: | ||
"""Initialize Sphinx extension.""" | ||
|
||
# Register plugin logic | ||
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 | ||
|
||
# Mathematics rendering | ||
inline_maths = HTMLTranslator.visit_math, _depart_maths | ||
block_maths = HTMLTranslator.visit_math_block, _depart_maths | ||
app.add_html_math_renderer("maths_to_html", inline_maths, block_maths) # Render maths to HTML | ||
|
||
# Parallel safety: https://www.sphinx-doc.org/en/master/extdev/index.html#extension-metadata | ||
return {"parallel_read_safe": True, "parallel_write_safe": True} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"""Miscellaneous configuration variables for the PEP Sphinx extensions.""" | ||
|
||
pep_stem = "pep-{:0>4}" | ||
pep_url = f"{pep_stem}.html" | ||
pep_vcs_url = "https://github.com/python/peps/blob/master/" | ||
pep_commits_url = "https://github.com/python/peps/commits/master/" |
86 changes: 86 additions & 0 deletions
86
pep_sphinx_extensions/pep_processor/html/pep_html_translator.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
from docutils import nodes | ||
import sphinx.writers.html5 as html5 | ||
|
||
if TYPE_CHECKING: | ||
from sphinx.builders import html | ||
|
||
|
||
class PEPTranslator(html5.HTML5Translator): | ||
"""Custom RST -> HTML translation rules for PEPs.""" | ||
|
||
def __init__(self, document: nodes.document, builder: html.StandaloneHTMLBuilder): | ||
super().__init__(document, builder) | ||
self.compact_simple: bool = False | ||
|
||
@staticmethod | ||
def should_be_compact_paragraph(node: nodes.paragraph) -> bool: | ||
"""Check if paragraph should be compact. | ||
|
||
Omitting <p/> tags around paragraph nodes gives visually compact lists. | ||
|
||
""" | ||
# Never compact paragraphs that are children of document or compound. | ||
if isinstance(node.parent, (nodes.document, nodes.compound)): | ||
return False | ||
|
||
# Check for custom attributes in paragraph. | ||
for key, value in node.non_default_attributes().items(): | ||
# if key equals "classes", carry on | ||
# if value is empty, or contains only "first", only "last", or both | ||
# "first" and "last", carry on | ||
# else return False | ||
if any((key != "classes", not set(value) <= {"first", "last"})): | ||
return False | ||
|
||
# Only first paragraph can be compact (ignoring initial label & invisible nodes) | ||
first = isinstance(node.parent[0], nodes.label) | ||
visible_siblings = [child for child in node.parent.children[first:] if not isinstance(child, nodes.Invisible)] | ||
if visible_siblings[0] is not node: | ||
return False | ||
|
||
# otherwise, the paragraph should be compact | ||
return True | ||
|
||
def visit_paragraph(self, node: nodes.paragraph) -> None: | ||
"""Remove <p> tags if possible.""" | ||
if self.should_be_compact_paragraph(node): | ||
self.context.append("") | ||
else: | ||
self.body.append(self.starttag(node, "p", "")) | ||
self.context.append("</p>\n") | ||
|
||
def depart_paragraph(self, _: nodes.paragraph) -> None: | ||
"""Add corresponding end tag from `visit_paragraph`.""" | ||
self.body.append(self.context.pop()) | ||
|
||
def depart_label(self, node) -> None: | ||
"""PEP link/citation block cleanup with italicised backlinks.""" | ||
if not self.settings.footnote_backlinks: | ||
self.body.append("</span>") | ||
self.body.append("</dt>\n<dd>") | ||
return | ||
|
||
# If only one reference to this footnote | ||
back_references = node.parent["backrefs"] | ||
if len(back_references) == 1: | ||
self.body.append("</a>") | ||
|
||
# Close the tag | ||
self.body.append("</span>") | ||
|
||
# If more than one reference | ||
if len(back_references) > 1: | ||
back_links = [f"<a href='#{ref}'>{i}</a>" for i, ref in enumerate(back_references, start=1)] | ||
back_links_str = ", ".join(back_links) | ||
self.body.append(f"<span class='fn-backref''><em> ({back_links_str}) </em></span>") | ||
|
||
# Close the def tags | ||
self.body.append("</dt>\n<dd>") | ||
|
||
def unknown_visit(self, node: nodes.Node) -> None: | ||
"""No processing for unknown node types.""" | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
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 | ||
|
||
if TYPE_CHECKING: | ||
from docutils import transforms | ||
|
||
|
||
class PEPParser(parsers.RSTParser): | ||
"""RST parser with custom PEP transforms.""" | ||
|
||
supported = ("pep", "python-enhancement-proposal") # for source_suffix in conf.py | ||
|
||
def __init__(self): | ||
"""Mark the document as containing RFC 2822 headers.""" | ||
super().__init__(rfc2822=True) | ||
|
||
def get_transforms(self) -> list[type[transforms.Transform]]: | ||
"""Use our custom PEP transform rules.""" | ||
return [ | ||
pep_headers.PEPHeaders, | ||
pep_title.PEPTitle, | ||
pep_contents.PEPContents, | ||
pep_footer.PEPFooter, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from sphinx import roles | ||
|
||
from pep_sphinx_extensions.config import pep_url | ||
|
||
|
||
class PEPRole(roles.PEP): | ||
"""Override the :pep: role""" | ||
|
||
def build_uri(self) -> str: | ||
"""Get PEP URI from role text.""" | ||
base_url = self.inliner.document.settings.pep_base_url | ||
pep_num, _, fragment = self.target.partition("#") | ||
pep_base = base_url + pep_url.format(int(pep_num)) | ||
if fragment: | ||
return f"{pep_base}#{fragment}" | ||
return pep_base |
63 changes: 63 additions & 0 deletions
63
pep_sphinx_extensions/pep_processor/transforms/pep_contents.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
from pathlib import Path | ||
|
||
from docutils import nodes | ||
from docutils import transforms | ||
from docutils.transforms import parts | ||
|
||
|
||
class PEPContents(transforms.Transform): | ||
"""Add TOC placeholder and horizontal rule after PEP title and headers.""" | ||
|
||
# Use same priority as docutils.transforms.Contents | ||
default_priority = 380 | ||
|
||
def apply(self) -> None: | ||
if not Path(self.document["source"]).match("pep-*"): | ||
return # not a PEP file, exit early | ||
|
||
# Create the contents placeholder section | ||
title = nodes.title("", "Contents") | ||
contents_topic = nodes.topic("", title, classes=["contents"]) | ||
if not self.document.has_name("contents"): | ||
contents_topic["names"].append("contents") | ||
self.document.note_implicit_target(contents_topic) | ||
|
||
# Add a table of contents builder | ||
pending = nodes.pending(Contents) | ||
contents_topic += pending | ||
self.document.note_pending(pending) | ||
|
||
# Insert the toc after title and PEP headers | ||
self.document.children[0].insert(2, contents_topic) | ||
|
||
# Add a horizontal rule before contents | ||
transition = nodes.transition() | ||
self.document[0].insert(2, transition) | ||
|
||
|
||
class Contents(parts.Contents): | ||
"""Build Table of Contents from document.""" | ||
def __init__(self, document, startnode=None): | ||
super().__init__(document, startnode) | ||
|
||
# used in parts.Contents.build_contents | ||
self.toc_id = None | ||
self.backlinks = None | ||
|
||
def apply(self) -> None: | ||
# used in parts.Contents.build_contents | ||
self.toc_id = self.startnode.parent["ids"][0] | ||
self.backlinks = self.document.settings.toc_backlinks | ||
|
||
# let the writer (or output software) build the contents list? | ||
if getattr(self.document.settings, "use_latex_toc", False): | ||
# move customisation settings to the parent node | ||
self.startnode.parent.attributes.update(self.startnode.details) | ||
self.startnode.parent.remove(self.startnode) | ||
else: | ||
contents = self.build_contents(self.document[0]) | ||
if contents: | ||
self.startnode.replace_self(contents) | ||
else: | ||
# if no contents, remove the empty placeholder | ||
self.startnode.parent.parent.remove(self.startnode.parent) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a bit brittle, is not a show stopper but I would prefer if we don't do this as this is a potential future breackage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is overriding
docutils.conf
, which I didn't want to touch following the discussion about running old + new at the same time, as it is implicitly used inpep2html.py
. I can add a explanatory comment if you'd like, as the end state would be to move this config intodocutils.conf
.This is all just disabling default docutils stuff, apart from the PEP/RFC references, which I believe are on by default but wanted to declare explicitly.