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

inside -#
 tags.  But if I change that, the result doesn't look very nice...
-DTD = ('')
-
-fixpat = re.compile(r"((https?|ftp):[-_a-zA-Z0-9/.+~:?#$=&,]+)|(pep-\d+(.txt|.rst)?)|"
-                    r"(RFC[- ]?(?P\d+))|"
-                    r"(PEP\s+(?P\d+))|"
-                    r".")
-
-EMPTYSTRING = ''
-SPACE = ' '
-COMMASPACE = ', '
-
-
-
-def usage(code, msg=''):
-    """Print usage message and exit.  Uses stderr if code != 0."""
-    if code == 0:
-        out = sys.stdout
-    else:
-        out = sys.stderr
-    print(__doc__ % globals(), file=out)
-    if msg:
-        print(msg, file=out)
-    sys.exit(code)
-
-
-
-def fixanchor(current, match):
-    text = match.group(0)
-    link = None
-    if (text.startswith('http:') or text.startswith('https:')
-        or text.startswith('ftp:')):
-        # Strip off trailing punctuation.  Pattern taken from faqwiz.
-        ltext = list(text)
-        while ltext:
-            c = ltext.pop()
-            if c not in '''();:,.?'"<>''':
-                ltext.append(c)
-                break
-        link = EMPTYSTRING.join(ltext)
-    elif text.startswith('pep-') and text != current:
-        link = os.path.splitext(text)[0] + ".html"
-    elif text.startswith('PEP'):
-        pepnum = int(match.group('pepnum'))
-        link = PEPURL % pepnum
-    elif text.startswith('RFC'):
-        rfcnum = int(match.group('rfcnum'))
-        link = RFCURL % rfcnum
-    if link:
-        return '%s' % (escape(link), escape(text))
-    return escape(match.group(0)) # really slow, but it works...
-
-
-
-NON_MASKED_EMAILS = [
-    'peps@python.org',
-    'python-list@python.org',
-    'python-dev@python.org',
-    ]
-
-def fixemail(address, pepno):
-    if address.lower() in NON_MASKED_EMAILS:
-        # return hyperlinked version of email address
-        return linkemail(address, pepno)
-    else:
-        # return masked version of email address
-        parts = address.split('@', 1)
-        return '%s at %s' % (parts[0], parts[1])
-
-
-def linkemail(address, pepno):
-    parts = address.split('@', 1)
-    return (''
-            '%s at %s'
-            % (parts[0], parts[1], pepno, parts[0], parts[1]))
-
-
-def fixfile(inpath, input_lines, outfile):
-    try:
-        from email.Utils import parseaddr
-    except ImportError:
-        from email.utils import parseaddr
-    basename = os.path.basename(inpath)
-    infile = iter(input_lines)
-    # convert plaintext pep to minimal XHTML markup
-    print(DTD, file=outfile)
-    print('', file=outfile)
-    print(COMMENT, file=outfile)
-    print('', file=outfile)
-    # head
-    header = []
-    pep = ""
-    title = ""
-    for line in infile:
-        if not line.strip():
-            break
-        if line[0].strip():
-            if ":" not in line:
-                break
-            key, value = line.split(":", 1)
-            value = value.strip()
-            header.append((key, value))
-        else:
-            # continuation line
-            key, value = header[-1]
-            value = value + line
-            header[-1] = key, value
-        if key.lower() == "title":
-            title = value
-        elif key.lower() == "pep":
-            pep = value
-    if pep:
-        title = "PEP " + pep + " -- " + title
-    if title:
-        print('  %s' % escape(title), file=outfile)
-    r = random.choice(list(range(64)))
-    print((
-        '  \n'
-        '\n'
-        '\n'
-        '\n'
-        '\n'
-        '', file=outfile)
-    print('
\n', 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( - '%s' % (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 += '%i ' % (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 = '%s ' % (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 = '%s ' % (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(' ' \ - % (escape(k), v), file=outfile) - print('
%s: %s
', file=outfile) - print('
', file=outfile) - print('
', file=outfile) - print('
', file=outfile) - need_pre = 1 - for line in infile: - if line[0] == '\f': - continue - if line.strip() == LOCALVARS: - break - if line[0].strip(): - if not need_pre: - print('
', file=outfile) - print('

%s

' % line.strip(), file=outfile) - need_pre = 1 - elif not line.strip() and need_pre: - continue - else: - # PEP 0 has some special treatment - if basename == 'pep-0000.txt': - parts = line.split() - if len(parts) > 1 and re.match(r'\s*\d{1,4}', parts[1]): - # This is a PEP summary line, which we need to hyperlink - url = PEPURL % int(parts[1]) - if need_pre: - print('
', file=outfile)
-                        need_pre = 0
-                    print(re.sub(
-                        parts[1],
-                        '%s' % (url, parts[1]),
-                        line, 1), end='', file=outfile)
-                    continue
-                elif parts and '@' in parts[-1]:
-                    # This is a pep email address line, so filter it.
-                    url = fixemail(parts[-1], pep)
-                    if need_pre:
-                        print('
', 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('
', file=outfile)
-                need_pre = 0
-            outfile.write(line)
-    if not need_pre:
-        print('
', file=outfile) - print('', file=outfile) - print('', file=outfile) - print('', file=outfile) - - -EXPLICIT_TITLE_RE = re.compile(r'^(.+?)\s*(?$', 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() diff --git a/pep2rss.py b/pep2rss.py deleted file mode 100755 index 52b532f51..000000000 --- a/pep2rss.py +++ /dev/null @@ -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('', 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 "Abstract" 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")) diff --git a/pep_sphinx_extensions/__init__.py b/pep_sphinx_extensions/__init__.py index 55f5c9368..bbd839449 100644 --- a/pep_sphinx_extensions/__init__.py +++ b/pep_sphinx_extensions/__init__.py @@ -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 diff --git a/pyramid-pep-template b/pyramid-pep-template deleted file mode 100644 index f65a5ab5e..000000000 --- a/pyramid-pep-template +++ /dev/null @@ -1,6 +0,0 @@ - -%(body)s diff --git a/roman.py b/roman.py deleted file mode 100644 index 394a43ae9..000000000 --- a/roman.py +++ /dev/null @@ -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 - diff --git a/style.css b/style.css deleted file mode 100644 index 064fe688c..000000000 --- a/style.css +++ /dev/null @@ -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; }