diff --git a/pep2html.py b/pep2html.py
index 253579334..9add9e2d0 100755
--- a/pep2html.py
+++ b/pep2html.py
@@ -50,6 +50,11 @@ try:
except ImportError:
from cgi import escape
+from docutils import core, nodes, utils
+from docutils.readers import standalone
+from docutils.transforms import peps, references, misc, frontmatter, Transform
+from docutils.parsers import rst
+
REQUIRES = {'python': '2.6',
'docutils': '0.2.7'}
PROGRAM = sys.argv[0]
@@ -315,15 +320,155 @@ docutils_settings = None
"""Runtime settings object used by Docutils. Can be set by the client
application when this module is imported."""
+class PEPHeaders(Transform):
+
+ """
+ Process fields in a PEP's initial RFC-2822 header.
+ """
+
+ default_priority = 360
+
+ pep_url = 'pep-%04d'
+ pep_cvs_url = PEPCVSURL
+ rcs_keyword_substitutions = (
+ (re.compile(r'\$' r'RCSfile: (.+),v \$$', re.IGNORECASE), r'\1'),
+ (re.compile(r'\$[a-zA-Z]+: (.+) \$$'), r'\1'),)
+
+ def apply(self):
+ if not len(self.document):
+ # @@@ replace these DataErrors with proper system messages
+ raise DataError('Document tree is empty.')
+ header = self.document[0]
+ if not isinstance(header, nodes.field_list) or \
+ 'rfc2822' not in header['classes']:
+ raise DataError('Document does not begin with an RFC-2822 '
+ 'header; it is not a PEP.')
+ pep = None
+ for field in header:
+ if field[0].astext().lower() == 'pep': # should be the first field
+ value = field[1].astext()
+ try:
+ pep = int(value)
+ cvs_url = self.pep_cvs_url % pep
+ except ValueError:
+ pep = value
+ cvs_url = None
+ msg = self.document.reporter.warning(
+ '"PEP" header must contain an integer; "%s" is an '
+ 'invalid value.' % pep, base_node=field)
+ msgid = self.document.set_id(msg)
+ prb = nodes.problematic(value, value or '(none)',
+ refid=msgid)
+ prbid = self.document.set_id(prb)
+ msg.add_backref(prbid)
+ if len(field[1]):
+ field[1][0][:] = [prb]
+ else:
+ field[1] += nodes.paragraph('', '', prb)
+ break
+ if pep is None:
+ raise DataError('Document does not contain an RFC-2822 "PEP" '
+ 'header.')
+ if pep == 0:
+ # Special processing for PEP 0.
+ pending = nodes.pending(PEPZero)
+ self.document.insert(1, pending)
+ self.document.note_pending(pending)
+ if len(header) < 2 or header[1][0].astext().lower() != 'title':
+ raise DataError('No title!')
+ for field in header:
+ name = field[0].astext().lower()
+ body = field[1]
+ if len(body) > 1:
+ raise DataError('PEP header field body contains multiple '
+ 'elements:\n%s' % field.pformat(level=1))
+ elif len(body) == 1:
+ if not isinstance(body[0], nodes.paragraph):
+ raise DataError('PEP header field body may only contain '
+ 'a single paragraph:\n%s'
+ % field.pformat(level=1))
+ elif name == 'last-modified':
+ date = time.strftime(
+ '%d-%b-%Y',
+ time.localtime(os.stat(self.document['source'])[8]))
+ if cvs_url:
+ body += nodes.paragraph(
+ '', '', nodes.reference('', date, refuri=cvs_url))
+ else:
+ # empty
+ continue
+ para = body[0]
+ if name == 'author':
+ 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', 'replaced-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 PEPReader(standalone.Reader):
+
+ supported = ('pep',)
+ """Contexts this reader supports."""
+
+ settings_spec = (
+ 'PEP Reader Option Defaults',
+ 'The --pep-references and --rfc-references options (for the '
+ 'reStructuredText parser) are on by default.',
+ ())
+
+ config_section = 'pep reader'
+ config_section_dependencies = ('readers', 'standalone reader')
+
+ def get_transforms(self):
+ transforms = standalone.Reader.get_transforms(self)
+ # We have PEP-specific frontmatter handling.
+ transforms.remove(frontmatter.DocTitle)
+ transforms.remove(frontmatter.SectionSubTitle)
+ transforms.remove(frontmatter.DocInfo)
+ transforms.extend([PEPHeaders, peps.Contents, peps.TargetNotes])
+ return transforms
+
+ settings_default_overrides = {'pep_references': 1, 'rfc_references': 1}
+
+ inliner_class = rst.states.Inliner
+
+ def __init__(self, parser=None, parser_name=None):
+ """`parser` should be ``None``."""
+ if parser is None:
+ parser = rst.Parser(rfc2822=True, inliner=self.inliner_class())
+ standalone.Reader.__init__(self, parser, '')
+
+
def fix_rst_pep(inpath, input_lines, outfile):
- from docutils import core
- from docutils.transforms.peps import Headers
- Headers.pep_cvs_url = PEPCVSURL
output = core.publish_string(
source=''.join(input_lines),
source_path=inpath,
destination_path=outfile.name,
- reader_name='pep',
+ reader=PEPReader(),
parser_name='restructuredtext',
writer_name='pep_html',
settings=docutils_settings,