root/galaxy-central/eggs/docutils-0.4-py2.6.egg/docutils/transforms/peps.py @ 3

リビジョン 3, 10.8 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

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