The PEP Rendering System is dead; long live the PEP Rendering System (#2399)

This commit is contained in:
Adam Turner 2022-03-10 08:27:31 +00:00 committed by GitHub
parent 08e37f7c23
commit 4bdabc6000
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 66 additions and 2835 deletions

6
.github/CODEOWNERS vendored
View File

@ -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

View File

@ -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 }}

View File

@ -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

View File

@ -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 <https://www.python.org/dev/peps/pep-0001/>`_.
start reading at `PEP 1 <https://peps.python.org/pep-0001/>`_.
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
<https://www.python.org/dev/peps/pep-0001/#start-with-an-idea-for-python>`_
<https://peps.python.org/pep-0001/#start-with-an-idea-for-python>`_
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.

View File

@ -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

View File

@ -1,456 +0,0 @@
"""PyRSS2Gen - A Python library for generating RSS 2.0 feeds."""
__name__ = "PyRSS2Gen"
__version__ = (1, 1, 0)
__author__ = "Andrew Dalke <dalke@dalkescientific.com>"
_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

View File

@ -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
<https://www.python.org/dev/peps/pep-0001/>`_.
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 <https://www.python.org/dev/peps/pep-0012/>`_.
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
<https://github.com/python/pythondotorg/tree/main/peps>`__.
When making changes to the PEP management process that may impact python.org's
rendering pipeline:
* Clone the `python.org repository <https://github.com/python/pythondotorg/>`_.
* Get `set up for local python.org development
<https://pythondotorg.readthedocs.io/install.html#manual-setup>`_.
* 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
<https://pythondotorg.readthedocs.io/pep_generation.html>`__.
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
<https://github.com/python/peps/issues/new>`_).
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

View File

@ -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

View File

@ -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:

View File

@ -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/

View File

@ -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))

View File

@ -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)

View File

@ -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 <https://www.python.org/dev/peps/>`__.
The PEP text files are automatically
`converted to HTML <https://peps.python.org/docs/rendering_system/>`__
for easier `online reading <https://peps.python.org/>`__.
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 <https://devguide.python.org/>`_
* Run ``./genpepindex.py`` and ``./pep2html.py <PEP Number>`` 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 <https://www.python.org/dev/peps/>`_
* `Index of Python Enhancement Proposals <https://peps.python.org/>`_
* `Following Python's Development
<https://devguide.python.org/communication/>`_
@ -833,10 +834,6 @@ Footnotes
for retrieving older revisions, and can also be browsed
`on GitHub <https://github.com/python/peps>`__.
.. [2] More details on the PEP rendering and publication process can be found
in the `PEPs repo README
<https://github.com/python/peps/blob/main/README.rst>`__.
.. _.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/

View File

@ -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/

View File

@ -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"

View File

@ -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:

View File

@ -4,7 +4,7 @@ Author: Adam Turner <python@quite.org.uk>
Sponsor: Mariatta <mariatta@python.org>
PEP-Delegate: Barry Warsaw <barry@python.org>
Discussions-To: https://discuss.python.org/t/10774
Status: Accepted
Status: Active
Type: Process
Content-Type: text/x-rst
Created: 01-Nov-2021

View File

