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
|
||||
requirements.txt @AA-Turner
|
||||
|
||||
pep0/ @AA-Turner
|
||||
docutils.conf @AA-Turner
|
||||
genpepindex.py @AA-Turner
|
||||
pep2html.py @AA-Turner
|
||||
pep2rss.py @AA-Turner
|
||||
|
||||
pep_sphinx_extensions/ @AA-Turner
|
||||
AUTHOR_OVERRIDES.csv @AA-Turner
|
||||
build.py @AA-Turner
|
||||
|
|
|
@ -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]
|
||||
|
||||
jobs:
|
||||
deploy-to-pages:
|
||||
name: Build & deploy to GitHub Pages
|
||||
render-peps:
|
||||
name: Render PEPs
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
@ -24,7 +24,7 @@ jobs:
|
|||
python -m pip install --upgrade pip
|
||||
python -m pip install --upgrade -r requirements.txt
|
||||
|
||||
- name: 🔧 Build PEPs
|
||||
- name: 🔧 Render PEPs
|
||||
run: make pages -j$(nproc)
|
||||
|
||||
# remove the .doctrees folder when building for deployment as it takes two thirds of disk space
|
|
@ -2,7 +2,7 @@ Contributing Guidelines
|
|||
=======================
|
||||
|
||||
To learn more about the purpose of PEPs and how to go about writing one, please
|
||||
start reading at `PEP 1 <https://www.python.org/dev/peps/pep-0001/>`_.
|
||||
start reading at `PEP 1 <https://peps.python.org/pep-0001/>`_.
|
||||
Also, make sure to check the `README <./README.rst>`_ for information
|
||||
on how to render the PEPs in this repository.
|
||||
Thanks again for your contributions, and we look forward to reviewing them!
|
||||
|
@ -12,7 +12,7 @@ Before writing a new PEP
|
|||
------------------------
|
||||
|
||||
Prior to submitting a pull request here with your draft PEP, see `PEP 1
|
||||
<https://www.python.org/dev/peps/pep-0001/#start-with-an-idea-for-python>`_
|
||||
<https://peps.python.org/pep-0001/#start-with-an-idea-for-python>`_
|
||||
for some important steps to consider, including proposing and discussing it
|
||||
first in an appropriate venue, drafting a PEP and gathering feedback, and
|
||||
developing at least a prototype reference implementation of your idea.
|
||||
|
|
69
Makefile
69
Makefile
|
@ -1,56 +1,32 @@
|
|||
# Builds PEP files to HTML using docutils or sphinx
|
||||
# Also contains testing targets
|
||||
|
||||
PEP2HTML=pep2html.py
|
||||
# Builds PEP files to HTML using sphinx
|
||||
|
||||
PYTHON=python3
|
||||
|
||||
VENV_DIR=venv
|
||||
JOBS=8
|
||||
RENDER_COMMAND=$(PYTHON) build.py -j $(JOBS)
|
||||
|
||||
.SUFFIXES: .txt .html .rst
|
||||
render:
|
||||
$(RENDER_COMMAND)
|
||||
|
||||
.txt.html:
|
||||
@$(PYTHON) $(PEP2HTML) $<
|
||||
pages: rss
|
||||
$(RENDER_COMMAND) --build-dirs
|
||||
|
||||
.rst.html:
|
||||
@$(PYTHON) $(PEP2HTML) $<
|
||||
fail-warning:
|
||||
$(RENDER_COMMAND) --fail-on-warning
|
||||
|
||||
TARGETS= $(patsubst %.rst,%.html,$(wildcard pep-????.rst)) $(patsubst %.txt,%.html,$(wildcard pep-????.txt)) pep-0000.html
|
||||
|
||||
all: pep-0000.rst $(TARGETS)
|
||||
|
||||
$(TARGETS): pep2html.py
|
||||
|
||||
pep-0000.rst: $(wildcard pep-????.txt) $(wildcard pep-????.rst) $(wildcard pep0/*.py) genpepindex.py
|
||||
$(PYTHON) genpepindex.py .
|
||||
check-links:
|
||||
$(RENDER_COMMAND) --check-links
|
||||
|
||||
rss:
|
||||
$(PYTHON) pep2rss.py .
|
||||
|
||||
install:
|
||||
echo "Installing is not necessary anymore. It will be done in post-commit."
|
||||
$(PYTHON) generate_rss.py
|
||||
|
||||
clean:
|
||||
-rm pep-0000.rst
|
||||
-rm *.html
|
||||
-rm -rf build
|
||||
|
||||
update:
|
||||
git pull https://github.com/python/peps.git
|
||||
|
||||
venv:
|
||||
$(PYTHON) -m venv $(VENV_DIR)
|
||||
./$(VENV_DIR)/bin/python -m pip install -r requirements.txt
|
||||
|
||||
package: all rss
|
||||
mkdir -p build/peps
|
||||
cp pep-*.txt build/peps/
|
||||
cp pep-*.rst build/peps/
|
||||
cp *.html build/peps/
|
||||
cp *.png build/peps/
|
||||
cp *.rss build/peps/
|
||||
tar -C build -czf build/peps.tar.gz peps
|
||||
|
||||
lint:
|
||||
pre-commit --version > /dev/null || $(PYTHON) -m pip install pre-commit
|
||||
pre-commit run --all-files
|
||||
|
@ -58,24 +34,3 @@ lint:
|
|||
spellcheck:
|
||||
pre-commit --version > /dev/null || $(PYTHON) -m pip install pre-commit
|
||||
pre-commit run --all-files --hook-stage manual codespell
|
||||
|
||||
# New Sphinx targets:
|
||||
|
||||
SPHINX_JOBS=8
|
||||
SPHINX_BUILD=$(PYTHON) build.py -j $(SPHINX_JOBS)
|
||||
|
||||
# TODO replace `rss:` with this when merged & tested
|
||||
pep_rss:
|
||||
$(PYTHON) generate_rss.py
|
||||
|
||||
pages: pep_rss
|
||||
$(SPHINX_BUILD) --build-dirs
|
||||
|
||||
sphinx:
|
||||
$(SPHINX_BUILD)
|
||||
|
||||
fail-warning:
|
||||
$(SPHINX_BUILD) --fail-on-warning
|
||||
|
||||
check-links:
|
||||
$(SPHINX_BUILD) --check-links
|
||||
|
|
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
|
||||
============================
|
||||
|
||||
.. image:: https://github.com/python/peps/actions/workflows/build.yml/badge.svg
|
||||
.. image:: https://github.com/python/peps/actions/workflows/render.yml/badge.svg
|
||||
:target: https://github.com/python/peps/actions
|
||||
|
||||
The PEPs in this repo are published automatically on the web at
|
||||
https://www.python.org/dev/peps/. To learn more about the purpose of
|
||||
PEPs and how to go about writing one, please start reading at `PEP 1
|
||||
<https://www.python.org/dev/peps/pep-0001/>`_.
|
||||
Note that PEP 0, the index PEP, is
|
||||
automatically generated and not committed to the repo.
|
||||
https://peps.python.org/. To learn more about the purpose of PEPs and how to go
|
||||
about writing one, please start reading at :pep:`1`. Note that the PEP Index
|
||||
(:pep:`0`) is automatically generated based on the metadata headers in other PEPs.
|
||||
|
||||
|
||||
Contributing to PEPs
|
||||
|
@ -18,135 +16,38 @@ Contributing to PEPs
|
|||
See the `Contributing Guidelines <./CONTRIBUTING.rst>`_.
|
||||
|
||||
|
||||
reStructuredText for PEPs
|
||||
=========================
|
||||
|
||||
PEP source text should be written in reStructuredText format,
|
||||
which is a constrained version of plaintext, and is described in
|
||||
`PEP 12 <https://www.python.org/dev/peps/pep-0012/>`_.
|
||||
The ``pep2html.py`` processing and installation script knows
|
||||
how to produce the HTML for the PEP format.
|
||||
|
||||
To render the PEPs, you'll first need to install the requirements,
|
||||
(preferably in a fresh virtual environment):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m pip install -r requirements.txt
|
||||
|
||||
|
||||
Generating the PEP Index
|
||||
========================
|
||||
|
||||
PEP 0 is automatically generated based on the metadata headers in other
|
||||
PEPs. The script handling this is ``genpepindex.py``, with supporting
|
||||
libraries in the ``pep0`` directory.
|
||||
|
||||
|
||||
Checking PEP formatting and rendering
|
||||
=====================================
|
||||
|
||||
Please don't commit changes with reStructuredText syntax errors that cause PEP
|
||||
generation to fail, or result in major rendering defects relative to what you
|
||||
intend. To check building the HTML output for your PEP (for example, PEP 12)
|
||||
using the current default docutils-based system, run the ``pep2html.py`` script
|
||||
with your PEP source file as its argument; e.g. for PEP 12,
|
||||
intend.
|
||||
|
||||
|
||||
Render PEPs locally
|
||||
-------------------
|
||||
|
||||
See the `build documentation <./docs/build.rst>`__ for full
|
||||
instructions on how to render PEPs locally. In summary:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python pep2html.py pep-0012.rst
|
||||
# Install requirements
|
||||
python -m pip install -U -r requirements.txt
|
||||
|
||||
If you're on a system with ``make``, you can instead execute, e.g.,
|
||||
# Render the PEPs
|
||||
make render
|
||||
|
||||
.. code-block:: bash
|
||||
# or, if you don't have 'make':
|
||||
python3 build.py
|
||||
|
||||
make pep-0012.rst
|
||||
|
||||
To generate HTML for all the PEPs, run the script/``make`` without a PEP
|
||||
file argument.
|
||||
The output HTML is found under the ``build`` directory.
|
||||
|
||||
By default, this will output a file (e.g. ``pep-0012.html``) in the root
|
||||
directory, which you can view to see the HTML output of your PEP.
|
||||
Note that the custom CSS stylesheet is not used by default, so
|
||||
the PEP will look rather plain, but all the basic formatting produced by the
|
||||
reStructuredText syntax in your source file should be visible.
|
||||
|
||||
You can also view your PEP locally with the Sphinx-based builder,
|
||||
which will show the PEP exactly as it will appear on the preview
|
||||
of the new rendering system proposed in :pep:`676`;
|
||||
see `Rendering PEPs with Sphinx`_ for details.
|
||||
PEP draughting aids
|
||||
-------------------
|
||||
|
||||
Finally, you can check for and fix common linting and spelling issues,
|
||||
You can check for and fix common linting and spelling issues,
|
||||
either on-demand or automatically as you commit, with our pre-commit suite.
|
||||
See the `Contributing Guide <./CONTRIBUTING.rst>`_ for details.
|
||||
|
||||
|
||||
Generating HTML for Python.org
|
||||
==============================
|
||||
|
||||
Python.org includes its own helper modules to render PEPs as HTML, with
|
||||
suitable links back to the source pages in the version control repository.
|
||||
|
||||
These can be found `in the python.org repository
|
||||
<https://github.com/python/pythondotorg/tree/main/peps>`__.
|
||||
|
||||
When making changes to the PEP management process that may impact python.org's
|
||||
rendering pipeline:
|
||||
|
||||
* Clone the `python.org repository <https://github.com/python/pythondotorg/>`_.
|
||||
* Get `set up for local python.org development
|
||||
<https://pythondotorg.readthedocs.io/install.html#manual-setup>`_.
|
||||
* Adjust ``PEP_REPO_PATH`` in ``pydotorg/settings/local.py`` to refer to your
|
||||
local clone of the PEP repository.
|
||||
* Run ``./manage.py generate_pep_pages`` as described the `python.org docs
|
||||
<https://pythondotorg.readthedocs.io/pep_generation.html>`__.
|
||||
|
||||
|
||||
Rendering PEPs with Sphinx
|
||||
==========================
|
||||
|
||||
There is a Sphinx-rendered version of the PEPs at https://python.github.io/peps/
|
||||
(updated on every push to ``main``).
|
||||
|
||||
**Warning:** This version is not, and should not be taken to be, a canonical
|
||||
source for PEPs whilst it remains in preview (please `report any rendering bugs
|
||||
<https://github.com/python/peps/issues/new>`_).
|
||||
The canonical source for PEPs remains https://www.python.org/dev/peps/
|
||||
|
||||
|
||||
Build PEPs with Sphinx locally
|
||||
------------------------------
|
||||
|
||||
See the `build documentation <./docs/build.rst>`__ for full step by step
|
||||
instructions on how to install, build and view the rendered PEPs with Sphinx.
|
||||
|
||||
In summary, after installing the dependencies (preferably in a virtual
|
||||
environment) with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m pip install -r requirements.txt
|
||||
|
||||
You can build the PEPs with sphinx by running, if your system has ``make``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
make sphinx
|
||||
|
||||
Otherwise, execute the ``build.py`` script directly:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python build.py
|
||||
|
||||
The output HTML can be found under the ``build`` directory.
|
||||
|
||||
|
||||
``build.py`` usage
|
||||
------------------
|
||||
|
||||
For details on the command-line options to the ``build.py`` script, run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python build.py --help
|
||||
|
|
|
@ -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
|
||||
|
||||
(venv) $ make sphinx
|
||||
(venv) $ make render
|
||||
|
||||
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)
|
||||
else:
|
||||
joined_authors = author
|
||||
url = f"https://www.python.org/dev/peps/pep-{pep_num:0>4}"
|
||||
url = f"https://peps.python.org/pep-{pep_num:0>4}"
|
||||
|
||||
item = entry.FeedEntry()
|
||||
item.title(f"PEP {pep_num}: {title}")
|
||||
|
@ -128,8 +128,8 @@ def main():
|
|||
|
||||
# Add metadata
|
||||
fg.title("Newest Python PEPs")
|
||||
fg.link(href="https://www.python.org/dev/peps")
|
||||
fg.link(href="https://www.python.org/dev/peps/peps.rss", rel="self")
|
||||
fg.link(href="https://peps.python.org")
|
||||
fg.link(href="https://peps.python.org/peps.rss", rel="self")
|
||||
fg.description(" ".join(desc.split()))
|
||||
fg.lastBuildDate(datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc))
|
||||
|
||||
|
|
|
@ -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>`
|
||||
for reStructuredText PEPs.
|
||||
|
||||
The PEP text files are automatically converted to HTML [2]_ for easier
|
||||
`online reading <https://www.python.org/dev/peps/>`__.
|
||||
The PEP text files are automatically
|
||||
`converted to HTML <https://peps.python.org/docs/rendering_system/>`__
|
||||
for easier `online reading <https://peps.python.org/>`__.
|
||||
|
||||
|
||||
PEP Header Preamble
|
||||
|
@ -792,9 +793,9 @@ Once the PEP is ready for the repository, a PEP editor will:
|
|||
* Add the PEP to a local fork of the `PEP repository`_. For workflow
|
||||
instructions, follow `The Python Developers Guide <https://devguide.python.org/>`_
|
||||
|
||||
* Run ``./genpepindex.py`` and ``./pep2html.py <PEP Number>`` to ensure they
|
||||
are generated without errors. If either triggers errors, then the web site
|
||||
will not be updated to reflect the PEP changes.
|
||||
* Run ``./build.py`` to ensure the PEPs are generated without errors. If the
|
||||
rendering triggers errors, then the web site will not be updated to reflect
|
||||
the PEP changes.
|
||||
|
||||
* Commit and push the new (or updated) PEP
|
||||
|
||||
|
@ -818,7 +819,7 @@ administrative & editorial part (which is generally a low volume task).
|
|||
|
||||
Resources:
|
||||
|
||||
* `Index of Python Enhancement Proposals <https://www.python.org/dev/peps/>`_
|
||||
* `Index of Python Enhancement Proposals <https://peps.python.org/>`_
|
||||
|
||||
* `Following Python's Development
|
||||
<https://devguide.python.org/communication/>`_
|
||||
|
@ -833,10 +834,6 @@ Footnotes
|
|||
for retrieving older revisions, and can also be browsed
|
||||
`on GitHub <https://github.com/python/peps>`__.
|
||||
|
||||
.. [2] More details on the PEP rendering and publication process can be found
|
||||
in the `PEPs repo README
|
||||
<https://github.com/python/peps/blob/main/README.rst>`__.
|
||||
|
||||
.. _.github/CODEOWNERS: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
|
||||
.. _issue tracker: https://bugs.python.org/
|
||||
|
|
|
@ -197,12 +197,12 @@ Resolution: https://mail.python.org/mailman/private/peps/2016-January/001165.htm
|
|||
References
|
||||
|
||||
[7] PEP 1, PEP Purpose and Guidelines, Warsaw, Hylton
|
||||
http://www.python.org/dev/peps/pep-0001/
|
||||
http://peps.python.org/pep-0001/
|
||||
|
||||
If you decide to provide an explicit URL for a PEP, please use
|
||||
this as the URL template:
|
||||
|
||||
http://www.python.org/dev/peps/pep-xxxx/
|
||||
http://peps.python.org/pep-xxxx/
|
||||
|
||||
PEP numbers in URLs must be padded with zeros from the left, so as
|
||||
to be exactly 4 characters wide, however PEP numbers in the text
|
||||
|
@ -212,10 +212,10 @@ Resolution: https://mail.python.org/mailman/private/peps/2016-January/001165.htm
|
|||
References
|
||||
|
||||
[1] PEP 1, PEP Purpose and Guidelines, Warsaw, Hylton
|
||||
http://www.python.org/dev/peps/pep-0001/
|
||||
http://peps.python.org/pep-0001/
|
||||
|
||||
[2] PEP 12, Sample reStructuredText PEP Template, Goodger, Warsaw
|
||||
http://www.python.org/dev/peps/pep-0012/
|
||||
http://peps.python.org/pep-0012/
|
||||
|
||||
[3] http://www.opencontent.org/openpub/
|
||||
|
||||
|
|
|
@ -564,7 +564,7 @@ Questions & Answers
|
|||
[1] http://www.example.org/
|
||||
|
||||
[2] PEP 9876, Let's Hope We Never Get Here
|
||||
http://www.python.org/dev/peps/pep-9876/
|
||||
http://peps.python.org/pep-9876/
|
||||
|
||||
[3] "Bogus Complexity Addition"
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
# https://www.python.org/dev/peps/pep-0465/
|
||||
# https://peps.python.org/pep-0465/
|
||||
# https://gist.github.com/njsmith/9157645
|
||||
|
||||
# usage:
|
||||
|
|
|
@ -4,7 +4,7 @@ Author: Adam Turner <python@quite.org.uk>
|
|||
Sponsor: Mariatta <mariatta@python.org>
|
||||
PEP-Delegate: Barry Warsaw <barry@python.org>
|
||||
Discussions-To: https://discuss.python.org/t/10774
|
||||
Status: Accepted
|
||||
Status: Active
|
||||
Type: Process
|
||||
Content-Type: text/x-rst
|
||||
Created: 01-Nov-2021
|
||||
|
|
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
|
||||
itself, now that Guido has announced his retirement as BDFL. For
|
||||
full details, see <a
|
||||
href="https://www.python.org/dev/peps/pep-8001/">PEP
|
||||
href="https://peps.python.org/pep-8001/">PEP
|
||||
8001</a>. Many discussions have occurred under <a
|
||||
href="https://discuss.python.org/tags/governance">the "governance"
|
||||
tag</a> on discuss.python.org.
|
||||
|
@ -156,13 +156,13 @@ Description of the poll::
|
|||
|
||||
Candidates (note: linebreaks are significant here)::
|
||||
|
||||
<a href="https://www.python.org/dev/peps/pep-8010/">PEP 8010: The Technical Leader Governance Model</a> (Warsaw) (<a href="https://github.com/python/peps/commits/main/pep-8010.rst">changelog</a>)
|
||||
<a href="https://www.python.org/dev/peps/pep-8011/">PEP 8011: Python Governance Model Lead by Trio of Pythonistas</a> (Mariatta, Warsaw) (<a href="https://github.com/python/peps/commits/main/pep-8011.rst">changelog</a>)
|
||||
<a href="https://www.python.org/dev/peps/pep-8012/">PEP 8012: The Community Governance Model</a> (Langa) (<a href="https://github.com/python/peps/commits/main/pep-8012.rst">changelog</a>)
|
||||
<a href="https://www.python.org/dev/peps/pep-8013/">PEP 8013: The External Council Governance Model</a> (Dower) (<a href="https://github.com/python/peps/commits/main/pep-8013.rst">changelog</a>)
|
||||
<a href="https://www.python.org/dev/peps/pep-8014/">PEP 8014: The Commons Governance Model</a> (Jansen) (<a href="https://github.com/python/peps/commits/main/pep-8014.rst">changelog</a>)
|
||||
<a href="https://www.python.org/dev/peps/pep-8015/">PEP 8015: Organization of the Python community</a> (Stinner) (<a href="https://github.com/python/peps/commits/main/pep-8015.rst">changelog</a>)
|
||||
<a href="https://www.python.org/dev/peps/pep-8016/">PEP 8016: The Steering Council Model</a> (Smith, Stufft) (<a href="https://github.com/python/peps/commits/main/pep-8016.rst">changelog</a>)
|
||||
<a href="https://peps.python.org/pep-8010/">PEP 8010: The Technical Leader Governance Model</a> (Warsaw) (<a href="https://github.com/python/peps/commits/main/pep-8010.rst">changelog</a>)
|
||||
<a href="https://peps.python.org/pep-8011/">PEP 8011: Python Governance Model Lead by Trio of Pythonistas</a> (Mariatta, Warsaw) (<a href="https://github.com/python/peps/commits/main/pep-8011.rst">changelog</a>)
|
||||
<a href="https://peps.python.org/pep-8012/">PEP 8012: The Community Governance Model</a> (Langa) (<a href="https://github.com/python/peps/commits/main/pep-8012.rst">changelog</a>)
|
||||
<a href="https://peps.python.org/pep-8013/">PEP 8013: The External Council Governance Model</a> (Dower) (<a href="https://github.com/python/peps/commits/main/pep-8013.rst">changelog</a>)
|
||||
<a href="https://peps.python.org/pep-8014/">PEP 8014: The Commons Governance Model</a> (Jansen) (<a href="https://github.com/python/peps/commits/main/pep-8014.rst">changelog</a>)
|
||||
<a href="https://peps.python.org/pep-8015/">PEP 8015: Organization of the Python community</a> (Stinner) (<a href="https://github.com/python/peps/commits/main/pep-8015.rst">changelog</a>)
|
||||
<a href="https://peps.python.org/pep-8016/">PEP 8016: The Steering Council Model</a> (Smith, Stufft) (<a href="https://github.com/python/peps/commits/main/pep-8016.rst">changelog</a>)
|
||||
Further discussion
|
||||
|
||||
Options::
|
||||
|
|
344
pep.css
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 docutils import nodes
|
||||
from docutils.parsers.rst import states
|
||||
from docutils.writers.html5_polyglot import HTMLTranslator
|
||||
from sphinx import environment
|
||||
|
||||
|
@ -19,10 +17,6 @@ from pep_sphinx_extensions.pep_zero_generator.pep_index_generator import create_
|
|||
if TYPE_CHECKING:
|
||||
from sphinx.application import Sphinx
|
||||
|
||||
# Monkeypatch sphinx.environment.default_settings as Sphinx doesn't allow custom settings or Readers
|
||||
# This disables reading configuration from docutils.conf so as not to affect pep2html.py
|
||||
environment.default_settings["_disable_config"] = True
|
||||
|
||||
|
||||
def _depart_maths():
|
||||
pass # No-op callable for the type checker
|
||||
|
|
|
@ -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