[Python-checkins] r54832 - in sandbox/trunk/pep0: authors.py pep0 pep0.py pep0/__init__.py pep0/authors.py pep0/output.py pep0/parse.py pep0/pep.py pep0/statictext.py test_pep0.py

brett.cannon python-checkins at python.org
Sun Apr 15 05:21:50 CEST 2007


Author: brett.cannon
Date: Sun Apr 15 05:21:46 2007
New Revision: 54832

Added:
   sandbox/trunk/pep0/pep0/
   sandbox/trunk/pep0/pep0/__init__.py   (contents, props changed)
   sandbox/trunk/pep0/pep0/authors.py   (contents, props changed)
   sandbox/trunk/pep0/pep0/output.py   (contents, props changed)
   sandbox/trunk/pep0/pep0/parse.py   (contents, props changed)
   sandbox/trunk/pep0/pep0/pep.py   (contents, props changed)
   sandbox/trunk/pep0/pep0/statictext.py   (contents, props changed)
Removed:
   sandbox/trunk/pep0/authors.py
   sandbox/trunk/pep0/pep0.py
Modified:
   sandbox/trunk/pep0/test_pep0.py
Log:
Start to refactor code into a package to make discrete steps in creating index
more obvious and easier to handle.

Still need to break out metadata parsing and validation into separate steps.
Also need to rework output.  See docstring in pep0.__init__ for discrete steps
that are planned.

Tests are now totally broken and need to be regrouped based on new package
structure.  But code can still be run (using ``-m pep0 <directory>``).


Deleted: /sandbox/trunk/pep0/authors.py
==============================================================================
--- /sandbox/trunk/pep0/authors.py	Sun Apr 15 05:21:46 2007
+++ (empty file)
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-email_addresses = {
-    'Aahz' : 'aahz at pobox.com',
-    'James C. Ahlstrom' : 'jim at interet.com',
-    'Jim Althoff' : 'james_althoff at i2.com',
-    'Kevin Altis' : 'altis at semi-retired.com',
-    'David Ascher' : 'davida at activestate.com',
-    'Peter Astrand' : 'astrand at lysator.liu.se',
-    'Carl Banks' : 'pythondev at aerojockey.com',
-    'Paul Barrett' : 'barrett at stsci.edu',
-    'Facundo Batista' : 'facundo at taniquetil.com.ar',
-    'Anthony Baxter' : 'anthony at interlink.com.au',
-    'Thomas Bellman' : 'bellman+pep-divmod at lysator.liu.se',
-    'Steven Bethard' : 'steven.bethard at gmail.com',
-    'Georg Brandl' : 'g.brandl at gmx.net',
-    'Brett Cannon' : 'brett at python.org',
-    'Josiah Carlson' : 'jcarlson at uci.edu',
-    'W Isaac Carroll' : 'icarroll at pobox.com',
-    'Nick Coghlan' : 'ncoghlan at gmail.com',
-    'Dave Cole' : 'djc at object-craft.com.au',
-    'Christopher Craig' : 'python-pep at ccraig.org',
-    'Laura Creighton' : 'lac at strakt.com',
-    'Walter Dörwald' : '',
-    'Fred L. Drake, Jr.' : 'fdrake at acm.org',
-    'Michael P. Dubner' : 'dubnerm at mindless.com',
-    'Paul F. Dubois' : 'paul at pfdubois.com',
-    'Phillip J. Eby' : 'pje at telecommunity.com',
-    'Micah Elliott' : 'mde at tracos.org',
-    'Jeff Epler' : 'jepler at unpythonic.net',
-    'David Eppstein' : 'eppstein at ics.uci.edu',
-    'Clark C. Evans' : 'cce at clarkevans.com',
-    'Greg Ewing' : 'greg at cosc.canterbury.ac.nz',
-    'Martijn Faassen' : 'faassen at infrae.com',
-    'Ben Finney' : 'ben+python at benfinney.id.au',
-    'Frédéric B. Giacometti' : 'fred at arakne.com',
-    'Scott Gilbert' : 'xscottg at yahoo.com',
-    'David Goodger' : 'goodger at python.org',
-    'Grant Griffin' : 'g2 at iowegian.com',
-    'Mark Hammond' : 'mhammond at skippinet.com.au',
-    'Peter Harris' : 'scav at blueyonder.co.uk',
-    'Thomas Heller' : 'theller at python.net',
-    'Magnus Lie Hetland' : 'magnus at hetland.org',
-    'Raymond D. Hettinger' : 'python at rcn.com',
-    'Neil Hodgson' : 'neilh at scintilla.org',
-    'Michael Hudson' : 'mwh at python.net',
-    'Jeremy Hylton' : 'jeremy at zope.com',
-    'Jack Jansen' : 'jack at cwi.nl',
-    'Jim Jewett' : 'jimjjewett at users.sourceforge.net',
-    'Richard Jones' : 'richard at mechanicalcat.net',
-    'Stepan Koltsov' : 'yozh at mx1.ru',
-    'A.M. Kuchling' : 'amk at amk.ca',
-    'Marc-Andre Lemburg' : 'mal at lemburg.com',
-    'Gregory Lielens' : 'gregory.lielens at fft.be',
-    'Björn Lindqvist' : 'bjourne at gmail.com',
-    'Martin von Löwis' : 'loewis at informatik.hu-berlin.de',
-    'Tony Lownds' : 'tony at pagedna.com',
-    'Alex Martelli' : 'aleax at aleax.it',
-    'Andrew McClelland' : 'eternalsquire at comcast.net',
-    'Gordon McMillan' : 'gmcm at hypernet.com',
-    'Andrew McNamara' : 'andrewm at object-craft.com.au',
-    'Trent Mick' : 'trentm at activestate.com',
-    'Mike Meyer' : 'mwm at mired.org',
-    'Skip Montanaro' : 'skip at pobox.com',
-    'Paul Moore' : 'gustav at morpheus.demon.co.uk',
-    'Ben North' : 'ben at redfrontdoor.org',
-    'Neal Norwitz' : 'nnorwitz at gmail.com',
-    'Travis Oliphant' : 'oliphant at ee.byu.edu',
-    'Jason Orendorff' : 'jason.orendorff at gmail.com',
-    'Samuele Pedroni' : 'pedronis at python.org',
-    'Michel Pelletier' : 'michel at users.sourceforge.net',
-    'Tim Peters' : 'tim at zope.com',
-    'Jason Petrone' : 'jp at demonseed.net',
-    'Paul Prescod' : 'paul at prescod.net',
-    'Terry Reedy' : 'tjreedy at udel.edu',
-    'Sean Reifschneider' : 'jafo-pep at tummy.com',
-    'Christian R. Reis' : 'kiko at async.com.br',
-    'Jonathan Riehl' : 'jriehl at spaceship.com',
-    'André Roberge' : 'andre.roberge at gmail.com',
-    'Guido van Rossum' : 'guido at python.org',
-    'Just van Rossum' : 'just at letterror.com',
-    'Vinay Sajip' : 'vinay_sajip at red-dove.com',
-    'Neil Schemenauer' : 'nas at arctrix.com',
-    'Peter Schneider-Kamp' : 'nowonder at nowonder.de',
-    'Jiwon Seo' : 'seojiwon at gmail.com',
-    'Kevin D. Smith' : 'Kevin.Smith at theMorgue.org',
-    'Greg Stein' : 'gstein at lyra.org',
-    'Daniel Stutzbach' : 'daniel.stutzbach at gmail.com',
-    'Roman Suzi' : 'rnd at onego.ru',
-    'Talin' : 'talin at acm.org',
-    'Steven Taschuk' : 'staschuk at telusplanet.net',
-    'Oren Tirosh' : 'oren at hishome.net',
-    'Mike Verdone' : 'mike.verdone at gmail.com',
-    'Gregory R. Warnes' : 'warnes at users.sourceforge.net',
-    'Barry Warsaw' : 'barry at python.org',
-    'Terence Way' : 'terry at wayforward.net',
-    'Cliff Wells' : 'LogiplexSoftware at earthlink.net',
-    'Greg Wilson' : 'gvwilson at ddj.com',
-    'Collin Winter' : 'collinw at gmail.com',
-    'Thomas Wouters' : 'thomas at python.org',
-    'Ka-Ping Yee' : 'ping at zesty.ca',
-    'Moshe Zadka' : 'moshez at zadka.site.co.il',
-    'Huaiyu Zhu' : 'hzhu at users.sourceforge.net',
-}
-
-nicknames = {
-    'Guido van Rossum' : 'GvR',
-    'Just van Rossum' : 'JvR',
-}