@ -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 <a
href="https://www.python.org/dev/peps/pep-8001/">PEP
href="https://peps.python.org/pep-8001/">PEP
8001</a>. Many discussions have occurred under <a
href="https://discuss.python.org/tags/governance">the "governance"
tag</a> on discuss.python.org.
@ -156,13 +156,13 @@ Description of the poll::
Candidates (note: linebreaks are significant here)::
<a href="https://www.python.org/dev/peps/pep-8010/">PEP 8010: The Technical Leader Governance Model</a> (Warsaw) (<a href="https://github.com/python/peps/commits/main/pep-8010.rst">changelog</a>)
<a href="https://www.python.org/dev/peps/pep-8011/">PEP 8011: Python Governance Model Lead by Trio of Pythonistas</a> (Mariatta, Warsaw) (<a href="https://github.com/python/peps/commits/main/pep-8011.rst">changelog</a>)
<a href="https://www.python.org/dev/peps/pep-8012/">PEP 8012: The Community Governance Model</a> (Langa) (<a href="https://github.com/python/peps/commits/main/pep-8012.rst">changelog</a>)
<a href="https://www.python.org/dev/peps/pep-8013/">PEP 8013: The External Council Governance Model</a> (Dower) (<a href="https://github.com/python/peps/commits/main/pep-8013.rst">changelog</a>)
<a href="https://www.python.org/dev/peps/pep-8014/">PEP 8014: The Commons Governance Model</a> (Jansen) (<a href="https://github.com/python/peps/commits/main/pep-8014.rst">changelog</a>)
<a href="https://www.python.org/dev/peps/pep-8015/">PEP 8015: Organization of the Python community</a> (Stinner) (<a href="https://github.com/python/peps/commits/main/pep-8015.rst">changelog</a>)
<a href="https://www.python.org/dev/peps/pep-8016/">PEP 8016: The Steering Council Model</a> (Smith, Stufft) (<a href="https://github.com/python/peps/commits/main/pep-8016.rst">changelog</a>)
<a href="https://peps.python.org/pep-8010/">PEP 8010: The Technical Leader Governance Model</a> (Warsaw) (<a href="https://github.com/python/peps/commits/main/pep-8010.rst">changelog</a>)
<a href="https://peps.python.org/pep-8011/">PEP 8011: Python Governance Model Lead by Trio of Pythonistas</a> (Mariatta, Warsaw) (<a href="https://github.com/python/peps/commits/main/pep-8011.rst">changelog</a>)
<a href="https://peps.python.org/pep-8012/">PEP 8012: The Community Governance Model</a> (Langa) (<a href="https://github.com/python/peps/commits/main/pep-8012.rst">changelog</a>)
<a href="https://peps.python.org/pep-8013/">PEP 8013: The External Council Governance Model</a> (Dower) (<a href="https://github.com/python/peps/commits/main/pep-8013.rst">changelog</a>)
<a href="https://peps.python.org/pep-8014/">PEP 8014: The Commons Governance Model</a> (Jansen) (<a href="https://github.com/python/peps/commits/main/pep-8014.rst">changelog</a>)
<a href="https://peps.python.org/pep-8015/">PEP 8015: Organization of the Python community</a> (Stinner) (<a href="https://github.com/python/peps/commits/main/pep-8015.rst">changelog</a>)
<a href="https://peps.python.org/pep-8016/">PEP 8016: The Steering Council Model</a> (Smith, Stufft) (<a href="https://github.com/python/peps/commits/main/pep-8016.rst">changelog</a>)
Further discussion
Options::

344
pep.css
View File

@ -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 }

View File

@ -1 +0,0 @@
# Empty

View File

