Repository: python/peps

Issue:
Header field for link to final outcome (e.g. documentation)?
https://github.com/python/peps/pull/2690 pushed a thought into my head about whether it would be helpful to have a link in the PEP to what the PEP led to (e.g. documentation link for what was added thanks to the PEP)? Maybe the rendering could emphasize that so people realize the link is now the canonical place for details and the PEP has become a historical document (for Standards Track PEPs)?

I'm basically trying to help guide users to not submit PRs here for updating examples and such and instead work to improve the actual outcome of the PEPs.


Discussion:
Funny enough, it was your own #2684 that in turn inspired that PR :smile: 

This would be a great idea, since this would not only redirect traffic here to more appropriate places, but also be a great informational resource for readers to quickly jump to the up to date, user-oriented version of whatever the PEP describes, and also help authors and ourselves keep track that we are actually providing such resources whenever appropriate (and identify PEPs missing them). Indeed, that PR had me thinking of something along those lines myself.

Only issue with adding another header is the continued expansion in the number of rendered headers (though they take up way less space now), which @gvanrossum no likey, and it would be easy to miss amongst a bunch of other headers, especially for users (i.e. most of the target audience here) that are less familiar with the PEP process.

One potential alternative is instead of having a header (or at least having it render as a header), instead have a custom directive that renders an admonition like on #2690 with a standardized message that includes the doc/etc. link at the top of the PEP, which would be a lot easier to spot and also allow a brief mention that the PEP is a historical document, etc, as well as any desired custom PEP-specific text.

Here's a rough sketch of what it could look like, for (picked at random) PEP-553:

```rst
.. canonicalcontent:: https://docs.python.org/3/library/functions.html#breakpoint
```

which could render as:

![image](https://user-images.githubusercontent.com/17051931/176559645-c2b0ff55-b881-4f4c-bf1f-cc3038ec985d.png)

We could have directive options and/or subclasses as needed for specific types of docs, like packaging, and any desired custom text could be added between the first and second lines underneath the directive, just like with e.g. `versionadded` and `deprecated` in the CPython docs.
A directive would be a great solution!
Agree on a directive. I'd also vote to keep as simple as possible -- perhaps even make the entire div a link target (the horror!)

A
Okay, great! I'll go ahead with a PR, then. If it gets merged before #2690 , I'll update that one to use a PyPA-specific version of this directive instead of manual admonitions, otherwise I'll merge that as is and do it once this one's in.

> perhaps even make the entire div a link target (the horror!)

Hmm, that's an interesting thought to make the target easier to find/click—but it would preclude multiple links, which both the generic example and the packaging ones have, isn't typical behavior users expect from admonitions, and would be somewhat more work to implement; with the above I can just create a normal `admonition` with the specified content, basically.
One thing with the directive is that a PEP might end up in multiple documentation locations (to use Diataxis terms, a tutorial, reference and explanation).

IMO, it would make sense to allow the directive to take multiple URLs as a body as well.
> IMO, it would make sense to allow the directive to take multiple URLs as a body as well.

Yup, the idea is to allow arbitrary custom reST text (which can include links, etc) in the body of the directive, which then gets included in the admonition after the first line.

Resolving patch:
diff --git a/conf.py b/conf.py
--- a/conf.py
+++ b/conf.py
@@ -10,7 +10,7 @@
 
 # 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 @@
 # -- 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 @@
     "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_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_sphinx_extensions/__init__.py b/pep_sphinx_extensions/__init__.py
--- a/pep_sphinx_extensions/__init__.py
+++ b/pep_sphinx_extensions/__init__.py
@@ -10,6 +10,7 @@
 
 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
--- /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 "
+        "<https://www.pypa.io/en/latest/specifications/#handling-fixes-and-other-minor-updates>`__ "
+        "for how to propose changes."
+    )
+
+    css_classes = [*PEPBanner.css_classes, "canonical-pypa-spec"]


Test patch: