', 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
- outfile.write(line)
- if not need_pre:
- print('
', file=outfile)
- print('
', file=outfile)
- print('', file=outfile)
- print('', file=outfile)
-
-
-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.
"""
@@ -334,8 +63,8 @@ class PEPHeaders(Transform):
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'),)
+ (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):
@@ -343,12 +72,12 @@ def apply(self):
raise DataError('Document tree is empty.')
header = self.document[0]
if not isinstance(header, nodes.field_list) or \
- 'rfc2822' not in header['classes']:
+ 'rfc2822' not in header['classes']:
raise DataError('Document does not begin with an RFC-2822 '
'header; it is not a PEP.')
- pep = None
+ pep = cvs_url = None
for field in header:
- if field[0].astext().lower() == 'pep': # should be the first field
+ if field[0].astext().lower() == 'pep': # should be the first field
value = field[1].astext()
try:
pep = int(value)
@@ -392,8 +121,8 @@ def apply(self):
% field.pformat(level=1))
elif name == 'last-modified':
date = time.strftime(
- '%d-%b-%Y',
- time.localtime(os.stat(self.document['source'])[8]))
+ '%d-%b-%Y',
+ time.localtime(os.stat(self.document['source'])[8]))
if cvs_url:
body += nodes.paragraph(
'', '', nodes.reference('', date, refuri=cvs_url))
@@ -419,7 +148,7 @@ def apply(self):
refuri=(self.document.settings.pep_base_url
+ self.pep_url % pepno)))
newbody.append(space)
- para[:] = newbody[:-1] # drop trailing space
+ para[:] = newbody[:-1] # drop trailing space
elif name == 'last-modified':
utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
if cvs_url:
@@ -432,8 +161,8 @@ def apply(self):
elif name == 'version' and len(body):
utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
-class PEPReader(standalone.Reader):
+class PEPReader(standalone.Reader):
supported = ('pep',)
"""Contexts this reader supports."""
@@ -459,7 +188,7 @@ def get_transforms(self):
inliner_class = rst.states.Inliner
- def __init__(self, parser=None, parser_name=None):
+ 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())
@@ -474,45 +203,24 @@ def fix_rst_pep(inpath, input_lines, outfile):
reader=PEPReader(),
parser_name='restructuredtext',
writer_name='pep_html',
- settings=docutils_settings,
+ settings=None,
# 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/plain" 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/plain'
- break
- elif line.startswith('pep: '):
- # Default PEP type, used if no explicit content-type specified:
- pep_type = 'text/plain'
- 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
+ except FileNotFoundError as e:
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
+ lines = infile.read().splitlines(True) # 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):
@@ -523,151 +231,29 @@ def find_pep(pep_str):
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] == None:
- pep_type_error(inpath, pep_type)
- return None
outpath = os.path.splitext(inpath)[0] + ".html"
if verbose:
- print(inpath, "(%s)" % pep_type, "->", outpath)
+ print(inpath, "(text/x-rst)", "->", outpath)
sys.stdout.flush()
outfile = open(outpath, "w", encoding='utf-8')
- PEP_TYPE_DISPATCH[pep_type](inpath, input_lines, outfile)
+ fix_rst_pep(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
+ opts, args = getopt.getopt(argv, 'bilhqu:', ['browse', 'install', 'local', 'help', 'quiet', 'user='])
if args:
pep_list = []
@@ -678,8 +264,6 @@ def main(argv=None):
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 = []
@@ -691,19 +275,7 @@ def main(argv=None):
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()
From 59d9c07587e11a82d803eeeafbfa0f431840d4c0 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Fri, 24 Apr 2020 23:19:24 +0100
Subject: [PATCH 002/110] Current state
- RFC2822 headers work
- Contents page generated
- Title autogeneration not working
- PEP 0 not started
---
_templates/layout.html | 4 ++
build.py | 15 ++++++
pepreader/__init__.py | 14 ++++++
peps/conf.py | 103 +++++++++++++++++++++++++++++++++++++++++
peps/index.rst | 27 +++++++++++
5 files changed, 163 insertions(+)
create mode 100644 _templates/layout.html
create mode 100644 build.py
create mode 100644 pepreader/__init__.py
create mode 100644 peps/conf.py
create mode 100644 peps/index.rst
diff --git a/_templates/layout.html b/_templates/layout.html
new file mode 100644
index 00000000000..2bd13e35d6d
--- /dev/null
+++ b/_templates/layout.html
@@ -0,0 +1,4 @@
+{% extends "!layout.html" %}
+
+{# Set the delimiter after the short title on the rel-bar #}
+{% set reldelim1 = ' 🡢' %}
\ No newline at end of file
diff --git a/build.py b/build.py
new file mode 100644
index 00000000000..aae70f95aa9
--- /dev/null
+++ b/build.py
@@ -0,0 +1,15 @@
+# Build script for Sphinx documentation
+
+from pathlib import Path
+from sphinx.application import Sphinx
+
+if __name__ == '__main__':
+ root_directory = Path('.').absolute()
+ docs_directory = Path(root_directory, 'peps')
+ source_directory = configuration_directory = docs_directory
+ build_directory = Path(root_directory, '_build')
+ doctree_directory = Path(build_directory, '.doctrees')
+ builder = 'html'
+
+ app = Sphinx(source_directory, configuration_directory, build_directory, doctree_directory, builder)
+ app.build(force_all=True)
diff --git a/pepreader/__init__.py b/pepreader/__init__.py
new file mode 100644
index 00000000000..b22124725e2
--- /dev/null
+++ b/pepreader/__init__.py
@@ -0,0 +1,14 @@
+"""Docutils CommonMark parser"""
+from sphinx.application import Sphinx
+
+__version__ = '1.0.0'
+
+
+def setup(app: Sphinx):
+ """Initialize Sphinx extension."""
+ from .pep_parser import PEPParser, PEPTitleCollector
+
+ # app.add_env_collector(PEPTitleCollector)
+ app.add_source_parser(PEPParser)
+
+ return {'version': __version__, 'parallel_read_safe': True}
\ No newline at end of file
diff --git a/peps/conf.py b/peps/conf.py
new file mode 100644
index 00000000000..26cb5a1e8bb
--- /dev/null
+++ b/peps/conf.py
@@ -0,0 +1,103 @@
+# Configuration file for the Sphinx documentation builder.
+
+# -- Path setup --------------------------------------------------------------
+
+import docutils
+from docutils.readers import standalone
+from docutils.transforms import frontmatter, references, misc
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'peps'
+copyright = '2020, AUTHNAME'
+author = 'AUTHNAME'
+
+# The full version, including alpha/beta/rc tags
+release = '1.0.0'
+
+html_title = "Python Enhancement Proposals (PEPs)"
+html_short_title = "PEPs Home" #
+html_show_copyright = False
+html_show_sphinx = False
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = ["pepreader"]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The file extensions of source files. Sphinx considers the files with
+# these suffixes as sources.
+source_suffix = {
+ '.rst': 'pep',
+ '.txt': 'pep',
+}
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = [
+ '_build',
+ 'Thumbs.db',
+ '.DS_Store',
+ 'venv',
+ 'build',
+ 'README.rst',
+ 'CONTRIBUTING.rst'
+]
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'classic'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+# html_static_path = ['_static']
+
+
+def new_get_transforms(self):
+ # Replicate result of standalone.Reader.get_transforms()
+ transforms = docutils.readers.Reader.get_transforms(self) + [
+ references.Substitutions,
+ references.PropagateTargets,
+ frontmatter.DocTitle,
+ frontmatter.SectionSubTitle,
+ frontmatter.DocInfo,
+ references.AnonymousHyperlinks,
+ references.IndirectHyperlinks,
+ references.Footnotes,
+ references.ExternalTargets,
+ references.InternalTargets,
+ references.DanglingReferences,
+ misc.Transitions,
+ ]
+
+ # Explicitly remove these transforms, as we implement PEP-specific pre-processing
+ # transforms.remove(frontmatter.DocTitle)
+ # transforms.remove(frontmatter.SectionSubTitle)
+ transforms.remove(frontmatter.DocInfo)
+
+ return transforms
+
+
+standalone.Reader.get_transforms = new_get_transforms
diff --git a/peps/index.rst b/peps/index.rst
new file mode 100644
index 00000000000..8c23bf7359a
--- /dev/null
+++ b/peps/index.rst
@@ -0,0 +1,27 @@
+PEP: 9999
+Title: Sphinx Index
+
+.. peps documentation master file, created by
+ sphinx-quickstart on Thu Apr 23 17:23:18 2020.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to peps's documentation!
+================================
+
+.. toctree::
+ :maxdepth: 3
+ :titlesonly:
+ :glob:
+ :caption: Contents:
+
+ pep-*
+
+
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
From 27fde33b344c175949fe77deed553f1b5036f6fa Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 25 Apr 2020 17:19:47 +0100
Subject: [PATCH 003/110] Fix title generation
Also add pep_parser as I missed it
---
pepreader/__init__.py | 3 +-
pepreader/pep_parser.py | 179 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 180 insertions(+), 2 deletions(-)
create mode 100644 pepreader/pep_parser.py
diff --git a/pepreader/__init__.py b/pepreader/__init__.py
index b22124725e2..1568c24623a 100644
--- a/pepreader/__init__.py
+++ b/pepreader/__init__.py
@@ -6,9 +6,8 @@
def setup(app: Sphinx):
"""Initialize Sphinx extension."""
- from .pep_parser import PEPParser, PEPTitleCollector
+ from .pep_parser import PEPParser
- # app.add_env_collector(PEPTitleCollector)
app.add_source_parser(PEPParser)
return {'version': __version__, 'parallel_read_safe': True}
\ No newline at end of file
diff --git a/pepreader/pep_parser.py b/pepreader/pep_parser.py
new file mode 100644
index 00000000000..0e4250dac80
--- /dev/null
+++ b/pepreader/pep_parser.py
@@ -0,0 +1,179 @@
+import re
+
+from docutils import nodes, utils
+from docutils.transforms import peps, Transform, parts
+from sphinx import parsers
+from docutils.parsers.rst import states
+
+
+class DataError(Exception):
+ pass
+
+
+class PEPParser(parsers.RSTParser):
+ supported = ("pep", "python-enhancement-proposal")
+
+ def __init__(self):
+ super().__init__(rfc2822=True)
+
+ def get_transforms(self):
+ transforms = super().get_transforms()
+ transforms.extend([
+ PEPHeaders,
+ PEPTitle,
+ PEPContents,
+ ])
+ return transforms
+
+
+# PEPHeaders is identical to docutils.transforms.peps.Headers excepting bdfl-delegate, sponsor & superseeded-by
+class PEPHeaders(Transform):
+
+ """
+ Process fields in a PEP's initial RFC-2822 header.
+ """
+
+ default_priority = 360
+
+ pep_url = "pep-%04d"
+ pep_cvs_url = "https://github.com/python/peps/blob/master/pep-%04d.txt"
+
+ rcs_keyword_substitutions = [(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.")
+
+ cvs_url = None
+ pep = None
+ pep_field = header[0]
+ if pep_field[0].astext().lower() == "pep": # should be the first field
+ value = pep_field[1].astext()
+ try:
+ pep = int(value)
+ cvs_url = self.pep_cvs_url % pep
+ except ValueError:
+ pep = value
+ msg = self.document.reporter.warning(
+ f"'PEP' header must contain an integer; '{pep}' is an invalid value.",
+ base_node=pep_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(pep_field[1]):
+ pep_field[1][0][:] = [prb]
+ else:
+ pep_field[1] += nodes.paragraph("", "", prb)
+
+ 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 there are less than two headers in the preamble, or if Title is absent
+ 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(f"PEP header field body contains multiple elements:\n{field.pformat(level=1)}")
+ elif len(body) == 1:
+ if not isinstance(body[0], nodes.paragraph):
+ raise DataError(f"PEP header field body may only contain a single paragraph:\n{field.pformat(level=1)}")
+ else:
+ # body is empty
+ continue
+
+ para = body[0]
+ if name in ("author", "bdfl-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 PEPTitle(Transform):
+
+ """
+ Insert an empty table of contents topic and a transform placeholder into
+ the document after the RFC 2822 header.
+ """
+
+ default_priority = 370
+
+ def apply(self):
+ title_str = 'PEP INDEX TTL TST'
+ pep_title_node = nodes.section()
+
+ textnode = nodes.Text(title_str, title_str)
+ titlenode = nodes.title(title_str, '', textnode)
+ name = states.normalize_name(titlenode.astext())
+ pep_title_node['names'].append(name)
+ pep_title_node += titlenode
+
+ document_children = self.document.children
+ self.document.children = [pep_title_node]
+ pep_title_node.extend(document_children)
+
+ self.document.note_implicit_target(pep_title_node, pep_title_node)
+
+
+class PEPContents(Transform):
+
+ """
+ Insert an empty table of contents topic and a transform placeholder into
+ the document after the RFC 2822 header.
+ """
+
+ default_priority = 380
+
+ def apply(self):
+ title = nodes.title('', 'contents')
+ topic = nodes.topic('', title, classes=['contents'])
+ name = nodes.fully_normalize_name('contents')
+ if not self.document.has_name(name):
+ topic['names'].append(name)
+ self.document.note_implicit_target(topic)
+ pending = nodes.pending(parts.Contents)
+ topic += pending
+ self.document.children[0].insert(1, topic)
+ self.document.note_pending(pending)
+
From 493aa25ce2bdd9c4c1293edbb1e0c77921dd6471 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 25 Apr 2020 17:20:49 +0100
Subject: [PATCH 004/110] Fix contents section location
---
pepreader/pep_parser.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pepreader/pep_parser.py b/pepreader/pep_parser.py
index 0e4250dac80..c0ac6041d53 100644
--- a/pepreader/pep_parser.py
+++ b/pepreader/pep_parser.py
@@ -174,6 +174,6 @@ def apply(self):
self.document.note_implicit_target(topic)
pending = nodes.pending(parts.Contents)
topic += pending
- self.document.children[0].insert(1, topic)
+ self.document.children[0].insert(2, topic)
self.document.note_pending(pending)
From 7bda45b3fa1482b766482fe623defccf58eea2bb Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 25 Apr 2020 23:42:34 +0100
Subject: [PATCH 005/110] Clarify contents page
---
peps/index.rst => contents.rst | 17 +++--------------
1 file changed, 3 insertions(+), 14 deletions(-)
rename peps/index.rst => contents.rst (57%)
diff --git a/peps/index.rst b/contents.rst
similarity index 57%
rename from peps/index.rst
rename to contents.rst
index 8c23bf7359a..ce25a569a64 100644
--- a/peps/index.rst
+++ b/contents.rst
@@ -1,27 +1,16 @@
PEP: 9999
-Title: Sphinx Index
+Title: Sphinx Index Page
.. peps documentation master file, created by
sphinx-quickstart on Thu Apr 23 17:23:18 2020.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
-Welcome to peps's documentation!
-================================
.. toctree::
:maxdepth: 3
:titlesonly:
:glob:
- :caption: Contents:
+ :caption: Sphinx Table of Contents:
- pep-*
-
-
-
-Indices and tables
-==================
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+ pep-*
\ No newline at end of file
From 157d0fa86548a33d192e8723ee6c415f01451146 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 25 Apr 2020 23:43:46 +0100
Subject: [PATCH 006/110] Split pep_parser to files
---
pepreader/__init__.py | 1 +
pepreader/pep_contents.py | 24 ++++++
pepreader/pep_headers.py | 113 ++++++++++++++++++++++++++
pepreader/pep_parser.py | 164 +-------------------------------------
pepreader/pep_title.py | 46 +++++++++++
5 files changed, 188 insertions(+), 160 deletions(-)
create mode 100644 pepreader/pep_contents.py
create mode 100644 pepreader/pep_headers.py
create mode 100644 pepreader/pep_title.py
diff --git a/pepreader/__init__.py b/pepreader/__init__.py
index 1568c24623a..33c02d5f360 100644
--- a/pepreader/__init__.py
+++ b/pepreader/__init__.py
@@ -7,6 +7,7 @@
def setup(app: Sphinx):
"""Initialize Sphinx extension."""
from .pep_parser import PEPParser
+ from .generate_pep_index import create_pep_zero
app.add_source_parser(PEPParser)
diff --git a/pepreader/pep_contents.py b/pepreader/pep_contents.py
new file mode 100644
index 00000000000..fd4e9c2a620
--- /dev/null
+++ b/pepreader/pep_contents.py
@@ -0,0 +1,24 @@
+from docutils import nodes
+from docutils.transforms import Transform, parts
+
+
+class PEPContents(Transform):
+
+ """
+ Insert an empty table of contents topic and a transform placeholder into
+ the document after the RFC 2822 header.
+ """
+
+ default_priority = 380
+
+ def apply(self):
+ title = nodes.title('', 'contents')
+ topic = nodes.topic('', title, classes=['contents'])
+ name = nodes.fully_normalize_name('contents')
+ if not self.document.has_name(name):
+ topic['names'].append(name)
+ self.document.note_implicit_target(topic)
+ pending = nodes.pending(parts.Contents)
+ topic += pending
+ self.document.children[0].insert(2, topic)
+ self.document.note_pending(pending)
diff --git a/pepreader/pep_headers.py b/pepreader/pep_headers.py
new file mode 100644
index 00000000000..3b602df43c0
--- /dev/null
+++ b/pepreader/pep_headers.py
@@ -0,0 +1,113 @@
+import re
+
+from docutils import nodes, utils
+from docutils.transforms import peps, Transform
+
+
+class DataError(Exception):
+ pass
+
+
+# PEPHeaders is identical to docutils.transforms.peps.Headers excepting bdfl-delegate, sponsor & superseeded-by
+class PEPHeaders(Transform):
+
+ """
+ Process fields in a PEP's initial RFC-2822 header.
+ """
+
+ default_priority = 330
+
+ pep_url = "pep-%04d"
+ pep_cvs_url = "https://github.com/python/peps/blob/master/pep-%04d.txt"
+
+ rcs_keyword_substitutions = [(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.")
+
+ cvs_url = None
+ pep = None
+ pep_field = header[0]
+ if pep_field[0].astext().lower() == "pep": # should be the first field
+ value = pep_field[1].astext()
+ try:
+ pep = int(value)
+ cvs_url = self.pep_cvs_url % pep
+ except ValueError:
+ pep = value
+ msg = self.document.reporter.warning(
+ f"'PEP' header must contain an integer; '{pep}' is an invalid value.",
+ base_node=pep_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(pep_field[1]):
+ pep_field[1][0][:] = [prb]
+ else:
+ pep_field[1] += nodes.paragraph("", "", prb)
+
+ 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 there are less than two headers in the preamble, or if Title is absent
+ 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(f"PEP header field body contains multiple elements:\n{field.pformat(level=1)}")
+ elif len(body) == 1:
+ if not isinstance(body[0], nodes.paragraph):
+ raise DataError(f"PEP header field body may only contain a single paragraph:\n{field.pformat(level=1)}")
+ else:
+ # body is empty
+ continue
+
+ para = body[0]
+ if name in ("author", "bdfl-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)
+
diff --git a/pepreader/pep_parser.py b/pepreader/pep_parser.py
index c0ac6041d53..da6f29c98c9 100644
--- a/pepreader/pep_parser.py
+++ b/pepreader/pep_parser.py
@@ -1,13 +1,10 @@
-import re
-
-from docutils import nodes, utils
-from docutils.transforms import peps, Transform, parts
from sphinx import parsers
-from docutils.parsers.rst import states
+from .pep_headers import PEPHeaders
+from .pep_title import PEPTitle
+from .pep_contents import PEPContents
+
-class DataError(Exception):
- pass
class PEPParser(parsers.RSTParser):
@@ -24,156 +21,3 @@ def get_transforms(self):
PEPContents,
])
return transforms
-
-
-# PEPHeaders is identical to docutils.transforms.peps.Headers excepting bdfl-delegate, sponsor & superseeded-by
-class PEPHeaders(Transform):
-
- """
- Process fields in a PEP's initial RFC-2822 header.
- """
-
- default_priority = 360
-
- pep_url = "pep-%04d"
- pep_cvs_url = "https://github.com/python/peps/blob/master/pep-%04d.txt"
-
- rcs_keyword_substitutions = [(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.")
-
- cvs_url = None
- pep = None
- pep_field = header[0]
- if pep_field[0].astext().lower() == "pep": # should be the first field
- value = pep_field[1].astext()
- try:
- pep = int(value)
- cvs_url = self.pep_cvs_url % pep
- except ValueError:
- pep = value
- msg = self.document.reporter.warning(
- f"'PEP' header must contain an integer; '{pep}' is an invalid value.",
- base_node=pep_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(pep_field[1]):
- pep_field[1][0][:] = [prb]
- else:
- pep_field[1] += nodes.paragraph("", "", prb)
-
- 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 there are less than two headers in the preamble, or if Title is absent
- 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(f"PEP header field body contains multiple elements:\n{field.pformat(level=1)}")
- elif len(body) == 1:
- if not isinstance(body[0], nodes.paragraph):
- raise DataError(f"PEP header field body may only contain a single paragraph:\n{field.pformat(level=1)}")
- else:
- # body is empty
- continue
-
- para = body[0]
- if name in ("author", "bdfl-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 PEPTitle(Transform):
-
- """
- Insert an empty table of contents topic and a transform placeholder into
- the document after the RFC 2822 header.
- """
-
- default_priority = 370
-
- def apply(self):
- title_str = 'PEP INDEX TTL TST'
- pep_title_node = nodes.section()
-
- textnode = nodes.Text(title_str, title_str)
- titlenode = nodes.title(title_str, '', textnode)
- name = states.normalize_name(titlenode.astext())
- pep_title_node['names'].append(name)
- pep_title_node += titlenode
-
- document_children = self.document.children
- self.document.children = [pep_title_node]
- pep_title_node.extend(document_children)
-
- self.document.note_implicit_target(pep_title_node, pep_title_node)
-
-
-class PEPContents(Transform):
-
- """
- Insert an empty table of contents topic and a transform placeholder into
- the document after the RFC 2822 header.
- """
-
- default_priority = 380
-
- def apply(self):
- title = nodes.title('', 'contents')
- topic = nodes.topic('', title, classes=['contents'])
- name = nodes.fully_normalize_name('contents')
- if not self.document.has_name(name):
- topic['names'].append(name)
- self.document.note_implicit_target(topic)
- pending = nodes.pending(parts.Contents)
- topic += pending
- self.document.children[0].insert(2, topic)
- self.document.note_pending(pending)
-
diff --git a/pepreader/pep_title.py b/pepreader/pep_title.py
new file mode 100644
index 00000000000..c27dffbf8a8
--- /dev/null
+++ b/pepreader/pep_title.py
@@ -0,0 +1,46 @@
+from docutils import nodes
+from docutils.transforms import Transform
+from docutils.parsers.rst import states, Directive
+
+
+class PEPTitle(Transform):
+
+ """
+ Insert an empty table of contents topic and a transform placeholder into
+ the document after the RFC 2822 header.
+ """
+
+ # needs to run before docutils.transforms.frontmatter.DocInfo
+ default_priority = 335
+
+ def apply(self):
+ # Directory to hold the PEP's RFC2822 header details, to extract a titke string
+ pep_header_details = {}
+
+ # Iterate through the header fields, which are the first section of the document
+ for field in self.document[0]:
+ row_attributes = {}
+ for sub in field:
+ # Hold details of the attribute's tag against its details
+ row_attributes[sub.tagname] = sub.rawsource
+ # Collapse the dictionary by removing tag names
+ pep_header_details[row_attributes["field_name"]] = row_attributes["field_body"]
+
+ # Create the title string for the PEP
+ pep_number = int(pep_header_details["PEP"])
+ pep_title = pep_header_details["Title"]
+ pep_title_string = f"PEP {pep_number} -- {pep_title}"
+
+ # Generate the title section node and its properties
+ pep_title_node = nodes.section()
+ textnode = nodes.Text(pep_title_string, pep_title_string)
+ titlenode = nodes.title(pep_title_string, '', textnode)
+ name = states.normalize_name(titlenode.astext())
+ pep_title_node['names'].append(name)
+ pep_title_node += titlenode
+
+ # Insert the title node as the root element, move children down
+ document_children = self.document.children
+ self.document.children = [pep_title_node]
+ pep_title_node.extend(document_children)
+ self.document.note_implicit_target(pep_title_node, pep_title_node)
From 2aa058227d54106041d9e88447447c7d1c873ba5 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 25 Apr 2020 23:43:59 +0100
Subject: [PATCH 007/110] Update conf
---
peps/conf.py => conf.py | 49 ++++++++---------------------------------
1 file changed, 9 insertions(+), 40 deletions(-)
rename peps/conf.py => conf.py (57%)
diff --git a/peps/conf.py b/conf.py
similarity index 57%
rename from peps/conf.py
rename to conf.py
index 26cb5a1e8bb..c39229a3ff2 100644
--- a/peps/conf.py
+++ b/conf.py
@@ -2,30 +2,24 @@
# -- Path setup --------------------------------------------------------------
-import docutils
-from docutils.readers import standalone
-from docutils.transforms import frontmatter, references, misc
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-#
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('.'))
+import os
+import sys
+sys.path.append(os.path.abspath('.'))
# -- Project information -----------------------------------------------------
-project = 'peps'
+project = 'PEPs'
copyright = '2020, AUTHNAME'
author = 'AUTHNAME'
# The full version, including alpha/beta/rc tags
release = '1.0.0'
+version = "1.x"
html_title = "Python Enhancement Proposals (PEPs)"
-html_short_title = "PEPs Home" #
+html_short_title = "PEPs Home"
html_show_copyright = False
html_show_sphinx = False
@@ -56,7 +50,8 @@
'venv',
'build',
'README.rst',
- 'CONTRIBUTING.rst'
+ 'CONTRIBUTING.rst',
+ 'requirements.txt',
]
# The name of the Pygments (syntax highlighting) style to use.
@@ -74,30 +69,4 @@
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
-
-def new_get_transforms(self):
- # Replicate result of standalone.Reader.get_transforms()
- transforms = docutils.readers.Reader.get_transforms(self) + [
- references.Substitutions,
- references.PropagateTargets,
- frontmatter.DocTitle,
- frontmatter.SectionSubTitle,
- frontmatter.DocInfo,
- references.AnonymousHyperlinks,
- references.IndirectHyperlinks,
- references.Footnotes,
- references.ExternalTargets,
- references.InternalTargets,
- references.DanglingReferences,
- misc.Transitions,
- ]
-
- # Explicitly remove these transforms, as we implement PEP-specific pre-processing
- # transforms.remove(frontmatter.DocTitle)
- # transforms.remove(frontmatter.SectionSubTitle)
- transforms.remove(frontmatter.DocInfo)
-
- return transforms
-
-
-standalone.Reader.get_transforms = new_get_transforms
+master_doc = 'contents'
\ No newline at end of file
From b4b93f2d58f3fa50020871df8b055481b6fe4dce Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 25 Apr 2020 23:53:24 +0100
Subject: [PATCH 008/110] clean imports
---
pepreader/pep_contents.py | 5 +++--
pepreader/pep_headers.py | 8 +++++---
pepreader/pep_parser.py | 12 ++++++------
pepreader/pep_title.py | 6 +++---
4 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/pepreader/pep_contents.py b/pepreader/pep_contents.py
index fd4e9c2a620..ac2c44f7e61 100644
--- a/pepreader/pep_contents.py
+++ b/pepreader/pep_contents.py
@@ -1,8 +1,9 @@
from docutils import nodes
-from docutils.transforms import Transform, parts
+from docutils import transforms
+from docutils.transforms import parts
-class PEPContents(Transform):
+class PEPContents(transforms.Transform):
"""
Insert an empty table of contents topic and a transform placeholder into
diff --git a/pepreader/pep_headers.py b/pepreader/pep_headers.py
index 3b602df43c0..098352a8c34 100644
--- a/pepreader/pep_headers.py
+++ b/pepreader/pep_headers.py
@@ -1,7 +1,9 @@
import re
-from docutils import nodes, utils
-from docutils.transforms import peps, Transform
+from docutils import nodes
+from docutils import utils
+from docutils import transforms
+from docutils.transforms import peps
class DataError(Exception):
@@ -9,7 +11,7 @@ class DataError(Exception):
# PEPHeaders is identical to docutils.transforms.peps.Headers excepting bdfl-delegate, sponsor & superseeded-by
-class PEPHeaders(Transform):
+class PEPHeaders(transforms.Transform):
"""
Process fields in a PEP's initial RFC-2822 header.
diff --git a/pepreader/pep_parser.py b/pepreader/pep_parser.py
index da6f29c98c9..92a69720cb6 100644
--- a/pepreader/pep_parser.py
+++ b/pepreader/pep_parser.py
@@ -1,7 +1,7 @@
from sphinx import parsers
-from .pep_headers import PEPHeaders
-from .pep_title import PEPTitle
-from .pep_contents import PEPContents
+from . import pep_headers
+from . import pep_title
+from . import pep_contents
@@ -16,8 +16,8 @@ def __init__(self):
def get_transforms(self):
transforms = super().get_transforms()
transforms.extend([
- PEPHeaders,
- PEPTitle,
- PEPContents,
+ pep_headers.PEPHeaders,
+ pep_title.PEPTitle,
+ pep_contents.PEPContents,
])
return transforms
diff --git a/pepreader/pep_title.py b/pepreader/pep_title.py
index c27dffbf8a8..6364e42287d 100644
--- a/pepreader/pep_title.py
+++ b/pepreader/pep_title.py
@@ -1,9 +1,9 @@
from docutils import nodes
-from docutils.transforms import Transform
-from docutils.parsers.rst import states, Directive
+import docutils.transforms as transforms
+from docutils.parsers.rst import states
-class PEPTitle(Transform):
+class PEPTitle(transforms.Transform):
"""
Insert an empty table of contents topic and a transform placeholder into
From 332b30b49b126fe2ffd1b3bf32ece0cd12690e6f Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 26 Apr 2020 02:53:30 +0100
Subject: [PATCH 009/110] get pep 0 generation to work
---
genpepindex.py | 68 ----
pep0/__init__.py | 1 -
pep0/output.py | 290 ------------------
pepreader/__init__.py | 5 +-
pepreader/generate_pep_index.py | 58 ++++
pep0/pep.py => pepreader/pep0.py | 176 +++++------
.../pep0_constants.py | 13 +-
pepreader/pep0_writer.py | 286 +++++++++++++++++
8 files changed, 443 insertions(+), 454 deletions(-)
delete mode 100755 genpepindex.py
delete mode 100644 pep0/__init__.py
delete mode 100644 pep0/output.py
create mode 100644 pepreader/generate_pep_index.py
rename pep0/pep.py => pepreader/pep0.py (59%)
rename pep0/constants.py => pepreader/pep0_constants.py (76%)
create mode 100644 pepreader/pep0_writer.py
diff --git a/genpepindex.py b/genpepindex.py
deleted file mode 100755
index 2ab6698a05a..00000000000
--- 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/pep0/__init__.py b/pep0/__init__.py
deleted file mode 100644
index b7db25411d0..00000000000
--- a/pep0/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# Empty
diff --git a/pep0/output.py b/pep0/output.py
deleted file mode 100644
index 10024c221b8..00000000000
--- a/pep0/output.py
+++ /dev/null
@@ -1,290 +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)
- # References for introduction footnotes
- emit_title("References", "references", output)
- print(constants.references, file=output)
- print(constants.footer, file=output)
diff --git a/pepreader/__init__.py b/pepreader/__init__.py
index 33c02d5f360..0b844fff03c 100644
--- a/pepreader/__init__.py
+++ b/pepreader/__init__.py
@@ -1,4 +1,4 @@
-"""Docutils CommonMark parser"""
+"""Sphinx extensions for performant PEP processing"""
from sphinx.application import Sphinx
__version__ = '1.0.0'
@@ -9,6 +9,7 @@ def setup(app: Sphinx):
from .pep_parser import PEPParser
from .generate_pep_index import create_pep_zero
+ app.connect("env-before-read-docs", create_pep_zero)
app.add_source_parser(PEPParser)
- return {'version': __version__, 'parallel_read_safe': True}
\ No newline at end of file
+ return {'version': __version__, 'parallel_read_safe': True, 'parallel_write_safe': True}
diff --git a/pepreader/generate_pep_index.py b/pepreader/generate_pep_index.py
new file mode 100644
index 00000000000..3ae3da29659
--- /dev/null
+++ b/pepreader/generate_pep_index.py
@@ -0,0 +1,58 @@
+"""Automatically create PEP 0 (the PEP index)
+
+This file generates and writes the PEP index to disk, ready for later
+processing by Sphinx. Firstly, we parse the individual PEP files, getting the
+RFC2822 header, and parsing and then validating that metadata.
+
+After collecting and validating all the PEP data, the creation of the index
+itself is in three steps:
+
+ 1. Output static text.
+ 2. Format an entry for the PEP.
+ 3. Output the PEP (both by the category and numerical index).
+
+We then add the newly created PEP 0 file to two Sphinx environment variables
+to allow it to be processed as normal.
+
+"""
+import re
+from operator import attrgetter
+from pathlib import Path
+
+from . import pep0
+from . import pep0_writer
+
+
+def create_pep_zero(_, env, docnames):
+ # app is unneeded by this function
+
+ # Read from root directory
+ path = Path('.')
+
+ pep_zero_filename = 'pep-0000'
+ peps = []
+ pep_pat = re.compile(r"pep-\d{4}") # Path.match() doesn't support regular expressions
+
+ for file_path in path.iterdir():
+ if not file_path.is_file():
+ continue # Skip directories etc.
+ if file_path.match('pep-0000*'):
+ continue # Skip pre-existing PEP 0 files
+ if pep_pat.match(str(file_path)) and file_path.suffix in (".txt", ".rst"):
+ file_path_absolute = path.joinpath(file_path).absolute()
+ pep_text = file_path_absolute.read_text("UTF8")
+ pep = pep0.PEP(pep_text, file_path_absolute)
+ if pep.number != int(file_path.stem[4:]):
+ raise pep0.PEPError(f'PEP number does not match file name ({file_path})', file_path, pep.number)
+ peps.append(pep)
+ peps.sort(key=attrgetter('number'))
+
+ pep_writer = pep0_writer.PEPZeroWriter()
+ pep0_text = pep_writer.write_pep0(peps)
+ with open(pep_zero_filename + ".rst", 'w', encoding='UTF-8') as pep0_file:
+ pep0_file.write(pep0_text)
+
+ # Add to files for builder
+ docnames.insert(1, pep_zero_filename)
+ # Add to files for writer
+ env.found_docs.add(pep_zero_filename)
diff --git a/pep0/pep.py b/pepreader/pep0.py
similarity index 59%
rename from pep0/pep.py
rename to pepreader/pep0.py
index e01518df539..29359173973 100644
--- a/pep0/pep.py
+++ b/pepreader/pep0.py
@@ -1,18 +1,15 @@
# -*- 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
+from . import pep0_constants
class PEPError(Exception):
-
def __init__(self, error, pep_file, pep_number=None):
super(PEPError, self).__init__(error)
self.filename = pep_file
@@ -21,9 +18,9 @@ def __init__(self, error, pep_file, pep_number=None):
def __str__(self):
error_msg = super(PEPError, self).__str__()
if self.number is not None:
- return "PEP %d: %r" % (self.number, error_msg)
+ return f"PEP {self.number}: {error_msg}"
else:
- return "(%s): %r" % (self.filename, error_msg)
+ return f"({self.filename}): {error_msg}"
class PEPParseError(PEPError):
@@ -69,16 +66,16 @@ def __init__(self, author_and_email_tuple):
name_sep = name.index(last_name_fragment)
self.first = name[:name_sep].rstrip()
self.last = last_name_fragment
- if self.last[1] == u'.':
+ if self.last[1] == ".":
# Add an escape to avoid docutils turning `v.` into `22.`.
- self.last = u'\\' + self.last
+ self.last = "\\" + self.last
self.suffix = suffix
if not self.first:
self.last_first = self.last
else:
- self.last_first = u', '.join([self.last, self.first])
+ self.last_first = ", ".join([self.last, self.first])
if self.suffix:
- self.last_first += u', ' + self.suffix
+ self.last_first += ", " + self.suffix
if self.last == "van Rossum":
# Special case for our beloved BDFL. :)
if self.first == "Guido":
@@ -86,8 +83,8 @@ def __init__(self, author_and_email_tuple):
elif self.first == "Just":
self.nick = "JvR"
else:
- raise ValueError("unknown van Rossum %r!" % self)
- self.last_first += " (%s)" % (self.nick,)
+ raise ValueError(f"unknown van Rossum {self}!")
+ self.last_first += f" ({self.nick})"
else:
self.nick = self.last
@@ -102,14 +99,15 @@ 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()
+ base = " ".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')
+ return unicodedata.normalize("NFKD", base)
- def _last_name(self, full_name):
+ @staticmethod
+ def _last_name(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
@@ -118,7 +116,7 @@ def _last_name(self, full_name):
through a comma, then drop the suffix.
"""
- name_partition = full_name.partition(u',')
+ name_partition = full_name.partition(",")
no_suffix = name_partition[0].strip()
suffix = name_partition[2].strip()
name_parts = no_suffix.split()
@@ -128,7 +126,7 @@ def _last_name(self, full_name):
else:
assert part_count > 2
if name_parts[-2].islower():
- return u' '.join(name_parts[-2:]), suffix
+ return " ".join(name_parts[-2:]), suffix
else:
return name_parts[-1], suffix
@@ -159,117 +157,118 @@ class PEP(object):
# 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),
- ('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),
- )
+ headers = (
+ ("PEP", True), ("Title", True), ("Version", False),
+ ("Last-Modified", False), ("Author", True), ("Sponsor", False),
+ ("BDFL-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")
+ type_values = ("Standards Track", "Informational", "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")
+ status_values = (
+ "Accepted", "Provisional", "Rejected", "Withdrawn",
+ "Deferred", "Final", "Active", "Draft", "Superseded",
+ )
- def __init__(self, pep_file):
+ def __init__(self, pep_file: str, filename: str):
"""Init object from an open PEP file object."""
# Parse the headers.
- self.filename = pep_file
+ self.filename = filename
pep_parser = HeaderParser()
- metadata = pep_parser.parse(pep_file)
+ metadata = pep_parser.parsestr(pep_file)
header_order = iter(self.headers)
+ current_header = ""
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)
+ raise PEPError(
+ "did not deal with "
+ f"{header_name} before having to handle {current_header}",
+ filename,
+ )
except StopIteration:
- raise PEPError("headers missing or out of order",
- pep_file.name)
+ raise PEPError("headers missing or out of order", filename)
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)
+ raise PEPError(f"PEP is missing its '{current_header}' header", filename)
except StopIteration:
pass
# 'PEP'.
try:
- self.number = int(metadata['PEP'])
+ self.number = int(metadata["PEP"])
except ValueError:
- raise PEPParseError("PEP number isn't an integer", pep_file.name)
+ raise PEPParseError("PEP number isn't an integer", filename)
# 'Title'.
- self.title = metadata['Title']
+ self.title = metadata["Title"]
# 'Type'.
- type_ = metadata['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)
+ raise PEPError(f"{type_} is not a valid Type value", filename, self.number)
self.type_ = type_
# 'Status'.
- status = metadata['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)
+ raise PEPError(f"{status} is not a valid Status value", filename, 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)
+ if status == "Active" and self.type_ not in ("Process", "Informational"):
+ raise PEPError(
+ "Only Process and Informational PEPs may " "have an Active status",
+ filename,
+ 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)
+ if status == "Provisional" and self.type_ != "Standards Track":
+ raise PEPError(
+ "Only Standards Track PEPs may " "have a Provisional status",
+ filename,
+ self.number,
+ )
self.status = status
# 'Author'.
- authors_and_emails = self._parse_author(metadata['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)
+ raise PEPError("no authors found", filename, self.number)
self.authors = list(map(Author, authors_and_emails))
- def _parse_author(self, data):
+ @staticmethod
+ def _parse_author(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[^,]+)')
+ angled = pep0_constants.text_type(r"(?P.+?) <(?P.+?)>")
+ paren = pep0_constants.text_type(r"(?P.+?) \((?P.+?)\)")
+ simple = pep0_constants.text_type(r"(?P[^,]+)")
author_list = []
for regex in (angled, paren, simple):
# Watch out for commas separating multiple names.
- regex += r'(,\s*)?'
+ 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('.'):
+ 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 = ''
+ author = ", ".join([prev_author, author])
+ if "email" not in match_dict:
+ email = ""
else:
- email = match_dict['email']
+ email = match_dict["email"]
author_list.append((author, email))
else:
# If authors were found then stop searching as only expect one
@@ -280,36 +279,37 @@ def _parse_author(self, data):
@property
def type_abbr(self):
- """Return the how the type is to be represented in the index."""
+ """Return 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' '
+ if self.status in ("Draft", "Active"):
+ return " "
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)
+ return ", ".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:
+ if len(self.title) <= pep0_constants.title_length:
return self.title
- wrapped_title = textwrap.wrap(self.title, constants.title_length - 4)
- return wrapped_title[0] + u' ...'
+ wrapped_title = textwrap.wrap(self.title, pep0_constants.title_length - 4)
+ return wrapped_title[0] + " ..."
- def __unicode__(self):
+ def __str__(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__
+ pep_info = {
+ "type": self.type_abbr,
+ "number": str(self.number),
+ "title": self.title_abbr,
+ "status": self.status_abbr,
+ "authors": self.author_abbr,
+ }
+ return pep0_constants.column_format(**pep_info)
diff --git a/pep0/constants.py b/pepreader/pep0_constants.py
similarity index 76%
rename from pep0/constants.py
rename to pepreader/pep0_constants.py
index e40293f44a9..57e96e9e1d7 100644
--- a/pep0/constants.py
+++ b/pepreader/pep0_constants.py
@@ -1,17 +1,20 @@
# -*- coding: utf-8 -*-
+from functools import partial
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)
+# column format is called as a function with a mapping containing field values
+column_format = partial(
+ "{type}{status}{number: >5} {title: <{title_length}} {authors}".format,
+ title_length=title_length
+)
header = """\
PEP: 0
Title: Index of Python Enhancement Proposals (PEPs)
Version: N/A
-Last-Modified: %s
+Last-Modified: {last_modified}
Author: python-dev
Status: Active
Type: Informational
@@ -31,7 +34,7 @@
.. [2] View PEP history online: https://github.com/python/peps
"""
-footer = """\
+footer = """\
..
Local Variables:
mode: indented-text
diff --git a/pepreader/pep0_writer.py b/pepreader/pep0_writer.py
new file mode 100644
index 00000000000..04902b9da7c
--- /dev/null
+++ b/pepreader/pep0_writer.py
@@ -0,0 +1,286 @@
+"""Code to handle the output of PEP 0."""
+import datetime
+import unicodedata
+
+from operator import attrgetter
+
+from . import pep0_constants
+from . import pep0
+
+# Type annotations
+from typing import List
+
+
+class PEPZeroWriter:
+ # 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"),
+ ]
+
+ def __init__(self):
+ self._output: List[str] = []
+
+ def output(self, content):
+ # Appends content argument to the _output list
+ self._output.append(content)
+
+ def emit_newline(self):
+ self.output('')
+
+ def emit_table_separator(self):
+ self.output(pep0_constants.table_separator)
+
+ def emit_author_table_separator(self, max_name_len):
+ author_table_separator = "=" * max_name_len + " " + "=" * len("email address")
+ self.output(author_table_separator)
+
+ def emit_column_headers(self):
+ """Output the column headers for the PEP indices."""
+ column_headers = {
+ "status": ".",
+ "type": ".",
+ "number": "PEP",
+ "title": "PEP Title",
+ "authors": "PEP Author(s)",
+ }
+ self.emit_table_separator()
+ self.output(pep0_constants.column_format(**column_headers))
+ self.emit_table_separator()
+
+ @staticmethod
+ 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 pep0.PEPError(f"unsorted ({pep.type_}/{pep.status})", pep.filename, pep.number)
+ return meta, info, provisional, accepted, open_, finished, historical, deferred, dead
+
+ @staticmethod
+ 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(f" {author}: {emails}")
+ raise ValueError(
+ "some authors have more than one email address listed:\n"
+ + "\n".join(err_output)
+ )
+
+ return valid_authors_dict
+
+ @staticmethod
+ def sort_authors(authors_dict):
+ authors_list = list(authors_dict.keys())
+ authors_list.sort(key=attrgetter("sort_by"))
+ return authors_list
+
+ @staticmethod
+ def normalized_last_first(name):
+ return len(unicodedata.normalize("NFC", name.last_first))
+
+ def emit_title(self, text, anchor, *, symbol="="):
+ self.output(".. _{anchor}:\n".format(anchor=anchor))
+ self.output(text)
+ self.output(symbol * len(text))
+ self.emit_newline()
+
+ def emit_subtitle(self, text, anchor):
+ self.emit_title(text, anchor, symbol="-")
+
+ def emit_pep_category(self, category, anchor, peps):
+ self.emit_subtitle(category, anchor)
+ self.emit_column_headers()
+ for pep in peps:
+ self.output(pep)
+ self.emit_table_separator()
+ self.emit_newline()
+
+ def write_pep0(self, peps: list):
+
+ # PEP metadata
+ today = datetime.date.today().strftime("%Y-%m-%d")
+ self.output(pep0_constants.header.format(last_modified=today))
+ self.emit_newline()
+
+ # Introduction
+ self.emit_title("Introduction", "intro")
+ self.output(pep0_constants.intro)
+ self.emit_newline()
+
+ # PEPs by category
+ self.emit_title("Index by Category", "by-category")
+ (meta, info, provisional, accepted, open_,
+ finished, historical, deferred, dead) = self.sort_peps(peps)
+ pep_categories = [
+ ("Meta-PEPs (PEPs about PEPs or Processes)", "by-category-meta", meta),
+ ("Other Informational PEPs", "by-category-other-info", info),
+ ("Provisional PEPs (provisionally accepted; interface may still change)", "by-category-provisional", provisional),
+ ("Accepted PEPs (accepted; may not be implemented yet)", "by-category-accepted", accepted),
+ ("Open PEPs (under consideration)", "by-category-open", open_),
+ ("Finished PEPs (done, with a stable interface)", "by-category-finished", finished),
+ ("Historical Meta-PEPs and Informational PEPs", "by-category-historical", historical),
+ ("Deferred PEPs (postponed pending further research or updates)", "by-category-deferred", deferred),
+ ("Abandoned, Withdrawn, and Rejected PEPs", "by-category-abandoned", dead),
+ ]
+ for pep_category in pep_categories:
+ category = pep_category[0]
+ anchor = pep_category[1]
+ peps = pep_category[2]
+ self.emit_pep_category(category, anchor, peps)
+
+ self.emit_newline()
+
+ # PEPs by number
+ self.emit_title("Numerical Index", "by-pep-number")
+ self.emit_column_headers()
+ prev_pep = 0
+ for pep in peps:
+ if pep.number - prev_pep > 1:
+ self.emit_newline()
+ self.output(pep0_constants.text_type(pep))
+ prev_pep = pep.number
+
+ self.emit_table_separator()
+ self.emit_newline()
+
+ # Reserved PEP numbers
+ self.emit_title("Reserved PEP Numbers", "reserved")
+ self.emit_column_headers()
+ for number, claimants in sorted(self.RESERVED):
+ self.output(pep0_constants.column_format(**{
+ "type": ".",
+ "status": ".",
+ "number": number,
+ "title": "RESERVED",
+ "authors": claimants,
+ }))
+
+ self.emit_table_separator()
+ self.emit_newline()
+
+ # PEP types key
+ self.emit_title("PEP Types Key", "type-key")
+ for type_ in sorted(pep0.PEP.type_values):
+ self.output(f" {type_[0]} - {type_} PEP")
+ self.emit_newline()
+
+ self.emit_newline()
+
+ # PEP status key
+ self.emit_title("PEP Status Key", "status-key")
+ for status in sorted(pep0.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)
+ self.output(msg)
+ self.emit_newline()
+
+ self.emit_newline()
+
+ # PEP owners
+ self.emit_title("Authors/Owners", "authors")
+ authors_dict = self.verify_email_addresses(peps)
+ max_name = max(authors_dict.keys(), key=self.normalized_last_first)
+ max_name_len = len(max_name.last_first)
+ self.emit_author_table_separator(max_name_len)
+ _author_header_fmt = f"{'Name':{max_name_len}} Email Address"
+ self.output(_author_header_fmt)
+ self.emit_author_table_separator(max_name_len)
+ sorted_authors = self.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,
+ )
+ self.output(_entry)
+ self.emit_author_table_separator(max_name_len)
+ self.emit_newline()
+ self.emit_newline()
+
+ # References for introduction footnotes
+ self.emit_title("References", "references")
+ self.output(pep0_constants.references)
+ self.output(pep0_constants.footer)
+
+ pep0_string = '\n'.join([str(s) for s in self._output])
+ return pep0_string
From a8744f291b563c367808e0e40d1fbcb1a6f2043d Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 26 Apr 2020 03:28:56 +0100
Subject: [PATCH 010/110] Update build and Makefile
---
Makefile | 34 ++++++++--------------------------
build.py | 10 +++++-----
2 files changed, 13 insertions(+), 31 deletions(-)
diff --git a/Makefile b/Makefile
index 213bb75325f..3c86efd9ce1 100644
--- a/Makefile
+++ b/Makefile
@@ -3,26 +3,12 @@
#
# Not really important, but convenient.
-PEP2HTML=pep2html.py
-
PYTHON=python3
-.SUFFIXES: .txt .html .rst
-
-.txt.html:
- @$(PYTHON) $(PEP2HTML) $<
-
-.rst.html:
- @$(PYTHON) $(PEP2HTML) $<
-
-TARGETS= $(patsubst %.rst,%.html,$(wildcard pep-????.rst)) $(patsubst %.txt,%.html,$(wildcard pep-????.txt)) pep-0000.html
-
-all: pep-0000.rst $(TARGETS)
-
-$(TARGETS): pep2html.py
+all: sphinx
-pep-0000.rst: $(wildcard pep-????.txt) $(wildcard pep-????.rst) $(wildcard pep0/*.py) genpepindex.py
- $(PYTHON) genpepindex.py .
+sphinx:
+ sphinx-build -j auto -b html . build
rss:
$(PYTHON) pep2rss.py .
@@ -32,7 +18,6 @@ install:
clean:
-rm pep-0000.rst
- -rm pep-0000.txt
-rm *.html
-rm -rf build
@@ -41,13 +26,10 @@ update:
venv:
$(PYTHON) -m venv venv
- ./venv/bin/python -m pip install -U docutils
+ ./venv/bin/python -m pip install -U docutils sphinx
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
+ mkdir -p package/peps
+ cp -R build/. package/peps
+ cp *.rss package/peps
+ tar -C package -czf package/peps.tar.gz peps
diff --git a/build.py b/build.py
index aae70f95aa9..7e24223597b 100644
--- a/build.py
+++ b/build.py
@@ -5,11 +5,11 @@
if __name__ == '__main__':
root_directory = Path('.').absolute()
- docs_directory = Path(root_directory, 'peps')
- source_directory = configuration_directory = docs_directory
- build_directory = Path(root_directory, '_build')
- doctree_directory = Path(build_directory, '.doctrees')
+ source_directory = root_directory
+ configuration_directory = source_directory
+ build_directory = root_directory / 'build'
+ doctree_directory = build_directory / '.doctrees'
builder = 'html'
app = Sphinx(source_directory, configuration_directory, build_directory, doctree_directory, builder)
- app.build(force_all=True)
+ app.build()
From 4844eb8bb35d82bd47e689006cb5b6d031a3c007 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 26 Apr 2020 03:37:38 +0100
Subject: [PATCH 011/110] Misc cleanup
---
conf.py | 13 ++++++-------
pepreader/pep_headers.py | 1 -
pepreader/pep_parser.py | 3 ---
3 files changed, 6 insertions(+), 11 deletions(-)
diff --git a/conf.py b/conf.py
index c39229a3ff2..70aeb93e0da 100644
--- a/conf.py
+++ b/conf.py
@@ -3,20 +3,19 @@
# -- Path setup --------------------------------------------------------------
-import os
import sys
-sys.path.append(os.path.abspath('.'))
+from pathlib import Path
+sys.path.append(Path('.').absolute())
# -- Project information -----------------------------------------------------
project = 'PEPs'
-copyright = '2020, AUTHNAME'
-author = 'AUTHNAME'
+copyright = '2020, PEP Authors'
+author = 'PEP Authors'
# The full version, including alpha/beta/rc tags
-release = '1.0.0'
-version = "1.x"
+# release = '1.0.0'
html_title = "Python Enhancement Proposals (PEPs)"
html_short_title = "PEPs Home"
@@ -69,4 +68,4 @@
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ['_static']
-master_doc = 'contents'
\ No newline at end of file
+master_doc = 'contents'
diff --git a/pepreader/pep_headers.py b/pepreader/pep_headers.py
index 098352a8c34..a5f3ce92c52 100644
--- a/pepreader/pep_headers.py
+++ b/pepreader/pep_headers.py
@@ -112,4 +112,3 @@ def apply(self):
para[:] = [nodes.reference("", pep_type, refuri=uri)]
elif name == "version" and len(body):
utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
-
diff --git a/pepreader/pep_parser.py b/pepreader/pep_parser.py
index 92a69720cb6..ad320d3e786 100644
--- a/pepreader/pep_parser.py
+++ b/pepreader/pep_parser.py
@@ -4,9 +4,6 @@
from . import pep_contents
-
-
-
class PEPParser(parsers.RSTParser):
supported = ("pep", "python-enhancement-proposal")
From bcd297c1c37aa6d9e6d10a37b3e5a8bd67596bee Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 26 Apr 2020 03:41:11 +0100
Subject: [PATCH 012/110] Add sphinx dependency
---
.travis.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index a2632a6fba6..acf7162b569 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,7 +7,7 @@ dist: xenial
cache: pip
before_install:
- - pip install docutils
+ - pip install docutils sphinx
script:
- make -j$(nproc)
From 17ccef39064618bc5b6e24667de254185fd63fc2 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 26 Apr 2020 04:26:45 +0100
Subject: [PATCH 013/110] update makefile
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 3c86efd9ce1..27bfbd07767 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ PYTHON=python3
all: sphinx
sphinx:
- sphinx-build -j auto -b html . build
+ $(PYTHON) build.py
rss:
$(PYTHON) pep2rss.py .
From 384262c49e5d4eccb788ed470a904c4a1f19f354 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 26 Apr 2020 07:14:50 +0100
Subject: [PATCH 014/110] fix for read the docs
---
conf.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/conf.py b/conf.py
index 70aeb93e0da..17a4200d663 100644
--- a/conf.py
+++ b/conf.py
@@ -5,7 +5,7 @@
import sys
from pathlib import Path
-sys.path.append(Path('.').absolute())
+sys.path.extend(str(Path('./pepreader').absolute()))
# -- Project information -----------------------------------------------------
From d4247030d84f0b77fb56773c0823e5760eea4806 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 26 Apr 2020 07:53:19 +0100
Subject: [PATCH 015/110] updates for contents
- exclude contents in transforms
- add custom text in contents.rst
---
contents.rst | 16 ++++++++++------
pepreader/pep_contents.py | 4 ++++
pepreader/pep_headers.py | 5 +++++
pepreader/pep_title.py | 5 +++++
4 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/contents.rst b/contents.rst
index ce25a569a64..1c8b3c2e276 100644
--- a/contents.rst
+++ b/contents.rst
@@ -1,15 +1,19 @@
-PEP: 9999
-Title: Sphinx Index Page
-.. peps documentation master file, created by
- sphinx-quickstart on Thu Apr 23 17:23:18 2020.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
+Python Enhancement Proposals (PEPs)
+***********************************
+
+
+
+Welcome!
+
+To get started, have a look at the :doc:`PEP Index`
+
.. toctree::
:maxdepth: 3
:titlesonly:
+ :hidden:
:glob:
:caption: Sphinx Table of Contents:
diff --git a/pepreader/pep_contents.py b/pepreader/pep_contents.py
index ac2c44f7e61..cb1b7ed92bc 100644
--- a/pepreader/pep_contents.py
+++ b/pepreader/pep_contents.py
@@ -1,3 +1,4 @@
+from pathlib import Path
from docutils import nodes
from docutils import transforms
from docutils.transforms import parts
@@ -13,6 +14,9 @@ class PEPContents(transforms.Transform):
default_priority = 380
def apply(self):
+ if not Path(self.document["source"]).match("pep-*"):
+ # not a PEP file
+ return
title = nodes.title('', 'contents')
topic = nodes.topic('', title, classes=['contents'])
name = nodes.fully_normalize_name('contents')
diff --git a/pepreader/pep_headers.py b/pepreader/pep_headers.py
index a5f3ce92c52..55658fd345e 100644
--- a/pepreader/pep_headers.py
+++ b/pepreader/pep_headers.py
@@ -1,4 +1,5 @@
import re
+from pathlib import Path
from docutils import nodes
from docutils import utils
@@ -29,6 +30,10 @@ def apply(self):
# @@@ replace these DataErrors with proper system messages
raise DataError("Document tree is empty.")
+ if not Path(self.document["source"]).match("pep-*"):
+ # not a PEP file
+ return
+
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.")
diff --git a/pepreader/pep_title.py b/pepreader/pep_title.py
index 6364e42287d..db9ac0e25f0 100644
--- a/pepreader/pep_title.py
+++ b/pepreader/pep_title.py
@@ -1,3 +1,4 @@
+from pathlib import Path
from docutils import nodes
import docutils.transforms as transforms
from docutils.parsers.rst import states
@@ -14,6 +15,10 @@ class PEPTitle(transforms.Transform):
default_priority = 335
def apply(self):
+ if not Path(self.document["source"]).match("pep-*"):
+ # not a PEP file
+ return
+
# Directory to hold the PEP's RFC2822 header details, to extract a titke string
pep_header_details = {}
From 02618e058a58fb2aabe1e3bf08e7e6c267cb2388 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 26 Apr 2020 17:36:21 +0100
Subject: [PATCH 016/110] support pythondotorg build process
---
Makefile | 5 ++++-
package.py | 40 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 44 insertions(+), 1 deletion(-)
create mode 100644 package.py
diff --git a/Makefile b/Makefile
index 27bfbd07767..98af7c7fd3b 100644
--- a/Makefile
+++ b/Makefile
@@ -30,6 +30,9 @@ venv:
package: all rss
mkdir -p package/peps
- cp -R build/. package/peps
+ $(PYTHON) package.py
+ cp pep-*.txt build/peps/
+ cp pep-*.rst build/peps/
+ cp *.png build/peps/
cp *.rss package/peps
tar -C package -czf package/peps.tar.gz peps
diff --git a/package.py b/package.py
new file mode 100644
index 00000000000..3f35ee56dd6
--- /dev/null
+++ b/package.py
@@ -0,0 +1,40 @@
+"""Transforms Sphinx HTML output into python.org input format"""
+
+from bs4 import BeautifulSoup
+from pathlib import Path
+
+if __name__ == '__main__':
+ root_path = Path(".")
+ html_path = root_path.joinpath('_build')
+ package_path = root_path.joinpath("package/peps")
+ package_path.mkdir(parents=True, exist_ok=True)
+
+ for file_path in html_path.glob("pep-*"):
+ if file_path.suffix not in '.html':
+ continue
+
+ soup = BeautifulSoup(file_path.read_text(encoding="UTF8"), 'lxml')
+ contents = soup.find('div', class_="body").div
+ dl = contents.find('dl')
+ for tag in dl.findChildren():
+ if tag.name == "dt":
+ tag.name = "th"
+ tag.find_next_sibling().name = "td"
+ tag.attrs['class'] = 'field-name'
+ tag.find_next_sibling()['class'] = 'field-body'
+ tr = soup.new_tag("tr", **{'class': 'field'})
+ tag.insert_before(tr)
+ tr.insert(0, tag.find_next_sibling())
+ tr.insert(0, tag)
+
+ dl.name = 'tbody'
+ tbl = soup.new_tag('table', **{'class': dl['class']})
+ dl.wrap(tbl)
+ dl['class'] = []
+ tbl.insert(0, soup.new_tag("col", **{'class': "field-body"}))
+ tbl.insert(0, soup.new_tag("col", **{'class': "field-name"}))
+
+ write_path = Path('./package/peps') / file_path.name
+ write_path.write_text(str(contents), encoding="UTF8")
+
+ del soup, contents, dl, tbl
From cf1558383659e8903ff782d135da4d49d8be4e9e Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Mon, 27 Apr 2020 18:59:25 +0100
Subject: [PATCH 017/110] fix urls for read the docs
---
docutils.conf | 4 +--
pepreader/__init__.py | 3 ++
pepreader/pep_headers.py | 14 ++++----
pepreader/pep_role.py | 13 +++++++
pepreader/pep_zero.py | 76 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 102 insertions(+), 8 deletions(-)
create mode 100644 pepreader/pep_role.py
create mode 100644 pepreader/pep_zero.py
diff --git a/docutils.conf b/docutils.conf
index e2f4c8cc16e..b9b28283358 100644
--- a/docutils.conf
+++ b/docutils.conf
@@ -15,7 +15,7 @@ template: pyramid-pep-template
embed-stylesheet: 0
# path to PEPs, for template:
-pep-home: /dev/peps/
+pep-home: /
# base URL for PEP references (no host so mirrors work):
-pep-base-url: /dev/peps/
+pep-base-url: /
diff --git a/pepreader/__init__.py b/pepreader/__init__.py
index 0b844fff03c..95496374c82 100644
--- a/pepreader/__init__.py
+++ b/pepreader/__init__.py
@@ -2,14 +2,17 @@
from sphinx.application import Sphinx
__version__ = '1.0.0'
+pep_url = "pep-{:0>4}.html"
def setup(app: Sphinx):
"""Initialize Sphinx extension."""
from .pep_parser import PEPParser
from .generate_pep_index import create_pep_zero
+ from .pep_role import PEPRole
app.connect("env-before-read-docs", create_pep_zero)
app.add_source_parser(PEPParser)
+ app.add_role('pep', PEPRole())
return {'version': __version__, 'parallel_read_safe': True, 'parallel_write_safe': True}
diff --git a/pepreader/pep_headers.py b/pepreader/pep_headers.py
index 55658fd345e..49cceacf190 100644
--- a/pepreader/pep_headers.py
+++ b/pepreader/pep_headers.py
@@ -5,6 +5,8 @@
from docutils import utils
from docutils import transforms
from docutils.transforms import peps
+from pepreader import pep_zero
+import pepreader
class DataError(Exception):
@@ -20,8 +22,8 @@ class PEPHeaders(transforms.Transform):
default_priority = 330
- pep_url = "pep-%04d"
- pep_cvs_url = "https://github.com/python/peps/blob/master/pep-%04d.txt"
+ pep_url = pepreader.pep_url
+ pep_cvs_url = "https://github.com/python/peps/blob/master/pep-{:0>4}.txt"
rcs_keyword_substitutions = [(re.compile(r"\$[a-zA-Z]+: (.+) \$$"), r"\1")]
@@ -45,7 +47,7 @@ def apply(self):
value = pep_field[1].astext()
try:
pep = int(value)
- cvs_url = self.pep_cvs_url % pep
+ cvs_url = self.pep_cvs_url.format(pep)
except ValueError:
pep = value
msg = self.document.reporter.warning(
@@ -66,7 +68,7 @@ def apply(self):
if pep == 0:
# Special processing for PEP 0.
- pending = nodes.pending(peps.PEPZero)
+ pending = nodes.pending(pep_zero.PEPZero)
self.document.insert(1, pending)
self.document.note_pending(pending)
@@ -103,7 +105,7 @@ def apply(self):
newbody.append(nodes.reference(
refpep, refpep,
refuri=(self.document.settings.pep_base_url
- + self.pep_url % pepno)))
+ + self.pep_url.format(pepno))))
newbody.append(space)
para[:] = newbody[:-1] # drop trailing space
elif name == "last-modified":
@@ -113,7 +115,7 @@ def apply(self):
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
+ uri = self.document.settings.pep_base_url + self.pep_url.format(12)
para[:] = [nodes.reference("", pep_type, refuri=uri)]
elif name == "version" and len(body):
utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
diff --git a/pepreader/pep_role.py b/pepreader/pep_role.py
new file mode 100644
index 00000000000..341c19f1849
--- /dev/null
+++ b/pepreader/pep_role.py
@@ -0,0 +1,13 @@
+from sphinx import roles
+import pepreader
+
+
+class PEPRole(roles.PEP):
+
+ def build_uri(self) -> str:
+ base_url = self.inliner.document.settings.pep_base_url
+ ret = self.target.split('#', 1)
+ if len(ret) == 2:
+ return base_url + (pepreader.pep_url + '#{}').format(int(ret[0]), ret[1])
+ else:
+ return base_url + pepreader.pep_url.format(int(ret[0]))
\ No newline at end of file
diff --git a/pepreader/pep_zero.py b/pepreader/pep_zero.py
new file mode 100644
index 00000000000..a487bfcbbb3
--- /dev/null
+++ b/pepreader/pep_zero.py
@@ -0,0 +1,76 @@
+from docutils import nodes
+from docutils import transforms
+from docutils.transforms import peps
+import pepreader
+
+
+class PEPZero(transforms.Transform):
+
+ """
+ Special processing for PEP 0.
+ """
+
+ default_priority = 760
+
+ def apply(self):
+ visitor = PEPZeroSpecial(self.document)
+ self.document.walk(visitor)
+ self.startnode.parent.remove(self.startnode)
+
+
+class PEPZeroSpecial(nodes.SparseNodeVisitor):
+
+ """
+ Perform the special processing needed by PEP 0:
+
+ - Mask email addresses.
+
+ - Link PEP numbers in the second column of 4-column tables to the PEPs
+ themselves.
+ """
+
+ pep_url = pepreader.pep_url
+
+ def __init__(self, document):
+ super().__init__(document)
+ self.pep_table = None
+ self.entry = None
+
+ def unknown_visit(self, node):
+ pass
+
+ @staticmethod
+ def visit_reference(node):
+ node.replace_self(peps.mask_email(node))
+
+ @staticmethod
+ def visit_field_list(node):
+ if 'rfc2822' in node['classes']:
+ raise nodes.SkipNode
+
+ def visit_tgroup(self, node):
+ self.pep_table = node['cols'] == 4
+ self.entry = 0
+
+ def visit_colspec(self, node):
+ self.entry += 1
+ if self.pep_table and self.entry == 2:
+ node['classes'].append('num')
+
+ def visit_row(self, node):
+ self.entry = 0
+
+ def visit_entry(self, node):
+ self.entry += 1
+ if self.pep_table and self.entry == 2 and len(node) == 1:
+ node['classes'].append('num')
+ p = node[0]
+ if isinstance(p, nodes.paragraph) and len(p) == 1:
+ text = p.astext()
+ try:
+ pep = int(text)
+ ref = (self.document.settings.pep_base_url
+ + self.pep_url.format(pep))
+ p[0] = nodes.reference(text, text, refuri=ref)
+ except ValueError:
+ pass
From 408bec4bb7eb1ca550d14b3317ffe53e8c28d61e Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Mon, 27 Apr 2020 19:08:08 +0100
Subject: [PATCH 018/110] fix import error
---
pepreader/pep_role.py | 36 ++++++++++++++++++++++++++++++++++--
1 file changed, 34 insertions(+), 2 deletions(-)
diff --git a/pepreader/pep_role.py b/pepreader/pep_role.py
index 341c19f1849..fa1572a0fa0 100644
--- a/pepreader/pep_role.py
+++ b/pepreader/pep_role.py
@@ -1,8 +1,40 @@
-from sphinx import roles
+from typing import List, Tuple
+
+from docutils import nodes
+from docutils.nodes import Node, system_message
+
+from sphinx import addnodes
+from sphinx.locale import _
+from sphinx.util.docutils import ReferenceRole
+
import pepreader
-class PEPRole(roles.PEP):
+class PEPRole(ReferenceRole):
+ def run(self) -> Tuple[List[Node], List[system_message]]:
+ target_id = 'index-%s' % self.env.new_serialno('index')
+ entries = [('single', _('Python Enhancement Proposals; PEP %s') % self.target,
+ target_id, '', None)]
+
+ index = addnodes.index(entries=entries)
+ target = nodes.target('', '', ids=[target_id])
+ self.inliner.document.note_explicit_target(target)
+
+ try:
+ refuri = self.build_uri()
+ reference = nodes.reference('', '', internal=False, refuri=refuri, classes=['pep'])
+ if self.has_explicit_title:
+ reference += nodes.strong(self.title, self.title)
+ else:
+ title = "PEP " + self.title
+ reference += nodes.strong(title, title)
+ except ValueError:
+ msg = self.inliner.reporter.error('invalid PEP number %s' % self.target,
+ line=self.lineno)
+ prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
+ return [prb], [msg]
+
+ return [index, target, reference], []
def build_uri(self) -> str:
base_url = self.inliner.document.settings.pep_base_url
From 54ee81eb6c3a542ef9ee04c7aa45dbaf2ab7b297 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Mon, 27 Apr 2020 19:27:10 +0100
Subject: [PATCH 019/110] make compatible with read the docs
- RtD still runs sphinx 1.8.5 so we have had to add compatibility
---
pepreader/pep_role.py | 85 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 82 insertions(+), 3 deletions(-)
diff --git a/pepreader/pep_role.py b/pepreader/pep_role.py
index fa1572a0fa0..84463875a35 100644
--- a/pepreader/pep_role.py
+++ b/pepreader/pep_role.py
@@ -1,16 +1,95 @@
-from typing import List, Tuple
+import re
+
+from typing import List, Tuple, Dict
from docutils import nodes
from docutils.nodes import Node, system_message
+from docutils.parsers.rst.states import Inliner
+from docutils.utils import unescape
from sphinx import addnodes
+from sphinx.errors import SphinxError
from sphinx.locale import _
-from sphinx.util.docutils import ReferenceRole
import pepreader
+if False:
+ # For type annotation
+ from typing import Type # for python3.5.1
+ from sphinx.builders import Builder
+ from sphinx.config import Config
+ from sphinx.environment import BuildEnvironment
+
+class PEPRole:
+ """A base class for reference roles.
+
+ The reference roles can accpet ``link title `` style as a text for
+ the role. The parsed result; link title and target will be stored to
+ ``self.title`` and ``self.target``.
+ """
+ has_explicit_title = None #: A boolean indicates the role has explicit title or not.
+ disabled = False #: A boolean indicates the reference is disabled.
+ title = None #: The link title for the interpreted text.
+ target = None #: The link target for the interpreted text.
+
+ # \x00 means the "<" was backslash-escaped
+ explicit_title_re = re.compile(r'^(.+?)\s*(?$', re.DOTALL)
+
+ # From ReferenceRole
+ def __call__(self, name: str, rawtext: str, text: str, lineno: int,
+ inliner: Inliner, options: Dict = {}, content: List[str] = []
+ ) -> Tuple[List[Node], List[system_message]]:
+ # if the first character is a bang, don't cross-reference at all
+ self.disabled = text.startswith('!')
+
+ matched = self.explicit_title_re.match(text)
+ if matched:
+ self.has_explicit_title = True
+ self.title = unescape(matched.group(1))
+ self.target = unescape(matched.group(2))
+ else:
+ self.has_explicit_title = False
+ self.title = unescape(text)
+ self.target = unescape(text)
+
+ # From SphinxRole
+ self.rawtext = rawtext
+ self.text = unescape(text)
+ self.lineno = lineno
+ self.inliner = inliner
+ self.options = options
+ self.content = content
+
+ # guess role type
+ if name:
+ self.name = name.lower()
+ else:
+ self.name = self.env.temp_data.get('default_role')
+ if not self.name:
+ self.name = self.env.config.default_role
+ if not self.name:
+ raise SphinxError('cannot determine default role!')
+
+ return self.run()
+
+ @property
+ def env(self) -> "BuildEnvironment":
+ """Reference to the :class:`.BuildEnvironment` object."""
+ return self.inliner.document.settings.env
+
+ @property
+ def config(self) -> "Config":
+ """Reference to the :class:`.Config` object."""
+ return self.env.config
+
+ def get_source_info(self, lineno: int = None) -> Tuple[str, int]:
+ if lineno is None:
+ lineno = self.lineno
+ return self.inliner.reporter.get_source_and_line(lineno) # type: ignore
+
+ def set_source_info(self, node: Node, lineno: int = None) -> None:
+ node.source, node.line = self.get_source_info(lineno)
-class PEPRole(ReferenceRole):
def run(self) -> Tuple[List[Node], List[system_message]]:
target_id = 'index-%s' % self.env.new_serialno('index')
entries = [('single', _('Python Enhancement Proposals; PEP %s') % self.target,
From fc37a1ca745c021e11acb467bb479f0b5dc697fd Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Mon, 27 Apr 2020 19:37:00 +0100
Subject: [PATCH 020/110] update base url
---
docutils.conf | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docutils.conf b/docutils.conf
index b9b28283358..8becc5f3062 100644
--- a/docutils.conf
+++ b/docutils.conf
@@ -15,7 +15,7 @@ template: pyramid-pep-template
embed-stylesheet: 0
# path to PEPs, for template:
-pep-home: /
+pep-home: /en/latest/
# base URL for PEP references (no host so mirrors work):
-pep-base-url: /
+pep-base-url: /en/latest/
From 56115d2e0dce65bc8cf568b375ceb671241f26bc Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Mon, 27 Apr 2020 20:19:56 +0100
Subject: [PATCH 021/110] Fix numerical index (pep list was being overwritten)
---
pepreader/pep0_writer.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pepreader/pep0_writer.py b/pepreader/pep0_writer.py
index 04902b9da7c..e7ec7b072b7 100644
--- a/pepreader/pep0_writer.py
+++ b/pepreader/pep0_writer.py
@@ -197,8 +197,8 @@ def write_pep0(self, peps: list):
for pep_category in pep_categories:
category = pep_category[0]
anchor = pep_category[1]
- peps = pep_category[2]
- self.emit_pep_category(category, anchor, peps)
+ peps_in_category = pep_category[2]
+ self.emit_pep_category(category, anchor, peps_in_category)
self.emit_newline()
From 9f80ed453d491f14a7fd7df26ef5f1d323dc98a6 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Mon, 27 Apr 2020 23:35:10 +0100
Subject: [PATCH 022/110] Fix name parsing
---
pepreader/pep0.py | 87 +++++++++++++++++++++++++++++++++--------------
1 file changed, 61 insertions(+), 26 deletions(-)
diff --git a/pepreader/pep0.py b/pepreader/pep0.py
index 29359173973..ddcff82ac99 100644
--- a/pepreader/pep0.py
+++ b/pepreader/pep0.py
@@ -59,23 +59,29 @@ class Author(object):
def __init__(self, author_and_email_tuple):
"""Parse the name and email address of an author."""
+ self.first = self.last = ''
+
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] == ".":
- # Add an escape to avoid docutils turning `v.` into `22.`.
- self.last = "\\" + self.last
- self.suffix = suffix
- if not self.first:
- self.last_first = self.last
+
+ name_dict = self._parse_name(name)
+ self.suffix = name_dict.get("suffix")
+ if name_dict.get("name"):
+ self.last_first = name_dict["name"]
+ self.nick = name_dict["name"]
else:
+ self.first = name_dict["forename"].rstrip()
+ self.last = name_dict["surname"]
+ if self.last[1] == ".":
+ # Add an escape to avoid docutils turning `v.` into `22.`.
+ self.last = "\\" + self.last
self.last_first = ", ".join([self.last, self.first])
- if self.suffix:
- self.last_first += ", " + self.suffix
+ self.nick = self.last
+
+ if self.suffix:
+ self.last_first += ", " + self.suffix
+
if self.last == "van Rossum":
# Special case for our beloved BDFL. :)
if self.first == "Guido":
@@ -85,8 +91,6 @@ def __init__(self, author_and_email_tuple):
else:
raise ValueError(f"unknown van Rossum {self}!")
self.last_first += f" ({self.nick})"
- else:
- self.nick = self.last
def __hash__(self):
return hash(self.first_last)
@@ -107,7 +111,7 @@ def sort_by(self):
return unicodedata.normalize("NFKD", base)
@staticmethod
- def _last_name(full_name):
+ def _parse_name(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
@@ -116,19 +120,50 @@ def _last_name(full_name):
through a comma, then drop the suffix.
"""
- name_partition = full_name.partition(",")
- 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
+ possible_suffixes = ["Jr", "Jr.", "II", "III"]
+ special_cases = ["The Python core team and community"]
+
+ if full_name in special_cases:
+ return {"name": full_name}
+
+ suffix_partition = full_name.partition(",")
+ pre_suffix = suffix_partition[0].strip()
+ suffix = suffix_partition[2].strip()
+
+ name_parts = pre_suffix.split(" ")
+ num_parts = len(name_parts)
+ name = {"suffix": suffix}
+
+ if num_parts == 0:
+ raise ValueError("Name is empty!")
+ elif num_parts == 1:
+ name.update({"name": name_parts[0]})
+ elif num_parts == 2:
+ name.update({"forename": name_parts[0], "surname": name_parts[1]})
+ elif num_parts > 2:
+ # handles III etc.
+ if name_parts[-1] in possible_suffixes:
+ new_suffix = " ".join([*name_parts[-1:], suffix]).strip()
+ name_parts.pop(-1)
+ name.update(suffix=new_suffix)
+
+ # handles von, van, v. etc.
if name_parts[-2].islower():
- return " ".join(name_parts[-2:]), suffix
+ forename = " ".join(name_parts[:-2])
+ surname = " ".join(name_parts[-2:])
+ name.update({"forename": forename, "surname": surname})
+ # handles double surnames after a middle initial (e.g.
+ elif any(s.endswith(".") for s in name_parts):
+ split_position = [i for i, x in enumerate(name_parts) if x.endswith(".")][-1] + 1
+ forename = " ".join(name_parts[:split_position])
+ surname = " ".join(name_parts[split_position:])
+ name.update({"forename": forename, "surname": surname})
else:
- return name_parts[-1], suffix
+ forename = " ".join(name_parts[:-1])
+ surname = " ".join(name_parts[-1:])
+ name.update({"forename": forename, "surname": surname})
+
+ return name
class PEP(object):
From 59a4fefc3992ccaf096cbc9db59f8bd6c5201dcd Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 02:24:36 +0100
Subject: [PATCH 023/110] Update name parsing in PEP 0
---
pepreader/pep0.py | 26 +++++++++++++++-----------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/pepreader/pep0.py b/pepreader/pep0.py
index ddcff82ac99..190eb17d02f 100644
--- a/pepreader/pep0.py
+++ b/pepreader/pep0.py
@@ -112,12 +112,14 @@ def sort_by(self):
@staticmethod
def _parse_name(full_name):
- """Find the last name (or nickname) of a full name.
+ """Decompose a full name into parts.
- 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.
+ If a mononym (e.g, 'Aahz') then return the full name. If there are
+ suffixes in the name (e.g. ', Jr.' or 'III'), then find and extract
+ them. If there is a middle initial followed by a full stop, then
+ combine the following words into a surname (e.g. N. Vander Weele). If
+ there is a leading, lowercase portion to the last name (e.g. 'van' or
+ 'von') then include it in the surname.
"""
possible_suffixes = ["Jr", "Jr.", "II", "III"]
@@ -137,9 +139,9 @@ def _parse_name(full_name):
if num_parts == 0:
raise ValueError("Name is empty!")
elif num_parts == 1:
- name.update({"name": name_parts[0]})
+ name.update(name=name_parts[0])
elif num_parts == 2:
- name.update({"forename": name_parts[0], "surname": name_parts[1]})
+ name.update(forename=name_parts[0], surname=name_parts[1])
elif num_parts > 2:
# handles III etc.
if name_parts[-1] in possible_suffixes:
@@ -151,17 +153,19 @@ def _parse_name(full_name):
if name_parts[-2].islower():
forename = " ".join(name_parts[:-2])
surname = " ".join(name_parts[-2:])
- name.update({"forename": forename, "surname": surname})
- # handles double surnames after a middle initial (e.g.
+ name.update(forename=forename, surname=surname)
+
+ # handles double surnames after a middle initial (e.g. N. Vander Weele)
elif any(s.endswith(".") for s in name_parts):
split_position = [i for i, x in enumerate(name_parts) if x.endswith(".")][-1] + 1
forename = " ".join(name_parts[:split_position])
surname = " ".join(name_parts[split_position:])
- name.update({"forename": forename, "surname": surname})
+ name.update(forename=forename, surname=surname)
+
else:
forename = " ".join(name_parts[:-1])
surname = " ".join(name_parts[-1:])
- name.update({"forename": forename, "surname": surname})
+ name.update(forename=forename, surname=surname)
return name
From 78a97b4e5bb2957d1b8c1119ced4984d22f2fd1e Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 02:25:00 +0100
Subject: [PATCH 024/110] Include requirements file
---
pepreader/pep_role.py | 117 ++----------------------------------------
requirements.txt | 4 ++
2 files changed, 7 insertions(+), 114 deletions(-)
create mode 100644 requirements.txt
diff --git a/pepreader/pep_role.py b/pepreader/pep_role.py
index 84463875a35..b4632668a87 100644
--- a/pepreader/pep_role.py
+++ b/pepreader/pep_role.py
@@ -1,119 +1,8 @@
-import re
-
-from typing import List, Tuple, Dict
-
-from docutils import nodes
-from docutils.nodes import Node, system_message
-from docutils.parsers.rst.states import Inliner
-from docutils.utils import unescape
-
-from sphinx import addnodes
-from sphinx.errors import SphinxError
-from sphinx.locale import _
-
+from sphinx import roles
import pepreader
-if False:
- # For type annotation
- from typing import Type # for python3.5.1
- from sphinx.builders import Builder
- from sphinx.config import Config
- from sphinx.environment import BuildEnvironment
-
-class PEPRole:
- """A base class for reference roles.
-
- The reference roles can accpet ``link title `` style as a text for
- the role. The parsed result; link title and target will be stored to
- ``self.title`` and ``self.target``.
- """
- has_explicit_title = None #: A boolean indicates the role has explicit title or not.
- disabled = False #: A boolean indicates the reference is disabled.
- title = None #: The link title for the interpreted text.
- target = None #: The link target for the interpreted text.
-
- # \x00 means the "<" was backslash-escaped
- explicit_title_re = re.compile(r'^(.+?)\s*(?$', re.DOTALL)
-
- # From ReferenceRole
- def __call__(self, name: str, rawtext: str, text: str, lineno: int,
- inliner: Inliner, options: Dict = {}, content: List[str] = []
- ) -> Tuple[List[Node], List[system_message]]:
- # if the first character is a bang, don't cross-reference at all
- self.disabled = text.startswith('!')
-
- matched = self.explicit_title_re.match(text)
- if matched:
- self.has_explicit_title = True
- self.title = unescape(matched.group(1))
- self.target = unescape(matched.group(2))
- else:
- self.has_explicit_title = False
- self.title = unescape(text)
- self.target = unescape(text)
-
- # From SphinxRole
- self.rawtext = rawtext
- self.text = unescape(text)
- self.lineno = lineno
- self.inliner = inliner
- self.options = options
- self.content = content
-
- # guess role type
- if name:
- self.name = name.lower()
- else:
- self.name = self.env.temp_data.get('default_role')
- if not self.name:
- self.name = self.env.config.default_role
- if not self.name:
- raise SphinxError('cannot determine default role!')
-
- return self.run()
-
- @property
- def env(self) -> "BuildEnvironment":
- """Reference to the :class:`.BuildEnvironment` object."""
- return self.inliner.document.settings.env
-
- @property
- def config(self) -> "Config":
- """Reference to the :class:`.Config` object."""
- return self.env.config
-
- def get_source_info(self, lineno: int = None) -> Tuple[str, int]:
- if lineno is None:
- lineno = self.lineno
- return self.inliner.reporter.get_source_and_line(lineno) # type: ignore
-
- def set_source_info(self, node: Node, lineno: int = None) -> None:
- node.source, node.line = self.get_source_info(lineno)
-
- def run(self) -> Tuple[List[Node], List[system_message]]:
- target_id = 'index-%s' % self.env.new_serialno('index')
- entries = [('single', _('Python Enhancement Proposals; PEP %s') % self.target,
- target_id, '', None)]
-
- index = addnodes.index(entries=entries)
- target = nodes.target('', '', ids=[target_id])
- self.inliner.document.note_explicit_target(target)
-
- try:
- refuri = self.build_uri()
- reference = nodes.reference('', '', internal=False, refuri=refuri, classes=['pep'])
- if self.has_explicit_title:
- reference += nodes.strong(self.title, self.title)
- else:
- title = "PEP " + self.title
- reference += nodes.strong(title, title)
- except ValueError:
- msg = self.inliner.reporter.error('invalid PEP number %s' % self.target,
- line=self.lineno)
- prb = self.inliner.problematic(self.rawtext, self.rawtext, msg)
- return [prb], [msg]
- return [index, target, reference], []
+class PEPRole(roles.PEP):
def build_uri(self) -> str:
base_url = self.inliner.document.settings.pep_base_url
@@ -121,4 +10,4 @@ def build_uri(self) -> str:
if len(ret) == 2:
return base_url + (pepreader.pep_url + '#{}').format(int(ret[0]), ret[1])
else:
- return base_url + pepreader.pep_url.format(int(ret[0]))
\ No newline at end of file
+ return base_url + pepreader.pep_url.format(int(ret[0]))
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 00000000000..241e78f21e3
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,4 @@
+# Requirements for building PEPs with Sphinx
+
+sphinx >= 3.0.3
+docutils >= 0.16
From 9a3ca3d2ef92d8f42bf3efa4e7b502d41fea8472 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 02:26:38 +0100
Subject: [PATCH 025/110] Update ToC to remove title
---
pepreader/pep_contents.py | 28 ++++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/pepreader/pep_contents.py b/pepreader/pep_contents.py
index cb1b7ed92bc..623f99b2ea3 100644
--- a/pepreader/pep_contents.py
+++ b/pepreader/pep_contents.py
@@ -17,13 +17,37 @@ def apply(self):
if not Path(self.document["source"]).match("pep-*"):
# not a PEP file
return
- title = nodes.title('', 'contents')
+ title = nodes.title('', 'Contents')
topic = nodes.topic('', title, classes=['contents'])
name = nodes.fully_normalize_name('contents')
if not self.document.has_name(name):
topic['names'].append(name)
self.document.note_implicit_target(topic)
- pending = nodes.pending(parts.Contents)
+ pending = nodes.pending(Contents)
topic += pending
self.document.children[0].insert(2, topic)
self.document.note_pending(pending)
+
+
+class Contents(parts.Contents):
+ def apply(self):
+ try: # let the writer (or output software) build the contents list?
+ toc_by_writer = self.document.settings.use_latex_toc
+ except AttributeError:
+ toc_by_writer = False
+
+ details = self.startnode.details
+ startnode = self.document[0]
+ self.toc_id = self.startnode.parent['ids'][0]
+ self.backlinks = self.document.settings.toc_backlinks
+
+ if toc_by_writer:
+ # move customization settings to the parent node
+ self.startnode.parent.attributes.update(details)
+ self.startnode.parent.remove(self.startnode)
+ else:
+ contents = self.build_contents(startnode)
+ if len(contents):
+ self.startnode.replace_self(contents)
+ else:
+ self.startnode.parent.parent.remove(self.startnode.parent)
From e71502abe517a65a81c02aaff4e3344d7e5eeeb9 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 02:50:07 +0100
Subject: [PATCH 026/110] Update Contents class
---
pepreader/pep_contents.py | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/pepreader/pep_contents.py b/pepreader/pep_contents.py
index 623f99b2ea3..b64e13ff7f2 100644
--- a/pepreader/pep_contents.py
+++ b/pepreader/pep_contents.py
@@ -30,8 +30,14 @@ def apply(self):
class Contents(parts.Contents):
+ def __init__(self, document, startnode=None):
+ super().__init__(document, startnode)
+ self.toc_id = None
+ self.backlinks = None
+
def apply(self):
- try: # let the writer (or output software) build the contents list?
+ # let the writer (or output software) build the contents list?
+ try:
toc_by_writer = self.document.settings.use_latex_toc
except AttributeError:
toc_by_writer = False
From b58a9018135b1842a2b3349c8e1ef8aca1cdb9f2 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 03:27:09 +0100
Subject: [PATCH 027/110] =?UTF-8?q?Fixes=20as=20per=20=C3=89ric=20Araujo's?=
=?UTF-8?q?=20comments?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pepreader/pep0.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pepreader/pep0.py b/pepreader/pep0.py
index 190eb17d02f..065c572dc82 100644
--- a/pepreader/pep0.py
+++ b/pepreader/pep0.py
@@ -67,7 +67,7 @@ def __init__(self, author_and_email_tuple):
name_dict = self._parse_name(name)
self.suffix = name_dict.get("suffix")
- if name_dict.get("name"):
+ if "name" in name_dict:
self.last_first = name_dict["name"]
self.nick = name_dict["name"]
else:
@@ -89,7 +89,7 @@ def __init__(self, author_and_email_tuple):
elif self.first == "Just":
self.nick = "JvR"
else:
- raise ValueError(f"unknown van Rossum {self}!")
+ raise ValueError(f"unknown van Rossum ({name})!")
self.last_first += f" ({self.nick})"
def __hash__(self):
From ed6701cf76711d8e0377a069765005d84acc94c6 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 03:38:48 +0100
Subject: [PATCH 028/110] move Travis CI to use requirements.txt
---
.travis.yml | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index acf7162b569..de77ffe6dc1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,12 +2,15 @@ language: python
python:
- 3.7
- 3.7-dev
+ - 3.8
+ - 3.8-dev
dist: xenial
cache: pip
before_install:
- - pip install docutils sphinx
+ - pip install -r requirements.txt
+
script:
- make -j$(nproc)
From 29567678a3ab4935557ff4162365e2990416304a Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 18:07:22 +0100
Subject: [PATCH 029/110] Update build to include 3.8 and sanity checks
---
.travis.yml | 50 ++++++++++++++++++++++++++++++++++++++++++++++++
build.py | 37 +++++++++++++++++++++++++++++++++--
requirements.txt | 3 +++
3 files changed, 88 insertions(+), 2 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index de77ffe6dc1..cd417f56049 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,6 +14,56 @@ before_install:
script:
- make -j$(nproc)
+
+jobs:
+ fast_finish: true
+
+ include:
+ # Main run, tests build, RSS, and packaging
+ - name: "3.7 Makefile Build"
+ python: 3.7
+ env: COMMAND="make -j$(nproc)"
+
+ # Tests build on 3.7-dev
+ - name: "3.7-dev Build Test"
+ python: 3.7-dev
+ env: COMMAND="./build.py"
+
+ # Tests build on 3.8
+ - name: "3.8 Build Test"
+ python: 3.8
+ env: COMMAND="./build.py"
+
+ # Tests build on 3.8-dev
+ - name: "3.8-dev Build Test"
+ python: 3.8-dev
+ env: COMMAND="./build.py"
+
+ # Tests build with Fail on Warning
+ - name: "3.8 Fail on Warning"
+ python: 3.8
+ env:
+ - COMMAND="./build.py -f"
+ - FAIL_ALLOWED=true
+
+ # Tests build with Fail on Warning (Nitpicky)
+ - name: "3.8 Fail on Warning Nitpicky"
+ python: 3.8
+ env:
+ - COMMAND="./build.py -f -n"
+ - FAIL_ALLOWED=true
+
+ # Checks link references within PEPs
+ - name: "3.8 Check Links"
+ python: 3.8
+ env:
+ - COMMAND="./build.py -c"
+ - FAIL_ALLOWED=true
+
+ allow_failures:
+ # Note test failure, but pass the build
+ - env: FAIL_ALLOWED=true
+
deploy:
provider: script
script: bash deploy.bash
diff --git a/build.py b/build.py
index 7e24223597b..69eb6c9ac48 100644
--- a/build.py
+++ b/build.py
@@ -1,15 +1,48 @@
# Build script for Sphinx documentation
+import argparse
from pathlib import Path
from sphinx.application import Sphinx
+
+def create_parser():
+
+ parser = argparse.ArgumentParser(description="Build PEP documents")
+ arguments = [
+ ('-b', '--builder', 'store'),
+ ('-d', '--dir-html', 'store_true'),
+ ('-c', '--check-links', 'store_true'),
+ ('-f', '--fail-on-warning', 'store_true'),
+ ('-n', '--nitpicky', 'store_true'),
+ ]
+ for arg in arguments:
+ parser.add_argument(arg[0], arg[1], action=arg[2])
+
+ return parser.parse_args()
+
+
if __name__ == '__main__':
+ args = create_parser()
+
root_directory = Path('.').absolute()
source_directory = root_directory
configuration_directory = source_directory
build_directory = root_directory / 'build'
doctree_directory = build_directory / '.doctrees'
- builder = 'html'
- app = Sphinx(source_directory, configuration_directory, build_directory, doctree_directory, builder)
+ if args.check_links:
+ builder = 'linkcheck'
+ elif args.dir_html:
+ builder = 'dirhtml'
+ else:
+ builder = 'html'
+
+ config_overrides = {}
+ if args.nitpicky:
+ config_overrides['nitpicky'] = True
+
+ app = Sphinx(
+ source_directory, configuration_directory, build_directory, doctree_directory, builder,
+ confoverrides=config_overrides, warningiserror=args.fail_on_warning, keep_going=args.fail_on_warning,
+ )
app.build()
diff --git a/requirements.txt b/requirements.txt
index 241e78f21e3..ad88d51c43b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,3 +2,6 @@
sphinx >= 3.0.3
docutils >= 0.16
+
+# For packaging to current python.org standards
+bs4
\ No newline at end of file
From ac25685204d80dd1d4de01d6f20cc5e97ab74f9f Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 18:07:31 +0100
Subject: [PATCH 030/110] Move to author metadata lookup for PEP index
---
AUTHORS.csv | 249 ++++++++++++++++++++++++++++++++
pepreader/generate_pep_index.py | 11 +-
pepreader/pep0.py | 95 +-----------
3 files changed, 266 insertions(+), 89 deletions(-)
create mode 100644 AUTHORS.csv
diff --git a/AUTHORS.csv b/AUTHORS.csv
new file mode 100644
index 00000000000..55a712b0fe0
--- /dev/null
+++ b/AUTHORS.csv
@@ -0,0 +1,249 @@
+"Full Name"; "Surname First"; "Name Reference"
+"Aahz"; "Aahz"; "Aahz"
+"James C. Ahlstrom"; "Ahlstrom, James C."; "Ahlstrom"
+"Jim Althoff"; "Althoff, Jim"; "Althoff"
+"Kevin Altis"; "Altis, Kevin"; "Altis"
+"Chris Angelico"; "Angelico, Chris"; "Angelico"
+"Philipp Angerer"; "Angerer, Philipp"; "Angerer"
+"David Ascher"; "Ascher, David"; "Ascher"
+"Peter Astrand"; "Astrand, Peter"; "Astrand"
+"Carl Banks"; "Banks, Carl"; "Banks"
+"Christopher Barker"; "Barker, Christopher"; "Barker"
+"Paul Barrett"; "Barrett, Paul"; "Barrett"
+"Facundo Batista"; "Batista, Facundo"; "Batista"
+"Anthony Baxter"; "Baxter, Anthony"; "Baxter"
+"Stefan Behnel"; "Behnel, Stefan"; "Behnel"
+"Thomas Bellman"; "Bellman, Thomas"; "Bellman"
+"Alexander Belopolsky"; "Belopolsky, Alexander"; "Belopolsky"
+"Eli Bendersky"; "Bendersky, Eli"; "Bendersky"
+"Cory Benfield"; "Benfield, Cory"; "Benfield"
+"Steven Bethard"; "Bethard, Steven"; "Bethard"
+"Stéphane Bidoul"; "Bidoul, Stéphane"; "Bidoul"
+"Stefano Borini"; "Borini, Stefano"; "Borini"
+"Georg Brandl"; "Brandl, Georg"; "Brandl"
+"Erik M. Bray"; "Bray, Erik M."; "Bray"
+"Gerald Britton"; "Britton, Gerald"; "Britton"
+"Oleg Broytman"; "Broytman, Oleg"; "Broytman"
+"Benoit Bryon"; "Bryon, Benoit"; "Bryon"
+"Brandt Bucher"; "Bucher, Brandt"; "Bucher"
+"Brett Cannon"; "Cannon, Brett"; "Cannon"
+"Justin Cappos"; "Cappos, Justin"; "Cappos"
+"Josiah Carlson"; "Carlson, Josiah"; "Carlson"
+"W Isaac Carroll"; "Carroll, W Isaac"; "Carroll"
+"Matt Chisholm"; "Chisholm, Matt"; "Chisholm"
+"Nick Coghlan"; "Coghlan, Nick"; "Coghlan"
+"Dave Cole"; "Cole, Dave"; "Cole"
+"Robert Collins"; "Collins, Robert"; "Collins"
+"Paul Colomiets"; "Colomiets, Paul"; "Colomiets"
+"Mario Corchero"; "Corchero, Mario"; "Corchero"
+"Christopher A. Craig"; "Craig, Christopher A."; "Craig"
+"Laura Creighton"; "Creighton, Laura"; "Creighton"
+"Steven D'Aprano"; "D'Aprano, Steven"; "D'Aprano"
+"Kushal Das"; "Das, Kushal"; "Das"
+"Ned Deily"; "Deily, Ned"; "Deily"
+"Tim Delaney"; "Delaney, Tim"; "Delaney"
+"Lois Anne DeLong"; "DeLong, Lois Anne"; "DeLong"
+"Jeroen Demeyer"; "Demeyer, Jeroen"; "Demeyer"
+"Vladimir Diaz"; "Diaz, Vladimir"; "Diaz"
+"Jack Diederich"; "Diederich, Jack"; "Diederich"
+"Steve Dower"; "Dower, Steve"; "Dower"
+"Walter Dörwald"; "Dörwald, Walter"; "Dörwald"
+"Fred L. Drake, Jr."; "Drake, Fred L., Jr."; "Drake"
+"Michael P. Dubner"; "Dubner, Michael P."; "Dubner"
+"Paul F. Dubois"; "Dubois, Paul F."; "Dubois"
+"Ernest W. Durbin III"; "Durbin, Ernest W., III"; "Durbin"
+"P.J. Eby"; "Eby, P.J."; "Eby"
+"Phillip J. Eby"; "Eby, Phillip J."; "Eby"
+"Tal Einat"; "Einat, Tal"; "Einat"
+"Micah Elliott"; "Elliott, Micah"; "Elliott"
+"Jeff Epler"; "Epler, Jeff"; "Epler"
+"David Eppstein"; "Eppstein, David"; "Eppstein"
+"Clark C. Evans"; "Evans, Clark C."; "Evans"
+"Gregory Ewing"; "Ewing, Gregory"; "Ewing"
+"Greg Ewing"; "Ewing, Greg"; "Ewing"
+"Martijn Faassen"; "Faassen, Martijn"; "Faassen"
+"Ben Finney"; "Finney, Ben"; "Finney"
+"Michael Foord"; "Foord, Michael"; "Foord"
+"Ethan Furman"; "Furman, Ethan"; "Furman"
+"Pablo Galindo"; "Galindo, Pablo"; "Galindo"
+"Paul Ganssle"; "Ganssle, Paul"; "Ganssle"
+"Alex Gaynor"; "Gaynor, Alex"; "Gaynor"
+"Pradyun Gedam"; "Gedam, Pradyun"; "Gedam"
+"Damien George"; "George, Damien"; "George"
+"Frédéric B. Giacometti"; "Giacometti, Frédéric B."; "Giacometti"
+"Scott Gilbert"; "Gilbert, Scott"; "Gilbert"
+"Ryan Gonzalez"; "Gonzalez, Ryan"; "Gonzalez"
+"David Goodger"; "Goodger, David"; "Goodger"
+"Grant Griffin"; "Griffin, Grant"; "Griffin"
+"Mark E. Haase"; "Haase, Mark E."; "Haase"
+"Mark Hammond"; "Hammond, Mark"; "Hammond"
+"Peter Harris"; "Harris, Peter"; "Harris"
+"Larry Hastings"; "Hastings, Larry"; "Hastings"
+"Christian Heimes"; "Heimes, Christian"; "Heimes"
+"Thomas Heller"; "Heller, Thomas"; "Heller"
+"Doug Hellmann"; "Hellmann, Doug"; "Hellmann"
+"Magnus Lie Hetland"; "Hetland, Magnus Lie"; "Hetland"
+"Raymond Hettinger"; "Hettinger, Raymond"; "Hettinger"
+"Neil Hodgson"; "Hodgson, Neil"; "Hodgson"
+"Daniel Holth"; "Holth, Daniel"; "Holth"
+"Philip House"; "House, Philip"; "House"
+"Laurens Van Houtven"; "Van Houtven, Laurens"; "Houtven"
+"Ben Hoyt"; "Hoyt, Ben"; "Hoyt"
+"Miro Hrončok"; "Hrončok, Miro"; "Hrončok"
+"Michael Hudson"; "Hudson, Michael"; "Hudson"
+"Jeremy Hylton"; "Hylton, Jeremy"; "Hylton"
+"Inada Naoki"; "Inada, Naoki"; "Inada"
+"Dustin Ingram"; "Ingram, Dustin"; "Ingram"
+"Atsuo Ishimoto"; "Ishimoto, Atsuo"; "Ishimoto"
+"Jack Jansen"; "Jansen, Jack"; "Jansen"
+"Chris Jerdonek"; "Jerdonek, Chris"; "Jerdonek"
+"Joseph Jevnik"; "Jevnik, Joseph"; "Jevnik"
+"Jim J. Jewett"; "Jewett, Jim J."; "Jewett"
+"Jim Jewett"; "Jewett, Jim"; "Jewett"
+"Ewa Jodlowska"; "Jodlowska, Ewa"; "Jodlowska"
+"Richard Jones"; "Jones, Richard"; "Jones"
+"Konstantin Kashin"; "Kashin, Konstantin"; "Kashin"
+"Reid Kleckner"; "Kleckner, Reid"; "Kleckner"
+"Thomas Kluyver"; "Kluyver, Thomas"; "Kluyver"
+"Stepan Koltsov"; "Koltsov, Stepan"; "Koltsov"
+"Stefan Krah"; "Krah, Stefan"; "Krah"
+"Sebastian Kreft"; "Kreft, Sebastian"; "Kreft"
+"Holger Krekel"; "Krekel, Holger"; "Krekel"
+"A.M. Kuchling"; "Kuchling, A.M."; "Kuchling"
+"Trishank Karthik Kuppusamy"; "Kuppusamy, Trishank Karthik"; "Kuppusamy"
+"Robert Kuska"; "Kuska, Robert"; "Kuska"
+"Joshua Landau"; "Landau, Joshua"; "Landau"
+"Łukasz Langa"; "Langa, Łukasz"; "Langa"
+"Michael Lee"; "Lee, Michael"; "Lee"
+"Jukka Lehtosalo"; "Lehtosalo, Jukka"; "Lehtosalo"
+"Marc-André Lemburg"; "Lemburg, Marc-André"; "Lemburg"
+"Ivan Levkivskyi"; "Levkivskyi, Ivan"; "Levkivskyi"
+"Gregory Lielens"; "Lielens, Gregory"; "Lielens"
+"Björn Lindqvist"; "Lindqvist, Björn"; "Lindqvist"
+"Joshua Lock"; "Lock, Joshua"; "Lock"
+"Tony Lownds"; "Lownds, Tony"; "Lownds"
+"Martin von Löwis"; "von Löwis, Martin"; "von Löwis"
+"Martin v. Löwis"; "\v. Löwis, Martin"; "\v. Löwis"
+"Mariatta"; "Mariatta"; "Mariatta"
+"Alex Martelli"; "Martelli, Alex"; "Martelli"
+"Joseph Martinot-Lagarde"; "Martinot-Lagarde, Joseph"; "Martinot-Lagarde"
+"Lino Mastrodomenico"; "Mastrodomenico, Lino"; "Mastrodomenico"
+"Patrick Maupin"; "Maupin, Patrick"; "Maupin"
+"Andrew McClelland"; "McClelland, Andrew"; "McClelland"
+"Charles R. McCreary"; "McCreary, Charles R."; "McCreary"
+"Chris McDonough"; "McDonough, Chris"; "McDonough"
+"Robert T. McGibbon"; "McGibbon, Robert T."; "McGibbon"
+"Gordon McMillan"; "McMillan, Gordon"; "McMillan"
+"Andrew McNamara"; "McNamara, Andrew"; "McNamara"
+"Ezio Melotti"; "Melotti, Ezio"; "Melotti"
+"Mark Mendoza"; "Mendoza, Mark"; "Mendoza"
+"Markus Meskanen"; "Meskanen, Markus"; "Meskanen"
+"Mike Meyer"; "Meyer, Mike"; "Meyer"
+"Carl Meyer"; "Meyer, Carl"; "Meyer"
+"Trent Mick"; "Mick, Trent"; "Mick"
+"Mike G. Miller"; "Miller, Mike G."; "Miller"
+"Skip Montanaro"; "Montanaro, Skip"; "Montanaro"
+"Peter Moody"; "Moody, Peter"; "Moody"
+"Marina Moore"; "Moore, Marina"; "Moore"
+"Paul Moore"; "Moore, Paul"; "Moore"
+"R David Murray"; "Murray, R David"; "Murray"
+"Charles-François Natali"; "Natali, Charles-François"; "Natali"
+"Lysandros Nikolaou"; "Nikolaou, Lysandros"; "Nikolaou"
+"Jesse Noller"; "Noller, Jesse"; "Noller"
+"Ben North"; "North, Ben"; "North"
+"Neal Norwitz"; "Norwitz, Neal"; "Norwitz"
+"Dirkjan Ochtman"; "Ochtman, Dirkjan"; "Ochtman"
+"Travis Oliphant"; "Oliphant, Travis"; "Oliphant"
+"Jason Orendorff"; "Orendorff, Jason"; "Orendorff"
+"Tomáš Orsava"; "Orsava, Tomáš"; "Orsava"
+"Richard Oudkerk"; "Oudkerk, Richard"; "Oudkerk"
+"Ronald Oussoren"; "Oussoren, Ronald"; "Oussoren"
+"Julien Palard"; "Palard, Julien"; "Palard"
+"Samuele Pedroni"; "Pedroni, Samuele"; "Pedroni"
+"Berker Peksag"; "Peksag, Berker"; "Peksag"
+"Michel Pelletier"; "Pelletier, Michel"; "Pelletier"
+"Tim Peters"; "Peters, Tim"; "Peters"
+"Benjamin Peterson"; "Peterson, Benjamin"; "Peterson"
+"Jason Petrone"; "Petrone, Jason"; "Petrone"
+"Antoine Pitrou"; "Pitrou, Antoine"; "Pitrou"
+"Marcel Plch"; "Plch, Marcel"; "Plch"
+"James Polley"; "Polley, James"; "Polley"
+"Philippe PRADOS"; "Prados, Philippe"; "Prados"
+"Elvis Pranskevichus"; "Pranskevichus, Elvis"; "Pranskevichus"
+"Paul Prescod"; "Prescod, Paul"; "Prescod"
+"(James) Eric Pruitt"; "Pruitt, (James) Eric"; "Pruitt"
+"Lukas Puehringer"; "Puehringer, Lukas"; "Puehringer"
+"Brian Quinlan"; "Quinlan, Brian"; "Quinlan"
+"Terry Reedy"; "Reedy, Terry"; "Reedy"
+"Lennart Regebro"; "Regebro, Lennart"; "Regebro"
+"Sean Reifschneider"; "Reifschneider, Sean"; "Reifschneider"
+"Christian R. Reis"; "Reis, Christian R."; "Reis"
+"Jonathan Riehl"; "Riehl, Jonathan"; "Riehl"
+"Lisa Roach"; "Roach, Lisa"; "Roach"
+"Andre Roberge"; "Roberge, Andre"; "Roberge"
+"Armin Ronacher"; "Ronacher, Armin"; "Ronacher"
+"Guido van Rossum"; "van Rossum, Guido (GvR)"; "GvR"
+"Just van Rossum"; "van Rossum, Just (JvR)"; "JvR"
+"Todd Rovito"; "Rovito, Todd"; "Rovito"
+"Lie Ryan"; "Ryan, Lie"; "Ryan"
+"Vinay Sajip"; "Sajip, Vinay"; "Sajip"
+"Pablo Galindo Salgado"; "Salgado, Pablo Galindo"; "Salgado"
+"Neil Schemenauer"; "Schemenauer, Neil"; "Schemenauer"
+"Peter Schneider-Kamp"; "Schneider-Kamp, Peter"; "Schneider-Kamp"
+"Ed Schofield"; "Schofield, Ed"; "Schofield"
+"Yury Selivanov"; "Selivanov, Yury"; "Selivanov"
+"Jiwon Seo"; "Seo, Jiwon"; "Seo"
+"Mark Shannon"; "Shannon, Mark"; "Shannon"
+"Cameron Simpson"; "Simpson, Cameron"; "Simpson"
+"Greg Slodkowicz"; "Slodkowicz, Greg"; "Slodkowicz"
+"Nathaniel J. Smith"; "Smith, Nathaniel J."; "Smith"
+"Gregory P. Smith"; "Smith, Gregory P."; "Smith"
+"Kevin D. Smith"; "Smith, Kevin D."; "Smith"
+"Ethan Smith"; "Smith, Ethan"; "Smith"
+"Nathaniel Smith"; "Smith, Nathaniel"; "Smith"
+"Eric V. Smith"; "Smith, Eric V."; "Smith"
+"Eric Snow"; "Snow, Eric"; "Snow"
+"Calvin Spealman"; "Spealman, Calvin"; "Spealman"
+"Kerrick Staley"; "Staley, Kerrick"; "Staley"
+"Greg Stein"; "Stein, Greg"; "Stein"
+"Victor Stinner"; "Stinner, Victor"; "Stinner"
+"Serhiy Storchaka"; "Storchaka, Serhiy"; "Storchaka"
+"Donald Stufft"; "Stufft, Donald"; "Stufft"
+"Daniel Stutzbach"; "Stutzbach, Daniel"; "Stutzbach"
+"Michael J. Sullivan"; "Sullivan, Michael J."; "Sullivan"
+"Roman Suzi"; "Suzi, Roman"; "Suzi"
+"Dennis Sweeney"; "Sweeney, Dennis"; "Sweeney"
+"Talin"; "Talin"; "Talin"
+"Steven Taschuk"; "Taschuk, Steven"; "Taschuk"
+"Batuhan Taskaya"; "Taskaya, Batuhan"; "Taskaya"
+"Martin Teichmann"; "Teichmann, Martin"; "Teichmann"
+"The Python core team and community"; "The Python core team and community"; "The Python core team and community"
+"Geoffrey Thomas"; "Thomas, Geoffrey"; "Thomas"
+"Oren Tirosh"; "Tirosh, Oren"; "Tirosh"
+"Stephen J. Turnbull"; "Turnbull, Stephen J."; "Turnbull"
+"Daniel Urban"; "Urban, Daniel"; "Urban"
+"Eric N. Vander Weele"; "Vander Weele, Eric N."; "Vander Weele"
+"Till Varoquaux"; "Varoquaux, Till"; "Varoquaux"
+"Alexandre Vassalotti"; "Vassalotti, Alexandre"; "Vassalotti"
+"Mike Verdone"; "Verdone, Mike"; "Verdone"
+"Dino Viehland"; "Viehland, Dino"; "Viehland"
+"Petr Viktorin"; "Viktorin, Petr"; "Viktorin"
+"Zachary Ware"; "Ware, Zachary"; "Ware"
+"Gregory R. Warnes"; "Warnes, Gregory R."; "Warnes"
+"Barry Warsaw"; "Warsaw, Barry"; "Warsaw"
+"Terence Way"; "Way, Terence"; "Way"
+"Cliff Wells"; "Wells, Cliff"; "Wells"
+"Jervis Whitley"; "Whitley, Jervis"; "Whitley"
+"Mark Williams"; "Williams, Mark"; "Williams"
+"Carol Willing"; "Willing, Carol"; "Willing"
+"Greg Wilson"; "Wilson, Greg"; "Wilson"
+"Collin Winter"; "Winter, Collin"; "Winter"
+"Thomas Wouters"; "Wouters, Thomas"; "Wouters"
+"Masayuki Yamamoto"; "Yamamoto, Masayuki"; "Yamamoto"
+"Jeffrey Yasskin"; "Yasskin, Jeffrey"; "Yasskin"
+"Ka-Ping Yee"; "Yee, Ka-Ping"; "Yee"
+"Moshe Zadka"; "Zadka, Moshe"; "Zadka"
+"Koos Zevenhoven"; "Zevenhoven, Koos"; "Zevenhoven"
+"Huaiyu Zhu"; "Zhu, Huaiyu"; "Zhu"
+"Shannon Zhu"; "Zhu, Shannon"; "Zhu"
+"Tarek Ziadé"; "Ziadé, Tarek"; "Ziadé"
\ No newline at end of file
diff --git a/pepreader/generate_pep_index.py b/pepreader/generate_pep_index.py
index 3ae3da29659..34149cbf23a 100644
--- a/pepreader/generate_pep_index.py
+++ b/pepreader/generate_pep_index.py
@@ -16,6 +16,7 @@
"""
import re
+import csv
from operator import attrgetter
from pathlib import Path
@@ -33,6 +34,14 @@ def create_pep_zero(_, env, docnames):
peps = []
pep_pat = re.compile(r"pep-\d{4}") # Path.match() doesn't support regular expressions
+ with open("AUTHORS.csv", "r", encoding="UTF8") as f:
+ read = csv.DictReader(f, delimiter=";")
+ author_data = {}
+ for line in read:
+ full_name = line.pop("Full Name").strip().strip("\"")
+ details = {k.strip().strip("\""): v.strip().strip("\"") for k, v in line.items()}
+ author_data[full_name] = details
+
for file_path in path.iterdir():
if not file_path.is_file():
continue # Skip directories etc.
@@ -41,7 +50,7 @@ def create_pep_zero(_, env, docnames):
if pep_pat.match(str(file_path)) and file_path.suffix in (".txt", ".rst"):
file_path_absolute = path.joinpath(file_path).absolute()
pep_text = file_path_absolute.read_text("UTF8")
- pep = pep0.PEP(pep_text, file_path_absolute)
+ pep = pep0.PEP(pep_text, file_path_absolute, author_data)
if pep.number != int(file_path.stem[4:]):
raise pep0.PEPError(f'PEP number does not match file name ({file_path})', file_path, pep.number)
peps.append(pep)
diff --git a/pepreader/pep0.py b/pepreader/pep0.py
index 065c572dc82..bd69929e5d4 100644
--- a/pepreader/pep0.py
+++ b/pepreader/pep0.py
@@ -57,7 +57,7 @@ class Author(object):
The author's email address.
"""
- def __init__(self, author_and_email_tuple):
+ def __init__(self, author_and_email_tuple, authors_lookup):
"""Parse the name and email address of an author."""
self.first = self.last = ''
@@ -65,32 +65,10 @@ def __init__(self, author_and_email_tuple):
self.first_last = name.strip()
self.email = email.lower()
- name_dict = self._parse_name(name)
- self.suffix = name_dict.get("suffix")
- if "name" in name_dict:
- self.last_first = name_dict["name"]
- self.nick = name_dict["name"]
- else:
- self.first = name_dict["forename"].rstrip()
- self.last = name_dict["surname"]
- if self.last[1] == ".":
- # Add an escape to avoid docutils turning `v.` into `22.`.
- self.last = "\\" + self.last
- self.last_first = ", ".join([self.last, self.first])
- self.nick = self.last
-
- if self.suffix:
- self.last_first += ", " + 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(f"unknown van Rossum ({name})!")
- self.last_first += f" ({self.nick})"
+ name_dict = authors_lookup[self.first_last]
+
+ self.last_first = name_dict["Surname First"]
+ self.nick = name_dict["Name Reference"]
def __hash__(self):
return hash(self.first_last)
@@ -110,65 +88,6 @@ def sort_by(self):
base = self.last.lower()
return unicodedata.normalize("NFKD", base)
- @staticmethod
- def _parse_name(full_name):
- """Decompose a full name into parts.
-
- If a mononym (e.g, 'Aahz') then return the full name. If there are
- suffixes in the name (e.g. ', Jr.' or 'III'), then find and extract
- them. If there is a middle initial followed by a full stop, then
- combine the following words into a surname (e.g. N. Vander Weele). If
- there is a leading, lowercase portion to the last name (e.g. 'van' or
- 'von') then include it in the surname.
-
- """
- possible_suffixes = ["Jr", "Jr.", "II", "III"]
- special_cases = ["The Python core team and community"]
-
- if full_name in special_cases:
- return {"name": full_name}
-
- suffix_partition = full_name.partition(",")
- pre_suffix = suffix_partition[0].strip()
- suffix = suffix_partition[2].strip()
-
- name_parts = pre_suffix.split(" ")
- num_parts = len(name_parts)
- name = {"suffix": suffix}
-
- if num_parts == 0:
- raise ValueError("Name is empty!")
- elif num_parts == 1:
- name.update(name=name_parts[0])
- elif num_parts == 2:
- name.update(forename=name_parts[0], surname=name_parts[1])
- elif num_parts > 2:
- # handles III etc.
- if name_parts[-1] in possible_suffixes:
- new_suffix = " ".join([*name_parts[-1:], suffix]).strip()
- name_parts.pop(-1)
- name.update(suffix=new_suffix)
-
- # handles von, van, v. etc.
- if name_parts[-2].islower():
- forename = " ".join(name_parts[:-2])
- surname = " ".join(name_parts[-2:])
- name.update(forename=forename, surname=surname)
-
- # handles double surnames after a middle initial (e.g. N. Vander Weele)
- elif any(s.endswith(".") for s in name_parts):
- split_position = [i for i, x in enumerate(name_parts) if x.endswith(".")][-1] + 1
- forename = " ".join(name_parts[:split_position])
- surname = " ".join(name_parts[split_position:])
- name.update(forename=forename, surname=surname)
-
- else:
- forename = " ".join(name_parts[:-1])
- surname = " ".join(name_parts[-1:])
- name.update(forename=forename, surname=surname)
-
- return name
-
class PEP(object):
@@ -213,7 +132,7 @@ class PEP(object):
"Deferred", "Final", "Active", "Draft", "Superseded",
)
- def __init__(self, pep_file: str, filename: str):
+ def __init__(self, pep_file: str, filename: str, author_lookup: dict):
"""Init object from an open PEP file object."""
# Parse the headers.
self.filename = filename
@@ -281,7 +200,7 @@ def __init__(self, pep_file: str, filename: str):
authors_and_emails = self._parse_author(metadata["Author"])
if len(authors_and_emails) < 1:
raise PEPError("no authors found", filename, self.number)
- self.authors = list(map(Author, authors_and_emails))
+ self.authors = [Author(author_email, author_lookup) for author_email in authors_and_emails]
@staticmethod
def _parse_author(data):
From 190ae92defd03384963bacf7ec81caf55fbdc026 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 18:11:18 +0100
Subject: [PATCH 031/110] update .travis.yml
---
.travis.yml | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index cd417f56049..eef1983de19 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,13 +8,9 @@ dist: xenial
cache: pip
-before_install:
+install:
- pip install -r requirements.txt
-script:
- - make -j$(nproc)
-
-
jobs:
fast_finish: true
From 1df8640afd331fa6ba0a5e1f984d0cf32361f4bf Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 18:40:38 +0100
Subject: [PATCH 032/110] Lint travis config with config.travis-ci.com
---
.travis.yml | 39 ++++++++++++++++-----------------------
1 file changed, 16 insertions(+), 23 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index eef1983de19..0ea318311d9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,11 @@
language: python
-python:
- - 3.7
- - 3.7-dev
- - 3.8
- - 3.8-dev
+os: linux
dist: xenial
cache: pip
install:
- - pip install -r requirements.txt
+ - pip install -r requirements.txt
jobs:
fast_finish: true
@@ -19,6 +15,11 @@ jobs:
- name: "3.7 Makefile Build"
python: 3.7
env: COMMAND="make -j$(nproc)"
+ deploy:
+ provider: script
+ script: bash deploy.bash
+ on:
+ branch: master
# Tests build on 3.7-dev
- name: "3.7-dev Build Test"
@@ -44,26 +45,18 @@ jobs:
# Tests build with Fail on Warning (Nitpicky)
- name: "3.8 Fail on Warning Nitpicky"
- python: 3.8
- env:
- - COMMAND="./build.py -f -n"
- - FAIL_ALLOWED=true
+ python: 3.8
+ env:
+ - COMMAND="./build.py -f -n"
+ - FAIL_ALLOWED=true
# Checks link references within PEPs
- name: "3.8 Check Links"
- python: 3.8
- env:
- - COMMAND="./build.py -c"
- - FAIL_ALLOWED=true
+ python: 3.8
+ env:
+ - COMMAND="./build.py -c"
+ - FAIL_ALLOWED=true
allow_failures:
# Note test failure, but pass the build
- - env: FAIL_ALLOWED=true
-
-deploy:
- provider: script
- script: bash deploy.bash
- skip_cleanup: true
- on:
- branch: master
- repo: python/peps
+ - env: FAIL_ALLOWED=true
\ No newline at end of file
From 051c0088beda0893655f7821a11665a7d2e11375 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 18:47:10 +0100
Subject: [PATCH 033/110] oops...
---
.travis.yml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.travis.yml b/.travis.yml
index 0ea318311d9..515853bb32b 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -59,4 +59,6 @@ jobs:
allow_failures:
# Note test failure, but pass the build
- - env: FAIL_ALLOWED=true
\ No newline at end of file
+ - env: FAIL_ALLOWED=true
+
+script: $COMMAND
\ No newline at end of file
From 5f32b8fcd7aea7421b2200edd99f53d08ca1cbda Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Tue, 28 Apr 2020 19:19:08 +0100
Subject: [PATCH 034/110] Correct command calls
---
.travis.yml | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 515853bb32b..0273a1e1e00 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,37 +24,37 @@ jobs:
# Tests build on 3.7-dev
- name: "3.7-dev Build Test"
python: 3.7-dev
- env: COMMAND="./build.py"
+ env: COMMAND="python3 ./build.py"
# Tests build on 3.8
- name: "3.8 Build Test"
python: 3.8
- env: COMMAND="./build.py"
+ env: COMMAND="python3 ./build.py"
# Tests build on 3.8-dev
- name: "3.8-dev Build Test"
python: 3.8-dev
- env: COMMAND="./build.py"
+ env: COMMAND="python3 ./build.py"
# Tests build with Fail on Warning
- name: "3.8 Fail on Warning"
python: 3.8
env:
- - COMMAND="./build.py -f"
+ - COMMAND="python3 ./build.py -f"
- FAIL_ALLOWED=true
# Tests build with Fail on Warning (Nitpicky)
- name: "3.8 Fail on Warning Nitpicky"
python: 3.8
env:
- - COMMAND="./build.py -f -n"
+ - COMMAND="python3 ./build.py -f -n"
- FAIL_ALLOWED=true
# Checks link references within PEPs
- name: "3.8 Check Links"
python: 3.8
env:
- - COMMAND="./build.py -c"
+ - COMMAND="python3 ./build.py -c"
- FAIL_ALLOWED=true
allow_failures:
From 042672cb9ddef6bfced4cba6bb93ff90215eeb38 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Thu, 30 Apr 2020 00:41:54 +0100
Subject: [PATCH 035/110] Move CSV to comma separated
---
.travis.yml | 3 +-
AUTHORS.csv | 498 ++++++++++++++++----------------
pepreader/generate_pep_index.py | 2 +-
3 files changed, 252 insertions(+), 251 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 0273a1e1e00..3ac9b40efc5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -59,6 +59,7 @@ jobs:
allow_failures:
# Note test failure, but pass the build
- - env: FAIL_ALLOWED=true
+ - env:
+ - FAIL_ALLOWED=true
script: $COMMAND
\ No newline at end of file
diff --git a/AUTHORS.csv b/AUTHORS.csv
index 55a712b0fe0..0270e4383c4 100644
--- a/AUTHORS.csv
+++ b/AUTHORS.csv
@@ -1,249 +1,249 @@
-"Full Name"; "Surname First"; "Name Reference"
-"Aahz"; "Aahz"; "Aahz"
-"James C. Ahlstrom"; "Ahlstrom, James C."; "Ahlstrom"
-"Jim Althoff"; "Althoff, Jim"; "Althoff"
-"Kevin Altis"; "Altis, Kevin"; "Altis"
-"Chris Angelico"; "Angelico, Chris"; "Angelico"
-"Philipp Angerer"; "Angerer, Philipp"; "Angerer"
-"David Ascher"; "Ascher, David"; "Ascher"
-"Peter Astrand"; "Astrand, Peter"; "Astrand"
-"Carl Banks"; "Banks, Carl"; "Banks"
-"Christopher Barker"; "Barker, Christopher"; "Barker"
-"Paul Barrett"; "Barrett, Paul"; "Barrett"
-"Facundo Batista"; "Batista, Facundo"; "Batista"
-"Anthony Baxter"; "Baxter, Anthony"; "Baxter"
-"Stefan Behnel"; "Behnel, Stefan"; "Behnel"
-"Thomas Bellman"; "Bellman, Thomas"; "Bellman"
-"Alexander Belopolsky"; "Belopolsky, Alexander"; "Belopolsky"
-"Eli Bendersky"; "Bendersky, Eli"; "Bendersky"
-"Cory Benfield"; "Benfield, Cory"; "Benfield"
-"Steven Bethard"; "Bethard, Steven"; "Bethard"
-"Stéphane Bidoul"; "Bidoul, Stéphane"; "Bidoul"
-"Stefano Borini"; "Borini, Stefano"; "Borini"
-"Georg Brandl"; "Brandl, Georg"; "Brandl"
-"Erik M. Bray"; "Bray, Erik M."; "Bray"
-"Gerald Britton"; "Britton, Gerald"; "Britton"
-"Oleg Broytman"; "Broytman, Oleg"; "Broytman"
-"Benoit Bryon"; "Bryon, Benoit"; "Bryon"
-"Brandt Bucher"; "Bucher, Brandt"; "Bucher"
-"Brett Cannon"; "Cannon, Brett"; "Cannon"
-"Justin Cappos"; "Cappos, Justin"; "Cappos"
-"Josiah Carlson"; "Carlson, Josiah"; "Carlson"
-"W Isaac Carroll"; "Carroll, W Isaac"; "Carroll"
-"Matt Chisholm"; "Chisholm, Matt"; "Chisholm"
-"Nick Coghlan"; "Coghlan, Nick"; "Coghlan"
-"Dave Cole"; "Cole, Dave"; "Cole"
-"Robert Collins"; "Collins, Robert"; "Collins"
-"Paul Colomiets"; "Colomiets, Paul"; "Colomiets"
-"Mario Corchero"; "Corchero, Mario"; "Corchero"
-"Christopher A. Craig"; "Craig, Christopher A."; "Craig"
-"Laura Creighton"; "Creighton, Laura"; "Creighton"
-"Steven D'Aprano"; "D'Aprano, Steven"; "D'Aprano"
-"Kushal Das"; "Das, Kushal"; "Das"
-"Ned Deily"; "Deily, Ned"; "Deily"
-"Tim Delaney"; "Delaney, Tim"; "Delaney"
-"Lois Anne DeLong"; "DeLong, Lois Anne"; "DeLong"
-"Jeroen Demeyer"; "Demeyer, Jeroen"; "Demeyer"
-"Vladimir Diaz"; "Diaz, Vladimir"; "Diaz"
-"Jack Diederich"; "Diederich, Jack"; "Diederich"
-"Steve Dower"; "Dower, Steve"; "Dower"
-"Walter Dörwald"; "Dörwald, Walter"; "Dörwald"
-"Fred L. Drake, Jr."; "Drake, Fred L., Jr."; "Drake"
-"Michael P. Dubner"; "Dubner, Michael P."; "Dubner"
-"Paul F. Dubois"; "Dubois, Paul F."; "Dubois"
-"Ernest W. Durbin III"; "Durbin, Ernest W., III"; "Durbin"
-"P.J. Eby"; "Eby, P.J."; "Eby"
-"Phillip J. Eby"; "Eby, Phillip J."; "Eby"
-"Tal Einat"; "Einat, Tal"; "Einat"
-"Micah Elliott"; "Elliott, Micah"; "Elliott"
-"Jeff Epler"; "Epler, Jeff"; "Epler"
-"David Eppstein"; "Eppstein, David"; "Eppstein"
-"Clark C. Evans"; "Evans, Clark C."; "Evans"
-"Gregory Ewing"; "Ewing, Gregory"; "Ewing"
-"Greg Ewing"; "Ewing, Greg"; "Ewing"
-"Martijn Faassen"; "Faassen, Martijn"; "Faassen"
-"Ben Finney"; "Finney, Ben"; "Finney"
-"Michael Foord"; "Foord, Michael"; "Foord"
-"Ethan Furman"; "Furman, Ethan"; "Furman"
-"Pablo Galindo"; "Galindo, Pablo"; "Galindo"
-"Paul Ganssle"; "Ganssle, Paul"; "Ganssle"
-"Alex Gaynor"; "Gaynor, Alex"; "Gaynor"
-"Pradyun Gedam"; "Gedam, Pradyun"; "Gedam"
-"Damien George"; "George, Damien"; "George"
-"Frédéric B. Giacometti"; "Giacometti, Frédéric B."; "Giacometti"
-"Scott Gilbert"; "Gilbert, Scott"; "Gilbert"
-"Ryan Gonzalez"; "Gonzalez, Ryan"; "Gonzalez"
-"David Goodger"; "Goodger, David"; "Goodger"
-"Grant Griffin"; "Griffin, Grant"; "Griffin"
-"Mark E. Haase"; "Haase, Mark E."; "Haase"
-"Mark Hammond"; "Hammond, Mark"; "Hammond"
-"Peter Harris"; "Harris, Peter"; "Harris"
-"Larry Hastings"; "Hastings, Larry"; "Hastings"
-"Christian Heimes"; "Heimes, Christian"; "Heimes"
-"Thomas Heller"; "Heller, Thomas"; "Heller"
-"Doug Hellmann"; "Hellmann, Doug"; "Hellmann"
-"Magnus Lie Hetland"; "Hetland, Magnus Lie"; "Hetland"
-"Raymond Hettinger"; "Hettinger, Raymond"; "Hettinger"
-"Neil Hodgson"; "Hodgson, Neil"; "Hodgson"
-"Daniel Holth"; "Holth, Daniel"; "Holth"
-"Philip House"; "House, Philip"; "House"
-"Laurens Van Houtven"; "Van Houtven, Laurens"; "Houtven"
-"Ben Hoyt"; "Hoyt, Ben"; "Hoyt"
-"Miro Hrončok"; "Hrončok, Miro"; "Hrončok"
-"Michael Hudson"; "Hudson, Michael"; "Hudson"
-"Jeremy Hylton"; "Hylton, Jeremy"; "Hylton"
-"Inada Naoki"; "Inada, Naoki"; "Inada"
-"Dustin Ingram"; "Ingram, Dustin"; "Ingram"
-"Atsuo Ishimoto"; "Ishimoto, Atsuo"; "Ishimoto"
-"Jack Jansen"; "Jansen, Jack"; "Jansen"
-"Chris Jerdonek"; "Jerdonek, Chris"; "Jerdonek"
-"Joseph Jevnik"; "Jevnik, Joseph"; "Jevnik"
-"Jim J. Jewett"; "Jewett, Jim J."; "Jewett"
-"Jim Jewett"; "Jewett, Jim"; "Jewett"
-"Ewa Jodlowska"; "Jodlowska, Ewa"; "Jodlowska"
-"Richard Jones"; "Jones, Richard"; "Jones"
-"Konstantin Kashin"; "Kashin, Konstantin"; "Kashin"
-"Reid Kleckner"; "Kleckner, Reid"; "Kleckner"
-"Thomas Kluyver"; "Kluyver, Thomas"; "Kluyver"
-"Stepan Koltsov"; "Koltsov, Stepan"; "Koltsov"
-"Stefan Krah"; "Krah, Stefan"; "Krah"
-"Sebastian Kreft"; "Kreft, Sebastian"; "Kreft"
-"Holger Krekel"; "Krekel, Holger"; "Krekel"
-"A.M. Kuchling"; "Kuchling, A.M."; "Kuchling"
-"Trishank Karthik Kuppusamy"; "Kuppusamy, Trishank Karthik"; "Kuppusamy"
-"Robert Kuska"; "Kuska, Robert"; "Kuska"
-"Joshua Landau"; "Landau, Joshua"; "Landau"
-"Łukasz Langa"; "Langa, Łukasz"; "Langa"
-"Michael Lee"; "Lee, Michael"; "Lee"
-"Jukka Lehtosalo"; "Lehtosalo, Jukka"; "Lehtosalo"
-"Marc-André Lemburg"; "Lemburg, Marc-André"; "Lemburg"
-"Ivan Levkivskyi"; "Levkivskyi, Ivan"; "Levkivskyi"
-"Gregory Lielens"; "Lielens, Gregory"; "Lielens"
-"Björn Lindqvist"; "Lindqvist, Björn"; "Lindqvist"
-"Joshua Lock"; "Lock, Joshua"; "Lock"
-"Tony Lownds"; "Lownds, Tony"; "Lownds"
-"Martin von Löwis"; "von Löwis, Martin"; "von Löwis"
-"Martin v. Löwis"; "\v. Löwis, Martin"; "\v. Löwis"
-"Mariatta"; "Mariatta"; "Mariatta"
-"Alex Martelli"; "Martelli, Alex"; "Martelli"
-"Joseph Martinot-Lagarde"; "Martinot-Lagarde, Joseph"; "Martinot-Lagarde"
-"Lino Mastrodomenico"; "Mastrodomenico, Lino"; "Mastrodomenico"
-"Patrick Maupin"; "Maupin, Patrick"; "Maupin"
-"Andrew McClelland"; "McClelland, Andrew"; "McClelland"
-"Charles R. McCreary"; "McCreary, Charles R."; "McCreary"
-"Chris McDonough"; "McDonough, Chris"; "McDonough"
-"Robert T. McGibbon"; "McGibbon, Robert T."; "McGibbon"
-"Gordon McMillan"; "McMillan, Gordon"; "McMillan"
-"Andrew McNamara"; "McNamara, Andrew"; "McNamara"
-"Ezio Melotti"; "Melotti, Ezio"; "Melotti"
-"Mark Mendoza"; "Mendoza, Mark"; "Mendoza"
-"Markus Meskanen"; "Meskanen, Markus"; "Meskanen"
-"Mike Meyer"; "Meyer, Mike"; "Meyer"
-"Carl Meyer"; "Meyer, Carl"; "Meyer"
-"Trent Mick"; "Mick, Trent"; "Mick"
-"Mike G. Miller"; "Miller, Mike G."; "Miller"
-"Skip Montanaro"; "Montanaro, Skip"; "Montanaro"
-"Peter Moody"; "Moody, Peter"; "Moody"
-"Marina Moore"; "Moore, Marina"; "Moore"
-"Paul Moore"; "Moore, Paul"; "Moore"
-"R David Murray"; "Murray, R David"; "Murray"
-"Charles-François Natali"; "Natali, Charles-François"; "Natali"
-"Lysandros Nikolaou"; "Nikolaou, Lysandros"; "Nikolaou"
-"Jesse Noller"; "Noller, Jesse"; "Noller"
-"Ben North"; "North, Ben"; "North"
-"Neal Norwitz"; "Norwitz, Neal"; "Norwitz"
-"Dirkjan Ochtman"; "Ochtman, Dirkjan"; "Ochtman"
-"Travis Oliphant"; "Oliphant, Travis"; "Oliphant"
-"Jason Orendorff"; "Orendorff, Jason"; "Orendorff"
-"Tomáš Orsava"; "Orsava, Tomáš"; "Orsava"
-"Richard Oudkerk"; "Oudkerk, Richard"; "Oudkerk"
-"Ronald Oussoren"; "Oussoren, Ronald"; "Oussoren"
-"Julien Palard"; "Palard, Julien"; "Palard"
-"Samuele Pedroni"; "Pedroni, Samuele"; "Pedroni"
-"Berker Peksag"; "Peksag, Berker"; "Peksag"
-"Michel Pelletier"; "Pelletier, Michel"; "Pelletier"
-"Tim Peters"; "Peters, Tim"; "Peters"
-"Benjamin Peterson"; "Peterson, Benjamin"; "Peterson"
-"Jason Petrone"; "Petrone, Jason"; "Petrone"
-"Antoine Pitrou"; "Pitrou, Antoine"; "Pitrou"
-"Marcel Plch"; "Plch, Marcel"; "Plch"
-"James Polley"; "Polley, James"; "Polley"
-"Philippe PRADOS"; "Prados, Philippe"; "Prados"
-"Elvis Pranskevichus"; "Pranskevichus, Elvis"; "Pranskevichus"
-"Paul Prescod"; "Prescod, Paul"; "Prescod"
-"(James) Eric Pruitt"; "Pruitt, (James) Eric"; "Pruitt"
-"Lukas Puehringer"; "Puehringer, Lukas"; "Puehringer"
-"Brian Quinlan"; "Quinlan, Brian"; "Quinlan"
-"Terry Reedy"; "Reedy, Terry"; "Reedy"
-"Lennart Regebro"; "Regebro, Lennart"; "Regebro"
-"Sean Reifschneider"; "Reifschneider, Sean"; "Reifschneider"
-"Christian R. Reis"; "Reis, Christian R."; "Reis"
-"Jonathan Riehl"; "Riehl, Jonathan"; "Riehl"
-"Lisa Roach"; "Roach, Lisa"; "Roach"
-"Andre Roberge"; "Roberge, Andre"; "Roberge"
-"Armin Ronacher"; "Ronacher, Armin"; "Ronacher"
-"Guido van Rossum"; "van Rossum, Guido (GvR)"; "GvR"
-"Just van Rossum"; "van Rossum, Just (JvR)"; "JvR"
-"Todd Rovito"; "Rovito, Todd"; "Rovito"
-"Lie Ryan"; "Ryan, Lie"; "Ryan"
-"Vinay Sajip"; "Sajip, Vinay"; "Sajip"
-"Pablo Galindo Salgado"; "Salgado, Pablo Galindo"; "Salgado"
-"Neil Schemenauer"; "Schemenauer, Neil"; "Schemenauer"
-"Peter Schneider-Kamp"; "Schneider-Kamp, Peter"; "Schneider-Kamp"
-"Ed Schofield"; "Schofield, Ed"; "Schofield"
-"Yury Selivanov"; "Selivanov, Yury"; "Selivanov"
-"Jiwon Seo"; "Seo, Jiwon"; "Seo"
-"Mark Shannon"; "Shannon, Mark"; "Shannon"
-"Cameron Simpson"; "Simpson, Cameron"; "Simpson"
-"Greg Slodkowicz"; "Slodkowicz, Greg"; "Slodkowicz"
-"Nathaniel J. Smith"; "Smith, Nathaniel J."; "Smith"
-"Gregory P. Smith"; "Smith, Gregory P."; "Smith"
-"Kevin D. Smith"; "Smith, Kevin D."; "Smith"
-"Ethan Smith"; "Smith, Ethan"; "Smith"
-"Nathaniel Smith"; "Smith, Nathaniel"; "Smith"
-"Eric V. Smith"; "Smith, Eric V."; "Smith"
-"Eric Snow"; "Snow, Eric"; "Snow"
-"Calvin Spealman"; "Spealman, Calvin"; "Spealman"
-"Kerrick Staley"; "Staley, Kerrick"; "Staley"
-"Greg Stein"; "Stein, Greg"; "Stein"
-"Victor Stinner"; "Stinner, Victor"; "Stinner"
-"Serhiy Storchaka"; "Storchaka, Serhiy"; "Storchaka"
-"Donald Stufft"; "Stufft, Donald"; "Stufft"
-"Daniel Stutzbach"; "Stutzbach, Daniel"; "Stutzbach"
-"Michael J. Sullivan"; "Sullivan, Michael J."; "Sullivan"
-"Roman Suzi"; "Suzi, Roman"; "Suzi"
-"Dennis Sweeney"; "Sweeney, Dennis"; "Sweeney"
-"Talin"; "Talin"; "Talin"
-"Steven Taschuk"; "Taschuk, Steven"; "Taschuk"
-"Batuhan Taskaya"; "Taskaya, Batuhan"; "Taskaya"
-"Martin Teichmann"; "Teichmann, Martin"; "Teichmann"
-"The Python core team and community"; "The Python core team and community"; "The Python core team and community"
-"Geoffrey Thomas"; "Thomas, Geoffrey"; "Thomas"
-"Oren Tirosh"; "Tirosh, Oren"; "Tirosh"
-"Stephen J. Turnbull"; "Turnbull, Stephen J."; "Turnbull"
-"Daniel Urban"; "Urban, Daniel"; "Urban"
-"Eric N. Vander Weele"; "Vander Weele, Eric N."; "Vander Weele"
-"Till Varoquaux"; "Varoquaux, Till"; "Varoquaux"
-"Alexandre Vassalotti"; "Vassalotti, Alexandre"; "Vassalotti"
-"Mike Verdone"; "Verdone, Mike"; "Verdone"
-"Dino Viehland"; "Viehland, Dino"; "Viehland"
-"Petr Viktorin"; "Viktorin, Petr"; "Viktorin"
-"Zachary Ware"; "Ware, Zachary"; "Ware"
-"Gregory R. Warnes"; "Warnes, Gregory R."; "Warnes"
-"Barry Warsaw"; "Warsaw, Barry"; "Warsaw"
-"Terence Way"; "Way, Terence"; "Way"
-"Cliff Wells"; "Wells, Cliff"; "Wells"
-"Jervis Whitley"; "Whitley, Jervis"; "Whitley"
-"Mark Williams"; "Williams, Mark"; "Williams"
-"Carol Willing"; "Willing, Carol"; "Willing"
-"Greg Wilson"; "Wilson, Greg"; "Wilson"
-"Collin Winter"; "Winter, Collin"; "Winter"
-"Thomas Wouters"; "Wouters, Thomas"; "Wouters"
-"Masayuki Yamamoto"; "Yamamoto, Masayuki"; "Yamamoto"
-"Jeffrey Yasskin"; "Yasskin, Jeffrey"; "Yasskin"
-"Ka-Ping Yee"; "Yee, Ka-Ping"; "Yee"
-"Moshe Zadka"; "Zadka, Moshe"; "Zadka"
-"Koos Zevenhoven"; "Zevenhoven, Koos"; "Zevenhoven"
-"Huaiyu Zhu"; "Zhu, Huaiyu"; "Zhu"
-"Shannon Zhu"; "Zhu, Shannon"; "Zhu"
-"Tarek Ziadé"; "Ziadé, Tarek"; "Ziadé"
\ No newline at end of file
+"Full Name", "Surname First", "Name Reference"
+"Aahz", "Aahz", "Aahz"
+"James C. Ahlstrom", "Ahlstrom, James C.", "Ahlstrom"
+"Jim Althoff", "Althoff, Jim", "Althoff"
+"Kevin Altis", "Altis, Kevin", "Altis"
+"Chris Angelico", "Angelico, Chris", "Angelico"
+"Philipp Angerer", "Angerer, Philipp", "Angerer"
+"David Ascher", "Ascher, David", "Ascher"
+"Peter Astrand", "Astrand, Peter", "Astrand"
+"Carl Banks", "Banks, Carl", "Banks"
+"Christopher Barker", "Barker, Christopher", "Barker"
+"Paul Barrett", "Barrett, Paul", "Barrett"
+"Facundo Batista", "Batista, Facundo", "Batista"
+"Anthony Baxter", "Baxter, Anthony", "Baxter"
+"Stefan Behnel", "Behnel, Stefan", "Behnel"
+"Thomas Bellman", "Bellman, Thomas", "Bellman"
+"Alexander Belopolsky", "Belopolsky, Alexander", "Belopolsky"
+"Eli Bendersky", "Bendersky, Eli", "Bendersky"
+"Cory Benfield", "Benfield, Cory", "Benfield"
+"Steven Bethard", "Bethard, Steven", "Bethard"
+"Stéphane Bidoul", "Bidoul, Stéphane", "Bidoul"
+"Stefano Borini", "Borini, Stefano", "Borini"
+"Georg Brandl", "Brandl, Georg", "Brandl"
+"Erik M. Bray", "Bray, Erik M.", "Bray"
+"Gerald Britton", "Britton, Gerald", "Britton"
+"Oleg Broytman", "Broytman, Oleg", "Broytman"
+"Benoit Bryon", "Bryon, Benoit", "Bryon"
+"Brandt Bucher", "Bucher, Brandt", "Bucher"
+"Brett Cannon", "Cannon, Brett", "Cannon"
+"Justin Cappos", "Cappos, Justin", "Cappos"
+"Josiah Carlson", "Carlson, Josiah", "Carlson"
+"W Isaac Carroll", "Carroll, W Isaac", "Carroll"
+"Matt Chisholm", "Chisholm, Matt", "Chisholm"
+"Nick Coghlan", "Coghlan, Nick", "Coghlan"
+"Dave Cole", "Cole, Dave", "Cole"
+"Robert Collins", "Collins, Robert", "Collins"
+"Paul Colomiets", "Colomiets, Paul", "Colomiets"
+"Mario Corchero", "Corchero, Mario", "Corchero"
+"Christopher A. Craig", "Craig, Christopher A.", "Craig"
+"Laura Creighton", "Creighton, Laura", "Creighton"
+"Steven D'Aprano", "D'Aprano, Steven", "D'Aprano"
+"Kushal Das", "Das, Kushal", "Das"
+"Ned Deily", "Deily, Ned", "Deily"
+"Tim Delaney", "Delaney, Tim", "Delaney"
+"Lois Anne DeLong", "DeLong, Lois Anne", "DeLong"
+"Jeroen Demeyer", "Demeyer, Jeroen", "Demeyer"
+"Vladimir Diaz", "Diaz, Vladimir", "Diaz"
+"Jack Diederich", "Diederich, Jack", "Diederich"
+"Steve Dower", "Dower, Steve", "Dower"
+"Walter Dörwald", "Dörwald, Walter", "Dörwald"
+"Fred L. Drake, Jr.", "Drake, Fred L., Jr.", "Drake"
+"Michael P. Dubner", "Dubner, Michael P.", "Dubner"
+"Paul F. Dubois", "Dubois, Paul F.", "Dubois"
+"Ernest W. Durbin III", "Durbin, Ernest W., III", "Durbin"
+"P.J. Eby", "Eby, P.J.", "Eby"
+"Phillip J. Eby", "Eby, Phillip J.", "Eby"
+"Tal Einat", "Einat, Tal", "Einat"
+"Micah Elliott", "Elliott, Micah", "Elliott"
+"Jeff Epler", "Epler, Jeff", "Epler"
+"David Eppstein", "Eppstein, David", "Eppstein"
+"Clark C. Evans", "Evans, Clark C.", "Evans"
+"Gregory Ewing", "Ewing, Gregory", "Ewing"
+"Greg Ewing", "Ewing, Greg", "Ewing"
+"Martijn Faassen", "Faassen, Martijn", "Faassen"
+"Ben Finney", "Finney, Ben", "Finney"
+"Michael Foord", "Foord, Michael", "Foord"
+"Ethan Furman", "Furman, Ethan", "Furman"
+"Pablo Galindo", "Galindo, Pablo", "Galindo"
+"Paul Ganssle", "Ganssle, Paul", "Ganssle"
+"Alex Gaynor", "Gaynor, Alex", "Gaynor"
+"Pradyun Gedam", "Gedam, Pradyun", "Gedam"
+"Damien George", "George, Damien", "George"
+"Frédéric B. Giacometti", "Giacometti, Frédéric B.", "Giacometti"
+"Scott Gilbert", "Gilbert, Scott", "Gilbert"
+"Ryan Gonzalez", "Gonzalez, Ryan", "Gonzalez"
+"David Goodger", "Goodger, David", "Goodger"
+"Grant Griffin", "Griffin, Grant", "Griffin"
+"Mark E. Haase", "Haase, Mark E.", "Haase"
+"Mark Hammond", "Hammond, Mark", "Hammond"
+"Peter Harris", "Harris, Peter", "Harris"
+"Larry Hastings", "Hastings, Larry", "Hastings"
+"Christian Heimes", "Heimes, Christian", "Heimes"
+"Thomas Heller", "Heller, Thomas", "Heller"
+"Doug Hellmann", "Hellmann, Doug", "Hellmann"
+"Magnus Lie Hetland", "Hetland, Magnus Lie", "Hetland"
+"Raymond Hettinger", "Hettinger, Raymond", "Hettinger"
+"Neil Hodgson", "Hodgson, Neil", "Hodgson"
+"Daniel Holth", "Holth, Daniel", "Holth"
+"Philip House", "House, Philip", "House"
+"Laurens Van Houtven", "Van Houtven, Laurens", "Houtven"
+"Ben Hoyt", "Hoyt, Ben", "Hoyt"
+"Miro Hrončok", "Hrončok, Miro", "Hrončok"
+"Michael Hudson", "Hudson, Michael", "Hudson"
+"Jeremy Hylton", "Hylton, Jeremy", "Hylton"
+"Inada Naoki", "Inada, Naoki", "Inada"
+"Dustin Ingram", "Ingram, Dustin", "Ingram"
+"Atsuo Ishimoto", "Ishimoto, Atsuo", "Ishimoto"
+"Jack Jansen", "Jansen, Jack", "Jansen"
+"Chris Jerdonek", "Jerdonek, Chris", "Jerdonek"
+"Joseph Jevnik", "Jevnik, Joseph", "Jevnik"
+"Jim J. Jewett", "Jewett, Jim J.", "Jewett"
+"Jim Jewett", "Jewett, Jim", "Jewett"
+"Ewa Jodlowska", "Jodlowska, Ewa", "Jodlowska"
+"Richard Jones", "Jones, Richard", "Jones"
+"Konstantin Kashin", "Kashin, Konstantin", "Kashin"
+"Reid Kleckner", "Kleckner, Reid", "Kleckner"
+"Thomas Kluyver", "Kluyver, Thomas", "Kluyver"
+"Stepan Koltsov", "Koltsov, Stepan", "Koltsov"
+"Stefan Krah", "Krah, Stefan", "Krah"
+"Sebastian Kreft", "Kreft, Sebastian", "Kreft"
+"Holger Krekel", "Krekel, Holger", "Krekel"
+"A.M. Kuchling", "Kuchling, A.M.", "Kuchling"
+"Trishank Karthik Kuppusamy", "Kuppusamy, Trishank Karthik", "Kuppusamy"
+"Robert Kuska", "Kuska, Robert", "Kuska"
+"Joshua Landau", "Landau, Joshua", "Landau"
+"Łukasz Langa", "Langa, Łukasz", "Langa"
+"Michael Lee", "Lee, Michael", "Lee"
+"Jukka Lehtosalo", "Lehtosalo, Jukka", "Lehtosalo"
+"Marc-André Lemburg", "Lemburg, Marc-André", "Lemburg"
+"Ivan Levkivskyi", "Levkivskyi, Ivan", "Levkivskyi"
+"Gregory Lielens", "Lielens, Gregory", "Lielens"
+"Björn Lindqvist", "Lindqvist, Björn", "Lindqvist"
+"Joshua Lock", "Lock, Joshua", "Lock"
+"Tony Lownds", "Lownds, Tony", "Lownds"
+"Martin von Löwis", "von Löwis, Martin", "von Löwis"
+"Martin v. Löwis", "\v. Löwis, Martin", "\v. Löwis"
+"Mariatta", "Mariatta", "Mariatta"
+"Alex Martelli", "Martelli, Alex", "Martelli"
+"Joseph Martinot-Lagarde", "Martinot-Lagarde, Joseph", "Martinot-Lagarde"
+"Lino Mastrodomenico", "Mastrodomenico, Lino", "Mastrodomenico"
+"Patrick Maupin", "Maupin, Patrick", "Maupin"
+"Andrew McClelland", "McClelland, Andrew", "McClelland"
+"Charles R. McCreary", "McCreary, Charles R.", "McCreary"
+"Chris McDonough", "McDonough, Chris", "McDonough"
+"Robert T. McGibbon", "McGibbon, Robert T.", "McGibbon"
+"Gordon McMillan", "McMillan, Gordon", "McMillan"
+"Andrew McNamara", "McNamara, Andrew", "McNamara"
+"Ezio Melotti", "Melotti, Ezio", "Melotti"
+"Mark Mendoza", "Mendoza, Mark", "Mendoza"
+"Markus Meskanen", "Meskanen, Markus", "Meskanen"
+"Mike Meyer", "Meyer, Mike", "Meyer"
+"Carl Meyer", "Meyer, Carl", "Meyer"
+"Trent Mick", "Mick, Trent", "Mick"
+"Mike G. Miller", "Miller, Mike G.", "Miller"
+"Skip Montanaro", "Montanaro, Skip", "Montanaro"
+"Peter Moody", "Moody, Peter", "Moody"
+"Marina Moore", "Moore, Marina", "Moore"
+"Paul Moore", "Moore, Paul", "Moore"
+"R David Murray", "Murray, R David", "Murray"
+"Charles-François Natali", "Natali, Charles-François", "Natali"
+"Lysandros Nikolaou", "Nikolaou, Lysandros", "Nikolaou"
+"Jesse Noller", "Noller, Jesse", "Noller"
+"Ben North", "North, Ben", "North"
+"Neal Norwitz", "Norwitz, Neal", "Norwitz"
+"Dirkjan Ochtman", "Ochtman, Dirkjan", "Ochtman"
+"Travis Oliphant", "Oliphant, Travis", "Oliphant"
+"Jason Orendorff", "Orendorff, Jason", "Orendorff"
+"Tomáš Orsava", "Orsava, Tomáš", "Orsava"
+"Richard Oudkerk", "Oudkerk, Richard", "Oudkerk"
+"Ronald Oussoren", "Oussoren, Ronald", "Oussoren"
+"Julien Palard", "Palard, Julien", "Palard"
+"Samuele Pedroni", "Pedroni, Samuele", "Pedroni"
+"Berker Peksag", "Peksag, Berker", "Peksag"
+"Michel Pelletier", "Pelletier, Michel", "Pelletier"
+"Tim Peters", "Peters, Tim", "Peters"
+"Benjamin Peterson", "Peterson, Benjamin", "Peterson"
+"Jason Petrone", "Petrone, Jason", "Petrone"
+"Antoine Pitrou", "Pitrou, Antoine", "Pitrou"
+"Marcel Plch", "Plch, Marcel", "Plch"
+"James Polley", "Polley, James", "Polley"
+"Philippe PRADOS", "Prados, Philippe", "Prados"
+"Elvis Pranskevichus", "Pranskevichus, Elvis", "Pranskevichus"
+"Paul Prescod", "Prescod, Paul", "Prescod"
+"(James) Eric Pruitt", "Pruitt, (James) Eric", "Pruitt"
+"Lukas Puehringer", "Puehringer, Lukas", "Puehringer"
+"Brian Quinlan", "Quinlan, Brian", "Quinlan"
+"Terry Reedy", "Reedy, Terry", "Reedy"
+"Lennart Regebro", "Regebro, Lennart", "Regebro"
+"Sean Reifschneider", "Reifschneider, Sean", "Reifschneider"
+"Christian R. Reis", "Reis, Christian R.", "Reis"
+"Jonathan Riehl", "Riehl, Jonathan", "Riehl"
+"Lisa Roach", "Roach, Lisa", "Roach"
+"Andre Roberge", "Roberge, Andre", "Roberge"
+"Armin Ronacher", "Ronacher, Armin", "Ronacher"
+"Guido van Rossum", "van Rossum, Guido (GvR)", "GvR"
+"Just van Rossum", "van Rossum, Just (JvR)", "JvR"
+"Todd Rovito", "Rovito, Todd", "Rovito"
+"Lie Ryan", "Ryan, Lie", "Ryan"
+"Vinay Sajip", "Sajip, Vinay", "Sajip"
+"Pablo Galindo Salgado", "Salgado, Pablo Galindo", "Salgado"
+"Neil Schemenauer", "Schemenauer, Neil", "Schemenauer"
+"Peter Schneider-Kamp", "Schneider-Kamp, Peter", "Schneider-Kamp"
+"Ed Schofield", "Schofield, Ed", "Schofield"
+"Yury Selivanov", "Selivanov, Yury", "Selivanov"
+"Jiwon Seo", "Seo, Jiwon", "Seo"
+"Mark Shannon", "Shannon, Mark", "Shannon"
+"Cameron Simpson", "Simpson, Cameron", "Simpson"
+"Greg Slodkowicz", "Slodkowicz, Greg", "Slodkowicz"
+"Nathaniel J. Smith", "Smith, Nathaniel J.", "Smith"
+"Gregory P. Smith", "Smith, Gregory P.", "Smith"
+"Kevin D. Smith", "Smith, Kevin D.", "Smith"
+"Ethan Smith", "Smith, Ethan", "Smith"
+"Nathaniel Smith", "Smith, Nathaniel", "Smith"
+"Eric V. Smith", "Smith, Eric V.", "Smith"
+"Eric Snow", "Snow, Eric", "Snow"
+"Calvin Spealman", "Spealman, Calvin", "Spealman"
+"Kerrick Staley", "Staley, Kerrick", "Staley"
+"Greg Stein", "Stein, Greg", "Stein"
+"Victor Stinner", "Stinner, Victor", "Stinner"
+"Serhiy Storchaka", "Storchaka, Serhiy", "Storchaka"
+"Donald Stufft", "Stufft, Donald", "Stufft"
+"Daniel Stutzbach", "Stutzbach, Daniel", "Stutzbach"
+"Michael J. Sullivan", "Sullivan, Michael J.", "Sullivan"
+"Roman Suzi", "Suzi, Roman", "Suzi"
+"Dennis Sweeney", "Sweeney, Dennis", "Sweeney"
+"Talin", "Talin", "Talin"
+"Steven Taschuk", "Taschuk, Steven", "Taschuk"
+"Batuhan Taskaya", "Taskaya, Batuhan", "Taskaya"
+"Martin Teichmann", "Teichmann, Martin", "Teichmann"
+"The Python core team and community", "The Python core team and community", "The Python core team and community"
+"Geoffrey Thomas", "Thomas, Geoffrey", "Thomas"
+"Oren Tirosh", "Tirosh, Oren", "Tirosh"
+"Stephen J. Turnbull", "Turnbull, Stephen J.", "Turnbull"
+"Daniel Urban", "Urban, Daniel", "Urban"
+"Eric N. Vander Weele", "Vander Weele, Eric N.", "Vander Weele"
+"Till Varoquaux", "Varoquaux, Till", "Varoquaux"
+"Alexandre Vassalotti", "Vassalotti, Alexandre", "Vassalotti"
+"Mike Verdone", "Verdone, Mike", "Verdone"
+"Dino Viehland", "Viehland, Dino", "Viehland"
+"Petr Viktorin", "Viktorin, Petr", "Viktorin"
+"Zachary Ware", "Ware, Zachary", "Ware"
+"Gregory R. Warnes", "Warnes, Gregory R.", "Warnes"
+"Barry Warsaw", "Warsaw, Barry", "Warsaw"
+"Terence Way", "Way, Terence", "Way"
+"Cliff Wells", "Wells, Cliff", "Wells"
+"Jervis Whitley", "Whitley, Jervis", "Whitley"
+"Mark Williams", "Williams, Mark", "Williams"
+"Carol Willing", "Willing, Carol", "Willing"
+"Greg Wilson", "Wilson, Greg", "Wilson"
+"Collin Winter", "Winter, Collin", "Winter"
+"Thomas Wouters", "Wouters, Thomas", "Wouters"
+"Masayuki Yamamoto", "Yamamoto, Masayuki", "Yamamoto"
+"Jeffrey Yasskin", "Yasskin, Jeffrey", "Yasskin"
+"Ka-Ping Yee", "Yee, Ka-Ping", "Yee"
+"Moshe Zadka", "Zadka, Moshe", "Zadka"
+"Koos Zevenhoven", "Zevenhoven, Koos", "Zevenhoven"
+"Huaiyu Zhu", "Zhu, Huaiyu", "Zhu"
+"Shannon Zhu", "Zhu, Shannon", "Zhu"
+"Tarek Ziadé", "Ziadé, Tarek", "Ziadé"
\ No newline at end of file
diff --git a/pepreader/generate_pep_index.py b/pepreader/generate_pep_index.py
index 34149cbf23a..5933478e0f0 100644
--- a/pepreader/generate_pep_index.py
+++ b/pepreader/generate_pep_index.py
@@ -35,7 +35,7 @@ def create_pep_zero(_, env, docnames):
pep_pat = re.compile(r"pep-\d{4}") # Path.match() doesn't support regular expressions
with open("AUTHORS.csv", "r", encoding="UTF8") as f:
- read = csv.DictReader(f, delimiter=";")
+ read = csv.DictReader(f, quotechar='"', skipinitialspace=True)
author_data = {}
for line in read:
full_name = line.pop("Full Name").strip().strip("\"")
From 3668d9f21540b04e9284010afea0b09f35e5b371 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Thu, 30 Apr 2020 01:39:05 +0100
Subject: [PATCH 036/110] Allow Failures in Travis
---
.travis.yml | 15 +++++----------
build.py | 2 +-
pepreader/__init__.py | 2 +-
3 files changed, 7 insertions(+), 12 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 3ac9b40efc5..8fb00df44f1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -43,13 +43,6 @@ jobs:
- COMMAND="python3 ./build.py -f"
- FAIL_ALLOWED=true
- # Tests build with Fail on Warning (Nitpicky)
- - name: "3.8 Fail on Warning Nitpicky"
- python: 3.8
- env:
- - COMMAND="python3 ./build.py -f -n"
- - FAIL_ALLOWED=true
-
# Checks link references within PEPs
- name: "3.8 Check Links"
python: 3.8
@@ -58,8 +51,10 @@ jobs:
- FAIL_ALLOWED=true
allow_failures:
- # Note test failure, but pass the build
- - env:
- - FAIL_ALLOWED=true
+ # Note test failure, but pass the build as a whole
+ - name: "3.8 Fail on Warning"
+
+ # Check links can fail as it is dependent on external pages
+ - name: "3.8 Check Links"
script: $COMMAND
\ No newline at end of file
diff --git a/build.py b/build.py
index 69eb6c9ac48..9ba00630f9c 100644
--- a/build.py
+++ b/build.py
@@ -43,6 +43,6 @@ def create_parser():
app = Sphinx(
source_directory, configuration_directory, build_directory, doctree_directory, builder,
- confoverrides=config_overrides, warningiserror=args.fail_on_warning, keep_going=args.fail_on_warning,
+ confoverrides=config_overrides, warningiserror=args.fail_on_warning,
)
app.build()
diff --git a/pepreader/__init__.py b/pepreader/__init__.py
index 95496374c82..f7529f3c431 100644
--- a/pepreader/__init__.py
+++ b/pepreader/__init__.py
@@ -13,6 +13,6 @@ def setup(app: Sphinx):
app.connect("env-before-read-docs", create_pep_zero)
app.add_source_parser(PEPParser)
- app.add_role('pep', PEPRole())
+ app.add_role('pep', PEPRole(), override=True)
return {'version': __version__, 'parallel_read_safe': True, 'parallel_write_safe': True}
From 3d8c632a35ad837038bc1f1eb938f19aefddf7ce Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Fri, 1 May 2020 06:27:34 +0100
Subject: [PATCH 037/110] Update Makefile
---
.travis.yml | 12 ++++++------
Makefile | 9 ++++++---
2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 8fb00df44f1..38bd56ede61 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,7 +14,7 @@ jobs:
# Main run, tests build, RSS, and packaging
- name: "3.7 Makefile Build"
python: 3.7
- env: COMMAND="make -j$(nproc)"
+ env: COMMAND="make sphinx -j$(nproc)"
deploy:
provider: script
script: bash deploy.bash
@@ -24,30 +24,30 @@ jobs:
# Tests build on 3.7-dev
- name: "3.7-dev Build Test"
python: 3.7-dev
- env: COMMAND="python3 ./build.py"
+ env: COMMAND="make sphinx -j$(nproc)"
# Tests build on 3.8
- name: "3.8 Build Test"
python: 3.8
- env: COMMAND="python3 ./build.py"
+ env: COMMAND="make sphinx -j$(nproc)"
# Tests build on 3.8-dev
- name: "3.8-dev Build Test"
python: 3.8-dev
- env: COMMAND="python3 ./build.py"
+ env: COMMAND="make sphinx -j$(nproc)"
# Tests build with Fail on Warning
- name: "3.8 Fail on Warning"
python: 3.8
env:
- - COMMAND="python3 ./build.py -f"
+ - COMMAND="make fail_on_warning -j$(nproc)"
- FAIL_ALLOWED=true
# Checks link references within PEPs
- name: "3.8 Check Links"
python: 3.8
env:
- - COMMAND="python3 ./build.py -c"
+ - COMMAND="make check_links -j$(nproc)"
- FAIL_ALLOWED=true
allow_failures:
diff --git a/Makefile b/Makefile
index 98af7c7fd3b..2bba494ca65 100644
--- a/Makefile
+++ b/Makefile
@@ -10,12 +10,15 @@ all: sphinx
sphinx:
$(PYTHON) build.py
+fail_on_warning:
+ $(PYTHON) build.py -f
+
+check_links:
+ $(PYTHON) build.py -c
+
rss:
$(PYTHON) pep2rss.py .
-install:
- echo "Installing is not necessary anymore. It will be done in post-commit."
-
clean:
-rm pep-0000.rst
-rm *.html
From 580ec933e2739b670c41cdb17d4ed110618ea7bb Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Fri, 1 May 2020 06:32:17 +0100
Subject: [PATCH 038/110] Re-organise extension
- Add custom HTML Translator
- Add ReST Target Footnotes
- Add module configuration file
---
conf.py | 4 +-
pep_extensions/__init__.py | 21 +++++++
pep_extensions/config.py | 8 +++
.../pep_processor}/pep_contents.py | 0
.../pep_processor}/pep_headers.py | 24 ++++----
.../pep_processor/pep_html_translator.py | 30 ++++++++++
.../pep_processor}/pep_parser.py | 9 ++-
.../pep_processor}/pep_role.py | 8 ++-
.../pep_processor/pep_target_notes.py | 60 +++++++++++++++++++
.../pep_processor}/pep_title.py | 0
.../pep_processor}/pep_zero.py | 8 +--
.../pepzero}/generate_pep_index.py | 0
{pepreader => pep_extensions/pepzero}/pep0.py | 6 +-
.../pepzero}/pep0_constants.py | 4 +-
.../pepzero}/pep0_writer.py | 2 +-
pepreader/__init__.py | 18 ------
16 files changed, 154 insertions(+), 48 deletions(-)
create mode 100644 pep_extensions/__init__.py
create mode 100644 pep_extensions/config.py
rename {pepreader => pep_extensions/pep_processor}/pep_contents.py (100%)
rename {pepreader => pep_extensions/pep_processor}/pep_headers.py (87%)
create mode 100644 pep_extensions/pep_processor/pep_html_translator.py
rename {pepreader => pep_extensions/pep_processor}/pep_parser.py (62%)
rename {pepreader => pep_extensions/pep_processor}/pep_role.py (52%)
create mode 100644 pep_extensions/pep_processor/pep_target_notes.py
rename {pepreader => pep_extensions/pep_processor}/pep_title.py (100%)
rename {pepreader => pep_extensions/pep_processor}/pep_zero.py (93%)
rename {pepreader => pep_extensions/pepzero}/generate_pep_index.py (100%)
rename {pepreader => pep_extensions/pepzero}/pep0.py (97%)
rename {pepreader => pep_extensions/pepzero}/pep0_constants.py (96%)
rename {pepreader => pep_extensions/pepzero}/pep0_writer.py (99%)
delete mode 100644 pepreader/__init__.py
diff --git a/conf.py b/conf.py
index 17a4200d663..57b6b6a1a87 100644
--- a/conf.py
+++ b/conf.py
@@ -5,7 +5,7 @@
import sys
from pathlib import Path
-sys.path.extend(str(Path('./pepreader').absolute()))
+sys.path.extend(str(Path('./pep_extensions').absolute()))
# -- Project information -----------------------------------------------------
@@ -27,7 +27,7 @@
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
-extensions = ["pepreader"]
+extensions = ["pep_extensions"]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
diff --git a/pep_extensions/__init__.py b/pep_extensions/__init__.py
new file mode 100644
index 00000000000..0dfc24c3762
--- /dev/null
+++ b/pep_extensions/__init__.py
@@ -0,0 +1,21 @@
+"""Sphinx extensions for performant PEP processing"""
+from sphinx.application import Sphinx
+
+from pep_extensions.config import __version__
+
+from pep_extensions.pep_processor import pep_parser
+from pep_extensions.pep_processor import pep_role
+from pep_extensions.pep_processor import pep_html_translator
+
+from pep_extensions.pepzero.generate_pep_index import create_pep_zero
+
+
+def setup(app: Sphinx):
+ """Initialize Sphinx extension."""
+
+ app.add_source_parser(pep_parser.PEPParser)
+ app.add_role('pep', pep_role.PEPRole(), override=True)
+ app.set_translator("html", pep_html_translator.PEPTranslator)
+ app.connect("env-before-read-docs", create_pep_zero)
+
+ return {'version': __version__, 'parallel_read_safe': True, 'parallel_write_safe': True}
diff --git a/pep_extensions/config.py b/pep_extensions/config.py
new file mode 100644
index 00000000000..7a6b6d97aa1
--- /dev/null
+++ b/pep_extensions/config.py
@@ -0,0 +1,8 @@
+"""Misc. config variables for the PEP extensions module."""
+import re
+
+__version__ = '1.0.0'
+pep_stem = "pep-{:0>4}"
+pep_url = f"{pep_stem}.html"
+pep_vcs_url = f"https://github.com/python/peps/blob/master/{pep_stem}.txt"
+rcs_keyword_substitutions = [(re.compile(r"\$[a-zA-Z]+: (.+) \$$"), r"\1")]
\ No newline at end of file
diff --git a/pepreader/pep_contents.py b/pep_extensions/pep_processor/pep_contents.py
similarity index 100%
rename from pepreader/pep_contents.py
rename to pep_extensions/pep_processor/pep_contents.py
diff --git a/pepreader/pep_headers.py b/pep_extensions/pep_processor/pep_headers.py
similarity index 87%
rename from pepreader/pep_headers.py
rename to pep_extensions/pep_processor/pep_headers.py
index 49cceacf190..1b308f6048b 100644
--- a/pepreader/pep_headers.py
+++ b/pep_extensions/pep_processor/pep_headers.py
@@ -5,8 +5,13 @@
from docutils import utils
from docutils import transforms
from docutils.transforms import peps
-from pepreader import pep_zero
-import pepreader
+
+from pep_extensions.pep_processor import pep_zero
+import pep_extensions.config
+
+pep_url = pep_extensions.config.pep_url
+pep_vcs_url = pep_extensions.config.pep_vcs_url
+rcs_keyword_substitutions = pep_extensions.config.rcs_keyword_substitutions
class DataError(Exception):
@@ -22,11 +27,6 @@ class PEPHeaders(transforms.Transform):
default_priority = 330
- pep_url = pepreader.pep_url
- pep_cvs_url = "https://github.com/python/peps/blob/master/pep-{:0>4}.txt"
-
- rcs_keyword_substitutions = [(re.compile(r"\$[a-zA-Z]+: (.+) \$$"), r"\1")]
-
def apply(self):
if not len(self.document):
# @@@ replace these DataErrors with proper system messages
@@ -47,7 +47,7 @@ def apply(self):
value = pep_field[1].astext()
try:
pep = int(value)
- cvs_url = self.pep_cvs_url.format(pep)
+ cvs_url = pep_vcs_url.format(pep)
except ValueError:
pep = value
msg = self.document.reporter.warning(
@@ -105,17 +105,17 @@ def apply(self):
newbody.append(nodes.reference(
refpep, refpep,
refuri=(self.document.settings.pep_base_url
- + self.pep_url.format(pepno))))
+ + pep_url.format(pepno))))
newbody.append(space)
para[:] = newbody[:-1] # drop trailing space
elif name == "last-modified":
- utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
+ utils.clean_rcs_keywords(para, 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.format(12)
+ uri = self.document.settings.pep_base_url + pep_url.format(12)
para[:] = [nodes.reference("", pep_type, refuri=uri)]
elif name == "version" and len(body):
- utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
+ utils.clean_rcs_keywords(para, rcs_keyword_substitutions)
diff --git a/pep_extensions/pep_processor/pep_html_translator.py b/pep_extensions/pep_processor/pep_html_translator.py
new file mode 100644
index 00000000000..50bc13618f1
--- /dev/null
+++ b/pep_extensions/pep_processor/pep_html_translator.py
@@ -0,0 +1,30 @@
+from docutils.nodes import Node
+import sphinx.writers.html5 as html5
+
+
+class PEPTranslator(html5.HTML5Translator):
+ def depart_label(self, node):
+ if not self.settings.footnote_backlinks:
+ self.body.append('')
+ self.body.append('\n
')
+ return
+
+ # If only one reference to this footnote
+ back_references = node.parent['backrefs']
+ if len(back_references) == 1:
+ self.body.append('')
+
+ # Close the tag
+ self.body.append('')
+
+ # If more than one reference
+ if len(back_references) > 1:
+ back_links = [f'{i}' for (i, ref) in enumerate(back_references, 1)]
+ back_links_str = ", ".join(back_links)
+ self.body.append(f" ({back_links_str}) ")
+
+ # Close the def tags
+ self.body.append('\n
')
+
+ def unknown_visit(self, node: Node) -> None:
+ pass
diff --git a/pepreader/pep_parser.py b/pep_extensions/pep_processor/pep_parser.py
similarity index 62%
rename from pepreader/pep_parser.py
rename to pep_extensions/pep_processor/pep_parser.py
index ad320d3e786..d0e2d033465 100644
--- a/pepreader/pep_parser.py
+++ b/pep_extensions/pep_processor/pep_parser.py
@@ -1,7 +1,9 @@
from sphinx import parsers
-from . import pep_headers
-from . import pep_title
-from . import pep_contents
+
+from pep_extensions.pep_processor import pep_headers
+from pep_extensions.pep_processor import pep_title
+from pep_extensions.pep_processor import pep_contents
+from pep_extensions.pep_processor import pep_target_notes
class PEPParser(parsers.RSTParser):
@@ -16,5 +18,6 @@ def get_transforms(self):
pep_headers.PEPHeaders,
pep_title.PEPTitle,
pep_contents.PEPContents,
+ pep_target_notes.PEPTargetNotes,
])
return transforms
diff --git a/pepreader/pep_role.py b/pep_extensions/pep_processor/pep_role.py
similarity index 52%
rename from pepreader/pep_role.py
rename to pep_extensions/pep_processor/pep_role.py
index b4632668a87..92ecbb729d5 100644
--- a/pepreader/pep_role.py
+++ b/pep_extensions/pep_processor/pep_role.py
@@ -1,5 +1,7 @@
from sphinx import roles
-import pepreader
+import pep_extensions.config
+
+pep_url = pep_extensions.config.pep_url
class PEPRole(roles.PEP):
@@ -8,6 +10,6 @@ def build_uri(self) -> str:
base_url = self.inliner.document.settings.pep_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
- return base_url + (pepreader.pep_url + '#{}').format(int(ret[0]), ret[1])
+ return base_url + (pep_url + '#{}').format(int(ret[0]), ret[1])
else:
- return base_url + pepreader.pep_url.format(int(ret[0]))
+ return base_url + pep_url.format(int(ret[0]))
diff --git a/pep_extensions/pep_processor/pep_target_notes.py b/pep_extensions/pep_processor/pep_target_notes.py
new file mode 100644
index 00000000000..b85cbf20e34
--- /dev/null
+++ b/pep_extensions/pep_processor/pep_target_notes.py
@@ -0,0 +1,60 @@
+from pathlib import Path
+
+import docutils.transforms as transforms
+from docutils.transforms import references
+from docutils.transforms import misc
+from docutils import nodes
+
+
+class PEPTargetNotes(transforms.Transform):
+
+ """
+ Locate the "References" section, insert a placeholder for an external
+ target footnote insertion transform at the end, and schedule the
+ transform to run immediately.
+ """
+
+ default_priority = 520
+
+ def apply(self):
+ if not Path(self.document["source"]).match("pep-*"):
+ # not a PEP file
+ return
+
+ doc = self.document[0]
+ i = len(doc) - 1
+ refsect = copyright = None
+ while i >= 0 and isinstance(doc[i], nodes.section):
+ title_words = doc[i][0].astext().lower().split()
+ if 'references' in title_words:
+ refsect = doc[i]
+ break
+ elif 'copyright' in title_words:
+ copyright = i
+ i -= 1
+ if not refsect:
+ refsect = nodes.section()
+ refsect += nodes.title('', 'References')
+ self.document.set_id(refsect)
+ if copyright:
+ # Put the new "References" section before "Copyright":
+ doc.insert(copyright, refsect)
+ else:
+ # Put the new "References" section at end of doc:
+ doc.append(refsect)
+ pending = nodes.pending(references.TargetNotes)
+ refsect.append(pending)
+ self.document.note_pending(pending, 0)
+ pending = nodes.pending(misc.CallBack, details={'callback': self.cleanup_callback})
+ refsect.append(pending)
+ self.document.note_pending(pending, 1)
+
+ @staticmethod
+ def cleanup_callback(pending):
+ """
+ Remove an empty "References" section.
+
+ Called after the `references.TargetNotes` transform is complete.
+ """
+ if len(pending.parent) == 2: # and
+ pending.parent.parent.remove(pending.parent)
diff --git a/pepreader/pep_title.py b/pep_extensions/pep_processor/pep_title.py
similarity index 100%
rename from pepreader/pep_title.py
rename to pep_extensions/pep_processor/pep_title.py
diff --git a/pepreader/pep_zero.py b/pep_extensions/pep_processor/pep_zero.py
similarity index 93%
rename from pepreader/pep_zero.py
rename to pep_extensions/pep_processor/pep_zero.py
index a487bfcbbb3..ce4132a03a8 100644
--- a/pepreader/pep_zero.py
+++ b/pep_extensions/pep_processor/pep_zero.py
@@ -1,7 +1,9 @@
from docutils import nodes
from docutils import transforms
from docutils.transforms import peps
-import pepreader
+import pep_extensions.config
+
+pep_url = pep_extensions.config.pep_url
class PEPZero(transforms.Transform):
@@ -29,8 +31,6 @@ class PEPZeroSpecial(nodes.SparseNodeVisitor):
themselves.
"""
- pep_url = pepreader.pep_url
-
def __init__(self, document):
super().__init__(document)
self.pep_table = None
@@ -70,7 +70,7 @@ def visit_entry(self, node):
try:
pep = int(text)
ref = (self.document.settings.pep_base_url
- + self.pep_url.format(pep))
+ + pep_url.format(pep))
p[0] = nodes.reference(text, text, refuri=ref)
except ValueError:
pass
diff --git a/pepreader/generate_pep_index.py b/pep_extensions/pepzero/generate_pep_index.py
similarity index 100%
rename from pepreader/generate_pep_index.py
rename to pep_extensions/pepzero/generate_pep_index.py
diff --git a/pepreader/pep0.py b/pep_extensions/pepzero/pep0.py
similarity index 97%
rename from pepreader/pep0.py
rename to pep_extensions/pepzero/pep0.py
index bd69929e5d4..1d18272b10c 100644
--- a/pepreader/pep0.py
+++ b/pep_extensions/pepzero/pep0.py
@@ -207,9 +207,9 @@ def _parse_author(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 = pep0_constants.text_type(r"(?P.+?) <(?P.+?)>")
- paren = pep0_constants.text_type(r"(?P.+?) \((?P.+?)\)")
- simple = pep0_constants.text_type(r"(?P[^,]+)")
+ angled = "(?P.+?) <(?P.+?)>"
+ paren = "(?P.+?) \((?P.+?)\)"
+ simple = "(?P[^,]+)"
author_list = []
for regex in (angled, paren, simple):
# Watch out for commas separating multiple names.
diff --git a/pepreader/pep0_constants.py b/pep_extensions/pepzero/pep0_constants.py
similarity index 96%
rename from pepreader/pep0_constants.py
rename to pep_extensions/pepzero/pep0_constants.py
index 57e96e9e1d7..83bfe1b08eb 100644
--- a/pepreader/pep0_constants.py
+++ b/pep_extensions/pepzero/pep0_constants.py
@@ -1,9 +1,9 @@
-# -*- coding: utf-8 -*-
from functools import partial
-text_type = str
+
title_length = 55
author_length = 40
table_separator = "== ==== " + "="*title_length + " " + "="*author_length
+
# column format is called as a function with a mapping containing field values
column_format = partial(
"{type}{status}{number: >5} {title: <{title_length}} {authors}".format,
diff --git a/pepreader/pep0_writer.py b/pep_extensions/pepzero/pep0_writer.py
similarity index 99%
rename from pepreader/pep0_writer.py
rename to pep_extensions/pepzero/pep0_writer.py
index e7ec7b072b7..39345c477c4 100644
--- a/pepreader/pep0_writer.py
+++ b/pep_extensions/pepzero/pep0_writer.py
@@ -209,7 +209,7 @@ def write_pep0(self, peps: list):
for pep in peps:
if pep.number - prev_pep > 1:
self.emit_newline()
- self.output(pep0_constants.text_type(pep))
+ self.output(str(pep))
prev_pep = pep.number
self.emit_table_separator()
diff --git a/pepreader/__init__.py b/pepreader/__init__.py
deleted file mode 100644
index f7529f3c431..00000000000
--- a/pepreader/__init__.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""Sphinx extensions for performant PEP processing"""
-from sphinx.application import Sphinx
-
-__version__ = '1.0.0'
-pep_url = "pep-{:0>4}.html"
-
-
-def setup(app: Sphinx):
- """Initialize Sphinx extension."""
- from .pep_parser import PEPParser
- from .generate_pep_index import create_pep_zero
- from .pep_role import PEPRole
-
- app.connect("env-before-read-docs", create_pep_zero)
- app.add_source_parser(PEPParser)
- app.add_role('pep', PEPRole(), override=True)
-
- return {'version': __version__, 'parallel_read_safe': True, 'parallel_write_safe': True}
From e0fa783ca0e1d7b95f7ecb07c425d89af30be181 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 2 May 2020 00:27:28 +0100
Subject: [PATCH 039/110] Update Makefile
---
Makefile | 15 ++++-----------
1 file changed, 4 insertions(+), 11 deletions(-)
diff --git a/Makefile b/Makefile
index 2bba494ca65..ee3d290a7f1 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,10 @@
-# Rules to only make the required HTML versions, not all of them,
-# without the user having to keep track of which.
-#
-# Not really important, but convenient.
-
-PYTHON=python3
+# Builds PEP files to HTML using sphinx
+# Also contains testing targets
all: sphinx
+PYTHON=python3
+
sphinx:
$(PYTHON) build.py
@@ -19,11 +17,6 @@ check_links:
rss:
$(PYTHON) pep2rss.py .
-clean:
- -rm pep-0000.rst
- -rm *.html
- -rm -rf build
-
update:
git pull https://github.com/python/peps.git
From 2ddd470a70e6693dcf4f2795ec964cac45cdad81 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Fri, 15 May 2020 22:20:44 +0100
Subject: [PATCH 040/110] Improve packaging process
(for transforms to pydotorg standards)
---
.gitignore | 3 +-
conf.py | 2 +-
package.py | 89 +++++++++++++++++++++++++++++++++++++++++++-----
requirements.txt | 3 +-
4 files changed, 86 insertions(+), 11 deletions(-)
diff --git a/.gitignore b/.gitignore
index 0be4dea5de6..7bdede6a092 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,5 @@ __pycache__
*env
.vscode
*.swp
-/build
+build
+package
diff --git a/conf.py b/conf.py
index 57b6b6a1a87..55c80936bdf 100644
--- a/conf.py
+++ b/conf.py
@@ -60,7 +60,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-#
+
html_theme = 'classic'
# Add any paths that contain custom static files (such as style sheets) here,
diff --git a/package.py b/package.py
index 3f35ee56dd6..98941d724cb 100644
--- a/package.py
+++ b/package.py
@@ -1,7 +1,7 @@
"""Transforms Sphinx HTML output into python.org input format"""
-from bs4 import BeautifulSoup
from pathlib import Path
+from bs4 import BeautifulSoup
if __name__ == '__main__':
root_path = Path(".")
@@ -12,29 +12,102 @@
for file_path in html_path.glob("pep-*"):
if file_path.suffix not in '.html':
continue
+ print(file_path.stem)
soup = BeautifulSoup(file_path.read_text(encoding="UTF8"), 'lxml')
contents = soup.find('div', class_="body").div
+
+ # Removes
tags from list item elements
+ for tag in contents.findAll("li"):
+ try:
+ tag.p.unwrap()
+ except AttributeError:
+ # If no
tag to unwrap
+ pass
+
+ # Removes all permalink elements
+ [tag.decompose() for tag in contents.findAll(class_="headerlink")]
+
+ # Replace brackets class with [ and ]
+ for tag in contents.findAll("a", class_="brackets"):
+ tag.insert(0, "[")
+ tag.append("]")
+ tag["class"].remove("brackets")
+
+ # Remove Sphinx Header
+ contents.h1.decompose()
+
+ # Promotes all remaining headers
+ for level in range(6 - 1):
+ h_level = level + 2
+ headers = contents.findAll(f"h{h_level}")
+ for header in headers:
+ header.name = f"h{h_level - 1}"
+
dl = contents.find('dl')
+
+ # Adds horizontal rule
+ dl.insert_after(soup.new_tag("hr"))
+
+ # Parses the PEP Info box to transform to pydotorg standards
for tag in dl.findChildren():
if tag.name == "dt":
tag.name = "th"
- tag.find_next_sibling().name = "td"
+ tag.string += ":"
tag.attrs['class'] = 'field-name'
- tag.find_next_sibling()['class'] = 'field-body'
+ value_tag = tag.find_next_sibling()
+ value_tag.name = "td"
+ value_tag.string = value_tag.text.strip("\n")
+ value_tag['class'] = 'field-body'
+
+ # Wrap the key-value pair in a
element
tr = soup.new_tag("tr", **{'class': 'field'})
tag.insert_before(tr)
- tr.insert(0, tag.find_next_sibling())
+ tr.insert(0, value_tag)
tr.insert(0, tag)
dl.name = 'tbody'
- tbl = soup.new_tag('table', **{'class': dl['class']})
+
+ classes = dl['class']
+ classes.remove("simple")
+ classes.append("docutils")
+ del dl['class']
+ tbl = soup.new_tag('table', **{'class': classes})
dl.wrap(tbl)
- dl['class'] = []
tbl.insert(0, soup.new_tag("col", **{'class': "field-body"}))
tbl.insert(0, soup.new_tag("col", **{'class': "field-name"}))
+ # Fix footnotes/references
+ dl_refs = contents.findAll('dl', class_="footnote brackets")
+
+ for ref in dl_refs:
+ footnote_rows = []
+ for tag in ref.findChildren():
+ if tag.name == "dt":
+ tag.name = "td"
+ tag.attrs['class'] = 'label'
+ if tag.span and "brackets" in tag.span.get("class"):
+ tag.span.insert(0, "[")
+ tag.span.append("]")
+ tag.span.unwrap()
+
+ value_tag = tag.find_next_sibling()
+ value_tag.name = "td"
+ value_tag.string = value_tag.text.strip("\n")
+
+ # Wrap the key-value pair in a
element
+ tr = soup.new_tag("tr")
+ tr.insert(0, value_tag)
+ tr.insert(0, tag)
+ footnote_rows.append(tr)
+ ref.name = 'table'
+ ref["class"] = "docutils footnote"
+ ref.contents = footnote_rows
+ # TODO combine all tables into one (relianbt on fixingf PEP8 table mismatch)
+
+ # Writes transformed HTML
write_path = Path('./package/peps') / file_path.name
- write_path.write_text(str(contents), encoding="UTF8")
+ html = [str(i) for i in contents.contents]
+ write_path.write_text(str("".join(html)), encoding="UTF8")
- del soup, contents, dl, tbl
+ del soup, contents, headers, dl, tbl, dl_refs, html
diff --git a/requirements.txt b/requirements.txt
index ad88d51c43b..51ddbfe60bc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,5 @@ sphinx >= 3.0.3
docutils >= 0.16
# For packaging to current python.org standards
-bs4
\ No newline at end of file
+bs4
+lxml
\ No newline at end of file
From 7d7dfb2cbea9fa17488654b6d451961b2b7f7797 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 16 May 2020 00:17:18 +0100
Subject: [PATCH 041/110] Transform to
---
package.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/package.py b/package.py
index 98941d724cb..7b10a32c0c9 100644
--- a/package.py
+++ b/package.py
@@ -34,6 +34,11 @@
tag.append("]")
tag["class"].remove("brackets")
+ # Reformat tags to
+ for tag in list(contents.findAll("code")):
+ tag.name = "tt"
+ [x.unwrap() if x.name else x for x in tag.contents]
+
# Remove Sphinx Header
contents.h1.decompose()
From e979d276ed37bab58e2b65f493a3db9c07f50ab0 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 16 May 2020 00:31:45 +0100
Subject: [PATCH 042/110] Transform literal blocks
---
package.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/package.py b/package.py
index 7b10a32c0c9..c5a4131c123 100644
--- a/package.py
+++ b/package.py
@@ -39,6 +39,13 @@
tag.name = "tt"
[x.unwrap() if x.name else x for x in tag.contents]
+ for tag in contents.findAll("div", class_="highlight-default"):
+ tag.div.unwrap()
+ tag.pre.unwrap()
+ tag.name = "pre"
+ tag["class"] = "literal-block"
+ tag.string = "\n" + tag.text.strip() + "\n"
+
# Remove Sphinx Header
contents.h1.decompose()
From 52fe4e63c2367db2350a16d3a6067ee11baf2463 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 16 May 2020 01:01:17 +0100
Subject: [PATCH 043/110] Transform blockquotes
---
package.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/package.py b/package.py
index c5a4131c123..96a30e01653 100644
--- a/package.py
+++ b/package.py
@@ -39,6 +39,7 @@
tag.name = "tt"
[x.unwrap() if x.name else x for x in tag.contents]
+ # Reformat code literal blocks
for tag in contents.findAll("div", class_="highlight-default"):
tag.div.unwrap()
tag.pre.unwrap()
@@ -46,6 +47,12 @@
tag["class"] = "literal-block"
tag.string = "\n" + tag.text.strip() + "\n"
+ # Transform blockquotes
+ for tag in contents.findAll("blockquote"):
+ tag.div.unwrap()
+ if tag.p:
+ tag.p.unwrap()
+
# Remove Sphinx Header
contents.h1.decompose()
From 70ecf71c5876478731dcb4e76242b249d438c569 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 16 May 2020 01:23:21 +0100
Subject: [PATCH 044/110] Fix author sort
---
pep_extensions/pepzero/pep0.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/pep_extensions/pepzero/pep0.py b/pep_extensions/pepzero/pep0.py
index 1d18272b10c..7ab4d852b7b 100644
--- a/pep_extensions/pepzero/pep0.py
+++ b/pep_extensions/pepzero/pep0.py
@@ -59,8 +59,6 @@ class Author(object):
def __init__(self, author_and_email_tuple, authors_lookup):
"""Parse the name and email address of an author."""
- self.first = self.last = ''
-
name, email = author_and_email_tuple
self.first_last = name.strip()
self.email = email.lower()
@@ -78,14 +76,15 @@ def __eq__(self, other):
@property
def sort_by(self):
- name_parts = self.last.split()
+ last = self.last_first.split(",")[0]
+ name_parts = last.split()
for index, part in enumerate(name_parts):
if part[0].isupper():
base = " ".join(name_parts[index:]).lower()
break
else:
# If no capitals, use the whole string
- base = self.last.lower()
+ base = last.lower()
return unicodedata.normalize("NFKD", base)
From 1e34ed0fed8856996b86dc08e9c1b7e196b769a8 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 16 May 2020 01:24:01 +0100
Subject: [PATCH 045/110] PEP0 transforms
---
package.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/package.py b/package.py
index 96a30e01653..2de5fffb289 100644
--- a/package.py
+++ b/package.py
@@ -17,6 +17,11 @@
soup = BeautifulSoup(file_path.read_text(encoding="UTF8"), 'lxml')
contents = soup.find('div', class_="body").div
+ # Handle PEP 0
+ if int(file_path.stem[-4:]) == 0:
+ [tag.p.unwrap() if tag.p else tag for tag in contents.findAll("th")]
+ [tag.p.unwrap() if tag.p else tag for tag in contents.findAll("td")]
+
# Removes
tags from list item elements
for tag in contents.findAll("li"):
try:
From 560e3d4f08d86c0d81088c6a6aec574bd5613d3f Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 16 May 2020 01:52:32 +0100
Subject: [PATCH 046/110] Fix minor regression in blocktest code
---
package.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.py b/package.py
index 2de5fffb289..a7ceb6f4712 100644
--- a/package.py
+++ b/package.py
@@ -55,7 +55,7 @@
# Transform blockquotes
for tag in contents.findAll("blockquote"):
tag.div.unwrap()
- if tag.p:
+ if tag.p and len(tag.contents) == 1:
tag.p.unwrap()
# Remove Sphinx Header
From 04839748453bf6b0df4a1e5fbcc968a6260fa035 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sat, 16 May 2020 02:31:17 +0100
Subject: [PATCH 047/110] Fix minor regression in li/p code
---
package.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/package.py b/package.py
index a7ceb6f4712..994b56cde87 100644
--- a/package.py
+++ b/package.py
@@ -24,11 +24,10 @@
# Removes
tags from list item elements
for tag in contents.findAll("li"):
- try:
+ if tag.p and len(tag.contents) == 1:
+ tag.p.unwrap()
+ elif "ul" in [t.name for t in tag.contents]:
tag.p.unwrap()
- except AttributeError:
- # If no
tag to unwrap
- pass
# Removes all permalink elements
[tag.decompose() for tag in contents.findAll(class_="headerlink")]
From 6c5588818296de9ca1413db265244b7935d6191a Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Wed, 17 Jun 2020 19:48:08 +0100
Subject: [PATCH 048/110] Try again on theming - starting over!
---
_templates/layout.html | 4 ---
conf.py | 5 ++--
pep_extensions/theme/layout.html | 13 +++++++++
.../theme/static/pep.css | 0
pep_extensions/theme/theme.conf | 28 +++++++++++++++++++
5 files changed, 44 insertions(+), 6 deletions(-)
delete mode 100644 _templates/layout.html
create mode 100644 pep_extensions/theme/layout.html
rename pep.css => pep_extensions/theme/static/pep.css (100%)
create mode 100644 pep_extensions/theme/theme.conf
diff --git a/_templates/layout.html b/_templates/layout.html
deleted file mode 100644
index 2bd13e35d6d..00000000000
--- a/_templates/layout.html
+++ /dev/null
@@ -1,4 +0,0 @@
-{% extends "!layout.html" %}
-
-{# Set the delimiter after the short title on the rel-bar #}
-{% set reldelim1 = ' 🡢' %}
\ No newline at end of file
diff --git a/conf.py b/conf.py
index 55c80936bdf..cc6d6372887 100644
--- a/conf.py
+++ b/conf.py
@@ -30,7 +30,7 @@
extensions = ["pep_extensions"]
# Add any paths that contain templates here, relative to this directory.
-templates_path = ['_templates']
+templates_path = ['pep_extensions/theme']
# The file extensions of source files. Sphinx considers the files with
# these suffixes as sources.
@@ -61,7 +61,8 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'classic'
+html_theme_path = ["pep_extensions"]
+html_theme = "theme"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
diff --git a/pep_extensions/theme/layout.html b/pep_extensions/theme/layout.html
new file mode 100644
index 00000000000..4b1a5bbc8d5
--- /dev/null
+++ b/pep_extensions/theme/layout.html
@@ -0,0 +1,13 @@
+{% extends "!layout.html" %}
+
+
+{# Set the stylesheets #}
+{% block linktags %}
+{{ super() }}
+
+
+{% endblock %}
+
+
+{# Set the delimiter after the short title on the rel-bar #}
+{% set reldelim1 = ' 🡢' %}
\ No newline at end of file
diff --git a/pep.css b/pep_extensions/theme/static/pep.css
similarity index 100%
rename from pep.css
rename to pep_extensions/theme/static/pep.css
diff --git a/pep_extensions/theme/theme.conf b/pep_extensions/theme/theme.conf
new file mode 100644
index 00000000000..95acf079627
--- /dev/null
+++ b/pep_extensions/theme/theme.conf
@@ -0,0 +1,28 @@
+[theme]
+inherit = default
+pygments_style = sphinx
+pygments_dark_style = monokai
+
+[options]
+bodyfont = 'Lucida Grande', Arial, sans-serif
+headfont = 'Lucida Grande', Arial, sans-serif
+footerbgcolor = white
+footertextcolor = white
+relbarbgcolor = white
+relbartextcolor = white
+relbarlinkcolor = white
+sidebarbgcolor = white
+sidebartextcolor = white
+sidebarlinkcolor = white
+bgcolor = white
+textcolor = white
+linkcolor = white
+visitedlinkcolor = white
+headtextcolor = white
+headbgcolor = white
+headlinkcolor = white
+
+root_name = Python
+root_url = https://www.python.org/
+root_icon =
+root_include_title = True
\ No newline at end of file
From 3ffd0d00ed852564009f158ac215d3e6ed07a828 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:33:31 +0100
Subject: [PATCH 049/110] Type hinting: pep_zero.py
---
pep_extensions/pep_processor/pep_zero.py | 24 +++++++++++-------------
1 file changed, 11 insertions(+), 13 deletions(-)
diff --git a/pep_extensions/pep_processor/pep_zero.py b/pep_extensions/pep_processor/pep_zero.py
index ce4132a03a8..fd31e6994ec 100644
--- a/pep_extensions/pep_processor/pep_zero.py
+++ b/pep_extensions/pep_processor/pep_zero.py
@@ -26,41 +26,40 @@ class PEPZeroSpecial(nodes.SparseNodeVisitor):
Perform the special processing needed by PEP 0:
- Mask email addresses.
-
- Link PEP numbers in the second column of 4-column tables to the PEPs
themselves.
"""
- def __init__(self, document):
+ def __init__(self, document: nodes.document):
super().__init__(document)
- self.pep_table = None
- self.entry = None
+ self.pep_table: int = 0
+ self.entry: int = 0
- def unknown_visit(self, node):
+ def unknown_visit(self, node: nodes.Node) -> None:
pass
@staticmethod
- def visit_reference(node):
+ def visit_reference(node: nodes.reference) -> None:
node.replace_self(peps.mask_email(node))
@staticmethod
- def visit_field_list(node):
+ def visit_field_list(node: nodes.field_list) -> None:
if 'rfc2822' in node['classes']:
raise nodes.SkipNode
- def visit_tgroup(self, node):
+ def visit_tgroup(self, node: nodes.tgroup) -> None:
self.pep_table = node['cols'] == 4
self.entry = 0
- def visit_colspec(self, node):
+ def visit_colspec(self, node: nodes.colspec) -> None:
self.entry += 1
if self.pep_table and self.entry == 2:
node['classes'].append('num')
- def visit_row(self, node):
+ def visit_row(self, _node: nodes.row) -> None:
self.entry = 0
- def visit_entry(self, node):
+ def visit_entry(self, node: nodes.entry) -> None:
self.entry += 1
if self.pep_table and self.entry == 2 and len(node) == 1:
node['classes'].append('num')
@@ -69,8 +68,7 @@ def visit_entry(self, node):
text = p.astext()
try:
pep = int(text)
- ref = (self.document.settings.pep_base_url
- + pep_url.format(pep))
+ ref = self.document.settings.pep_base_url + pep_url.format(pep)
p[0] = nodes.reference(text, text, refuri=ref)
except ValueError:
pass
From 9eb858adec6147a170b34f3e88e37c16c5076252 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:37:43 +0100
Subject: [PATCH 050/110] clean imports in PEPTargetNotes
---
.../pep_processor/pep_target_notes.py | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/pep_extensions/pep_processor/pep_target_notes.py b/pep_extensions/pep_processor/pep_target_notes.py
index b85cbf20e34..548c837c538 100644
--- a/pep_extensions/pep_processor/pep_target_notes.py
+++ b/pep_extensions/pep_processor/pep_target_notes.py
@@ -1,9 +1,9 @@
from pathlib import Path
-import docutils.transforms as transforms
-from docutils.transforms import references
-from docutils.transforms import misc
from docutils import nodes
+from docutils import transforms
+from docutils.transforms import misc
+from docutils.transforms import references
class PEPTargetNotes(transforms.Transform):
@@ -23,30 +23,30 @@ def apply(self):
doc = self.document[0]
i = len(doc) - 1
- refsect = copyright = None
+ reference_section = copyright = None
while i >= 0 and isinstance(doc[i], nodes.section):
title_words = doc[i][0].astext().lower().split()
if 'references' in title_words:
- refsect = doc[i]
+ reference_section = doc[i]
break
elif 'copyright' in title_words:
copyright = i
i -= 1
- if not refsect:
- refsect = nodes.section()
- refsect += nodes.title('', 'References')
- self.document.set_id(refsect)
+ if not reference_section:
+ reference_section = nodes.section()
+ reference_section += nodes.title('', 'References')
+ self.document.set_id(reference_section)
if copyright:
# Put the new "References" section before "Copyright":
- doc.insert(copyright, refsect)
+ doc.insert(copyright, reference_section)
else:
# Put the new "References" section at end of doc:
- doc.append(refsect)
+ doc.append(reference_section)
pending = nodes.pending(references.TargetNotes)
- refsect.append(pending)
+ reference_section.append(pending)
self.document.note_pending(pending, 0)
pending = nodes.pending(misc.CallBack, details={'callback': self.cleanup_callback})
- refsect.append(pending)
+ reference_section.append(pending)
self.document.note_pending(pending, 1)
@staticmethod
From daf9f6447d3c6ba8f70b49954a1c78f55887aee4 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:39:23 +0100
Subject: [PATCH 051/110] Add github source link transform
---
pep_extensions/pep_processor/pep_target_notes.py | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/pep_extensions/pep_processor/pep_target_notes.py b/pep_extensions/pep_processor/pep_target_notes.py
index 548c837c538..84893dcf93d 100644
--- a/pep_extensions/pep_processor/pep_target_notes.py
+++ b/pep_extensions/pep_processor/pep_target_notes.py
@@ -5,6 +5,7 @@
from docutils.transforms import misc
from docutils.transforms import references
+import pep_extensions.config as pep_config
class PEPTargetNotes(transforms.Transform):
@@ -17,7 +18,8 @@ class PEPTargetNotes(transforms.Transform):
default_priority = 520
def apply(self):
- if not Path(self.document["source"]).match("pep-*"):
+ pep_source_path = Path(self.document["source"])
+ if not pep_source_path.match("pep-*"):
# not a PEP file
return
@@ -49,6 +51,8 @@ def apply(self):
reference_section.append(pending)
self.document.note_pending(pending, 1)
+ self.add_source_link(pep_source_path)
+
@staticmethod
def cleanup_callback(pending):
"""
@@ -58,3 +62,9 @@ def cleanup_callback(pending):
"""
if len(pending.parent) == 2: #
and
pending.parent.parent.remove(pending.parent)
+
+ def add_source_link(self, pep_source_path: Path) -> None:
+ source_link = pep_config.pep_vcs_url.format(pep_source_path.name)
+ link_node = nodes.reference("", source_link, refuri=source_link)
+ span_node = nodes.inline("", "Source: ", link_node)
+ self.document.append(span_node)
From e3b002b79169c28d6021f3bdcf6bcbcfcb87dd83 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:40:36 +0100
Subject: [PATCH 052/110] Rename pep_target_notes.py to better reflect role
---
.../{pep_target_notes.py => pep_footer.py} | 18 ++++++++++++++----
pep_extensions/pep_processor/pep_parser.py | 4 ++--
2 files changed, 16 insertions(+), 6 deletions(-)
rename pep_extensions/pep_processor/{pep_target_notes.py => pep_footer.py} (82%)
diff --git a/pep_extensions/pep_processor/pep_target_notes.py b/pep_extensions/pep_processor/pep_footer.py
similarity index 82%
rename from pep_extensions/pep_processor/pep_target_notes.py
rename to pep_extensions/pep_processor/pep_footer.py
index 84893dcf93d..40129b58dd8 100644
--- a/pep_extensions/pep_processor/pep_target_notes.py
+++ b/pep_extensions/pep_processor/pep_footer.py
@@ -7,12 +7,22 @@
import pep_extensions.config as pep_config
-class PEPTargetNotes(transforms.Transform):
+
+class PEPFooter(transforms.Transform):
"""
- Locate the "References" section, insert a placeholder for an external
- target footnote insertion transform at the end, and schedule the
- transform to run immediately.
+ Relevant footer transforms for PEPs, including appending external
+ links to footnotes and creating a link to the (GitHub) source.
+
+ TargetNotes:
+ Locate the "References" section, insert a placeholder at the end
+ for an external target footnote insertion transform, and schedule
+ the transform to run immediately.
+
+ Source Link:
+ Create the link to the source file from the document source path,
+ and append the text to the end of the document.
+
"""
default_priority = 520
diff --git a/pep_extensions/pep_processor/pep_parser.py b/pep_extensions/pep_processor/pep_parser.py
index d0e2d033465..be87d6ff109 100644
--- a/pep_extensions/pep_processor/pep_parser.py
+++ b/pep_extensions/pep_processor/pep_parser.py
@@ -3,7 +3,7 @@
from pep_extensions.pep_processor import pep_headers
from pep_extensions.pep_processor import pep_title
from pep_extensions.pep_processor import pep_contents
-from pep_extensions.pep_processor import pep_target_notes
+from pep_extensions.pep_processor import pep_footer
class PEPParser(parsers.RSTParser):
@@ -18,6 +18,6 @@ def get_transforms(self):
pep_headers.PEPHeaders,
pep_title.PEPTitle,
pep_contents.PEPContents,
- pep_target_notes.PEPTargetNotes,
+ pep_footer.PEPFooter,
])
return transforms
From ea3a37179b881a04bbbe22d5934ae0e86e471201 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:42:25 +0100
Subject: [PATCH 053/110] Remove header fields as per pydotorg converters
---
pep_extensions/config.py | 4 +---
pep_extensions/pep_processor/pep_headers.py | 25 ++++++---------------
2 files changed, 8 insertions(+), 21 deletions(-)
diff --git a/pep_extensions/config.py b/pep_extensions/config.py
index 7a6b6d97aa1..a83be45a232 100644
--- a/pep_extensions/config.py
+++ b/pep_extensions/config.py
@@ -1,8 +1,6 @@
"""Misc. config variables for the PEP extensions module."""
-import re
__version__ = '1.0.0'
pep_stem = "pep-{:0>4}"
pep_url = f"{pep_stem}.html"
-pep_vcs_url = f"https://github.com/python/peps/blob/master/{pep_stem}.txt"
-rcs_keyword_substitutions = [(re.compile(r"\$[a-zA-Z]+: (.+) \$$"), r"\1")]
\ No newline at end of file
+pep_vcs_url = "https://github.com/python/peps/blob/master/{}"
diff --git a/pep_extensions/pep_processor/pep_headers.py b/pep_extensions/pep_processor/pep_headers.py
index 1b308f6048b..052a470dab6 100644
--- a/pep_extensions/pep_processor/pep_headers.py
+++ b/pep_extensions/pep_processor/pep_headers.py
@@ -2,7 +2,6 @@
from pathlib import Path
from docutils import nodes
-from docutils import utils
from docutils import transforms
from docutils.transforms import peps
@@ -10,8 +9,6 @@
import pep_extensions.config
pep_url = pep_extensions.config.pep_url
-pep_vcs_url = pep_extensions.config.pep_vcs_url
-rcs_keyword_substitutions = pep_extensions.config.rcs_keyword_substitutions
class DataError(Exception):
@@ -40,14 +37,12 @@ def apply(self):
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.")
- cvs_url = None
pep = None
pep_field = header[0]
if pep_field[0].astext().lower() == "pep": # should be the first field
value = pep_field[1].astext()
try:
pep = int(value)
- cvs_url = pep_vcs_url.format(pep)
except ValueError:
pep = value
msg = self.document.reporter.warning(
@@ -76,6 +71,7 @@ def apply(self):
if len(header) < 2 or header[1][0].astext().lower() != "title":
raise DataError("No title!")
+ fields_to_remove = []
for field in header:
name = field[0].astext().lower()
body = field[1]
@@ -104,18 +100,11 @@ def apply(self):
pepno = int(refpep)
newbody.append(nodes.reference(
refpep, refpep,
- refuri=(self.document.settings.pep_base_url
- + pep_url.format(pepno))))
+ refuri=(self.document.settings.pep_base_url + pep_url.format(pepno))))
newbody.append(space)
para[:] = newbody[:-1] # drop trailing space
- elif name == "last-modified":
- utils.clean_rcs_keywords(para, 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 + pep_url.format(12)
- para[:] = [nodes.reference("", pep_type, refuri=uri)]
- elif name == "version" and len(body):
- utils.clean_rcs_keywords(para, rcs_keyword_substitutions)
+ elif name in ("last-modified", "content-type", "version"):
+ fields_to_remove.append(field)
+
+ for field in fields_to_remove:
+ field.parent.remove(field)
From c199a7e09426a22f8f7bbe109cd7eda4886e5dda Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:43:45 +0100
Subject: [PATCH 054/110] prevent unneeded source copying
---
build.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/build.py b/build.py
index 9ba00630f9c..fb4f64ef353 100644
--- a/build.py
+++ b/build.py
@@ -45,4 +45,5 @@ def create_parser():
source_directory, configuration_directory, build_directory, doctree_directory, builder,
confoverrides=config_overrides, warningiserror=args.fail_on_warning,
)
+ app.builder.copysource = False # Prevent unneeded source copying - we link direct to VCS
app.build()
From e2ca10f7db34de5b9ef272ce5806894735a60cd0 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:44:08 +0100
Subject: [PATCH 055/110] Clean up docutils.conf
---
docutils.conf | 16 ++--------------
1 file changed, 2 insertions(+), 14 deletions(-)
diff --git a/docutils.conf b/docutils.conf
index 8becc5f3062..9af15466166 100644
--- a/docutils.conf
+++ b/docutils.conf
@@ -2,20 +2,8 @@
# 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:
+# path to PEPs, for template: [TODO REMOVE; UNNEEDED FOR SPHINX]
pep-home: /en/latest/
# base URL for PEP references (no host so mirrors work):
-pep-base-url: /en/latest/
+pep-base-url:
From b9684d2054af3def818836396772ccd5805140f0 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:44:49 +0100
Subject: [PATCH 056/110] Clean contents.rst - emphasise that we only use it
for Sphinx
---
contents.rst | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/contents.rst b/contents.rst
index 1c8b3c2e276..658655e4044 100644
--- a/contents.rst
+++ b/contents.rst
@@ -3,11 +3,7 @@ Python Enhancement Proposals (PEPs)
***********************************
-
-Welcome!
-
-To get started, have a look at the :doc:`PEP Index`
-
+This is an internal Sphinx page, please go to the :doc:`PEP Index`.
.. toctree::
@@ -15,6 +11,6 @@ To get started, have a look at the :doc:`PEP Index`
:titlesonly:
:hidden:
:glob:
- :caption: Sphinx Table of Contents:
+ :caption: PEP Table of Contents (needed for Sphinx):
pep-*
\ No newline at end of file
From 4fb6c49310ddeda55ae85a8335ce6c687daf3fde Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:45:54 +0100
Subject: [PATCH 057/110] PEP header styling changes
---
pep_extensions/pep_processor/pep_contents.py | 4 ++++
pep_extensions/pep_processor/pep_title.py | 3 ++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/pep_extensions/pep_processor/pep_contents.py b/pep_extensions/pep_processor/pep_contents.py
index b64e13ff7f2..bd336deeb29 100644
--- a/pep_extensions/pep_processor/pep_contents.py
+++ b/pep_extensions/pep_processor/pep_contents.py
@@ -28,6 +28,10 @@ def apply(self):
self.document.children[0].insert(2, topic)
self.document.note_pending(pending)
+ # Add horizontal rule before contents
+ transition = nodes.transition()
+ self.document[0].insert(2, transition)
+
class Contents(parts.Contents):
def __init__(self, document, startnode=None):
diff --git a/pep_extensions/pep_processor/pep_title.py b/pep_extensions/pep_processor/pep_title.py
index db9ac0e25f0..f0fa5a2ccaf 100644
--- a/pep_extensions/pep_processor/pep_title.py
+++ b/pep_extensions/pep_processor/pep_title.py
@@ -19,7 +19,7 @@ def apply(self):
# not a PEP file
return
- # Directory to hold the PEP's RFC2822 header details, to extract a titke string
+ # Directory to hold the PEP's RFC2822 header details, to extract a title string
pep_header_details = {}
# Iterate through the header fields, which are the first section of the document
@@ -40,6 +40,7 @@ def apply(self):
pep_title_node = nodes.section()
textnode = nodes.Text(pep_title_string, pep_title_string)
titlenode = nodes.title(pep_title_string, '', textnode)
+ titlenode['classes'].append("page-title")
name = states.normalize_name(titlenode.astext())
pep_title_node['names'].append(name)
pep_title_node += titlenode
From 7851bcd1f3d251dce0e2a8e29d91160f5acc4b40 Mon Sep 17 00:00:00 2001
From: AA Turner <9087854+AA-Turner@users.noreply.github.com>
Date: Sun, 21 Jun 2020 19:56:55 +0100
Subject: [PATCH 058/110] Add custom paragraph and ordered list visitors (based
on html4css1 writer)
---
.../pep_processor/pep_html_translator.py | 63 ++++++++++++++++++-
1 file changed, 60 insertions(+), 3 deletions(-)
diff --git a/pep_extensions/pep_processor/pep_html_translator.py b/pep_extensions/pep_processor/pep_html_translator.py
index 50bc13618f1..f4d45a347ea 100644
--- a/pep_extensions/pep_processor/pep_html_translator.py
+++ b/pep_extensions/pep_processor/pep_html_translator.py
@@ -1,9 +1,66 @@
-from docutils.nodes import Node
+from docutils import nodes
import sphinx.writers.html5 as html5
+import re
class PEPTranslator(html5.HTML5Translator):
- def depart_label(self, node):
+ compact_field_list = True
+ phrases_and_newlines = re.compile(r'[^\n]+|\n')
+
+ def __init__(self, *args):
+ super(PEPTranslator, self).__init__(*args)
+ self.compact_simple: bool = False
+
+ # Omit
tags to produce visually compact lists
+ def should_be_compact_paragraph(self, node: nodes.paragraph) -> bool:
+ """Determine if
tags around paragraph ``node`` can be omitted."""
+
+ # Never compact paragraphs in document or compound.
+ if isinstance(node.parent, (nodes.document, nodes.compound)):
+ return False
+
+ # Check for custom attributes in paragraph.
+ for key, value in node.non_default_attributes().items():
+ if key != 'classes' or value not in ([], ['first'], ['last'], ['first', 'last']):
+ return False
+
+ # Only first paragraph can be compact (ignoring initial label & invisible nodes)
+ first = isinstance(node.parent[0], nodes.label)
+ visible_siblings = [child for child in node.parent.children[first:] if not isinstance(child, nodes.Invisible)]
+ if visible_siblings[0] is not node:
+ return False
+
+ if self.compact_simple or self.compact_field_list:
+ return True
+
+ parent_length = sum([1 for n in node.parent if not isinstance(n, (nodes.Invisible, nodes.label))])
+ if self.compact_p and parent_length == 1:
+ return True
+
+ return False
+
+ def visit_enumerated_list(self, node: nodes.enumerated_list) -> None:
+ self.context.append(self.compact_simple)
+ self.compact_simple = self.is_compactable(node)
+ super().visit_enumerated_list(node)
+
+ def depart_enumerated_list(self, node: nodes.enumerated_list) -> None:
+ self.compact_simple = self.context.pop()
+ super().depart_enumerated_list(node)
+
+ def visit_paragraph(self, node: nodes.paragraph) -> None:
+ """Remove
tags if possible (PEPs historically h )"""
+ if self.should_be_compact_paragraph(node):
+ self.context.append('')
+ else:
+ self.body.append(self.starttag(node, 'p', ''))
+ self.context.append('
\n')
+
+ def depart_paragraph(self, _: nodes.paragraph) -> None:
+ """Add corresponding end tag from `visit_paragraph`. Node param isn't needed."""
+ self.body.append(self.context.pop())
+
+ def depart_label(self, node) -> None:
if not self.settings.footnote_backlinks:
self.body.append('')
self.body.append('\n
')
@@ -26,5 +83,5 @@ def depart_label(self, node):
# Close the def tags
self.body.append('\n