diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index f265b199b..5b250f392 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -10,12 +10,6 @@
Makefile @AA-Turner
requirements.txt @AA-Turner
-pep0/ @AA-Turner
-docutils.conf @AA-Turner
-genpepindex.py @AA-Turner
-pep2html.py @AA-Turner
-pep2rss.py @AA-Turner
-
pep_sphinx_extensions/ @AA-Turner
AUTHOR_OVERRIDES.csv @AA-Turner
build.py @AA-Turner
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index 622f5b2bf..000000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Docutils Build
-
-on: [push, pull_request, workflow_dispatch]
-
-jobs:
- build:
- name: Build with Docutils
- runs-on: ubuntu-latest
-
- steps:
- - name: Check out repo
- uses: actions/checkout@v2
-
- - name: Set up Python
- uses: actions/setup-python@v2
- with:
- python-version: 3.8
-
- - name: Install dependencies
- run: |
- python -m pip install -U pip
- python -m pip install -r requirements.txt
-
- - name: Build
- run: |
- make rss
- make -j$(nproc)
-
- - name: Deploy
- if: >
- (
- github.repository == 'python/peps' &&
- github.ref == 'refs/heads/main'
- )
- run: |
- bash deploy.bash
- env:
- AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
diff --git a/.github/workflows/deploy-gh-pages.yml b/.github/workflows/render.yml
similarity index 91%
rename from .github/workflows/deploy-gh-pages.yml
rename to .github/workflows/render.yml
index de95eb344..c208e91c5 100644
--- a/.github/workflows/deploy-gh-pages.yml
+++ b/.github/workflows/render.yml
@@ -1,10 +1,10 @@
-name: Sphinx Build
+name: Render PEPs
on: [push, pull_request, workflow_dispatch]
jobs:
- deploy-to-pages:
- name: Build & deploy to GitHub Pages
+ render-peps:
+ name: Render PEPs
runs-on: ubuntu-latest
steps:
@@ -24,7 +24,7 @@ jobs:
python -m pip install --upgrade pip
python -m pip install --upgrade -r requirements.txt
- - name: 🔧 Build PEPs
+ - name: 🔧 Render PEPs
run: make pages -j$(nproc)
# remove the .doctrees folder when building for deployment as it takes two thirds of disk space
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 108f0efdf..636f99e47 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -2,7 +2,7 @@ Contributing Guidelines
=======================
To learn more about the purpose of PEPs and how to go about writing one, please
-start reading at `PEP 1 `_.
+start reading at `PEP 1 `_.
Also, make sure to check the `README <./README.rst>`_ for information
on how to render the PEPs in this repository.
Thanks again for your contributions, and we look forward to reviewing them!
@@ -12,7 +12,7 @@ Before writing a new PEP
------------------------
Prior to submitting a pull request here with your draft PEP, see `PEP 1
-`_
+`_
for some important steps to consider, including proposing and discussing it
first in an appropriate venue, drafting a PEP and gathering feedback, and
developing at least a prototype reference implementation of your idea.
diff --git a/Makefile b/Makefile
index bdf0752d8..0a095abfc 100644
--- a/Makefile
+++ b/Makefile
@@ -1,56 +1,32 @@
-# Builds PEP files to HTML using docutils or sphinx
-# Also contains testing targets
-
-PEP2HTML=pep2html.py
+# Builds PEP files to HTML using sphinx
PYTHON=python3
-
VENV_DIR=venv
+JOBS=8
+RENDER_COMMAND=$(PYTHON) build.py -j $(JOBS)
-.SUFFIXES: .txt .html .rst
+render:
+ $(RENDER_COMMAND)
-.txt.html:
- @$(PYTHON) $(PEP2HTML) $<
+pages: rss
+ $(RENDER_COMMAND) --build-dirs
-.rst.html:
- @$(PYTHON) $(PEP2HTML) $<
+fail-warning:
+ $(RENDER_COMMAND) --fail-on-warning
-TARGETS= $(patsubst %.rst,%.html,$(wildcard pep-????.rst)) $(patsubst %.txt,%.html,$(wildcard pep-????.txt)) pep-0000.html
-
-all: pep-0000.rst $(TARGETS)
-
-$(TARGETS): pep2html.py
-
-pep-0000.rst: $(wildcard pep-????.txt) $(wildcard pep-????.rst) $(wildcard pep0/*.py) genpepindex.py
- $(PYTHON) genpepindex.py .
+check-links:
+ $(RENDER_COMMAND) --check-links
rss:
- $(PYTHON) pep2rss.py .
-
-install:
- echo "Installing is not necessary anymore. It will be done in post-commit."
+ $(PYTHON) generate_rss.py
clean:
- -rm pep-0000.rst
- -rm *.html
-rm -rf build
-update:
- git pull https://github.com/python/peps.git
-
venv:
$(PYTHON) -m venv $(VENV_DIR)
./$(VENV_DIR)/bin/python -m pip install -r requirements.txt
-package: all rss
- mkdir -p build/peps
- cp pep-*.txt build/peps/
- cp pep-*.rst build/peps/
- cp *.html build/peps/
- cp *.png build/peps/
- cp *.rss build/peps/
- tar -C build -czf build/peps.tar.gz peps
-
lint:
pre-commit --version > /dev/null || $(PYTHON) -m pip install pre-commit
pre-commit run --all-files
@@ -58,24 +34,3 @@ lint:
spellcheck:
pre-commit --version > /dev/null || $(PYTHON) -m pip install pre-commit
pre-commit run --all-files --hook-stage manual codespell
-
-# New Sphinx targets:
-
-SPHINX_JOBS=8
-SPHINX_BUILD=$(PYTHON) build.py -j $(SPHINX_JOBS)
-
-# TODO replace `rss:` with this when merged & tested
-pep_rss:
- $(PYTHON) generate_rss.py
-
-pages: pep_rss
- $(SPHINX_BUILD) --build-dirs
-
-sphinx:
- $(SPHINX_BUILD)
-
-fail-warning:
- $(SPHINX_BUILD) --fail-on-warning
-
-check-links:
- $(SPHINX_BUILD) --check-links
diff --git a/PyRSS2Gen.py b/PyRSS2Gen.py
deleted file mode 100644
index 65c1f0983..000000000
--- a/PyRSS2Gen.py
+++ /dev/null
@@ -1,456 +0,0 @@
-"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds."""
-
-__name__ = "PyRSS2Gen"
-__version__ = (1, 1, 0)
-__author__ = "Andrew Dalke "
-
-_generator_name = __name__ + "-" + ".".join(map(str, __version__))
-
-import datetime
-
-import sys
-
-if sys.version_info[0] == 3:
- # Python 3
- basestring = str
- from io import StringIO
-else:
- # Python 2
- try:
- from cStringIO import StringIO
- except ImportError:
- # Very old (or memory constrained) systems might
- # have left out the compiled C version. Fall back
- # to the pure Python one. Haven't seen this sort
- # of system since the early 2000s.
- from StringIO import StringIO
-
-# Could make this the base class; will need to add 'publish'
-class WriteXmlMixin:
- def write_xml(self, outfile, encoding = "iso-8859-1"):
- from xml.sax import saxutils
- handler = saxutils.XMLGenerator(outfile, encoding)
- handler.startDocument()
- self.publish(handler)
- handler.endDocument()
-
- def to_xml(self, encoding = "iso-8859-1"):
- f = StringIO()
- self.write_xml(f, encoding)
- return f.getvalue()
-
-
-def _element(handler, name, obj, d = {}):
- if isinstance(obj, basestring) or obj is None:
- # special-case handling to make the API easier
- # to use for the common case.
- handler.startElement(name, d)
- if obj is not None:
- handler.characters(obj)
- handler.endElement(name)
- else:
- # It better know how to emit the correct XML.
- obj.publish(handler)
-
-def _opt_element(handler, name, obj):
- if obj is None:
- return
- _element(handler, name, obj)
-
-
-def _format_date(dt):
- """convert a datetime into an RFC 822 formatted date
-
- Input date must be in GMT.
- """
- # Looks like:
- # Sat, 07 Sep 2002 00:00:01 GMT
- # Can't use strftime because that's locale dependent
- #
- # Isn't there a standard way to do this for Python? The
- # rfc822 and email.Utils modules assume a timestamp. The
- # following is based on the rfc822 module.
- return "%s, %02d %s %04d %02d:%02d:%02d GMT" % (
- ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"][dt.weekday()],
- dt.day,
- ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][dt.month-1],
- dt.year, dt.hour, dt.minute, dt.second)
-
-
-##
-# A couple simple wrapper objects for the fields which
-# take a simple value other than a string.
-class IntElement:
- """implements the 'publish' API for integers
-
- Takes the tag name and the integer value to publish.
-
- (Could be used for anything which uses str() to be published
- to text for XML.)
- """
- element_attrs = {}
- def __init__(self, name, val):
- self.name = name
- self.val = val
- def publish(self, handler):
- handler.startElement(self.name, self.element_attrs)
- handler.characters(str(self.val))
- handler.endElement(self.name)
-
-class DateElement:
- """implements the 'publish' API for a datetime.datetime
-
- Takes the tag name and the datetime to publish.
-
- Converts the datetime to RFC 2822 timestamp (4-digit year).
- """
- def __init__(self, name, dt):
- self.name = name
- self.dt = dt
- def publish(self, handler):
- _element(handler, self.name, _format_date(self.dt))
-####
-
-class Category:
- """Publish a category element"""
- def __init__(self, category, domain = None):
- self.category = category
- self.domain = domain
- def publish(self, handler):
- d = {}
- if self.domain is not None:
- d["domain"] = self.domain
- _element(handler, "category", self.category, d)
-
-class Cloud:
- """Publish a cloud"""
- def __init__(self, domain, port, path,
- registerProcedure, protocol):
- self.domain = domain
- self.port = port
- self.path = path
- self.registerProcedure = registerProcedure
- self.protocol = protocol
- def publish(self, handler):
- _element(handler, "cloud", None, {
- "domain": self.domain,
- "port": str(self.port),
- "path": self.path,
- "registerProcedure": self.registerProcedure,
- "protocol": self.protocol})
-
-class Image:
- """Publish a channel Image"""
- element_attrs = {}
- def __init__(self, url, title, link,
- width = None, height = None, description = None):
- self.url = url
- self.title = title
- self.link = link
- self.width = width
- self.height = height
- self.description = description
-
- def publish(self, handler):
- handler.startElement("image", self.element_attrs)
-
- _element(handler, "url", self.url)
- _element(handler, "title", self.title)
- _element(handler, "link", self.link)
-
- width = self.width
- if isinstance(width, int):
- width = IntElement("width", width)
- _opt_element(handler, "width", width)
-
- height = self.height
- if isinstance(height, int):
- height = IntElement("height", height)
- _opt_element(handler, "height", height)
-
- _opt_element(handler, "description", self.description)
-
- handler.endElement("image")
-
-class Guid:
- """Publish a guid
-
- Defaults to being a permalink, which is the assumption if it's
- omitted. Hence strings are always permalinks.
- """
- def __init__(self, guid, isPermaLink = 1):
- self.guid = guid
- self.isPermaLink = isPermaLink
- def publish(self, handler):
- d = {}
- if self.isPermaLink:
- d["isPermaLink"] = "true"
- else:
- d["isPermaLink"] = "false"
- _element(handler, "guid", self.guid, d)
-
-class TextInput:
- """Publish a textInput
-
- Apparently this is rarely used.
- """
- element_attrs = {}
- def __init__(self, title, description, name, link):
- self.title = title
- self.description = description
- self.name = name
- self.link = link
-
- def publish(self, handler):
- handler.startElement("textInput", self.element_attrs)
- _element(handler, "title", self.title)
- _element(handler, "description", self.description)
- _element(handler, "name", self.name)
- _element(handler, "link", self.link)
- handler.endElement("textInput")
-
-
-class Enclosure:
- """Publish an enclosure"""
- def __init__(self, url, length, type):
- self.url = url
- self.length = length
- self.type = type
- def publish(self, handler):
- _element(handler, "enclosure", None,
- {"url": self.url,
- "length": str(self.length),
- "type": self.type,
- })
-
-class Source:
- """Publish the item's original source, used by aggregators"""
- def __init__(self, name, url):
- self.name = name
- self.url = url
- def publish(self, handler):
- _element(handler, "source", self.name, {"url": self.url})
-
-class SkipHours:
- """Publish the skipHours
-
- This takes a list of hours, as integers.
- """
- element_attrs = {}
- def __init__(self, hours):
- self.hours = hours
- def publish(self, handler):
- if self.hours:
- handler.startElement("skipHours", self.element_attrs)
- for hour in self.hours:
- _element(handler, "hour", str(hour))
- handler.endElement("skipHours")
-
-class SkipDays:
- """Publish the skipDays
-
- This takes a list of days as strings.
- """
- element_attrs = {}
- def __init__(self, days):
- self.days = days
- def publish(self, handler):
- if self.days:
- handler.startElement("skipDays", self.element_attrs)
- for day in self.days:
- _element(handler, "day", day)
- handler.endElement("skipDays")
-
-class RSS2(WriteXmlMixin):
- """The main RSS class.
-
- Stores the channel attributes, with the "category" elements under
- ".categories" and the RSS items under ".items".
- """
-
- rss_attrs = {"version": "2.0"}
- element_attrs = {}
- def __init__(self,
- title,
- link,
- description,
-
- language = None,
- copyright = None,
- managingEditor = None,
- webMaster = None,
- pubDate = None, # a datetime, *in* *GMT*
- lastBuildDate = None, # a datetime
-
- categories = None, # list of strings or Category
- generator = _generator_name,
- docs = "http://blogs.law.harvard.edu/tech/rss",
- cloud = None, # a Cloud
- ttl = None, # integer number of minutes
-
- image = None, # an Image
- rating = None, # a string; I don't know how it's used
- textInput = None, # a TextInput
- skipHours = None, # a SkipHours with a list of integers
- skipDays = None, # a SkipDays with a list of strings
-
- items = None, # list of RSSItems
- ):
- self.title = title
- self.link = link
- self.description = description
- self.language = language
- self.copyright = copyright
- self.managingEditor = managingEditor
-
- self.webMaster = webMaster
- self.pubDate = pubDate
- self.lastBuildDate = lastBuildDate
-
- if categories is None:
- categories = []
- self.categories = categories
- self.generator = generator
- self.docs = docs
- self.cloud = cloud
- self.ttl = ttl
- self.image = image
- self.rating = rating
- self.textInput = textInput
- self.skipHours = skipHours
- self.skipDays = skipDays
-
- if items is None:
- items = []
- self.items = items
-
- def publish(self, handler):
- handler.startElement("rss", self.rss_attrs)
- handler.startElement("channel", self.element_attrs)
- _element(handler, "title", self.title)
- _element(handler, "link", self.link)
- _element(handler, "description", self.description)
-
- self.publish_extensions(handler)
-
- _opt_element(handler, "language", self.language)
- _opt_element(handler, "copyright", self.copyright)
- _opt_element(handler, "managingEditor", self.managingEditor)
- _opt_element(handler, "webMaster", self.webMaster)
-
- pubDate = self.pubDate
- if isinstance(pubDate, datetime.datetime):
- pubDate = DateElement("pubDate", pubDate)
- _opt_element(handler, "pubDate", pubDate)
-
- lastBuildDate = self.lastBuildDate
- if isinstance(lastBuildDate, datetime.datetime):
- lastBuildDate = DateElement("lastBuildDate", lastBuildDate)
- _opt_element(handler, "lastBuildDate", lastBuildDate)
-
- for category in self.categories:
- if isinstance(category, basestring):
- category = Category(category)
- category.publish(handler)
-
- _opt_element(handler, "generator", self.generator)
- _opt_element(handler, "docs", self.docs)
-
- if self.cloud is not None:
- self.cloud.publish(handler)
-
- ttl = self.ttl
- if isinstance(self.ttl, int):
- ttl = IntElement("ttl", ttl)
- _opt_element(handler, "ttl", ttl)
-
- if self.image is not None:
- self.image.publish(handler)
-
- _opt_element(handler, "rating", self.rating)
- if self.textInput is not None:
- self.textInput.publish(handler)
- if self.skipHours is not None:
- self.skipHours.publish(handler)
- if self.skipDays is not None:
- self.skipDays.publish(handler)
-
- for item in self.items:
- item.publish(handler)
-
- handler.endElement("channel")
- handler.endElement("rss")
-
- def publish_extensions(self, handler):
- # Derived classes can hook into this to insert
- # output after the three required fields.
- pass
-
-
-
-class RSSItem(WriteXmlMixin):
- """Publish an RSS Item"""
- element_attrs = {}
- def __init__(self,
- title = None, # string
- link = None, # url as string
- description = None, # string
- author = None, # email address as string
- categories = None, # list of string or Category
- comments = None, # url as string
- enclosure = None, # an Enclosure
- guid = None, # a unique string
- pubDate = None, # a datetime
- source = None, # a Source
- ):
-
- if title is None and description is None:
- raise TypeError(
- "must define at least one of 'title' or 'description'")
- self.title = title
- self.link = link
- self.description = description
- self.author = author
- if categories is None:
- categories = []
- self.categories = categories
- self.comments = comments
- self.enclosure = enclosure
- self.guid = guid
- self.pubDate = pubDate
- self.source = source
- # It sure does get tedious typing these names three times...
-
- def publish(self, handler):
- handler.startElement("item", self.element_attrs)
- _opt_element(handler, "title", self.title)
- _opt_element(handler, "link", self.link)
- self.publish_extensions(handler)
- _opt_element(handler, "description", self.description)
- _opt_element(handler, "author", self.author)
-
- for category in self.categories:
- if isinstance(category, basestring):
- category = Category(category)
- category.publish(handler)
-
- _opt_element(handler, "comments", self.comments)
- if self.enclosure is not None:
- self.enclosure.publish(handler)
- _opt_element(handler, "guid", self.guid)
-
- pubDate = self.pubDate
- if isinstance(pubDate, datetime.datetime):
- pubDate = DateElement("pubDate", pubDate)
- _opt_element(handler, "pubDate", pubDate)
-
- if self.source is not None:
- self.source.publish(handler)
-
- handler.endElement("item")
-
- def publish_extensions(self, handler):
- # Derived classes can hook into this to insert
- # output after the title and link elements
- pass
diff --git a/README.rst b/README.rst
index 8178cb344..f30a3e51b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,15 +1,13 @@
Python Enhancement Proposals
============================
-.. image:: https://github.com/python/peps/actions/workflows/build.yml/badge.svg
+.. image:: https://github.com/python/peps/actions/workflows/render.yml/badge.svg
:target: https://github.com/python/peps/actions
The PEPs in this repo are published automatically on the web at
-https://www.python.org/dev/peps/. To learn more about the purpose of
-PEPs and how to go about writing one, please start reading at `PEP 1
-`_.
-Note that PEP 0, the index PEP, is
-automatically generated and not committed to the repo.
+https://peps.python.org/. To learn more about the purpose of PEPs and how to go
+about writing one, please start reading at :pep:`1`. Note that the PEP Index
+(:pep:`0`) is automatically generated based on the metadata headers in other PEPs.
Contributing to PEPs
@@ -18,135 +16,38 @@ Contributing to PEPs
See the `Contributing Guidelines <./CONTRIBUTING.rst>`_.
-reStructuredText for PEPs
-=========================
-
-PEP source text should be written in reStructuredText format,
-which is a constrained version of plaintext, and is described in
-`PEP 12 `_.
-The ``pep2html.py`` processing and installation script knows
-how to produce the HTML for the PEP format.
-
-To render the PEPs, you'll first need to install the requirements,
-(preferably in a fresh virtual environment):
-
-.. code-block:: bash
-
- python -m pip install -r requirements.txt
-
-
-Generating the PEP Index
-========================
-
-PEP 0 is automatically generated based on the metadata headers in other
-PEPs. The script handling this is ``genpepindex.py``, with supporting
-libraries in the ``pep0`` directory.
-
-
Checking PEP formatting and rendering
=====================================
Please don't commit changes with reStructuredText syntax errors that cause PEP
generation to fail, or result in major rendering defects relative to what you
-intend. To check building the HTML output for your PEP (for example, PEP 12)
-using the current default docutils-based system, run the ``pep2html.py`` script
-with your PEP source file as its argument; e.g. for PEP 12,
+intend.
+
+
+Render PEPs locally
+-------------------
+
+See the `build documentation <./docs/build.rst>`__ for full
+instructions on how to render PEPs locally. In summary:
.. code-block:: bash
- python pep2html.py pep-0012.rst
+ # Install requirements
+ python -m pip install -U -r requirements.txt
-If you're on a system with ``make``, you can instead execute, e.g.,
+ # Render the PEPs
+ make render
-.. code-block:: bash
+ # or, if you don't have 'make':
+ python3 build.py
- make pep-0012.rst
-To generate HTML for all the PEPs, run the script/``make`` without a PEP
-file argument.
+The output HTML is found under the ``build`` directory.
-By default, this will output a file (e.g. ``pep-0012.html``) in the root
-directory, which you can view to see the HTML output of your PEP.
-Note that the custom CSS stylesheet is not used by default, so
-the PEP will look rather plain, but all the basic formatting produced by the
-reStructuredText syntax in your source file should be visible.
-You can also view your PEP locally with the Sphinx-based builder,
-which will show the PEP exactly as it will appear on the preview
-of the new rendering system proposed in :pep:`676`;
-see `Rendering PEPs with Sphinx`_ for details.
+PEP draughting aids
+-------------------
-Finally, you can check for and fix common linting and spelling issues,
+You can check for and fix common linting and spelling issues,
either on-demand or automatically as you commit, with our pre-commit suite.
See the `Contributing Guide <./CONTRIBUTING.rst>`_ for details.
-
-
-Generating HTML for Python.org
-==============================
-
-Python.org includes its own helper modules to render PEPs as HTML, with
-suitable links back to the source pages in the version control repository.
-
-These can be found `in the python.org repository
-`__.
-
-When making changes to the PEP management process that may impact python.org's
-rendering pipeline:
-
-* Clone the `python.org repository `_.
-* Get `set up for local python.org development
- `_.
-* Adjust ``PEP_REPO_PATH`` in ``pydotorg/settings/local.py`` to refer to your
- local clone of the PEP repository.
-* Run ``./manage.py generate_pep_pages`` as described the `python.org docs
- `__.
-
-
-Rendering PEPs with Sphinx
-==========================
-
-There is a Sphinx-rendered version of the PEPs at https://python.github.io/peps/
-(updated on every push to ``main``).
-
-**Warning:** This version is not, and should not be taken to be, a canonical
-source for PEPs whilst it remains in preview (please `report any rendering bugs
-`_).
-The canonical source for PEPs remains https://www.python.org/dev/peps/
-
-
-Build PEPs with Sphinx locally
-------------------------------
-
-See the `build documentation <./docs/build.rst>`__ for full step by step
-instructions on how to install, build and view the rendered PEPs with Sphinx.
-
-In summary, after installing the dependencies (preferably in a virtual
-environment) with:
-
-.. code-block:: bash
-
- python -m pip install -r requirements.txt
-
-You can build the PEPs with sphinx by running, if your system has ``make``:
-
-.. code-block:: bash
-
- make sphinx
-
-Otherwise, execute the ``build.py`` script directly:
-
-.. code-block:: bash
-
- python build.py
-
-The output HTML can be found under the ``build`` directory.
-
-
-``build.py`` usage
-------------------
-
-For details on the command-line options to the ``build.py`` script, run:
-
-.. code-block:: bash
-
- python build.py --help
diff --git a/deploy.bash b/deploy.bash
deleted file mode 100755
index c4350fa6c..000000000
--- a/deploy.bash
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-set -ex
-make package
-pip install awscli
-aws s3 cp --acl public-read build/peps.tar.gz s3://pythondotorg-assets-staging/peps.tar.gz
-aws s3 cp --acl public-read build/peps.tar.gz s3://pythondotorg-assets/peps.tar.gz
diff --git a/docs/build.rst b/docs/build.rst
index 0d7aa144d..86756a71c 100644
--- a/docs/build.rst
+++ b/docs/build.rst
@@ -41,7 +41,7 @@ Render PEPs locally
.. code-block:: console
- (venv) $ make sphinx
+ (venv) $ make render
If you don't have access to ``make``, run:
diff --git a/docutils.conf b/docutils.conf
deleted file mode 100644
index e2f4c8cc1..000000000
--- a/docutils.conf
+++ /dev/null
@@ -1,21 +0,0 @@
-# Configuration file for Docutils.
-# See http://docutils.sf.net/docs/tools.html
-
-[general]
-# These entries are for the page footer:
-source-link: 1
-datestamp: %Y-%m-%d %H:%M UTC
-generator: 1
-
-# use the local stylesheet
-stylesheet: pep.css
-template: pyramid-pep-template
-
-# link to the stylesheet; don't embed it
-embed-stylesheet: 0
-
-# path to PEPs, for template:
-pep-home: /dev/peps/
-
-# base URL for PEP references (no host so mirrors work):
-pep-base-url: /dev/peps/
diff --git a/generate_rss.py b/generate_rss.py
index b57fac7cc..2322fbd43 100755
--- a/generate_rss.py
+++ b/generate_rss.py
@@ -102,7 +102,7 @@ def main():
joined_authors = ", ".join(f"{name} ({email_address})" for name, email_address in parsed_authors)
else:
joined_authors = author
- url = f"https://www.python.org/dev/peps/pep-{pep_num:0>4}"
+ url = f"https://peps.python.org/pep-{pep_num:0>4}"
item = entry.FeedEntry()
item.title(f"PEP {pep_num}: {title}")
@@ -128,8 +128,8 @@ def main():
# Add metadata
fg.title("Newest Python PEPs")
- fg.link(href="https://www.python.org/dev/peps")
- fg.link(href="https://www.python.org/dev/peps/peps.rss", rel="self")
+ fg.link(href="https://peps.python.org")
+ fg.link(href="https://peps.python.org/peps.rss", rel="self")
fg.description(" ".join(desc.split()))
fg.lastBuildDate(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc))
diff --git a/genpepindex.py b/genpepindex.py
deleted file mode 100755
index 2ab6698a0..000000000
--- a/genpepindex.py
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env python
-"""Auto-generate PEP 0 (PEP index).
-
-Generating the PEP index is a multi-step process. To begin, you must first
-parse the PEP files themselves, which in and of itself takes a couple of steps:
-
- 1. Parse metadata.
- 2. Validate metadata.
-
-With the PEP information collected, to create the index itself you must:
-
- 1. Output static text.
- 2. Format an entry for the PEP.
- 3. Output the PEP (both by category and numerical index).
-
-"""
-from __future__ import absolute_import, with_statement
-from __future__ import print_function
-
-import sys
-import os
-import codecs
-
-from operator import attrgetter
-
-from pep0.output import write_pep0
-from pep0.pep import PEP, PEPError
-
-
-def main(argv):
- if not argv[1:]:
- path = '.'
- else:
- path = argv[1]
-
- peps = []
- if os.path.isdir(path):
- for file_path in os.listdir(path):
- if file_path.startswith('pep-0000.'):
- continue
- abs_file_path = os.path.join(path, file_path)
- if not os.path.isfile(abs_file_path):
- continue
- if file_path.startswith("pep-") and file_path.endswith((".txt", "rst")):
- with codecs.open(abs_file_path, 'r', encoding='UTF-8') as pep_file:
- try:
- pep = PEP(pep_file)
- if pep.number != int(file_path[4:-4]):
- raise PEPError('PEP number does not match file name',
- file_path, pep.number)
- peps.append(pep)
- except PEPError as e:
- errmsg = "Error processing PEP %s (%s), excluding:" % \
- (e.number, e.filename)
- print(errmsg, e, file=sys.stderr)
- sys.exit(1)
- peps.sort(key=attrgetter('number'))
- elif os.path.isfile(path):
- with open(path, 'r') as pep_file:
- peps.append(PEP(pep_file))
- else:
- raise ValueError("argument must be a directory or file path")
-
- with codecs.open('pep-0000.rst', 'w', encoding='UTF-8') as pep0_file:
- write_pep0(peps, pep0_file)
-
-if __name__ == "__main__":
- main(sys.argv)
diff --git a/pep-0001.txt b/pep-0001.txt
index 074c1cc6d..586a2b559 100644
--- a/pep-0001.txt
+++ b/pep-0001.txt
@@ -574,8 +574,9 @@ read, but also results in good-looking and functional HTML. :pep:`12`
contains instructions and a :pep:`template <12#suggested-sections>`
for reStructuredText PEPs.
-The PEP text files are automatically converted to HTML [2]_ for easier
-`online reading `__.
+The PEP text files are automatically
+`converted to HTML `__
+for easier `online reading `__.
PEP Header Preamble
@@ -792,9 +793,9 @@ Once the PEP is ready for the repository, a PEP editor will:
* Add the PEP to a local fork of the `PEP repository`_. For workflow
instructions, follow `The Python Developers Guide `_
-* Run ``./genpepindex.py`` and ``./pep2html.py `` to ensure they
- are generated without errors. If either triggers errors, then the web site
- will not be updated to reflect the PEP changes.
+* Run ``./build.py`` to ensure the PEPs are generated without errors. If the
+ rendering triggers errors, then the web site will not be updated to reflect
+ the PEP changes.
* Commit and push the new (or updated) PEP
@@ -818,7 +819,7 @@ administrative & editorial part (which is generally a low volume task).
Resources:
-* `Index of Python Enhancement Proposals `_
+* `Index of Python Enhancement Proposals `_
* `Following Python's Development
`_
@@ -833,10 +834,6 @@ Footnotes
for retrieving older revisions, and can also be browsed
`on GitHub `__.
-.. [2] More details on the PEP rendering and publication process can be found
- in the `PEPs repo README
- `__.
-
.. _.github/CODEOWNERS: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
.. _issue tracker: https://bugs.python.org/
diff --git a/pep-0009.txt b/pep-0009.txt
index 4a9f3d668..2431524a2 100644
--- a/pep-0009.txt
+++ b/pep-0009.txt
@@ -197,12 +197,12 @@ Resolution: https://mail.python.org/mailman/private/peps/2016-January/001165.htm
References
[7] PEP 1, PEP Purpose and Guidelines, Warsaw, Hylton
- http://www.python.org/dev/peps/pep-0001/
+ http://peps.python.org/pep-0001/
If you decide to provide an explicit URL for a PEP, please use
this as the URL template:
- http://www.python.org/dev/peps/pep-xxxx/
+ http://peps.python.org/pep-xxxx/
PEP numbers in URLs must be padded with zeros from the left, so as
to be exactly 4 characters wide, however PEP numbers in the text
@@ -212,10 +212,10 @@ Resolution: https://mail.python.org/mailman/private/peps/2016-January/001165.htm
References
[1] PEP 1, PEP Purpose and Guidelines, Warsaw, Hylton
- http://www.python.org/dev/peps/pep-0001/
+ http://peps.python.org/pep-0001/
[2] PEP 12, Sample reStructuredText PEP Template, Goodger, Warsaw
- http://www.python.org/dev/peps/pep-0012/
+ http://peps.python.org/pep-0012/
[3] http://www.opencontent.org/openpub/
diff --git a/pep-0287.txt b/pep-0287.txt
index 7bdf6e5d0..3d6ddfd36 100644
--- a/pep-0287.txt
+++ b/pep-0287.txt
@@ -564,7 +564,7 @@ Questions & Answers
[1] http://www.example.org/
[2] PEP 9876, Let's Hope We Never Get Here
- http://www.python.org/dev/peps/pep-9876/
+ http://peps.python.org/pep-9876/
[3] "Bogus Complexity Addition"
diff --git a/pep-0465/scan-ops.py b/pep-0465/scan-ops.py
index ac99037cb..052cf63bf 100755
--- a/pep-0465/scan-ops.py
+++ b/pep-0465/scan-ops.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# https://www.python.org/dev/peps/pep-0465/
+# https://peps.python.org/pep-0465/
# https://gist.github.com/njsmith/9157645
# usage:
diff --git a/pep-0676.rst b/pep-0676.rst
index 49b968098..5d532948e 100644
--- a/pep-0676.rst
+++ b/pep-0676.rst
@@ -4,7 +4,7 @@ Author: Adam Turner
Sponsor: Mariatta
PEP-Delegate: Barry Warsaw
Discussions-To: https://discuss.python.org/t/10774
-Status: Accepted
+Status: Active
Type: Process
Content-Type: text/x-rst
Created: 01-Nov-2021
diff --git a/pep-8001.rst b/pep-8001.rst
index 64c667bf7..3bf398aa7 100644
--- a/pep-8001.rst
+++ b/pep-8001.rst
@@ -106,7 +106,7 @@ Description of the poll::
This is the vote to choose how the CPython project will govern
itself, now that Guido has announced his retirement as BDFL. For
full details, see PEP
+ href="https://peps.python.org/pep-8001/">PEP
8001. Many discussions have occurred under the "governance"
tag on discuss.python.org.
@@ -156,13 +156,13 @@ Description of the poll::
Candidates (note: linebreaks are significant here)::
- PEP 8010: The Technical Leader Governance Model (Warsaw) (changelog)
- PEP 8011: Python Governance Model Lead by Trio of Pythonistas (Mariatta, Warsaw) (changelog)
- PEP 8012: The Community Governance Model (Langa) (changelog)
- PEP 8013: The External Council Governance Model (Dower) (changelog)
- PEP 8014: The Commons Governance Model (Jansen) (changelog)
- PEP 8015: Organization of the Python community (Stinner) (changelog)
- PEP 8016: The Steering Council Model (Smith, Stufft) (changelog)
+ PEP 8010: The Technical Leader Governance Model (Warsaw) (changelog)
+ PEP 8011: Python Governance Model Lead by Trio of Pythonistas (Mariatta, Warsaw) (changelog)
+ PEP 8012: The Community Governance Model (Langa) (changelog)
+ PEP 8013: The External Council Governance Model (Dower) (changelog)
+ PEP 8014: The Commons Governance Model (Jansen) (changelog)
+ PEP 8015: Organization of the Python community (Stinner) (changelog)
+ PEP 8016: The Steering Council Model (Smith, Stufft) (changelog)
Further discussion
Options::
diff --git a/pep.css b/pep.css
deleted file mode 100644
index d75dff1d8..000000000
--- a/pep.css
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
-:Author: David Goodger
-:Contact: goodger@python.org
-:date: $Date$
-:version: $Revision$
-:copyright: This stylesheet has been placed in the public domain.
-
-Default cascading style sheet for the PEP HTML output of Docutils.
-*/
-
-/* "! important" is used here to override other ``margin-top`` and
- ``margin-bottom`` styles that are later in the stylesheet or
- more specific. See http://www.w3.org/TR/CSS1#the-cascade */
-.first {
- margin-top: 0 ! important }
-
-.last, .with-subtitle {
- margin-bottom: 0 ! important }
-
-.hidden {
- display: none }
-
-.navigation {
- width: 100% ;
- background: #99ccff ;
- margin-top: 0px ;
- margin-bottom: 0px }
-
-.navigation .navicon {
- width: 150px ;
- height: 35px }
-
-.navigation .textlinks {
- padding-left: 1em ;
- text-align: left }
-
-.navigation td, .navigation th {
- padding-left: 0em ;
- padding-right: 0em ;
- vertical-align: middle }
-
-.rfc2822 {
- margin-top: 0.5em ;
- margin-left: 0.5em ;
- margin-right: 0.5em ;
- margin-bottom: 0em }
-
-.rfc2822 td {
- text-align: left }
-
-.rfc2822 th.field-name {
- text-align: right ;
- font-family: sans-serif ;
- padding-right: 0.5em ;
- font-weight: bold ;
- margin-bottom: 0em }
-
-a.toc-backref {
- text-decoration: none ;
- color: black }
-
-blockquote.epigraph {
- margin: 2em 5em ; }
-
-body {
- margin: 0px ;
- margin-bottom: 1em ;
- padding: 0px }
-
-dl.docutils dd {
- margin-bottom: 0.5em }
-
-div.section {
- margin-left: 1em ;
- margin-right: 1em ;
- margin-bottom: 1.5em }
-
-div.section div.section {
- margin-left: 0em ;
- margin-right: 0em ;
- margin-top: 1.5em }
-
-div.abstract {
- margin: 2em 5em }
-
-div.abstract p.topic-title {
- font-weight: bold ;
- text-align: center }
-
-div.admonition, div.attention, div.caution, div.danger, div.error,
-div.hint, div.important, div.note, div.tip, div.warning {
- margin: 2em ;
- border: medium outset ;
- padding: 1em }
-
-div.admonition p.admonition-title, div.hint p.admonition-title,
-div.important p.admonition-title, div.note p.admonition-title,
-div.tip p.admonition-title {
- font-weight: bold ;
- font-family: sans-serif }
-
-div.attention p.admonition-title, div.caution p.admonition-title,
-div.danger p.admonition-title, div.error p.admonition-title,
-div.warning p.admonition-title {
- color: red ;
- font-weight: bold ;
- font-family: sans-serif }
-
-/* Uncomment (and remove this text!) to get reduced vertical space in
- compound paragraphs.
-div.compound .compound-first, div.compound .compound-middle {
- margin-bottom: 0.5em }
-
-div.compound .compound-last, div.compound .compound-middle {
- margin-top: 0.5em }
-*/
-
-div.dedication {
- margin: 2em 5em ;
- text-align: center ;
- font-style: italic }
-
-div.dedication p.topic-title {
- font-weight: bold ;
- font-style: normal }
-
-div.figure {
- margin-left: 2em ;
- margin-right: 2em }
-
-div.footer, div.header {
- clear: both;
- font-size: smaller }
-
-div.footer {
- margin-left: 1em ;
- margin-right: 1em }
-
-div.line-block {
- display: block ;
- margin-top: 1em ;
- margin-bottom: 1em }
-
-div.line-block div.line-block {
- margin-top: 0 ;
- margin-bottom: 0 ;
- margin-left: 1.5em }
-
-div.sidebar {
- margin-left: 1em ;
- border: medium outset ;
- padding: 1em ;
- background-color: #ffffee ;
- width: 40% ;
- float: right ;
- clear: right }
-
-div.sidebar p.rubric {
- font-family: sans-serif ;
- font-size: medium }
-
-div.system-messages {
- margin: 5em }
-
-div.system-messages h1 {
- color: red }
-
-div.system-message {
- border: medium outset ;
- padding: 1em }
-
-div.system-message p.system-message-title {
- color: red ;
- font-weight: bold }
-
-div.topic {
- margin: 2em }
-
-h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
-h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
- margin-top: 0.4em }
-
-h1 {
- font-family: sans-serif ;
- font-size: large }
-
-h2 {
- font-family: sans-serif ;
- font-size: medium }
-
-h3 {
- font-family: sans-serif ;
- font-size: small }
-
-h4 {
- font-family: sans-serif ;
- font-style: italic ;
- font-size: small }
-
-h5 {
- font-family: sans-serif;
- font-size: x-small }
-
-h6 {
- font-family: sans-serif;
- font-style: italic ;
- font-size: x-small }
-
-hr.docutils {
- width: 75% }
-
-img.align-left {
- clear: left }
-
-img.align-right {
- clear: right }
-
-img.borderless {
- border: 0 }
-
-ol.simple, ul.simple {
- margin-bottom: 1em }
-
-ol.arabic {
- list-style: decimal }
-
-ol.loweralpha {
- list-style: lower-alpha }
-
-ol.upperalpha {
- list-style: upper-alpha }
-
-ol.lowerroman {
- list-style: lower-roman }
-
-ol.upperroman {
- list-style: upper-roman }
-
-p.attribution {
- text-align: right ;
- margin-left: 50% }
-
-p.caption {
- font-style: italic }
-
-p.credits {
- font-style: italic ;
- font-size: smaller }
-
-p.label {
- white-space: nowrap }
-
-p.rubric {
- font-weight: bold ;
- font-size: larger ;
- color: maroon ;
- text-align: center }
-
-p.sidebar-title {
- font-family: sans-serif ;
- font-weight: bold ;
- font-size: larger }
-
-p.sidebar-subtitle {
- font-family: sans-serif ;
- font-weight: bold }
-
-p.topic-title {
- font-family: sans-serif ;
- font-weight: bold }
-
-pre.address {
- margin-bottom: 0 ;
- margin-top: 0 ;
- font-family: serif ;
- font-size: 100% }
-
-pre.literal-block, pre.doctest-block {
- margin-left: 2em ;
- margin-right: 2em }
-
-span.classifier {
- font-family: sans-serif ;
- font-style: oblique }
-
-span.classifier-delimiter {
- font-family: sans-serif ;
- font-weight: bold }
-
-span.interpreted {
- font-family: sans-serif }
-
-span.option {
- white-space: nowrap }
-
-span.option-argument {
- font-style: italic }
-
-span.pre {
- white-space: pre }
-
-span.problematic {
- color: red }
-
-span.section-subtitle {
- /* font-size relative to parent (h1..h6 element) */
- font-size: 80% }
-
-table.citation {
- border-left: solid 1px gray;
- margin-left: 1px }
-
-table.docinfo {
- margin: 2em 4em }
-
-table.docutils {
- margin-top: 0.5em ;
- margin-bottom: 0.5em }
-
-table.footnote {
- border-left: solid 1px black;
- margin-left: 1px }
-
-table.docutils td, table.docutils th,
-table.docinfo td, table.docinfo th {
- padding-left: 0.5em ;
- padding-right: 0.5em ;
- vertical-align: top }
-
-td.num {
- text-align: right }
-
-th.field-name {
- font-weight: bold ;
- text-align: left ;
- white-space: nowrap ;
- padding-left: 0 }
-
-h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
-h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
- font-size: 100% }
-
-ul.auto-toc {
- list-style-type: none }
diff --git a/pep0/__init__.py b/pep0/__init__.py
deleted file mode 100644
index b7db25411..000000000
--- a/pep0/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Empty
diff --git a/pep0/constants.py b/pep0/constants.py
deleted file mode 100644
index 3f48dd488..000000000
--- a/pep0/constants.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-text_type = str
-title_length = 55
-author_length = 40
-table_separator = "== ==== " + "="*title_length + " " + "="*author_length
-column_format = (
- '%(type)1s%(status)1s %(number)4s %(title)-{title_length}s %(authors)-s'
-).format(title_length=title_length)
-
-header = """\
-PEP: 0
-Title: Index of Python Enhancement Proposals (PEPs)
-Version: N/A
-Last-Modified: %s
-Author: python-dev
-Status: Active
-Type: Informational
-Content-Type: text/x-rst
-Created: 13-Jul-2000
-"""
-
-intro = """\
-This PEP contains the index of all Python Enhancement Proposals,
-known as PEPs. PEP numbers are :pep:`assigned <1#pep-editors>` by the
-PEP editors, and once assigned are never changed.
-The `version control history`_ of the PEP texts represent
-their historical record.
-"""
-
-references = """\
-.. _version control history: https://github.com/python/peps
-"""
-
-footer = """\
-..
- Local Variables:
- mode: indented-text
- indent-tabs-mode: nil
- sentence-end-double-space: t
- fill-column: 70
- coding: utf-8
- End:\
-"""
diff --git a/pep0/output.py b/pep0/output.py
deleted file mode 100644
index 34e69bf6c..000000000
--- a/pep0/output.py
+++ /dev/null
@@ -1,288 +0,0 @@
-"""Code to handle the output of PEP 0."""
-from __future__ import absolute_import
-from __future__ import print_function
-import datetime
-import sys
-import unicodedata
-
-from operator import attrgetter
-
-from . import constants
-from .pep import PEP, PEPError
-
-# This is a list of reserved PEP numbers. Reservations are not to be used for
-# the normal PEP number allocation process - just give out the next available
-# PEP number. These are for "special" numbers that may be used for semantic,
-# humorous, or other such reasons, e.g. 401, 666, 754.
-#
-# PEP numbers may only be reserved with the approval of a PEP editor. Fields
-# here are the PEP number being reserved and the claimants for the PEP.
-# Although the output is sorted when PEP 0 is generated, please keep this list
-# sorted as well.
-RESERVED = [
- (801, 'Warsaw'),
- ]
-
-
-indent = u' '
-
-def emit_column_headers(output):
- """Output the column headers for the PEP indices."""
- column_headers = {'status': '.', 'type': '.', 'number': 'PEP',
- 'title': 'PEP Title', 'authors': 'PEP Author(s)'}
- print(constants.table_separator, file=output)
- print(constants.column_format % column_headers, file=output)
- print(constants.table_separator, file=output)
-
-
-def sort_peps(peps):
- """Sort PEPs into meta, informational, accepted, open, finished,
- and essentially dead."""
- meta = []
- info = []
- provisional = []
- accepted = []
- open_ = []
- finished = []
- historical = []
- deferred = []
- dead = []
- for pep in peps:
- # Order of 'if' statement important. Key Status values take precedence
- # over Type value, and vice-versa.
- if pep.status == 'Draft':
- open_.append(pep)
- elif pep.status == 'Deferred':
- deferred.append(pep)
- elif pep.type_ == 'Process':
- if pep.status == "Active":
- meta.append(pep)
- elif pep.status in ("Withdrawn", "Rejected"):
- dead.append(pep)
- else:
- historical.append(pep)
- elif pep.status in ('Rejected', 'Withdrawn',
- 'Incomplete', 'Superseded'):
- dead.append(pep)
- elif pep.type_ == 'Informational':
- # Hack until the conflict between the use of "Final"
- # for both API definition PEPs and other (actually
- # obsolete) PEPs is addressed
- if (pep.status == "Active" or
- "Release Schedule" not in pep.title):
- info.append(pep)
- else:
- historical.append(pep)
- elif pep.status == 'Provisional':
- provisional.append(pep)
- elif pep.status in ('Accepted', 'Active'):
- accepted.append(pep)
- elif pep.status == 'Final':
- finished.append(pep)
- else:
- raise PEPError("unsorted (%s/%s)" %
- (pep.type_, pep.status),
- pep.filename, pep.number)
- return (meta, info, provisional, accepted, open_,
- finished, historical, deferred, dead)
-
-
-def verify_email_addresses(peps):
- authors_dict = {}
- for pep in peps:
- for author in pep.authors:
- # If this is the first time we have come across an author, add them.
- if author not in authors_dict:
- authors_dict[author] = [author.email]
- else:
- found_emails = authors_dict[author]
- # If no email exists for the author, use the new value.
- if not found_emails[0]:
- authors_dict[author] = [author.email]
- # If the new email is an empty string, move on.
- elif not author.email:
- continue
- # If the email has not been seen, add it to the list.
- elif author.email not in found_emails:
- authors_dict[author].append(author.email)
-
- valid_authors_dict = {}
- too_many_emails = []
- for author, emails in authors_dict.items():
- if len(emails) > 1:
- too_many_emails.append((author.first_last, emails))
- else:
- valid_authors_dict[author] = emails[0]
- if too_many_emails:
- err_output = []
- for author, emails in too_many_emails:
- err_output.append(" %s: %r" % (author, emails))
- raise ValueError("some authors have more than one email address "
- "listed:\n" + '\n'.join(err_output))
-
- return valid_authors_dict
-
-
-def sort_authors(authors_dict):
- authors_list = list(authors_dict.keys())
- authors_list.sort(key=attrgetter('sort_by'))
- return authors_list
-
-def normalized_last_first(name):
- return len(unicodedata.normalize('NFC', name.last_first))
-
-def emit_title(text, anchor, output, *, symbol="="):
- print(".. _{anchor}:\n".format(anchor=anchor), file=output)
- print(text, file=output)
- print(symbol*len(text), file=output)
- print(file=output)
-
-def emit_subtitle(text, anchor, output):
- emit_title(text, anchor, output, symbol="-")
-
-def emit_pep_category(output, category, anchor, peps):
- emit_subtitle(category, anchor, output)
- emit_column_headers(output)
- for pep in peps:
- print(pep, file=output)
- print(constants.table_separator, file=output)
- print(file=output)
-
-def write_pep0(peps, output=sys.stdout):
- # PEP metadata
- today = datetime.date.today().strftime("%Y-%m-%d")
- print(constants.header % today, file=output)
- print(file=output)
- # Introduction
- emit_title("Introduction", "intro", output)
- print(constants.intro, file=output)
- print(file=output)
- # PEPs by category
- (meta, info, provisional, accepted, open_,
- finished, historical, deferred, dead) = sort_peps(peps)
- emit_title("Index by Category", "by-category", output)
- emit_pep_category(
- category="Meta-PEPs (PEPs about PEPs or Processes)",
- anchor="by-category-meta",
- peps=meta,
- output=output,
- )
- emit_pep_category(
- category="Other Informational PEPs",
- anchor="by-category-other-info",
- peps=info,
- output=output,
- )
- emit_pep_category(
- category="Provisional PEPs (provisionally accepted; interface may still change)",
- anchor="by-category-provisional",
- peps=provisional,
- output=output,
- )
- emit_pep_category(
- category="Accepted PEPs (accepted; may not be implemented yet)",
- anchor="by-category-accepted",
- peps=accepted,
- output=output,
- )
- emit_pep_category(
- category="Open PEPs (under consideration)",
- anchor="by-category-open",
- peps=open_,
- output=output,
- )
- emit_pep_category(
- category="Finished PEPs (done, with a stable interface)",
- anchor="by-category-finished",
- peps=finished,
- output=output,
- )
- emit_pep_category(
- category="Historical Meta-PEPs and Informational PEPs",
- anchor="by-category-historical",
- peps=historical,
- output=output,
- )
- emit_pep_category(
- category="Deferred PEPs (postponed pending further research or updates)",
- anchor="by-category-deferred",
- peps=deferred,
- output=output,
- )
- emit_pep_category(
- category="Abandoned, Withdrawn, and Rejected PEPs",
- anchor="by-category-abandoned",
- peps=dead,
- output=output,
- )
- print(file=output)
- # PEPs by number
- emit_title("Numerical Index", "by-pep-number", output)
- emit_column_headers(output)
- prev_pep = 0
- for pep in peps:
- if pep.number - prev_pep > 1:
- print(file=output)
- print(constants.text_type(pep), file=output)
- prev_pep = pep.number
- print(constants.table_separator, file=output)
- print(file=output)
- # Reserved PEP numbers
- emit_title('Reserved PEP Numbers', "reserved", output)
- emit_column_headers(output)
- for number, claimants in sorted(RESERVED):
- print(constants.column_format % {
- 'type': '.',
- 'status': '.',
- 'number': number,
- 'title': 'RESERVED',
- 'authors': claimants,
- }, file=output)
- print(constants.table_separator, file=output)
- print(file=output)
- # PEP types key
- emit_title("PEP Types Key", "type-key", output)
- for type_ in sorted(PEP.type_values):
- print(u" %s - %s PEP" % (type_[0], type_), file=output)
- print(file=output)
- print(file=output)
- # PEP status key
- emit_title("PEP Status Key", "status-key", output)
- for status in sorted(PEP.status_values):
- # Draft PEPs have no status displayed, Active shares a key with Accepted
- if status in ("Active", "Draft"):
- continue
- if status == "Accepted":
- msg = " A - Accepted (Standards Track only) or Active proposal"
- else:
- msg = " {status[0]} - {status} proposal".format(status=status)
- print(msg, file=output)
- print(file=output)
-
- print(file=output)
- # PEP owners
- emit_title("Authors/Owners", "authors", output)
- authors_dict = verify_email_addresses(peps)
- max_name = max(authors_dict.keys(), key=normalized_last_first)
- max_name_len = len(max_name.last_first)
- author_table_separator = "="*max_name_len + " " + "="*len("email address")
- print(author_table_separator, file=output)
- _author_header_fmt = "{name:{max_name_len}} Email Address"
- print(_author_header_fmt.format(name="Name", max_name_len=max_name_len), file=output)
- print(author_table_separator, file=output)
- sorted_authors = sort_authors(authors_dict)
- _author_fmt = "{author.last_first:{max_name_len}} {author_email}"
- for author in sorted_authors:
- # Use the email from authors_dict instead of the one from 'author' as
- # the author instance may have an empty email.
- _entry = _author_fmt.format(
- author=author,
- author_email=authors_dict[author],
- max_name_len=max_name_len,
- )
- print(_entry, file=output)
- print(author_table_separator, file=output)
- print(file=output)
- print(file=output)
- print(constants.references, file=output)
- print(constants.footer, file=output)
diff --git a/pep0/pep.py b/pep0/pep.py
deleted file mode 100644
index 1a4d83213..000000000
--- a/pep0/pep.py
+++ /dev/null
@@ -1,316 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Code for handling object representation of a PEP."""
-from __future__ import absolute_import
-import re
-import sys
-import textwrap
-import unicodedata
-
-from email.parser import HeaderParser
-
-from . import constants
-
-
-class PEPError(Exception):
-
- def __init__(self, error, pep_file, pep_number=None):
- super(PEPError, self).__init__(error)
- self.filename = pep_file
- self.number = pep_number
-
- def __str__(self):
- error_msg = super(PEPError, self).__str__()
- if self.number is not None:
- return "PEP %d: %r" % (self.number, error_msg)
- else:
- return "(%s): %r" % (self.filename, error_msg)
-
-
-class PEPParseError(PEPError):
-
- pass
-
-
-class Author(object):
-
- """Represent PEP authors.
-
- Attributes:
-
- + first_last : str
- The author's full name.
-
- + last_first : str
- Output the author's name in Last, First, Suffix order.
-
- + first : str
- The author's first name. A middle initial may be included.
-
- + last : str
- The author's last name.
-
- + suffix : str
- A person's suffix (can be the empty string).
-
- + sort_by : str
- Modification of the author's last name that should be used for
- sorting.
-
- + email : str
- The author's email address.
- """
-
- def __init__(self, author_and_email_tuple):
- """Parse the name and email address of an author."""
- name, email = author_and_email_tuple
- self.first_last = name.strip()
- self.email = email.lower()
- last_name_fragment, suffix = self._last_name(name)
- name_sep = name.index(last_name_fragment)
- self.first = name[:name_sep].rstrip()
- self.last = last_name_fragment
- if self.last[1] == u'.':
- # Add an escape to avoid docutils turning `v.` into `22.`.
- self.last = u'\\' + self.last
- self.suffix = suffix
- if not self.first:
- self.last_first = self.last
- else:
- self.last_first = u', '.join([self.last, self.first])
- if self.suffix:
- self.last_first += u', ' + self.suffix
- if self.last == "van Rossum":
- # Special case for our beloved BDFL. :)
- if self.first == "Guido":
- self.nick = "GvR"
- elif self.first == "Just":
- self.nick = "JvR"
- else:
- raise ValueError("unknown van Rossum %r!" % self)
- self.last_first += " (%s)" % (self.nick,)
- else:
- self.nick = self.last
-
- def __hash__(self):
- return hash(self.first_last)
-
- def __eq__(self, other):
- return self.first_last == other.first_last
-
- @property
- def sort_by(self):
- name_parts = self.last.split()
- for index, part in enumerate(name_parts):
- if part[0].isupper():
- base = u' '.join(name_parts[index:]).lower()
- break
- else:
- # If no capitals, use the whole string
- base = self.last.lower()
- return unicodedata.normalize('NFKD', base).encode('ASCII', 'ignore')
-
- def _last_name(self, full_name):
- """Find the last name (or nickname) of a full name.
-
- If no last name (e.g, 'Aahz') then return the full name. If there is
- a leading, lowercase portion to the last name (e.g., 'van' or 'von')
- then include it. If there is a suffix (e.g., 'Jr.') that is appended
- through a comma, then drop the suffix.
-
- """
- name_partition = full_name.partition(u',')
- no_suffix = name_partition[0].strip()
- suffix = name_partition[2].strip()
- name_parts = no_suffix.split()
- part_count = len(name_parts)
- if part_count == 1 or part_count == 2:
- return name_parts[-1], suffix
- else:
- assert part_count > 2
- if name_parts[-2].islower():
- return u' '.join(name_parts[-2:]), suffix
- else:
- return name_parts[-1], suffix
-
-
-class PEP(object):
-
- """Representation of PEPs.
-
- Attributes:
-
- + number : int
- PEP number.
-
- + title : str
- PEP title.
-
- + type_ : str
- The type of PEP. Can only be one of the values from
- PEP.type_values.
-
- + status : str
- The PEP's status. Value must be found in PEP.status_values.
-
- + authors : Sequence(Author)
- A list of the authors.
- """
-
- # The various RFC 822 headers that are supported.
- # The second item in the nested tuples represents if the header is
- # required or not.
- headers = (('PEP', True), ('Title', True), ('Version', False),
- ('Last-Modified', False), ('Author', True),
- ('Sponsor', False), ('BDFL-Delegate', False),
- ('PEP-Delegate', False),
- ('Discussions-To', False), ('Status', True), ('Type', True),
- ('Content-Type', False), ('Requires', False),
- ('Created', True), ('Python-Version', False),
- ('Post-History', False), ('Replaces', False),
- ('Superseded-By', False), ('Resolution', False),
- )
- # Valid values for the Type header.
- type_values = (u"Standards Track", u"Informational", u"Process")
- # Valid values for the Status header.
- # Active PEPs can only be for Informational or Process PEPs.
- status_values = (u"Accepted", u"Provisional",
- u"Rejected", u"Withdrawn", u"Deferred",
- u"Final", u"Active", u"Draft", u"Superseded")
-
- def __init__(self, pep_file):
- """Init object from an open PEP file object."""
- # Parse the headers.
- self.filename = pep_file
- pep_parser = HeaderParser()
- metadata = pep_parser.parse(pep_file)
- header_order = iter(self.headers)
- try:
- for header_name in metadata.keys():
- current_header, required = next(header_order)
- while header_name != current_header and not required:
- current_header, required = next(header_order)
- if header_name != current_header:
- raise PEPError("did not deal with "
- "%r before having to handle %r" %
- (header_name, current_header),
- pep_file.name)
- except StopIteration:
- raise PEPError("headers missing or out of order",
- pep_file.name)
- required = False
- try:
- while not required:
- current_header, required = next(header_order)
- else:
- raise PEPError("PEP is missing its %r" % (current_header,),
- pep_file.name)
- except StopIteration:
- pass
- # 'PEP'.
- try:
- self.number = int(metadata['PEP'])
- except ValueError:
- raise PEPParseError("PEP number isn't an integer", pep_file.name)
- # 'Title'.
- self.title = metadata['Title']
- # 'Type'.
- type_ = metadata['Type']
- if type_ not in self.type_values:
- raise PEPError('%r is not a valid Type value' % (type_,),
- pep_file.name, self.number)
- self.type_ = type_
- # 'Status'.
- status = metadata['Status']
- if status not in self.status_values:
- if status == "April Fool!":
- # See PEP 401 :)
- status = "Rejected"
- else:
- raise PEPError("%r is not a valid Status value" %
- (status,), pep_file.name, self.number)
- # Special case for Active PEPs.
- if (status == u"Active" and
- self.type_ not in ("Process", "Informational")):
- raise PEPError("Only Process and Informational PEPs may "
- "have an Active status", pep_file.name,
- self.number)
- # Special case for Provisional PEPs.
- if (status == u"Provisional" and self.type_ != "Standards Track"):
- raise PEPError("Only Standards Track PEPs may "
- "have a Provisional status", pep_file.name,
- self.number)
- self.status = status
- # 'Author'.
- authors_and_emails = self._parse_author(metadata['Author'])
- if len(authors_and_emails) < 1:
- raise PEPError("no authors found", pep_file.name,
- self.number)
- self.authors = list(map(Author, authors_and_emails))
-
- def _parse_author(self, data):
- """Return a list of author names and emails."""
- # XXX Consider using email.utils.parseaddr (doesn't work with names
- # lacking an email address.
- angled = constants.text_type(r'(?P.+?) <(?P.+?)>')
- paren = constants.text_type(r'(?P.+?) \((?P.+?)\)')
- simple = constants.text_type(r'(?P[^,]+)')
- author_list = []
- for regex in (angled, paren, simple):
- # Watch out for commas separating multiple names.
- regex += r'(,\s*)?'
- for match in re.finditer(regex, data):
- # Watch out for suffixes like 'Jr.' when they are comma-separated
- # from the name and thus cause issues when *all* names are only
- # separated by commas.
- match_dict = match.groupdict()
- author = match_dict['author']
- if not author.partition(' ')[1] and author.endswith('.'):
- prev_author = author_list.pop()
- author = ', '.join([prev_author, author])
- if u'email' not in match_dict:
- email = ''
- else:
- email = match_dict['email']
- author_list.append((author, email))
- else:
- # If authors were found then stop searching as only expect one
- # style of author citation.
- if author_list:
- break
- return author_list
-
- @property
- def type_abbr(self):
- """Return the how the type is to be represented in the index."""
- return self.type_[0].upper()
-
- @property
- def status_abbr(self):
- """Return how the status should be represented in the index."""
- if self.status in ('Draft', 'Active'):
- return u' '
- else:
- return self.status[0].upper()
-
- @property
- def author_abbr(self):
- """Return the author list as a comma-separated with only last names."""
- return u', '.join(x.nick for x in self.authors)
-
- @property
- def title_abbr(self):
- """Shorten the title to be no longer than the max title length."""
- if len(self.title) <= constants.title_length:
- return self.title
- wrapped_title = textwrap.wrap(self.title, constants.title_length - 4)
- return wrapped_title[0] + u' ...'
-
- def __unicode__(self):
- """Return the line entry for the PEP."""
- pep_info = {'type': self.type_abbr, 'number': str(self.number),
- 'title': self.title_abbr, 'status': self.status_abbr,
- 'authors': self.author_abbr}
- return constants.column_format % pep_info
-
- if sys.version_info[0] > 2:
- __str__ = __unicode__
diff --git a/pep2html.py b/pep2html.py
deleted file mode 100755
index ba0ec100b..000000000
--- a/pep2html.py
+++ /dev/null
@@ -1,796 +0,0 @@
-#!/usr/bin/env python3.9
-"""Convert PEPs to (X)HTML - courtesy of /F
-
-Usage: %(PROGRAM)s [options] [ ...]
-
-Options:
-
--u, --user
- python.org username
-
--b, --browse
- After generating the HTML, direct your web browser to view it
- (using the Python webbrowser module). If both -i and -b are
- given, this will browse the on-line HTML; otherwise it will
- browse the local HTML. If no pep arguments are given, this
- will browse PEP 0.
-
--i, --install
- After generating the HTML, install it and the plaintext source file
- (.txt) on python.org. In that case the user's name is used in the scp
- and ssh commands, unless "-u username" is given (in which case, it is
- used instead). Without -i, -u is ignored.
-
--l, --local
- Same as -i/--install, except install on the local machine. Use this
- when logged in to the python.org machine (dinsdale).
-
--q, --quiet
- Turn off verbose messages.
-
--h, --help
- Print this help message and exit.
-
-The optional arguments ``peps`` are either pep numbers, .rst or .txt files.
-"""
-
-from __future__ import print_function, unicode_literals
-
-import sys
-import os
-import re
-import glob
-import getopt
-import errno
-import random
-import time
-from io import open
-from pathlib import Path
-try:
- from html import escape
-except ImportError:
- from cgi import escape
-
-from docutils import core, nodes, utils
-from docutils.readers import standalone
-from docutils.transforms import frontmatter, peps, Transform
-from docutils.parsers import rst
-from docutils.parsers.rst import roles
-
-class DataError(Exception):
- pass
-
-REQUIRES = {'python': '2.6',
- 'docutils': '0.2.7'}
-PROGRAM = sys.argv[0]
-RFCURL = 'http://www.faqs.org/rfcs/rfc%d.html'
-PEPURL = 'pep-%04d.html'
-PEPCVSURL = ('https://hg.python.org/peps/file/tip/pep-%04d.txt')
-PEPDIRRUL = 'http://www.python.org/peps/'
-
-
-HOST = "dinsdale.python.org" # host for update
-HDIR = "/data/ftp.python.org/pub/www.python.org/peps" # target host directory
-LOCALVARS = "Local Variables:"
-
-COMMENT = """"""
-
-# The generated HTML doesn't validate -- you cannot use and
inside
-#
tags. But if I change that, the result doesn't look very nice...
-DTD = ('')
-
-fixpat = re.compile(r"((https?|ftp):[-_a-zA-Z0-9/.+~:?#$=&,]+)|(pep-\d+(.txt|.rst)?)|"
- r"(RFC[- ]?(?P\d+))|"
- r"(PEP\s+(?P\d+))|"
- r".")
-
-EMPTYSTRING = ''
-SPACE = ' '
-COMMASPACE = ', '
-
-
-
-def usage(code, msg=''):
- """Print usage message and exit. Uses stderr if code != 0."""
- if code == 0:
- out = sys.stdout
- else:
- out = sys.stderr
- print(__doc__ % globals(), file=out)
- if msg:
- print(msg, file=out)
- sys.exit(code)
-
-
-
-def fixanchor(current, match):
- text = match.group(0)
- link = None
- if (text.startswith('http:') or text.startswith('https:')
- or text.startswith('ftp:')):
- # Strip off trailing punctuation. Pattern taken from faqwiz.
- ltext = list(text)
- while ltext:
- c = ltext.pop()
- if c not in '''();:,.?'"<>''':
- ltext.append(c)
- break
- link = EMPTYSTRING.join(ltext)
- elif text.startswith('pep-') and text != current:
- link = os.path.splitext(text)[0] + ".html"
- elif text.startswith('PEP'):
- pepnum = int(match.group('pepnum'))
- link = PEPURL % pepnum
- elif text.startswith('RFC'):
- rfcnum = int(match.group('rfcnum'))
- link = RFCURL % rfcnum
- if link:
- return '%s' % (escape(link), escape(text))
- return escape(match.group(0)) # really slow, but it works...
-
-
-
-NON_MASKED_EMAILS = [
- 'peps@python.org',
- 'python-list@python.org',
- 'python-dev@python.org',
- ]
-
-def fixemail(address, pepno):
- if address.lower() in NON_MASKED_EMAILS:
- # return hyperlinked version of email address
- return linkemail(address, pepno)
- else:
- # return masked version of email address
- parts = address.split('@', 1)
- return '%s at %s' % (parts[0], parts[1])
-
-
-def linkemail(address, pepno):
- parts = address.split('@', 1)
- return (''
- '%s at %s'
- % (parts[0], parts[1], pepno, parts[0], parts[1]))
-
-
-def fixfile(inpath, input_lines, outfile):
- try:
- from email.Utils import parseaddr
- except ImportError:
- from email.utils import parseaddr
- basename = os.path.basename(inpath)
- infile = iter(input_lines)
- # convert plaintext pep to minimal XHTML markup
- print(DTD, file=outfile)
- print('', file=outfile)
- print(COMMENT, file=outfile)
- print('', file=outfile)
- # head
- header = []
- pep = ""
- title = ""
- for line in infile:
- if not line.strip():
- break
- if line[0].strip():
- if ":" not in line:
- break
- key, value = line.split(":", 1)
- value = value.strip()
- header.append((key, value))
- else:
- # continuation line
- key, value = header[-1]
- value = value + line
- header[-1] = key, value
- if key.lower() == "title":
- title = value
- elif key.lower() == "pep":
- pep = value
- if pep:
- title = "PEP " + pep + " -- " + title
- if title:
- print(' %s' % escape(title), file=outfile)
- r = random.choice(list(range(64)))
- print((
- ' \n'
- '\n'
- '\n'
- '
', file=outfile)
- need_pre = 1
- for line in infile:
- if line[0] == '\f':
- continue
- if line.strip() == LOCALVARS:
- break
- if line[0].strip():
- if not need_pre:
- print('', file=outfile)
- print('
%s
' % line.strip(), file=outfile)
- need_pre = 1
- elif not line.strip() and need_pre:
- continue
- else:
- # PEP 0 has some special treatment
- if basename == 'pep-0000.txt':
- parts = line.split()
- if len(parts) > 1 and re.match(r'\s*\d{1,4}', parts[1]):
- # This is a PEP summary line, which we need to hyperlink
- url = PEPURL % int(parts[1])
- if need_pre:
- print('
', file=outfile)
- need_pre = 0
- print(re.sub(
- parts[1],
- '%s' % (url, parts[1]),
- line, 1), end='', file=outfile)
- continue
- elif parts and '@' in parts[-1]:
- # This is a pep email address line, so filter it.
- url = fixemail(parts[-1], pep)
- if need_pre:
- print('