buildframework/helium/external/python/lib/common/docutils-0.5-py2.5.egg/docutils/transforms/peps.py
changeset 179 d8ac696cc51f
equal deleted inserted replaced
1:be27ed110b50 179:d8ac696cc51f
       
     1 # $Id: peps.py 4564 2006-05-21 20:44:42Z wiemann $
       
     2 # Author: David Goodger <goodger@python.org>
       
     3 # Copyright: This module has been placed in the public domain.
       
     4 
       
     5 """
       
     6 Transforms for PEP processing.
       
     7 
       
     8 - `Headers`: Used to transform a PEP's initial RFC-2822 header.  It remains a
       
     9   field list, but some entries get processed.
       
    10 - `Contents`: Auto-inserts a table of contents.
       
    11 - `PEPZero`: Special processing for PEP 0.
       
    12 """
       
    13 
       
    14 __docformat__ = 'reStructuredText'
       
    15 
       
    16 import sys
       
    17 import os
       
    18 import re
       
    19 import time
       
    20 from docutils import nodes, utils, languages
       
    21 from docutils import ApplicationError, DataError
       
    22 from docutils.transforms import Transform, TransformError
       
    23 from docutils.transforms import parts, references, misc
       
    24 
       
    25 
       
    26 class Headers(Transform):
       
    27 
       
    28     """
       
    29     Process fields in a PEP's initial RFC-2822 header.
       
    30     """
       
    31 
       
    32     default_priority = 360
       
    33 
       
    34     pep_url = 'pep-%04d'
       
    35     pep_cvs_url = ('http://svn.python.org/view/*checkout*'
       
    36                    '/peps/trunk/pep-%04d.txt')
       
    37     rcs_keyword_substitutions = (
       
    38           (re.compile(r'\$' r'RCSfile: (.+),v \$$', re.IGNORECASE), r'\1'),
       
    39           (re.compile(r'\$[a-zA-Z]+: (.+) \$$'), r'\1'),)
       
    40 
       
    41     def apply(self):
       
    42         if not len(self.document):
       
    43             # @@@ replace these DataErrors with proper system messages
       
    44             raise DataError('Document tree is empty.')
       
    45         header = self.document[0]
       
    46         if not isinstance(header, nodes.field_list) or \
       
    47               'rfc2822' not in header['classes']:
       
    48             raise DataError('Document does not begin with an RFC-2822 '
       
    49                             'header; it is not a PEP.')
       
    50         pep = None
       
    51         for field in header:
       
    52             if field[0].astext().lower() == 'pep': # should be the first field
       
    53                 value = field[1].astext()
       
    54                 try:
       
    55                     pep = int(value)
       
    56                     cvs_url = self.pep_cvs_url % pep
       
    57                 except ValueError:
       
    58                     pep = value
       
    59                     cvs_url = None
       
    60                     msg = self.document.reporter.warning(
       
    61                         '"PEP" header must contain an integer; "%s" is an '
       
    62                         'invalid value.' % pep, base_node=field)
       
    63                     msgid = self.document.set_id(msg)
       
    64                     prb = nodes.problematic(value, value or '(none)',
       
    65                                             refid=msgid)
       
    66                     prbid = self.document.set_id(prb)
       
    67                     msg.add_backref(prbid)
       
    68                     if len(field[1]):
       
    69                         field[1][0][:] = [prb]
       
    70                     else:
       
    71                         field[1] += nodes.paragraph('', '', prb)
       
    72                 break
       
    73         if pep is None:
       
    74             raise DataError('Document does not contain an RFC-2822 "PEP" '
       
    75                             'header.')
       
    76         if pep == 0:
       
    77             # Special processing for PEP 0.
       
    78             pending = nodes.pending(PEPZero)
       
    79             self.document.insert(1, pending)
       
    80             self.document.note_pending(pending)
       
    81         if len(header) < 2 or header[1][0].astext().lower() != 'title':
       
    82             raise DataError('No title!')
       
    83         for field in header:
       
    84             name = field[0].astext().lower()
       
    85             body = field[1]
       
    86             if len(body) > 1:
       
    87                 raise DataError('PEP header field body contains multiple '
       
    88                                 'elements:\n%s' % field.pformat(level=1))
       
    89             elif len(body) == 1:
       
    90                 if not isinstance(body[0], nodes.paragraph):
       
    91                     raise DataError('PEP header field body may only contain '
       
    92                                     'a single paragraph:\n%s'
       
    93                                     % field.pformat(level=1))
       
    94             elif name == 'last-modified':
       
    95                 date = time.strftime(
       
    96                       '%d-%b-%Y',
       
    97                       time.localtime(os.stat(self.document['source'])[8]))
       
    98                 if cvs_url:
       
    99                     body += nodes.paragraph(
       
   100                         '', '', nodes.reference('', date, refuri=cvs_url))
       
   101             else:
       
   102                 # empty
       
   103                 continue
       
   104             para = body[0]
       
   105             if name == 'author':
       
   106                 for node in para:
       
   107                     if isinstance(node, nodes.reference):
       
   108                         node.replace_self(mask_email(node))
       
   109             elif name == 'discussions-to':
       
   110                 for node in para:
       
   111                     if isinstance(node, nodes.reference):
       
   112                         node.replace_self(mask_email(node, pep))
       
   113             elif name in ('replaces', 'replaced-by', 'requires'):
       
   114                 newbody = []
       
   115                 space = nodes.Text(' ')
       
   116                 for refpep in re.split(',?\s+', body.astext()):
       
   117                     pepno = int(refpep)
       
   118                     newbody.append(nodes.reference(
       
   119                         refpep, refpep,
       
   120                         refuri=(self.document.settings.pep_base_url
       
   121                                 + self.pep_url % pepno)))
       
   122                     newbody.append(space)
       
   123                 para[:] = newbody[:-1] # drop trailing space
       
   124             elif name == 'last-modified':
       
   125                 utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
       
   126                 if cvs_url:
       
   127                     date = para.astext()
       
   128                     para[:] = [nodes.reference('', date, refuri=cvs_url)]
       
   129             elif name == 'content-type':
       
   130                 pep_type = para.astext()
       
   131                 uri = self.document.settings.pep_base_url + self.pep_url % 12
       
   132                 para[:] = [nodes.reference('', pep_type, refuri=uri)]
       
   133             elif name == 'version' and len(body):
       
   134                 utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions)
       
   135 
       
   136 
       
   137 class Contents(Transform):
       
   138 
       
   139     """
       
   140     Insert an empty table of contents topic and a transform placeholder into
       
   141     the document after the RFC 2822 header.
       
   142     """
       
   143 
       
   144     default_priority = 380
       
   145 
       
   146     def apply(self):
       
   147         language = languages.get_language(self.document.settings.language_code)
       
   148         name = language.labels['contents']
       
   149         title = nodes.title('', name)
       
   150         topic = nodes.topic('', title, classes=['contents'])
       
   151         name = nodes.fully_normalize_name(name)
       
   152         if not self.document.has_name(name):
       
   153             topic['names'].append(name)
       
   154         self.document.note_implicit_target(topic)
       
   155         pending = nodes.pending(parts.Contents)
       
   156         topic += pending
       
   157         self.document.insert(1, topic)
       
   158         self.document.note_pending(pending)
       
   159 
       
   160 
       
   161 class TargetNotes(Transform):
       
   162 
       
   163     """
       
   164     Locate the "References" section, insert a placeholder for an external
       
   165     target footnote insertion transform at the end, and schedule the
       
   166     transform to run immediately.
       
   167     """
       
   168 
       
   169     default_priority = 520
       
   170 
       
   171     def apply(self):
       
   172         doc = self.document
       
   173         i = len(doc) - 1
       
   174         refsect = copyright = None
       
   175         while i >= 0 and isinstance(doc[i], nodes.section):
       
   176             title_words = doc[i][0].astext().lower().split()
       
   177             if 'references' in title_words:
       
   178                 refsect = doc[i]
       
   179                 break
       
   180             elif 'copyright' in title_words:
       
   181                 copyright = i
       
   182             i -= 1
       
   183         if not refsect:
       
   184             refsect = nodes.section()
       
   185             refsect += nodes.title('', 'References')
       
   186             doc.set_id(refsect)
       
   187             if copyright:
       
   188                 # Put the new "References" section before "Copyright":
       
   189                 doc.insert(copyright, refsect)
       
   190             else:
       
   191                 # Put the new "References" section at end of doc:
       
   192                 doc.append(refsect)
       
   193         pending = nodes.pending(references.TargetNotes)
       
   194         refsect.append(pending)
       
   195         self.document.note_pending(pending, 0)
       
   196         pending = nodes.pending(misc.CallBack,
       
   197                                 details={'callback': self.cleanup_callback})
       
   198         refsect.append(pending)
       
   199         self.document.note_pending(pending, 1)
       
   200 
       
   201     def cleanup_callback(self, pending):
       
   202         """
       
   203         Remove an empty "References" section.
       
   204 
       
   205         Called after the `references.TargetNotes` transform is complete.
       
   206         """
       
   207         if len(pending.parent) == 2:    # <title> and <pending>
       
   208             pending.parent.parent.remove(pending.parent)
       
   209 
       
   210 
       
   211 class PEPZero(Transform):
       
   212 
       
   213     """
       
   214     Special processing for PEP 0.
       
   215     """
       
   216 
       
   217     default_priority =760
       
   218 
       
   219     def apply(self):
       
   220         visitor = PEPZeroSpecial(self.document)
       
   221         self.document.walk(visitor)
       
   222         self.startnode.parent.remove(self.startnode)
       
   223 
       
   224 
       
   225 class PEPZeroSpecial(nodes.SparseNodeVisitor):
       
   226 
       
   227     """
       
   228     Perform the special processing needed by PEP 0:
       
   229 
       
   230     - Mask email addresses.
       
   231 
       
   232     - Link PEP numbers in the second column of 4-column tables to the PEPs
       
   233       themselves.
       
   234     """
       
   235 
       
   236     pep_url = Headers.pep_url
       
   237 
       
   238     def unknown_visit(self, node):
       
   239         pass
       
   240 
       
   241     def visit_reference(self, node):
       
   242         node.replace_self(mask_email(node))
       
   243 
       
   244     def visit_field_list(self, node):
       
   245         if 'rfc2822' in node['classes']:
       
   246             raise nodes.SkipNode
       
   247 
       
   248     def visit_tgroup(self, node):
       
   249         self.pep_table = node['cols'] == 4
       
   250         self.entry = 0
       
   251 
       
   252     def visit_colspec(self, node):
       
   253         self.entry += 1
       
   254         if self.pep_table and self.entry == 2:
       
   255             node['classes'].append('num')
       
   256 
       
   257     def visit_row(self, node):
       
   258         self.entry = 0
       
   259 
       
   260     def visit_entry(self, node):
       
   261         self.entry += 1
       
   262         if self.pep_table and self.entry == 2 and len(node) == 1:
       
   263             node['classes'].append('num')
       
   264             p = node[0]
       
   265             if isinstance(p, nodes.paragraph) and len(p) == 1:
       
   266                 text = p.astext()
       
   267                 try:
       
   268                     pep = int(text)
       
   269                     ref = (self.document.settings.pep_base_url
       
   270                            + self.pep_url % pep)
       
   271                     p[0] = nodes.reference(text, text, refuri=ref)
       
   272                 except ValueError:
       
   273                     pass
       
   274 
       
   275 
       
   276 non_masked_addresses = ('peps@python.org',
       
   277                         'python-list@python.org',
       
   278                         'python-dev@python.org')
       
   279 
       
   280 def mask_email(ref, pepno=None):
       
   281     """
       
   282     Mask the email address in `ref` and return a replacement node.
       
   283 
       
   284     `ref` is returned unchanged if it contains no email address.
       
   285 
       
   286     For email addresses such as "user@host", mask the address as "user at
       
   287     host" (text) to thwart simple email address harvesters (except for those
       
   288     listed in `non_masked_addresses`).  If a PEP number (`pepno`) is given,
       
   289     return a reference including a default email subject.
       
   290     """
       
   291     if ref.hasattr('refuri') and ref['refuri'].startswith('mailto:'):
       
   292         if ref['refuri'][8:] in non_masked_addresses:
       
   293             replacement = ref[0]
       
   294         else:
       
   295             replacement_text = ref.astext().replace('@', '&#32;&#97;t&#32;')
       
   296             replacement = nodes.raw('', replacement_text, format='html')
       
   297         if pepno is None:
       
   298             return replacement
       
   299         else:
       
   300             ref['refuri'] += '?subject=PEP%%20%s' % pepno
       
   301             ref[:] = [replacement]
       
   302             return ref
       
   303     else:
       
   304         return ref