@ -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 <python-dev@python.org>
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:\
"""

View File

@ -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)

View File

@ -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<author>.+?) <(?P<email>.+?)>')
paren = constants.text_type(r'(?P<email>.+?) \((?P<author>.+?)\)')
simple = constants.text_type(r'(?P<author>[^,]+)')
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__

View File

@ -1,796 +0,0 @@
#!/usr/bin/env python3.9
"""Convert PEPs to (X)HTML - courtesy of /F
Usage: %(PROGRAM)s [options] [<peps> ...]
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 = """<!--
This HTML is auto-generated. DO NOT EDIT THIS FILE! If you are writing a new
PEP, see http://www.python.org/peps/pep-0001.html for instructions and links
to templates. DO NOT USE THIS HTML FILE AS YOUR TEMPLATE!
-->"""
# The generated HTML doesn't validate -- you cannot use <hr> and <h3> inside
# <pre> tags. But if I change that, the result doesn't look very nice...
DTD = ('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"\n'
' "http://www.w3.org/TR/REC-html40/loose.dtd">')
fixpat = re.compile(r"((https?|ftp):[-_a-zA-Z0-9/.+~:?#$=&,]+)|(pep-\d+(.txt|.rst)?)|"
r"(RFC[- ]?(?P<rfcnum>\d+))|"
r"(PEP\s+(?P<pepnum>\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 '<a href="%s">%s</a>' % (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&#32;&#97;t&#32;%s' % (parts[0], parts[1])
def linkemail(address, pepno):
parts = address.split('@', 1)
return ('<a href="mailto:%s&#64;%s?subject=PEP%%20%s">'
'%s&#32;&#97;t&#32;%s</a>'
% (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('<html>', file=outfile)
print(COMMENT, file=outfile)
print('<head>', 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(' <title>%s</title>' % escape(title), file=outfile)
r = random.choice(list(range(64)))
print((
' <link rel="STYLESHEET" href="style.css" type="text/css" />\n'
'</head>\n'
'<body bgcolor="white">\n'
'<table class="navigation" cellpadding="0" cellspacing="0"\n'
' width="100%%" border="0">\n'
'<tr><td class="navicon" width="150" height="35">\n'
'<a href="../" title="Python Home Page">\n'
'<img src="../pics/PyBanner%03d.gif" alt="[Python]"\n'
' border="0" width="150" height="35" /></a></td>\n'
'<td class="textlinks" align="left">\n'
'[<b><a href="../">Python Home</a></b>]' % r), file=outfile)
if basename != 'pep-0000.txt':
print('[<b><a href=".">PEP Index</a></b>]', file=outfile)
if pep:
try:
print(('[<b><a href="pep-%04d.txt">PEP Source</a>'
'</b>]' % int(pep)), file=outfile)
except ValueError as error:
print(('ValueError (invalid PEP number): %s'
% error), file=sys.stderr)
print('</td></tr></table>', file=outfile)
print('<div class="header">\n<table border="0">', file=outfile)
for k, v in header:
if k.lower() in ('author', 'pep-delegate', 'bdfl-delegate', 'discussions-to',
'sponsor'):
mailtos = []
for part in re.split(r',\s*', v):
if '@' in part:
realname, addr = parseaddr(part)
if k.lower() == 'discussions-to':
m = linkemail(addr, pep)
else:
m = fixemail(addr, pep)
mailtos.append('%s &lt;%s&gt;' % (realname, m))
elif part.startswith('http:'):
mailtos.append(
'<a href="%s">%s</a>' % (part, part))
else:
mailtos.append(part)
v = COMMASPACE.join(mailtos)
elif k.lower() in ('replaces', 'superseded-by', 'requires'):
otherpeps = ''
for otherpep in re.split(r',?\s+', v):
otherpep = int(otherpep)
otherpeps += '<a href="pep-%04d.html">%i</a> ' % (otherpep,
otherpep)
v = otherpeps
elif k.lower() in ('last-modified',):
date = v or time.strftime('%d-%b-%Y',
time.localtime(os.stat(inpath)[8]))
if date.startswith('$' 'Date: ') and date.endswith(' $'):
date = date[6:-2]
if basename == 'pep-0000.txt':
v = date
else:
try:
url = PEPCVSURL % int(pep)
v = '<a href="%s">%s</a> ' % (url, escape(date))
except ValueError as error:
v = date
elif k.lower() in ('content-type',):
url = PEPURL % 9
pep_type = v or 'text/x-rst'
v = '<a href="%s">%s</a> ' % (url, escape(pep_type))
elif k.lower() == 'version':
if v.startswith('$' 'Revision: ') and v.endswith(' $'):
v = escape(v[11:-2])
else:
v = escape(v)
print(' <tr><th>%s:&nbsp;</th><td>%s</td></tr>' \
% (escape(k), v), file=outfile)
print('</table>', file=outfile)
print('</div>', file=outfile)
print('<hr />', file=outfile)
print('<div class="content">', 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('</pre>', file=outfile)
print('<h3>%s</h3>' % 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('<pre>', file=outfile)
need_pre = 0
print(re.sub(
parts[1],
'<a href="%s">%s</a>' % (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('<pre>', file=outfile)
need_pre = 0
print(re.sub(
parts[-1], url, line, 1), end='', file=outfile)
continue
line = fixpat.sub(lambda x, c=inpath: fixanchor(c, x), line)
if need_pre:
print('<pre>', file=outfile)
need_pre = 0
outfile.write(line)
if not need_pre:
print('</pre>', file=outfile)
print('</div>', file=outfile)
print('</body>', file=outfile)
print('</html>', file=outfile)
EXPLICIT_TITLE_RE = re.compile(r'^(.+?)\s*(?<!\x00)<(.*?)>$', re.DOTALL)
def _pep_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
matched = EXPLICIT_TITLE_RE.match(text)
if matched:
title = utils.unescape(matched.group(1))
target = utils.unescape(matched.group(2))
else:
target = utils.unescape(text)
title = "PEP " + utils.unescape(text)
pep_str, _, fragment = target.partition("#")
try:
pepnum = int(pep_str)
if pepnum < 0 or pepnum > 9999:
raise ValueError
except ValueError:
msg = inliner.reporter.error(
'PEP number must be a number from 0 to 9999; "%s" is invalid.'
% pep_str, line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
# Base URL mainly used by inliner.pep_reference; so this is correct:
ref = (inliner.document.settings.pep_base_url
+ inliner.document.settings.pep_file_url_template % pepnum)
if fragment:
ref += "#" + fragment
roles.set_classes(options)
return [nodes.reference(rawtext, title, refuri=ref, **options)], []
def _rfc_reference_role(role, rawtext, text, lineno, inliner,
options={}, content=[]):
matched = EXPLICIT_TITLE_RE.match(text)
if matched:
title = utils.unescape(matched.group(1))
target = utils.unescape(matched.group(2))
else:
target = utils.unescape(text)
title = "RFC " + utils.unescape(text)
pep_str, _, fragment = target.partition("#")
try:
rfcnum = int(pep_str)
if rfcnum < 0 or rfcnum > 9999:
raise ValueError
except ValueError:
msg = inliner.reporter.error(
'RFC number must be a number from 0 to 9999; "%s" is invalid.'
% pep_str, line=lineno)
prb = inliner.problematic(rawtext, rawtext, msg)
return [prb], [msg]
# Base URL mainly used by inliner.pep_reference; so this is correct:
ref = (inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum)
if fragment:
ref += "#" + fragment
roles.set_classes(options)
return [nodes.reference(rawtext, title, refuri=ref, **options)], []
roles.register_canonical_role("pep-reference", _pep_reference_role)
roles.register_canonical_role("rfc-reference", _rfc_reference_role)
docutils_settings = None
"""Runtime settings object used by Docutils. Can be set by the client
application when this module is imported."""
class PEPHeaders(Transform):
"""
Process fields in a PEP's initial RFC-2822 header.
"""
default_priority = 360
pep_url = 'pep-%04d'
pep_cvs_url = PEPCVSURL
rcs_keyword_substitutions = (
(re.compile(r'\$' r'RCSfile: (.+),v \$$', re.IGNORECASE), r'\1'),
(re.compile(r'\$[a-zA-Z]+: (.+) \$$'), r'\1'),)
def apply(self):
if not len(self.document):
# @@@ replace these DataErrors with proper system messages
raise DataError('Document tree is empty.')
header = self.document[0]
if not isinstance(header, nodes.field_list) or \
'rfc2822' not in header['classes']:
raise DataError('Document does not begin with an RFC-2822 '
'header; it is not a PEP.')
pep = None
for field in header:
if field[0].astext().lower() == 'pep': # should be the first field
value = field[1].astext()
try:
pep = int(value)
cvs_url = self.pep_cvs_url % pep
except ValueError:
pep = value
cvs_url = None
msg = self.document.reporter.warning(
'"PEP" header must contain an integer; "%s" is an '
'invalid value.' % pep, base_node=field)
msgid = self.document.set_id(msg)
prb = nodes.problematic(value, value or '(none)',
refid=msgid)
prbid = self.document.set_id(prb)
msg.add_backref(prbid)
if len(field[1]):
field[1][0][:] = [prb]
else:
field[1] += nodes.paragraph('', '', prb)
break
if pep is None:
raise DataError('Document does not contain an RFC-2822 "PEP" '
'header.')
if pep == 0:
# Special processing for PEP 0.
pending = nodes.pending(peps.PEPZero)
self.document.insert(1, pending)
self.document.note_pending(pending)
if len(header) < 2 or header[1][0].astext().lower() != 'title':
raise DataError('No title!')
for field in header:
name = field[0].astext().lower()
body = field[1]
if len(body) > 1:
raise DataError('PEP header field body contains multiple '
'elements:\n%s' % field.pformat(level=1))
elif len(body) == 1:
if not isinstance(body[0], nodes.paragraph):
raise DataError('PEP header field body may only contain '
'a single paragraph:\n%s'
% field.pformat(level=1))
elif name == 'last-modified':
date = time.strftime(
'%d-%b-%Y',
time.localtime(os.stat(self.document['source'])[8]))
if cvs_url:
body += nodes.paragraph(
'', '', nodes.reference('', date, refuri=cvs_url))
else:
# empty
continue
para = body[0]
if name in ('author', 'bdfl-delegate', 'pep-delegate', 'sponsor'):
for node in para:
if isinstance(node, nodes.reference):
node.replace_self(peps.mask_email(node))
elif name == 'discussions-to':
for node in para:
if isinstance(node, nodes.reference):
node.replace_self(peps.mask_email(node, pep))
elif name in ('replaces', 'superseded-by', 'requires'):
newbody = []
space = nodes.Text(' ')
for refpep in re.split(r',?\s+', body.astext()):
pepno = int(refpep)
newbody.append(nodes.reference(
refpep, refpep,
refuri=(self.document.settings.pep_base_url
+ self.pep_url % pepno)))
newbody.append(space)
para[:] = newbody[:-1] # drop trailing space
elif name == 'last-modified':
utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
if cvs_url:
date = para.astext()
para[:] = [nodes.reference('', date, refuri=cvs_url)]
elif name == 'content-type':
pep_type = para.astext()
uri = self.document.settings.pep_base_url + self.pep_url % 12
para[:] = [nodes.reference('', pep_type, refuri=uri)]
elif name == 'version' and len(body):
utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
class PEPFooter(Transform):
"""Remove the References/Footnotes section if it is empty when rendered."""
# Set low priority so ref targets aren't removed before they are needed
default_priority = 999
def apply(self):
pep_source_path = Path(self.document['source'])
if not pep_source_path.match('pep-*'):
return # not a PEP file, exit early
# Iterate through sections from the end of the document
for section in reversed(self.document):
if not isinstance(section, nodes.section):
continue
title_words = {*section[0].astext().lower().split()}
if {"references", "footnotes"} & title_words:
# Remove references/footnotes sections if there is no displayed
# content (i.e. they only have title & link target nodes)
if all(isinstance(ref_node, (nodes.title, nodes.target))
for ref_node in section):
section.parent.remove(section)
class PEPReader(standalone.Reader):
supported = ('pep',)
"""Contexts this reader supports."""
settings_spec = (
'PEP Reader Option Defaults',
'The --pep-references and --rfc-references options (for the '
'reStructuredText parser) are on by default.',
())
config_section = 'pep reader'
config_section_dependencies = ('readers', 'standalone reader')
def get_transforms(self):
transforms = standalone.Reader.get_transforms(self)
# We have PEP-specific frontmatter handling.
transforms.remove(frontmatter.DocTitle)
transforms.remove(frontmatter.SectionSubTitle)
transforms.remove(frontmatter.DocInfo)
transforms.extend([PEPHeaders, peps.Contents, PEPFooter])
return transforms
settings_default_overrides = {'pep_references': 1, 'rfc_references': 1}
inliner_class = rst.states.Inliner
def __init__(self, parser=None, parser_name=None):
"""`parser` should be ``None``."""
if parser is None:
parser = rst.Parser(rfc2822=True, inliner=self.inliner_class())
standalone.Reader.__init__(self, parser, '')
def fix_rst_pep(inpath, input_lines, outfile):
output = core.publish_string(
source=''.join(input_lines),
source_path=inpath,
destination_path=outfile.name,
reader=PEPReader(),
parser_name='restructuredtext',
writer_name='pep_html',
settings=docutils_settings,
# Allow Docutils traceback if there's an exception:
settings_overrides={'traceback': 1, 'halt_level': 2})
outfile.write(output.decode('utf-8'))
def get_pep_type(input_lines):
"""
Return the Content-Type of the input. "text/x-rst" is the default.
Return ``None`` if the input is not a PEP.
"""
pep_type = None
for line in input_lines:
line = line.rstrip().lower()
if not line:
# End of the RFC 2822 header (first blank line).
break
elif line.startswith('content-type: '):
pep_type = line.split()[1] or 'text/x-rst'
break
elif line.startswith('pep: '):
# Default PEP type, used if no explicit content-type specified:
pep_type = 'text/x-rst'
return pep_type
def get_input_lines(inpath):
try:
infile = open(inpath, encoding='utf-8')
except IOError as e:
if e.errno != errno.ENOENT: raise
print('Error: Skipping missing PEP file:', e.filename, file=sys.stderr)
sys.stderr.flush()
return None
lines = infile.read().splitlines(1) # handles x-platform line endings
infile.close()
return lines
def find_pep(pep_str):
"""Find the .rst or .txt file indicated by a cmd line argument"""
if os.path.exists(pep_str):
return pep_str
num = int(pep_str)
rstpath = "pep-%04d.rst" % num
if os.path.exists(rstpath):
return rstpath
return "pep-%04d.txt" % num
def make_html(inpath, verbose=0):
input_lines = get_input_lines(inpath)
if input_lines is None:
return None
pep_type = get_pep_type(input_lines)
if pep_type is None:
print('Error: Input file %s is not a PEP.' % inpath, file=sys.stderr)
sys.stdout.flush()
return None
elif pep_type not in PEP_TYPE_DISPATCH:
print(('Error: Unknown PEP type for input file %s: %s'
% (inpath, pep_type)), file=sys.stderr)
sys.stdout.flush()
return None
elif PEP_TYPE_DISPATCH[pep_type] is None:
pep_type_error(inpath, pep_type)
return None
outpath = os.path.splitext(inpath)[0] + ".html"
if verbose:
print(inpath, "(%s)" % pep_type, "->", outpath)
sys.stdout.flush()
outfile = open(outpath, "w", encoding='utf-8')
PEP_TYPE_DISPATCH[pep_type](inpath, input_lines, outfile)
outfile.close()
os.chmod(outfile.name, 0o664)
return outpath
def push_pep(htmlfiles, txtfiles, username, verbose, local=0):
quiet = ""
if local:
if verbose:
quiet = "-v"
target = HDIR
copy_cmd = "cp"
chmod_cmd = "chmod"
else:
if not verbose:
quiet = "-q"
if username:
username = username + "@"
target = username + HOST + ":" + HDIR
copy_cmd = "scp"
chmod_cmd = "ssh %s%s chmod" % (username, HOST)
files = htmlfiles[:]
files.extend(txtfiles)
files.append("style.css")
files.append("pep.css")
filelist = SPACE.join(files)
rc = os.system("%s %s %s %s" % (copy_cmd, quiet, filelist, target))
if rc:
sys.exit(rc)
## rc = os.system("%s 664 %s/*" % (chmod_cmd, HDIR))
## if rc:
## sys.exit(rc)
PEP_TYPE_DISPATCH = {'text/plain': fixfile,
'text/x-rst': fix_rst_pep}
PEP_TYPE_MESSAGES = {}
def check_requirements():
# Check Python:
# This is pretty much covered by the __future__ imports...
if sys.version_info < (2, 6, 0):
PEP_TYPE_DISPATCH['text/plain'] = None
PEP_TYPE_MESSAGES['text/plain'] = (
'Python %s or better required for "%%(pep_type)s" PEP '
'processing; %s present (%%(inpath)s).'
% (REQUIRES['python'], sys.version.split()[0]))
# Check Docutils:
try:
import docutils
except ImportError:
PEP_TYPE_DISPATCH['text/x-rst'] = None
PEP_TYPE_MESSAGES['text/x-rst'] = (
'Docutils not present for "%(pep_type)s" PEP file %(inpath)s. '
'See README.rst for installation.')
else:
installed = [int(part) for part in docutils.__version__.split('.')]
required = [int(part) for part in REQUIRES['docutils'].split('.')]
if installed < required:
PEP_TYPE_DISPATCH['text/x-rst'] = None
PEP_TYPE_MESSAGES['text/x-rst'] = (
'Docutils must be reinstalled for "%%(pep_type)s" PEP '
'processing (%%(inpath)s). Version %s or better required; '
'%s present. See README.rst for installation.'
% (REQUIRES['docutils'], docutils.__version__))
def pep_type_error(inpath, pep_type):
print('Error: ' + PEP_TYPE_MESSAGES[pep_type] % locals(), file=sys.stderr)
sys.stdout.flush()
def browse_file(pep):
import webbrowser
file = find_pep(pep)
if file.startswith('pep-') and file.endswith((".txt", '.rst')):
file = file[:-3] + "html"
file = os.path.abspath(file)
url = "file:" + file
webbrowser.open(url)
def browse_remote(pep):
import webbrowser
file = find_pep(pep)
if file.startswith('pep-') and file.endswith((".txt", '.rst')):
file = file[:-3] + "html"
url = PEPDIRRUL + file
webbrowser.open(url)
def main(argv=None):
# defaults
update = 0
local = 0
username = ''
verbose = 1
browse = 0
check_requirements()
if argv is None:
argv = sys.argv[1:]
try:
opts, args = getopt.getopt(
argv, 'bilhqu:',
['browse', 'install', 'local', 'help', 'quiet', 'user='])
except getopt.error as msg:
usage(1, msg)
for opt, arg in opts:
if opt in ('-h', '--help'):
usage(0)
elif opt in ('-i', '--install'):
update = 1
elif opt in ('-l', '--local'):
update = 1
local = 1
elif opt in ('-u', '--user'):
username = arg
elif opt in ('-q', '--quiet'):
verbose = 0
elif opt in ('-b', '--browse'):
browse = 1
if args:
pep_list = []
html = []
for pep in args:
file = find_pep(pep)
pep_list.append(file)
newfile = make_html(file, verbose=verbose)
if newfile:
html.append(newfile)
if browse and not update:
browse_file(pep)
else:
# do them all
pep_list = []
html = []
files = glob.glob("pep-*.txt") + glob.glob("pep-*.rst")
files.sort()
for file in files:
pep_list.append(file)
newfile = make_html(file, verbose=verbose)
if newfile:
html.append(newfile)
if browse and not update:
browse_file("0")
if update:
push_pep(html, pep_list, username, verbose, local=local)
if browse:
if args:
for pep in args:
browse_remote(pep)
else:
browse_remote("0")
if __name__ == "__main__":
main()

View File

@ -1,126 +0,0 @@
#!/usr/bin/env python3
# usage: python3 pep2rss.py .
import datetime
import glob
import os
import re
import sys
import time
import PyRSS2Gen as rssgen
import docutils.frontend
import docutils.nodes
import docutils.parsers.rst
import docutils.utils
RSS_PATH = os.path.join(sys.argv[1], 'peps.rss')
def remove_prefix(text: str, prefix: str) -> str:
try:
# Python 3.9+
return text.removeprefix(prefix)
except AttributeError:
if text.startswith(prefix):
return text[len(prefix):]
return text
def parse_rst(text: str) -> docutils.nodes.document:
parser = docutils.parsers.rst.Parser()
components = (docutils.parsers.rst.Parser,)
settings = docutils.frontend.OptionParser(components=components).get_default_values()
document = docutils.utils.new_document('<rst-doc>', settings=settings)
parser.parse(text, document)
return document
def pep_abstract(full_path: str) -> str:
"""Return the first paragraph of the PEP abstract"""
abstract = None
with open(full_path, encoding="utf-8") as f:
text = f.read()
document = parse_rst(text)
nodes = list(document)
for node in nodes:
if "<title>Abstract</title>" in str(node):
for child in node:
if child.tagname == "paragraph":
abstract = child.astext()
# Just fetch the first paragraph
break
return abstract
def firstline_startingwith(full_path, text):
for line in open(full_path, encoding="utf-8"):
if line.startswith(text):
return line[len(text):].strip()
return None
# get list of peps with creation time
# (from "Created:" string in pep .rst or .txt)
peps = glob.glob('pep-*.txt')
peps.extend(glob.glob('pep-*.rst'))
def pep_creation_dt(full_path):
created_str = firstline_startingwith(full_path, 'Created:')
# bleh, I was hoping to avoid re but some PEPs editorialize
# on the Created line
m = re.search(r'''(\d+-\w+-\d{4})''', created_str)
if not m:
# some older ones have an empty line, that's okay, if it's old
# we ipso facto don't care about it.
# "return None" would make the most sense but datetime objects
# refuse to compare with that. :-|
return datetime.datetime(*time.localtime(0)[:6])
created_str = m.group(1)
try:
t = time.strptime(created_str, '%d-%b-%Y')
except ValueError:
t = time.strptime(created_str, '%d-%B-%Y')
return datetime.datetime(*t[:6])
peps_with_dt = [(pep_creation_dt(full_path), full_path) for full_path in peps]
# sort peps by date, newest first
peps_with_dt.sort(reverse=True)
# generate rss items for 10 most recent peps
items = []
for dt, full_path in peps_with_dt[:10]:
try:
n = int(full_path.split('-')[-1].split('.')[0])
except ValueError:
pass
title = firstline_startingwith(full_path, 'Title:')
author = firstline_startingwith(full_path, 'Author:')
abstract = pep_abstract(full_path)
url = 'https://www.python.org/dev/peps/pep-%0.4d/' % n
item = rssgen.RSSItem(
title='PEP %d: %s' % (n, title),
link=url,
description=abstract,
author=author,
guid=rssgen.Guid(url),
pubDate=dt)
items.append(item)
# the rss envelope
desc = """
Newest Python Enhancement Proposals (PEPs) - Information on new
language features, and some meta-information like release
procedure and schedules
""".strip()
rss = rssgen.RSS2(
title='Newest Python PEPs',
link = 'https://www.python.org/dev/peps/',
description=desc,
lastBuildDate=datetime.datetime.now(),
items=items)
with open(RSS_PATH, 'w', encoding="utf-8") as fp:
fp.write(rss.to_xml(encoding="utf-8"))

View File

@ -4,8 +4,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from docutils import nodes
from docutils.parsers.rst import states
from docutils.writers.html5_polyglot import HTMLTranslator
from sphinx import environment
@ -19,10 +17,6 @@ from pep_sphinx_extensions.pep_zero_generator.pep_index_generator import create_
if TYPE_CHECKING:
from sphinx.application import Sphinx
# Monkeypatch sphinx.environment.default_settings as Sphinx doesn't allow custom settings or Readers
# This disables reading configuration from docutils.conf so as not to affect pep2html.py
environment.default_settings["_disable_config"] = True
def _depart_maths():
pass # No-op callable for the type checker

View File

@ -1,6 +0,0 @@
<!--
This HTML is auto-generated. DO NOT EDIT THIS FILE! If you are writing a new
PEP, see http://www.python.org/dev/peps/pep-0001 for instructions and links
to templates. DO NOT USE THIS HTML FILE AS YOUR TEMPLATE!
-->
%(body)s

View File

@ -1,81 +0,0 @@
"""Convert to and from Roman numerals"""
__author__ = "Mark Pilgrim (f8dy@diveintopython.org)"
__version__ = "1.4"
__date__ = "8 August 2001"
__copyright__ = """Copyright (c) 2001 Mark Pilgrim
This program is part of "Dive Into Python", a free Python tutorial for
experienced programmers. Visit http://diveintopython.org/ for the
latest version.
This program is free software; you can redistribute it and/or modify
it under the terms of the Python 2.1.1 license, available at
http://www.python.org/2.1.1/license.html
"""
import re
# Define exceptions
class RomanError(Exception): pass
class OutOfRangeError(RomanError): pass
class NotIntegerError(RomanError): pass
class InvalidRomanNumeralError(RomanError): pass
#Define digit mapping
romanNumeralMap = (('M', 1000),
('CM', 900),
('D', 500),
('CD', 400),
('C', 100),
('XC', 90),
('L', 50),
('XL', 40),
('X', 10),
('IX', 9),
('V', 5),
('IV', 4),
('I', 1))
def toRoman(n):
"""convert integer to Roman numeral"""
if not (0 < n < 5000):
raise OutOfRangeError("number out of range (must be 1..4999)")
if int(n) != n:
raise NotIntegerError("decimals can not be converted")
result = ""
for numeral, integer in romanNumeralMap:
while n >= integer:
result += numeral
n -= integer
return result
#Define pattern to detect valid Roman numerals
romanNumeralPattern = re.compile("""
^ # beginning of string
M{0,4} # thousands - 0 to 4 M's
(CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
# or 500-800 (D, followed by 0 to 3 C's)
(XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
# or 50-80 (L, followed by 0 to 3 X's)
(IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
# or 5-8 (V, followed by 0 to 3 I's)
$ # end of string
""" ,re.VERBOSE)
def fromRoman(s):
"""convert Roman numeral to integer"""
if not s:
raise InvalidRomanNumeralError('Input can not be blank')
if not romanNumeralPattern.search(s):
raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s)
result = 0
index = 0
for numeral, integer in romanNumeralMap:
while s[index:index+len(numeral)] == numeral:
result += integer
index += len(numeral)
return result

View File

@ -1,19 +0,0 @@
body { margin: 0px;
padding: 0px; }
.navigation { width: 100%;
background: #99ccff; }
.navigation .navicon { width: 150px;
height: 35; }
.navigation .textlinks { padding-left: 1em;
text-align: left; }
.header { margin-top: 0.5em; }
.header, .content { margin-left: 1em;
margin-right: 1em; }
.header table td { text-align: left; }
.header table th { text-align: right;
font-family: sans-serif;
padding-right: 0.5em; }
h3 { font-family: sans-serif; }