The PEP Rendering System is dead; long live the PEP Rendering System (#2399)
This commit is contained in:
parent
08e37f7c23
commit
4bdabc6000
|
@ -10,12 +10,6 @@
|
||||||
Makefile @AA-Turner
|
Makefile @AA-Turner
|
||||||
requirements.txt @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
|
pep_sphinx_extensions/ @AA-Turner
|
||||||
AUTHOR_OVERRIDES.csv @AA-Turner
|
AUTHOR_OVERRIDES.csv @AA-Turner
|
||||||
build.py @AA-Turner
|
build.py @AA-Turner
|
||||||
|
|
|
@ -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 }}
|
|
|
@ -1,10 +1,10 @@
|
||||||
name: Sphinx Build
|
name: Render PEPs
|
||||||
|
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-to-pages:
|
render-peps:
|
||||||
name: Build & deploy to GitHub Pages
|
name: Render PEPs
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
@ -24,7 +24,7 @@ jobs:
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade -r requirements.txt
|
python -m pip install --upgrade -r requirements.txt
|
||||||
|
|
||||||
- name: 🔧 Build PEPs
|
- name: 🔧 Render PEPs
|
||||||
run: make pages -j$(nproc)
|
run: make pages -j$(nproc)
|
||||||
|
|
||||||
# remove the .doctrees folder when building for deployment as it takes two thirds of disk space
|
# remove the .doctrees folder when building for deployment as it takes two thirds of disk space
|
|
@ -2,7 +2,7 @@ Contributing Guidelines
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
To learn more about the purpose of PEPs and how to go about writing one, please
|
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
|
Also, make sure to check the `README <./README.rst>`_ for information
|
||||||
on how to render the PEPs in this repository.
|
on how to render the PEPs in this repository.
|
||||||
Thanks again for your contributions, and we look forward to reviewing them!
|
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
|
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
|
for some important steps to consider, including proposing and discussing it
|
||||||
first in an appropriate venue, drafting a PEP and gathering feedback, and
|
first in an appropriate venue, drafting a PEP and gathering feedback, and
|
||||||
developing at least a prototype reference implementation of your idea.
|
developing at least a prototype reference implementation of your idea.
|
||||||
|
|
69
Makefile
69
Makefile
|
@ -1,56 +1,32 @@
|
||||||
# Builds PEP files to HTML using docutils or sphinx
|
# Builds PEP files to HTML using sphinx
|
||||||
# Also contains testing targets
|
|
||||||
|
|
||||||
PEP2HTML=pep2html.py
|
|
||||||
|
|
||||||
PYTHON=python3
|
PYTHON=python3
|
||||||
|
|
||||||
VENV_DIR=venv
|
VENV_DIR=venv
|
||||||
|
JOBS=8
|
||||||
|
RENDER_COMMAND=$(PYTHON) build.py -j $(JOBS)
|
||||||
|
|
||||||
.SUFFIXES: .txt .html .rst
|
render:
|
||||||
|
$(RENDER_COMMAND)
|
||||||
|
|
||||||
.txt.html:
|
pages: rss
|
||||||
@$(PYTHON) $(PEP2HTML) $<
|
$(RENDER_COMMAND) --build-dirs
|
||||||
|
|
||||||
.rst.html:
|
fail-warning:
|
||||||
@$(PYTHON) $(PEP2HTML) $<
|
$(RENDER_COMMAND) --fail-on-warning
|
||||||
|
|
||||||
TARGETS= $(patsubst %.rst,%.html,$(wildcard pep-????.rst)) $(patsubst %.txt,%.html,$(wildcard pep-????.txt)) pep-0000.html
|
check-links:
|
||||||
|
$(RENDER_COMMAND) --check-links
|
||||||
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 .
|
|
||||||
|
|
||||||
rss:
|
rss:
|
||||||
$(PYTHON) pep2rss.py .
|
$(PYTHON) generate_rss.py
|
||||||
|
|
||||||
install:
|
|
||||||
echo "Installing is not necessary anymore. It will be done in post-commit."
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
-rm pep-0000.rst
|
|
||||||
-rm *.html
|
|
||||||
-rm -rf build
|
-rm -rf build
|
||||||
|
|
||||||
update:
|
|
||||||
git pull https://github.com/python/peps.git
|
|
||||||
|
|
||||||
venv:
|
venv:
|
||||||
$(PYTHON) -m venv $(VENV_DIR)
|
$(PYTHON) -m venv $(VENV_DIR)
|
||||||
./$(VENV_DIR)/bin/python -m pip install -r requirements.txt
|
./$(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:
|
lint:
|
||||||
pre-commit --version > /dev/null || $(PYTHON) -m pip install pre-commit
|
pre-commit --version > /dev/null || $(PYTHON) -m pip install pre-commit
|
||||||
pre-commit run --all-files
|
pre-commit run --all-files
|
||||||
|
@ -58,24 +34,3 @@ lint:
|
||||||
spellcheck:
|
spellcheck:
|
||||||
pre-commit --version > /dev/null || $(PYTHON) -m pip install pre-commit
|
pre-commit --version > /dev/null || $(PYTHON) -m pip install pre-commit
|
||||||
pre-commit run --all-files --hook-stage manual codespell
|
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
|
|
||||||
|
|
456
PyRSS2Gen.py
456
PyRSS2Gen.py
|
@ -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
|
|
143
README.rst
143
README.rst
|
@ -1,15 +1,13 @@
|
||||||
Python Enhancement Proposals
|
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
|
:target: https://github.com/python/peps/actions
|
||||||
|
|
||||||
The PEPs in this repo are published automatically on the web at
|
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
|
https://peps.python.org/. To learn more about the purpose of PEPs and how to go
|
||||||
PEPs and how to go about writing one, please start reading at `PEP 1
|
about writing one, please start reading at :pep:`1`. Note that the PEP Index
|
||||||
<https://www.python.org/dev/peps/pep-0001/>`_.
|
(:pep:`0`) is automatically generated based on the metadata headers in other PEPs.
|
||||||
Note that PEP 0, the index PEP, is
|
|
||||||
automatically generated and not committed to the repo.
|
|
||||||
|
|
||||||
|
|
||||||
Contributing to PEPs
|
Contributing to PEPs
|
||||||
|
@ -18,135 +16,38 @@ Contributing to PEPs
|
||||||
See the `Contributing Guidelines <./CONTRIBUTING.rst>`_.
|
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
|
Checking PEP formatting and rendering
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
Please don't commit changes with reStructuredText syntax errors that cause PEP
|
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
|
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)
|
intend.
|
||||||
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,
|
|
||||||
|
Render PEPs locally
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
See the `build documentation <./docs/build.rst>`__ for full
|
||||||
|
instructions on how to render PEPs locally. In summary:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. 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
|
The output HTML is found under the ``build`` directory.
|
||||||
file argument.
|
|
||||||
|
|
||||||
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,
|
PEP draughting aids
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
either on-demand or automatically as you commit, with our pre-commit suite.
|
||||||
See the `Contributing Guide <./CONTRIBUTING.rst>`_ for details.
|
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
|
|
||||||
|
|
|
@ -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
|
|
|
@ -41,7 +41,7 @@ Render PEPs locally
|
||||||
|
|
||||||
.. code-block:: console
|
.. code-block:: console
|
||||||
|
|
||||||
(venv) $ make sphinx
|
(venv) $ make render
|
||||||
|
|
||||||
If you don't have access to ``make``, run:
|
If you don't have access to ``make``, run:
|
||||||
|
|
||||||
|
|
|
@ -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/
|
|
|
@ -102,7 +102,7 @@ def main():
|
||||||
joined_authors = ", ".join(f"{name} ({email_address})" for name, email_address in parsed_authors)
|
joined_authors = ", ".join(f"{name} ({email_address})" for name, email_address in parsed_authors)
|
||||||
else:
|
else:
|
||||||
joined_authors = author
|
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 = entry.FeedEntry()
|
||||||
item.title(f"PEP {pep_num}: {title}")
|
item.title(f"PEP {pep_num}: {title}")
|
||||||
|
@ -128,8 +128,8 @@ def main():
|
||||||
|
|
||||||
# Add metadata
|
# Add metadata
|
||||||
fg.title("Newest Python PEPs")
|
fg.title("Newest Python PEPs")
|
||||||
fg.link(href="https://www.python.org/dev/peps")
|
fg.link(href="https://peps.python.org")
|
||||||
fg.link(href="https://www.python.org/dev/peps/peps.rss", rel="self")
|
fg.link(href="https://peps.python.org/peps.rss", rel="self")
|
||||||
fg.description(" ".join(desc.split()))
|
fg.description(" ".join(desc.split()))
|
||||||
fg.lastBuildDate(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc))
|
fg.lastBuildDate(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc))
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
17
pep-0001.txt
17
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>`
|
contains instructions and a :pep:`template <12#suggested-sections>`
|
||||||
for reStructuredText PEPs.
|
for reStructuredText PEPs.
|
||||||
|
|
||||||
The PEP text files are automatically converted to HTML [2]_ for easier
|
The PEP text files are automatically
|
||||||
`online reading <https://www.python.org/dev/peps/>`__.
|
`converted to HTML <https://peps.python.org/docs/rendering_system/>`__
|
||||||
|
for easier `online reading <https://peps.python.org/>`__.
|
||||||
|
|
||||||
|
|
||||||
PEP Header Preamble
|
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
|
* Add the PEP to a local fork of the `PEP repository`_. For workflow
|
||||||
instructions, follow `The Python Developers Guide <https://devguide.python.org/>`_
|
instructions, follow `The Python Developers Guide <https://devguide.python.org/>`_
|
||||||
|
|
||||||
* Run ``./genpepindex.py`` and ``./pep2html.py <PEP Number>`` to ensure they
|
* Run ``./build.py`` to ensure the PEPs are generated without errors. If the
|
||||||
are generated without errors. If either triggers errors, then the web site
|
rendering triggers errors, then the web site will not be updated to reflect
|
||||||
will not be updated to reflect the PEP changes.
|
the PEP changes.
|
||||||
|
|
||||||
* Commit and push the new (or updated) PEP
|
* Commit and push the new (or updated) PEP
|
||||||
|
|
||||||
|
@ -818,7 +819,7 @@ administrative & editorial part (which is generally a low volume task).
|
||||||
|
|
||||||
Resources:
|
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
|
* `Following Python's Development
|
||||||
<https://devguide.python.org/communication/>`_
|
<https://devguide.python.org/communication/>`_
|
||||||
|
@ -833,10 +834,6 @@ Footnotes
|
||||||
for retrieving older revisions, and can also be browsed
|
for retrieving older revisions, and can also be browsed
|
||||||
`on GitHub <https://github.com/python/peps>`__.
|
`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
|
.. _.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/
|
.. _issue tracker: https://bugs.python.org/
|
||||||
|
|
|
@ -197,12 +197,12 @@ Resolution: https://mail.python.org/mailman/private/peps/2016-January/001165.htm
|
||||||
References
|
References
|
||||||
|
|
||||||
[7] PEP 1, PEP Purpose and Guidelines, Warsaw, Hylton
|
[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
|
If you decide to provide an explicit URL for a PEP, please use
|
||||||
this as the URL template:
|
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
|
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
|
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
|
References
|
||||||
|
|
||||||
[1] PEP 1, PEP Purpose and Guidelines, Warsaw, Hylton
|
[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
|
[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/
|
[3] http://www.opencontent.org/openpub/
|
||||||
|
|
||||||
|
|
|
@ -564,7 +564,7 @@ Questions & Answers
|
||||||
[1] http://www.example.org/
|
[1] http://www.example.org/
|
||||||
|
|
||||||
[2] PEP 9876, Let's Hope We Never Get Here
|
[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"
|
[3] "Bogus Complexity Addition"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# https://www.python.org/dev/peps/pep-0465/
|
# https://peps.python.org/pep-0465/
|
||||||
# https://gist.github.com/njsmith/9157645
|
# https://gist.github.com/njsmith/9157645
|
||||||
|
|
||||||
# usage:
|
# usage:
|
||||||
|
|
|
@ -4,7 +4,7 @@ Author: Adam Turner <python@quite.org.uk>
|
||||||
Sponsor: Mariatta <mariatta@python.org>
|
Sponsor: Mariatta <mariatta@python.org>
|
||||||
PEP-Delegate: Barry Warsaw <barry@python.org>
|
PEP-Delegate: Barry Warsaw <barry@python.org>
|
||||||
Discussions-To: https://discuss.python.org/t/10774
|
Discussions-To: https://discuss.python.org/t/10774
|
||||||
Status: Accepted
|
Status: Active
|
||||||
Type: Process
|
Type: Process
|
||||||
Content-Type: text/x-rst
|
Content-Type: text/x-rst
|
||||||
Created: 01-Nov-2021
|
Created: 01-Nov-2021
|
||||||
|
|
16
pep-8001.rst
16
pep-8001.rst
|
@ -106,7 +106,7 @@ Description of the poll::
|
||||||
This is the vote to choose how the CPython project will govern
|
This is the vote to choose how the CPython project will govern
|
||||||
itself, now that Guido has announced his retirement as BDFL. For
|
itself, now that Guido has announced his retirement as BDFL. For
|
||||||
full details, see <a
|
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
|
8001</a>. Many discussions have occurred under <a
|
||||||
href="https://discuss.python.org/tags/governance">the "governance"
|
href="https://discuss.python.org/tags/governance">the "governance"
|
||||||
tag</a> on discuss.python.org.
|
tag</a> on discuss.python.org.
|
||||||
|
@ -156,13 +156,13 @@ Description of the poll::
|
||||||
|
|
||||||
Candidates (note: linebreaks are significant here)::
|
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://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://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://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://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://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://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://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://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://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://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://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://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-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
|
Further discussion
|
||||||
|
|
||||||
Options::
|
Options::
|
||||||
|
|
344
pep.css
344
pep.css
|
@ -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 }
|
|
|
@ -1 +0,0 @@
|
||||||
# Empty
|
|
|
@ -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:\
|
|
||||||
"""
|
|
288
pep0/output.py
288
pep0/output.py
|
@ -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)
|
|
316
pep0/pep.py
316
pep0/pep.py
|
@ -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__
|
|
796
pep2html.py
796
pep2html.py
|
@ -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 at %s' % (parts[0], parts[1])
|
|
||||||
|
|
||||||
|
|
||||||
def linkemail(address, pepno):
|
|
||||||
parts = address.split('@', 1)
|
|
||||||
return ('<a href="mailto:%s@%s?subject=PEP%%20%s">'
|
|
||||||
'%s at %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 <%s>' % (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: </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()
|
|
126
pep2rss.py
126
pep2rss.py
|
@ -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"))
|
|
|
@ -4,8 +4,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from docutils import nodes
|
|
||||||
from docutils.parsers.rst import states
|
|
||||||
from docutils.writers.html5_polyglot import HTMLTranslator
|
from docutils.writers.html5_polyglot import HTMLTranslator
|
||||||
from sphinx import environment
|
from sphinx import environment
|
||||||
|
|
||||||
|
@ -19,10 +17,6 @@ from pep_sphinx_extensions.pep_zero_generator.pep_index_generator import create_
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sphinx.application import Sphinx
|
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():
|
def _depart_maths():
|
||||||
pass # No-op callable for the type checker
|
pass # No-op callable for the type checker
|
||||||
|
|
|
@ -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
|
|
81
roman.py
81
roman.py
|
@ -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
|
|
||||||
|
|
19
style.css
19
style.css
|
@ -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; }
|
|
Loading…
Reference in New Issue