diff --git a/conf.py b/conf.py index 3d39d1182..44ff3e3eb 100644 --- a/conf.py +++ b/conf.py @@ -10,7 +10,7 @@ sys.path.append(str(Path("pep_sphinx_extensions").absolute())) # Add 'include_patterns' as a config variable from sphinx.config import Config -Config.config_values['include_patterns'] = [], 'env', [] +Config.config_values["include_patterns"] = [], "env", [] del Config # -- Project information ----------------------------------------------------- @@ -21,7 +21,11 @@ master_doc = "contents" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. -extensions = ["pep_sphinx_extensions", "sphinx.ext.githubpages"] +extensions = [ +"pep_sphinx_extensions", +"sphinx.ext.intersphinx", +"sphinx.ext.githubpages", +] # The file extensions of source files. Sphinx uses these suffixes as sources. source_suffix = { @@ -46,6 +50,13 @@ exclude_patterns = [ "pep-0012/pep-NNNN.rst", ] +# Intersphinx configuration +intersphinx_mapping = { + 'python': ('https://docs.python.org/3/', None), + 'packaging': ('https://packaging.python.org/en/latest/', None), +} +intersphinx_disabled_reftypes = [] + # -- Options for HTML output ------------------------------------------------- # HTML output settings @@ -60,4 +71,4 @@ html_permalinks = False # handled in the PEPContents transform html_baseurl = "https://peps.python.org" # to create the CNAME file gettext_auto_build = False # speed-ups -templates_path = ['pep_sphinx_extensions/pep_theme/templates'] # Theme template relative paths from `confdir` +templates_path = ["pep_sphinx_extensions/pep_theme/templates"] # Theme template relative paths from `confdir` diff --git a/pep-0012.rst b/pep-0012.rst index 11b7e3399..3c488e10c 100644 --- a/pep-0012.rst +++ b/pep-0012.rst @@ -649,6 +649,66 @@ If you find that you need to use a backslash in your text, consider using inline literals or a literal block instead. +Canonical Documentation and Intersphinx +--------------------------------------- + +As :pep:`PEP 1 describes <1#pep-maintenance>`, +PEPs are considered historical documents once marked Final, +and their canonical documentation/specification should be moved elsewhere. +To indicate this, use the ``canonical-docs`` directive +or an appropriate subclass +(currently ``canonical-pypa-spec`` for packaging standards). + +Furthermore, you can use +`Intersphinx references +`_ +to other Sphinx sites, +currently the `Python documentation `_ +and `packaging.python.org `_, +to easily cross-reference pages, sections and Python/C objects. +This works with both the "canonical" directives and anywhere in your PEP. + +Add the directive between the headers and the first section of the PEP +(typically the Abstract) +and pass as an argument an Intersphinx reference of the canonical doc/spec +(or if the target is not on a Sphinx site, a `reST hyperlink `__). + +For example, +to create a banner pointing to the :mod:`python:sqlite3` docs, +you would write the following:: + + .. canonical-doc:: :mod:`python:sqlite3` + +which would generate the banner: + + .. canonical-doc:: :mod:`python:sqlite3` + +Or for a PyPA spec, +such as the :ref:`packaging:core-metadata`, +you would use:: + + .. canonical-pypa-spec:: :ref:`packaging:core-metadata` + +which renders as: + + .. canonical-pypa-spec:: :ref:`packaging:core-metadata` + +The argument accepts arbitrary reST, +so you can include multiple linked docs/specs and name them whatever you like, +and you can also include directive content that will be inserted into the text. +The following advanced example:: + + .. canonical-doc:: the :ref:`python:sqlite3-connection-objects` and :exc:`python:~sqlite3.DataError` docs + + Also, see the :ref:`Data Persistence docs ` for other examples. + +would render as: + + .. canonical-doc:: the :ref:`python:sqlite3-connection-objects` and :exc:`python:sqlite3.DataError` docs + + Also, see the :ref:`Data Persistence docs ` for other examples. + + Habits to Avoid =============== diff --git a/pep_sphinx_extensions/__init__.py b/pep_sphinx_extensions/__init__.py index 17ed80b2c..554e7b046 100644 --- a/pep_sphinx_extensions/__init__.py +++ b/pep_sphinx_extensions/__init__.py @@ -10,6 +10,7 @@ from sphinx import project from pep_sphinx_extensions.pep_processor.html import pep_html_builder from pep_sphinx_extensions.pep_processor.html import pep_html_translator +from pep_sphinx_extensions.pep_processor.parsing import pep_banner_directive 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_processor.transforms import pep_references @@ -93,6 +94,14 @@ def setup(app: Sphinx) -> dict[str, bool]: app.add_post_transform(pep_references.PEPReferenceRoleTitleText) + # Register custom directives + app.add_directive( + "pep-banner", pep_banner_directive.PEPBanner) + app.add_directive( + "canonical-doc", pep_banner_directive.CanonicalDocBanner) + app.add_directive( + "canonical-pypa-spec", pep_banner_directive.CanonicalPyPASpecBanner) + # Register event callbacks app.connect("builder-inited", _update_config_for_builder) # Update configuration values for builder used app.connect("env-before-read-docs", create_pep_zero) # PEP 0 hook diff --git a/pep_sphinx_extensions/pep_processor/parsing/pep_banner_directive.py b/pep_sphinx_extensions/pep_processor/parsing/pep_banner_directive.py new file mode 100644 index 000000000..4a42186be --- /dev/null +++ b/pep_sphinx_extensions/pep_processor/parsing/pep_banner_directive.py @@ -0,0 +1,101 @@ +"""Roles to insert custom admonitions pointing readers to canonical content.""" + +from __future__ import annotations + +from docutils import nodes +from docutils.parsers import rst + + +PYPA_SPEC_BASE_URL = "https://packaging.python.org/en/latest/specifications/" + + +class PEPBanner(rst.Directive): + """Insert a special banner admonition in a PEP document.""" + + has_content = True + required_arguments = 0 + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {} + + admonition_pre_template = "" + admonition_pre_text = "" + admonition_post_text = "" + + admonition_class = nodes.important + css_classes = ["pep-banner"] + + + def run(self) -> list[nodes.admonition]: + + if self.arguments: + link_content = self.arguments[0] + pre_text = self.admonition_pre_template.format( + link_content=link_content) + else: + pre_text = self.admonition_pre_text + + pre_text_node = nodes.paragraph(pre_text) + pre_text_node.line = self.lineno + pre_node, pre_msg = self.state.inline_text(pre_text, self.lineno) + pre_text_node.extend(pre_node + pre_msg) + + post_text = self.admonition_post_text + post_text_node = nodes.paragraph(post_text) + post_text_node.line = self.lineno + post_node, post_msg = self.state.inline_text(post_text, self.lineno) + post_text_node.extend(post_node + post_msg) + + source_lines = [pre_text] + list(self.content or []) + [post_text] + admonition_node = self.admonition_class( + "\n".join(source_lines), classes=self.css_classes) + + admonition_node.append(pre_text_node) + if self.content: + self.state.nested_parse( + self.content, self.content_offset, admonition_node) + admonition_node.append(post_text_node) + + return [admonition_node] + + +class CanonicalDocBanner(PEPBanner): + """Insert an admonition pointing readers to a PEP's canonical docs.""" + + admonition_pre_template = ( + "This PEP is a historical document. " + "The up-to-date, canonical documentation can now be found " + "at {link_content}." + ) + admonition_pre_text = ( + "This PEP is a historical document. " + "The up-to-date, canonical documentation can now be found elsewhere." + ) + admonition_post_text = ( + "See :pep:`1` for how to propose changes." + ) + + css_classes = [*PEPBanner.css_classes, "canonical-doc"] + + + +class CanonicalPyPASpecBanner(PEPBanner): + """Insert a specialized admonition for PyPA packaging specifications.""" + + admonition_pre_template = ( + "This PEP is a historical document. " + "the up-to-date, canonical spec, {link_content}, is maintained on " + f"the `PyPA specs page <{PYPA_SPEC_BASE_URL}>`__." + ) + admonition_pre_text = ( + "This PEP is a historical document. " + "The up-to-date, canonical specification is maintained on " + f"the `PyPA specs page <{PYPA_SPEC_BASE_URL}>`__." + ) + admonition_post_text = ( + "See the `PyPA specification update process " + "`__ " + "for how to propose changes." + ) + + css_classes = [*PEPBanner.css_classes, "canonical-pypa-spec"]