Deleted: /sandbox/trunk/pep0/pep0.py
==============================================================================
--- /sandbox/trunk/pep0/pep0.py	Sun Apr 15 05:21:46 2007
+++ (empty file)
@@ -1,329 +0,0 @@
-"""Auto-generate PEP 0 (PEP index).  """
-from __future__ import with_statement
-import authors
-import os
-import re
-
-# Don't start on a new line to make easier to read as that would cause output to
-# start on a blank line.
-header = """PEP: 0
-Title: Index of Python Enhancement Proposals (PEPs)
-Version: $Revision$
-Last-Modified: $Date$
-Author: David Goodger <goodger at python.org>,
-        Barry A. Warsaw <barry at python.org>
-Status: Active
-Type: Informational
-Created: 13-Jul-2000
-"""
-
-intro = """
-    The PEP contains the index of all Python Enhancement Proposals,
-    known as PEPs.  PEP numbers are assigned by the PEP Editor, and
-    once assigned are never changed.  The SVN history[1] of the PEP
-    texts represent their historical record.
-
-    The BDFL maintains his own Pronouncements page[2] at
-    http://www.python.org/doc/essays/pepparade.html which contains his
-    musings on the various outstanding PEPs.
-"""
-
-references = """
-    [1] View PEP history online
-        http://svn.python.org/projects/peps/trunk/
-
-    [2] The Benevolent Dictator For Life's Parade of PEPs
-        http://www.python.org/doc/essays/pepparade.html
-"""
-
-footer = """
-Local Variables:
-mode: indented-text
-indent-tabs-mode: nil
-sentence-end-double-space: t
-fill-column: 70
-coding: utf-8
-End:"""
-
-
-type_values = ("Standards Track", "Informational", "Process")
-# Active and Draft are not listed in the index.
-status_values = ("Accepted", "Rejected", "Withdrawn", "Deferred", "Final",
-                 "Active", "Draft")
-
-def consume_headers(directory='.'):
-    """Pull out metadata for every PEP in the specified directory and return
-    them in a list sorted by PEP name."""
-    peps = []
-    for file_name in os.listdir(directory):
-        if file_name.startswith('pep-') and file_name.endswith('.txt'):
-            peps.append(consume_pep(os.path.join(directory, file_name)))
-    peps.sort(key=lambda pep: pep['PEP'])
-    return peps
-
-def consume_pep(path):
-    """Consume the specified file as a PEP to get its metadata."""
-    pep_info = {}
-    with open(path, 'rU') as pep_file:
-        try:
-            for line in pep_file:
-                if line == '\n':
-                    break
-                elif line[1].isspace():
-                    type_ = parse_metadata(pep_info, line, type_)
-                else:
-                    type_ = parse_metadata(pep_info, line)
-        except Exception:
-            print "In", pep_file
-            raise
-    if not 'PEP' in pep_info:
-        raise ValueError("PEP at file %s lacks a PEP number" % path)
-    if not 'Author' in pep_info:
-        raise ValueError("PEP %s is missing the Author field" %
-                         pep_info['PEP'])
-    if len(pep_info['Author']) < 1:
-        raise ValueError("PEP %s is lacking authors" % pep_info['PEP'])
-    if pep_info['Type'] not in type_values:
-        raise ValueError("%s is an invalid Type value for PEP %s" %
-                         (pep_info['Type'], pep_info['PEP']))
-    if pep_info['Status'] not in status_values:
-        raise ValueError("%s is an invalid Status value for PEP %s" %
-                         (pep_info['Status'], pep_info['PEP']))
-    return pep_info
-
-def parse_metadata(pep_info, line, previous_type=None):
-    """Parse the given line for PEP metadata, adding on to existing metadata if
-    previous_type is specified, returning the last type of metadata handled."""
-    if previous_type:
-        type_ = previous_type
-        data = line
-    else:
-        type_, data = line.split(':', 1)
-    type_ = type_.strip()
-    data = data.strip()
-    handler = handlers.get(type_, handle_generic)
-    result = handler(data)
-    if previous_type:
-        previous_data = pep_info[type_]
-        if not isinstance(previous_data, list):
-            previous_data = [previous_data]
-            pep_info[type_] = previous_data
-        previous_data.extend(result)
-    else:
-        pep_info[type_] = result
-    return type_
-
-def handle_generic(data):
-    """Default handler for PEP metadata."""
-    return data
-
-def handle_pep_num(data):
-    """Return the integer for the PEP number."""
-    return int(data)
-
-def handle_author(data):
-    """Return a list of author names."""
-    angled = r'(?P<author>.+?) <.+?>'
-    paren = r'.+? \((?P<author>.+?)\)'
-    simple = r'(?P<author>[^,]+)'
-    author_list = []
-    for regex in (angled, paren, simple):
-        # Watch out for commas separating multiple names.
-        regex += '(,\s+)?'
-        for match in re.finditer(regex, data):
-            author = match.group('author')
-            # 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.
-            author = match.group('author')
-            if not author.partition(' ')[1] and author.endswith('.'):
-                prev_author = author_list.pop()
-                author = ', '.join([prev_author, author])
-            author_list.append(author)
-        else:
-            # If authors were found then stop searching as only expect one
-            # style of author citation.
-            if author_list:
-                break
-    return author_list
-
-def handle_csv(data):
-    """Handle the Post-History."""
-    return [value.strip() for value in data.split(',') if value]
-
-handlers = {'Author': handle_author,
-            'PEP': handle_pep_num,
-            'Post-History': handle_csv,
-           }
-
-def last_name(full_name, nicknames={}):
-    """Find the last name (or nickname) of a full name.
-
-    If no last name (e.g, 'Aahz') then return the full name.  If there is a
-    leading, lowercase portion to the last name (e.g., 'van' or 'von') then
-    include it.  If there is a suffix (e.g., 'Jr.') that is appended through a
-    comma, then drop the suffix.
-
-    """
-    nickname = nicknames.get(full_name)
-    if nickname:
-        return nickname
-    no_suffix = full_name.partition(',')[0]
-    name_parts = no_suffix.split()
-    part_count = len(name_parts)
-    if part_count == 1 or part_count == 2:
-        return name_parts[-1]
-    else:
-        assert part_count > 2
-        if name_parts[-2].islower():
-            return ' '.join(name_parts[-2:])
-        else:
-            return name_parts[-1]
-
-def sort_peps(peps):
-    """Sort PEPs into meta, informational, accepted, open, finished, empty,
-    and essentially dead."""
-    meta = []
-    info = []
-    accepted = []
-    open_ = []
-    finished = []
-    empty = []
-    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'] in ('Rejected', 'Withdrawn', 'Deferred',
-                'Incomplete'):
-            dead.append(pep)
-        elif pep['Type'] == 'Process':
-            meta.append(pep)
-        elif pep['Type'] == 'Informational':
-            info.append(pep)
-        elif pep['Status'] == 'Accepted':
-            accepted.append(pep)
-        elif pep['Status'] == 'Final':
-            finished.append(pep)
-    return meta, info, accepted, open_, finished, empty, dead
-
-def write_pep(pep, output):
-    """Write PEP info to 'output'."""
-    type_abbr = pep['Type'][0].upper()
-    status = pep['Status']
-    if status in ('Draft', 'Active'):
-        status_abbr = ' '
-    else:
-        status_abbr = status[0].upper()
-    number = str(pep['PEP']).rjust(4)
-    title = pep['Title']
-    authors_list = []
-    author_string = ', '.join(last_name(author, authors.nicknames)
-            for author in pep['Author'])
-    output.write(" %s%s %s  %s %s\n" %
-                    (type_abbr, status_abbr, number, title.ljust(44),
-                        author_string))
-
-def write_column_headers(output):
-    """Output the column headers for the PEP indices."""
-    output.write('%s  %s %s\n' % ("num".rjust(8), "title".ljust(44), "owner"))
-    output.write('%s  %s %s\n' % ((len("num")*'-').rjust(8),
-                         (len("title")*'-').ljust(44), len("owner")*'-'))
-
-
-
-if __name__ == '__main__':
-    from sys import argv, stdout
-    
-    if not argv[1:]:
-        path = '.'
-    else:
-        path = argv[1]
-    if os.path.isdir(path):
-        peps = consume_headers(path)
-    else:
-        peps = [consume_pep(path)]
-
-    print header
-    print
-    print "Introduction"
-    print intro
-    print
-    print "Index by Category"
-    print
-    write_column_headers(stdout)
-    meta, info, accepted, open_, done, empty, dead = sort_peps(peps)
-    print
-    print " Meta-PEPs (PEPs about PEPs or Processs)"
-    print
-    for pep in meta:
-        write_pep(pep, stdout)
-    print
-    print " Other Informational PEPs"
-    print
-    for pep in info:
-        write_pep(pep, stdout)
-    print
-    print " Accepted PEPs (accepted; may not be implemented yet)"
-    print
-    for pep in accepted:
-        write_pep(pep, stdout)
-    print
-    print " Open PEPs (under consideration)"
-    print
-    for pep in open_:
-        write_pep(pep, stdout)
-    print
-    print " Finished PEPs (done, implemented in code repository)"
-    print
-    for pep in done:
-        write_pep(pep, stdout)
-    print
-    print " Empty PEPs (or containing only abstract)"
-    print
-    for pep in empty:
-        write_pep(pep, stdout)
-    print
-    print " Deferred, Abandoned, Withdrawn, and Rejected PEPs"
-    print
-    for pep in dead:
-        write_pep(pep, stdout)
-    print
-    print
-    print " Numerical Index"
-    print
-    write_column_headers(stdout)
-    prev_pep = 0
-    for pep in peps:
-        if pep['PEP'] - prev_pep > 1:
-            print
-        write_pep(pep, stdout)
-        prev_pep = pep['PEP']
-    print
-    print
-    print "Key"
-    print
-    for type_ in type_values:
-        print "    %s - %s PEP" % (type_[0], type_)
-    print
-    for status in status_values:
-        print "    %s - %s proposal" % (status[0], status)
-
-    print
-    print
-    print "Owners"
-    print
-    # XXX
-    # * get "last, first I." of each name.
-    # * add nickname.
-    # * find longest name.
-    # * column headers.
-    # * name/email with a two-space separation between longest name and email.
-    print '    XXX'
-    print
-    print
-    print "References"
-    print
-    print references
-    print footer

Added: sandbox/trunk/pep0/pep0/__init__.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pep0/pep0/__init__.py	Sun Apr 15 05:21:46 2007
@@ -0,0 +1,117 @@
+"""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
+
+if __name__ == '__main__':
+    from pep0.parse import consume_headers, consume_pep
+    from pep0.pep import sort_peps, PEP
+    from pep0.output import write_column_headers, write_pep
+    from pep0.statictext import header, intro, references, footer
+
+    import os
+    from sys import argv, stdout
+    
+    if not argv[1:]:
+        path = '.'
+    else:
+        path = argv[1]
+    if os.path.isdir(path):
+        peps = consume_headers(path)
+    else:
+        peps = [consume_pep(path)]
+
+    print header
+    print
+    print "Introduction"
+    print intro
+    print
+    print "Index by Category"
+    print
+    write_column_headers(stdout)
+    meta, info, accepted, open_, done, empty, dead = sort_peps(peps)
+    print
+    print " Meta-PEPs (PEPs about PEPs or Processs)"
+    print
+    for pep in meta:
+        write_pep(pep, stdout)
+    print
+    print " Other Informational PEPs"
+    print
+    for pep in info:
+        write_pep(pep, stdout)
+    print
+    print " Accepted PEPs (accepted; may not be implemented yet)"
+    print
+    for pep in accepted:
+        write_pep(pep, stdout)
+    print
+    print " Open PEPs (under consideration)"
+    print
+    for pep in open_:
+        write_pep(pep, stdout)
+    print
+    print " Finished PEPs (done, implemented in code repository)"
+    print
+    for pep in done:
+        write_pep(pep, stdout)
+    print
+    print " Empty PEPs (or containing only abstract)"
+    print
+    for pep in empty:
+        write_pep(pep, stdout)
+    print
+    print " Deferred, Abandoned, Withdrawn, and Rejected PEPs"
+    print
+    for pep in dead:
+        write_pep(pep, stdout)
+    print
+    print
+    print " Numerical Index"
+    print
+    write_column_headers(stdout)
+    prev_pep = 0
+    for pep in peps:
+        if pep['PEP'] - prev_pep > 1:
+            print
+        write_pep(pep, stdout)
+        prev_pep = pep['PEP']
+    print
+    print
+    print "Key"
+    print
+    for type_ in PEP.type_values:
+        print "    %s - %s PEP" % (type_[0], type_)
+    print
+    for status in PEP.status_values:
+        print "    %s - %s proposal" % (status[0], status)
+
+    print
+    print
+    print "Owners"
+    print
+    # XXX
+    # * get "last, first I." of each name.
+    # * add nickname.
+    # * find longest name.
+    # * column headers.
+    # * name/email with a two-space separation between longest name and email.
+    print '    XXX'
+    print
+    print
+    print "References"
+    print
+    print references
+    print footer

Added: sandbox/trunk/pep0/pep0/authors.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pep0/pep0/authors.py	Sun Apr 15 05:21:46 2007
@@ -0,0 +1,108 @@
+# -*- coding: utf-8 -*-
+email_addresses = {
+    'Aahz' : 'aahz at pobox.com',
+    'James C. Ahlstrom' : 'jim at interet.com',
+    'Jim Althoff' : 'james_althoff at i2.com',
+    'Kevin Altis' : 'altis at semi-retired.com',
+    'David Ascher' : 'davida at activestate.com',
+    'Peter Astrand' : 'astrand at lysator.liu.se',
+    'Carl Banks' : 'pythondev at aerojockey.com',
+    'Paul Barrett' : 'barrett at stsci.edu',
+    'Facundo Batista' : 'facundo at taniquetil.com.ar',
+    'Anthony Baxter' : 'anthony at interlink.com.au',
+    'Thomas Bellman' : 'bellman+pep-divmod at lysator.liu.se',
+    'Steven Bethard' : 'steven.bethard at gmail.com',
+    'Georg Brandl' : 'g.brandl at gmx.net',
+    'Brett Cannon' : 'brett at python.org',
+    'Josiah Carlson' : 'jcarlson at uci.edu',
+    'W Isaac Carroll' : 'icarroll at pobox.com',
+    'Nick Coghlan' : 'ncoghlan at gmail.com',
+    'Dave Cole' : 'djc at object-craft.com.au',
+    'Christopher Craig' : 'python-pep at ccraig.org',
+    'Laura Creighton' : 'lac at strakt.com',
+    'Walter Dörwald' : '',
+    'Fred L. Drake, Jr.' : 'fdrake at acm.org',
+    'Michael P. Dubner' : 'dubnerm at mindless.com',
+    'Paul F. Dubois' : 'paul at pfdubois.com',
+    'Phillip J. Eby' : 'pje at telecommunity.com',
+    'Micah Elliott' : 'mde at tracos.org',
+    'Jeff Epler' : 'jepler at unpythonic.net',
+    'David Eppstein' : 'eppstein at ics.uci.edu',
+    'Clark C. Evans' : 'cce at clarkevans.com',
+    'Greg Ewing' : 'greg at cosc.canterbury.ac.nz',
+    'Martijn Faassen' : 'faassen at infrae.com',
+    'Ben Finney' : 'ben+python at benfinney.id.au',
+    'Frédéric B. Giacometti' : 'fred at arakne.com',
+    'Scott Gilbert' : 'xscottg at yahoo.com',
+    'David Goodger' : 'goodger at python.org',
+    'Grant Griffin' : 'g2 at iowegian.com',
+    'Mark Hammond' : 'mhammond at skippinet.com.au',
+    'Peter Harris' : 'scav at blueyonder.co.uk',
+    'Thomas Heller' : 'theller at python.net',
+    'Magnus Lie Hetland' : 'magnus at hetland.org',
+    'Raymond D. Hettinger' : 'python at rcn.com',
+    'Neil Hodgson' : 'neilh at scintilla.org',
+    'Michael Hudson' : 'mwh at python.net',
+    'Jeremy Hylton' : 'jeremy at zope.com',
+    'Jack Jansen' : 'jack at cwi.nl',
+    'Jim Jewett' : 'jimjjewett at users.sourceforge.net',
+    'Richard Jones' : 'richard at mechanicalcat.net',
+    'Stepan Koltsov' : 'yozh at mx1.ru',
+    'A.M. Kuchling' : 'amk at amk.ca',
+    'Marc-Andre Lemburg' : 'mal at lemburg.com',
+    'Gregory Lielens' : 'gregory.lielens at fft.be',
+    'Björn Lindqvist' : 'bjourne at gmail.com',
+    'Martin von Löwis' : 'loewis at informatik.hu-berlin.de',
+    'Tony Lownds' : 'tony at pagedna.com',
+    'Alex Martelli' : 'aleax at aleax.it',
+    'Andrew McClelland' : 'eternalsquire at comcast.net',
+    'Gordon McMillan' : 'gmcm at hypernet.com',
+    'Andrew McNamara' : 'andrewm at object-craft.com.au',
+    'Trent Mick' : 'trentm at activestate.com',
+    'Mike Meyer' : 'mwm at mired.org',
+    'Skip Montanaro' : 'skip at pobox.com',
+    'Paul Moore' : 'gustav at morpheus.demon.co.uk',
+    'Ben North' : 'ben at redfrontdoor.org',
+    'Neal Norwitz' : 'nnorwitz at gmail.com',
+    'Travis Oliphant' : 'oliphant at ee.byu.edu',
+    'Jason Orendorff' : 'jason.orendorff at gmail.com',
+    'Samuele Pedroni' : 'pedronis at python.org',
+    'Michel Pelletier' : 'michel at users.sourceforge.net',
+    'Tim Peters' : 'tim at zope.com',
+    'Jason Petrone' : 'jp at demonseed.net',
+    'Paul Prescod' : 'paul at prescod.net',
+    'Terry Reedy' : 'tjreedy at udel.edu',
+    'Sean Reifschneider' : 'jafo-pep at tummy.com',
+    'Christian R. Reis' : 'kiko at async.com.br',
+    'Jonathan Riehl' : 'jriehl at spaceship.com',
+    'André Roberge' : 'andre.roberge at gmail.com',
+    'Guido van Rossum' : 'guido at python.org',
+    'Just van Rossum' : 'just at letterror.com',
+    'Vinay Sajip' : 'vinay_sajip at red-dove.com',
+    'Neil Schemenauer' : 'nas at arctrix.com',
+    'Peter Schneider-Kamp' : 'nowonder at nowonder.de',
+    'Jiwon Seo' : 'seojiwon at gmail.com',
+    'Kevin D. Smith' : 'Kevin.Smith at theMorgue.org',
+    'Greg Stein' : 'gstein at lyra.org',
+    'Daniel Stutzbach' : 'daniel.stutzbach at gmail.com',
+    'Roman Suzi' : 'rnd at onego.ru',
+    'Talin' : 'talin at acm.org',
+    'Steven Taschuk' : 'staschuk at telusplanet.net',
+    'Oren Tirosh' : 'oren at hishome.net',
+    'Mike Verdone' : 'mike.verdone at gmail.com',
+    'Gregory R. Warnes' : 'warnes at users.sourceforge.net',
+    'Barry Warsaw' : 'barry at python.org',
+    'Terence Way' : 'terry at wayforward.net',
+    'Cliff Wells' : 'LogiplexSoftware at earthlink.net',
+    'Greg Wilson' : 'gvwilson at ddj.com',
+    'Collin Winter' : 'collinw at gmail.com',
+    'Thomas Wouters' : 'thomas at python.org',
+    'Ka-Ping Yee' : 'ping at zesty.ca',
+    'Moshe Zadka' : 'moshez at zadka.site.co.il',
+    'Huaiyu Zhu' : 'hzhu at users.sourceforge.net',
+}
+
+nicknames = {
+    'Guido van Rossum' : 'GvR',
+    'Just van Rossum' : 'JvR',
+}

Added: sandbox/trunk/pep0/pep0/output.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pep0/pep0/output.py	Sun Apr 15 05:21:46 2007
@@ -0,0 +1,58 @@
+from . import authors
+from .pep import last_name
+
+indent = ' '
+
+def make_entry(type_, status, number, title, owners):
+    """Return the string representation for a PEP entry (or anything that is
+    similarly formatted.
+
+    If type_ or status have values of None they are filled in with whitespace.
+    All arguments are expected to be valid.
+
+    """
+    entry = [indent]
+    # Type.
+    if type_:
+        entry.append(type_[0].upper())
+    else:
+        entry.append(' ')
+    # Status.
+    if status and status not in ('Active', 'Draft'):
+        entry.append(type_[0].upper())
+    else:
+        entry.append(' ')
+    # Number.
+    entry.append(str(number).ljust(4))
+    # Title.
+    entry.append(title.rjust(44))
+    # owners.
+    entry.append(', '.join(last_name(owner) for owner in owners))
+
+    # Indent, Type, Statue, Number, Title, Owners.
+    return "%s%s%s %s  %s  %s" % entry
+
+def write_pep(pep, output):
+    """Write PEP info to 'output'."""
+    type_abbr = pep['Type'][0].upper()
+    status = pep['Status']
+    if status in ('Draft', 'Active'):
+        status_abbr = ' '
+    else:
+        status_abbr = status[0].upper()
+    number = str(pep['PEP']).rjust(4)
+    title = pep['Title']
+    authors_list = []
+    author_string = ', '.join(last_name(author, authors.nicknames)
+            for author in pep['Author'])
+    output.write(" %s%s %s  %s %s\n" %
+                    (type_abbr, status_abbr, number, title.ljust(44),
+                        author_string))
+
+def write_column_headers(output):
+    """Output the column headers for the PEP indices."""
+    output.write('%s  %s %s\n' % ("num".rjust(8), "title".ljust(44), "owner"))
+    output.write('%s  %s %s\n' % ((len("num")*'-').rjust(8),
+                         (len("title")*'-').ljust(44), len("owner")*'-'))
+
+

Added: sandbox/trunk/pep0/pep0/parse.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pep0/pep0/parse.py	Sun Apr 15 05:21:46 2007
@@ -0,0 +1,111 @@
+from __future__ import with_statement
+from .pep import PEP
+
+import os
+import re
+
+def consume_headers(directory='.'):
+    """Pull out metadata for every PEP in the specified directory and return
+    them in a list sorted by PEP name."""
+    peps = []
+    for file_name in os.listdir(directory):
+        if file_name.startswith('pep-') and file_name.endswith('.txt'):
+            peps.append(consume_pep(os.path.join(directory, file_name)))
+    peps.sort(key=lambda pep: pep['PEP'])
+    return peps
+
+def consume_pep(path):
+    """Consume the specified file as a PEP to get its metadata."""
+    pep_info = {}
+    with open(path, 'rU') as pep_file:
+        try:
+            for line in pep_file:
+                if line == '\n':
+                    break
+                elif line[1].isspace():
+                    type_ = parse_metadata(pep_info, line, type_)
+                else:
+                    type_ = parse_metadata(pep_info, line)
+        except Exception:
+            print "*** In", pep_file
+            raise
+    if not 'PEP' in pep_info:
+        raise ValueError("PEP at file %s lacks a PEP number" % path)
+    if not 'Author' in pep_info:
+        raise ValueError("PEP %s is missing the Author field" %
+                         pep_info['PEP'])
+    if len(pep_info['Author']) < 1:
+        raise ValueError("PEP %s is lacking authors" % pep_info['PEP'])
+    if pep_info['Type'] not in PEP.type_values:
+        raise ValueError("%s is an invalid Type value for PEP %s" %
+                         (pep_info['Type'], pep_info['PEP']))
+    if pep_info['Status'] not in PEP.status_values:
+        raise ValueError("%s is an invalid Status value for PEP %s" %
+                         (pep_info['Status'], pep_info['PEP']))
+    return pep_info
+
+def parse_metadata(pep_info, line, previous_type=None):
+    """Parse the given line for PEP metadata, adding on to existing metadata if
+    previous_type is specified, returning the last type of metadata handled."""
+    if previous_type:
+        type_ = previous_type
+        data = line
+    else:
+        type_, data = line.split(':', 1)
+    type_ = type_.strip()
+    data = data.strip()
+    handler = handlers.get(type_, handle_generic)
+    result = handler(data)
+    if previous_type:
+        previous_data = pep_info[type_]
+        if not isinstance(previous_data, list):
+            previous_data = [previous_data]
+            pep_info[type_] = previous_data
+        previous_data.extend(result)
+    else:
+        pep_info[type_] = result
+    return type_
+
+def handle_generic(data):
+    """Default handler for PEP metadata."""
+    return data
+
+def handle_pep_num(data):
+    """Return the integer for the PEP number."""
+    return int(data)
+
+def handle_author(data):
+    """Return a list of author names."""
+    angled = r'(?P<author>.+?) <.+?>'
+    paren = r'.+? \((?P<author>.+?)\)'
+    simple = r'(?P<author>[^,]+)'
+    author_list = []
+    for regex in (angled, paren, simple):
+        # Watch out for commas separating multiple names.
+        regex += '(,\s+)?'
+        for match in re.finditer(regex, data):
+            author = match.group('author')
+            # 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.
+            author = match.group('author')
+            if not author.partition(' ')[1] and author.endswith('.'):
+                prev_author = author_list.pop()
+                author = ', '.join([prev_author, author])
+            author_list.append(author)
+        else:
+            # If authors were found then stop searching as only expect one
+            # style of author citation.
+            if author_list:
+                break
+    return author_list
+
+def handle_csv(data):
+    """Handle the Post-History."""
+    return [value.strip() for value in data.split(',') if value]
+
+handlers = {'Author': handle_author,
+            'PEP': handle_pep_num,
+            'Post-History': handle_csv,
+           }
+

Added: sandbox/trunk/pep0/pep0/pep.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pep0/pep0/pep.py	Sun Apr 15 05:21:46 2007
@@ -0,0 +1,76 @@
+class PEP(object):
+
+    """Representation of PEPs."""
+
+    type_values = ("Standards Track", "Informational", "Process")
+    # Active and Draft are not listed in the index.
+    status_values = ("Accepted", "Rejected", "Withdrawn", "Deferred", "Final",
+                     "Active", "Draft")
+
+    def __init__(self, metadata_dict):
+        """Init object based on dict containing metadata from a file."""
+        pass
+
+    def validate(self):
+        """Validate that the instance data."""
+        pass
+
+    def __str__(self):
+        """Return the line entry for the PEP."""
+        return ''
+        pass
+
+
+def last_name(full_name, nicknames={}):
+    """Find the last name (or nickname) of a full name.
+
+    If no last name (e.g, 'Aahz') then return the full name.  If there is a
+    leading, lowercase portion to the last name (e.g., 'van' or 'von') then
+    include it.  If there is a suffix (e.g., 'Jr.') that is appended through a
+    comma, then drop the suffix.
+
+    """
+    nickname = nicknames.get(full_name)
+    if nickname:
+        return nickname
+    no_suffix = full_name.partition(',')[0]
+    name_parts = no_suffix.split()
+    part_count = len(name_parts)
+    if part_count == 1 or part_count == 2:
+        return name_parts[-1]
+    else:
+        assert part_count > 2
+        if name_parts[-2].islower():
+            return ' '.join(name_parts[-2:])
+        else:
+            return name_parts[-1]
+
+def sort_peps(peps):
+    """Sort PEPs into meta, informational, accepted, open, finished, empty,
+    and essentially dead."""
+    meta = []
+    info = []
+    accepted = []
+    open_ = []
+    finished = []
+    empty = []
+    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'] in ('Rejected', 'Withdrawn', 'Deferred',
+                'Incomplete'):
+            dead.append(pep)
+        elif pep['Type'] == 'Process':
+            meta.append(pep)
+        elif pep['Type'] == 'Informational':
+            info.append(pep)
+        elif pep['Status'] == 'Accepted':
+            accepted.append(pep)
+        elif pep['Status'] == 'Final':
+            finished.append(pep)
+    return meta, info, accepted, open_, finished, empty, dead
+
+

Added: sandbox/trunk/pep0/pep0/statictext.py
==============================================================================
--- (empty file)
+++ sandbox/trunk/pep0/pep0/statictext.py	Sun Apr 15 05:21:46 2007
@@ -0,0 +1,40 @@
+# Don't start on a new line to make easier to read as that would cause output to
+# start on a blank line.
+header = """PEP: 0
+Title: Index of Python Enhancement Proposals (PEPs)
+Version: $Revision$
+Last-Modified: $Date$
+Author: David Goodger <goodger at python.org>,
+        Barry A. Warsaw <barry at python.org>
+Status: Active
+Type: Informational
+Created: 13-Jul-2000
+"""
+
+intro = """
+    The PEP contains the index of all Python Enhancement Proposals,
+    known as PEPs.  PEP numbers are assigned by the PEP Editor, and
+    once assigned are never changed.  The SVN history[1] of the PEP
+    texts represent their historical record.
+
+    The BDFL maintains his own Pronouncements page[2] at
+    http://www.python.org/doc/essays/pepparade.html which contains his
+    musings on the various outstanding PEPs.
+"""
+
+references = """
+    [1] View PEP history online
+        http://svn.python.org/projects/peps/trunk/
+
+    [2] The Benevolent Dictator For Life's Parade of PEPs
+        http://www.python.org/doc/essays/pepparade.html
+"""
+
+footer = """
+Local Variables:
+mode: indented-text
+indent-tabs-mode: nil
+sentence-end-double-space: t
+fill-column: 70
+coding: utf-8
+End:"""

Modified: sandbox/trunk/pep0/test_pep0.py
==============================================================================
--- sandbox/trunk/pep0/test_pep0.py	(original)
+++ sandbox/trunk/pep0/test_pep0.py	Sun Apr 15 05:21:46 2007
@@ -8,6 +8,8 @@
 
 class HandlerTests(unittest.TestCase):
 
+    """Test the PEP field handlers for parsing data."""
+
     def test_handle_generic(self):
         # Identity function.
         for data in ('spam', 'spam,', '', 'spam, monty'):
@@ -54,6 +56,8 @@
 
 class ParseMetaDataTests(unittest.TestCase):
 
+    """Test the parsing of the headers of PEPs."""
+
     def test_parse_known_formats(self):
         # Handle specific metadata format types.
         assert "PEP" in pep0.handlers
@@ -88,6 +92,34 @@
         self.failUnlessEqual(info[type_][1], data)
 
 
+class PEPClassTests(unittest.TestCase):
+
+    """Test the PEP class."""
+
+    def test_valid_init(self):
+        # Make sure that a PEP instance can be created from a dict with valid
+        # entries.
+        pass
+
+    # Number cannot be invalid as that is checked during parsing of the PEP.
+
+    def test_invalid_status(self):
+        # Invalid Status entry raises an exception.
+        pass
+
+    def test_invalid_type(self):
+        # Invalid type should raise an exception.
+        pass
+
+    def test_invalid_authors(self):
+        # There should be at least one author.
+        pass
+
+    def test_str(self):
+        # String representation of the PEP.
+        pass
+
+
 @contextmanager
 def test_file(path):
     try:
@@ -101,6 +133,8 @@
 
 class ConsumePepTests(unittest.TestCase):
 
+    """Test the reading and parsing of a PEP file."""
+
     def test_single_line(self):
         # Test a PEP that only has a single line of metadata.
         type_ = 'Spam'
@@ -143,8 +177,66 @@
             self.failUnless('Post-History' in metadata)
             self.failUnlessEqual(metadata['Post-History'], dates)
 
-
-class OutputTests(unittest.TestCase):
+    def test_missing_fields(self):
+        # Missing key fields should raise ValueError.
+        # PEP
+        # Author
+        # Type
+        # Status
+        pass
+
+    def test_pep_field(self):
+        # PEP field should only contain a number.
+        pass
+
+    def test_author_field(self):
+        # Author field should have at least a single author.
+        pass
+
+    def test_type_field(self):
+        # Type field should contain only a valid value.
+        pass
+
+    def test_status_field(self):
+        # Status field should contain only a valid value.
+        pass
+
+
+class EntryOutputTests(unittest.TestCase):
+
+    """Tests for single-line output into the index."""
+
+    def test_make_entry_type(self):
+        # Test handling of type_ argument.
+        pass
+
+    def test_make_entry_status(self):
+        # make_entry's status argument.
+        pass
+
+    def test_make_entry_number(self):
+        # make_entry's number argument.
+        pass
+
+    def test_make_entry_number(self):
+        # make_entry's title argument.
+        pass
+
+    def test_make_entry_title(self):
+        # make_entry's title argument.
+        pass
+
+    def test_make_entry_owners(self):
+        # make_entry's owners argument.
+        pass
+
+    def test_make_entry_column_headers(self):
+        # Should be able to output column headers properly.
+        pass
+
+    def test_make_entry_underline(self):
+        # Underlining of column headers should be okay.
+        pass
 
     def test_author_last_name(self):
         # Test that last names are discovered properly.
@@ -173,8 +265,9 @@
     test_support.run_unittest(
                 HandlerTests,
                 ParseMetaDataTests,
+                PEPClassTests,
                 ConsumePepTests,
-                OutputTests,
+                EntryOutputTests,
             )
 
 


More information about the Python-checkins mailing list