~ubuntu-branches/ubuntu/trusty/python3.4/trusty-proposed

« back to all changes in this revision

Viewing changes to Doc/tools/sphinxext/pyspecific.py

  • Committer: Package Import Robot
  • Author(s): Matthias Klose
  • Date: 2013-11-25 09:44:27 UTC
  • Revision ID: package-import@ubuntu.com-20131125094427-lzxj8ap5w01lmo7f
Tags: upstream-3.4~b1
ImportĀ upstreamĀ versionĀ 3.4~b1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
"""
 
3
    pyspecific.py
 
4
    ~~~~~~~~~~~~~
 
5
 
 
6
    Sphinx extension with Python doc-specific markup.
 
7
 
 
8
    :copyright: 2008-2013 by Georg Brandl.
 
9
    :license: Python license.
 
10
"""
 
11
 
 
12
ISSUE_URI = 'http://bugs.python.org/issue%s'
 
13
SOURCE_URI = 'http://hg.python.org/cpython/file/default/%s'
 
14
 
 
15
from docutils import nodes, utils
 
16
 
 
17
import sphinx
 
18
from sphinx.util.nodes import split_explicit_title
 
19
from sphinx.writers.html import HTMLTranslator
 
20
from sphinx.writers.latex import LaTeXTranslator
 
21
from sphinx.locale import versionlabels
 
22
 
 
23
# monkey-patch reST parser to disable alphabetic and roman enumerated lists
 
24
from docutils.parsers.rst.states import Body
 
25
Body.enum.converters['loweralpha'] = \
 
26
    Body.enum.converters['upperalpha'] = \
 
27
    Body.enum.converters['lowerroman'] = \
 
28
    Body.enum.converters['upperroman'] = lambda x: None
 
29
 
 
30
if sphinx.__version__[:3] < '1.2':
 
31
    # monkey-patch HTML translator to give versionmodified paragraphs a class
 
32
    def new_visit_versionmodified(self, node):
 
33
        self.body.append(self.starttag(node, 'p', CLASS=node['type']))
 
34
        text = versionlabels[node['type']] % node['version']
 
35
        if len(node):
 
36
            text += ':'
 
37
        else:
 
38
            text += '.'
 
39
        self.body.append('<span class="versionmodified">%s</span> ' % text)
 
40
    HTMLTranslator.visit_versionmodified = new_visit_versionmodified
 
41
 
 
42
# monkey-patch HTML and LaTeX translators to keep doctest blocks in the
 
43
# doctest docs themselves
 
44
orig_visit_literal_block = HTMLTranslator.visit_literal_block
 
45
def new_visit_literal_block(self, node):
 
46
    meta = self.builder.env.metadata[self.builder.current_docname]
 
47
    old_trim_doctest_flags = self.highlighter.trim_doctest_flags
 
48
    if 'keepdoctest' in meta:
 
49
        self.highlighter.trim_doctest_flags = False
 
50
    try:
 
51
        orig_visit_literal_block(self, node)
 
52
    finally:
 
53
        self.highlighter.trim_doctest_flags = old_trim_doctest_flags
 
54
 
 
55
HTMLTranslator.visit_literal_block = new_visit_literal_block
 
56
 
 
57
orig_depart_literal_block = LaTeXTranslator.depart_literal_block
 
58
def new_depart_literal_block(self, node):
 
59
    meta = self.builder.env.metadata[self.curfilestack[-1]]
 
60
    old_trim_doctest_flags = self.highlighter.trim_doctest_flags
 
61
    if 'keepdoctest' in meta:
 
62
        self.highlighter.trim_doctest_flags = False
 
63
    try:
 
64
        orig_depart_literal_block(self, node)
 
65
    finally:
 
66
        self.highlighter.trim_doctest_flags = old_trim_doctest_flags
 
67
 
 
68
LaTeXTranslator.depart_literal_block = new_depart_literal_block
 
69
 
 
70
# Support for marking up and linking to bugs.python.org issues
 
71
 
 
72
def issue_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
 
73
    issue = utils.unescape(text)
 
74
    text = 'issue ' + issue
 
75
    refnode = nodes.reference(text, text, refuri=ISSUE_URI % issue)
 
76
    return [refnode], []
 
77
 
 
78
 
 
79
# Support for linking to Python source files easily
 
80
 
 
81
def source_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
 
82
    has_t, title, target = split_explicit_title(text)
 
83
    title = utils.unescape(title)
 
84
    target = utils.unescape(target)
 
85
    refnode = nodes.reference(title, title, refuri=SOURCE_URI % target)
 
86
    return [refnode], []
 
87
 
 
88
 
 
89
# Support for marking up implementation details
 
90
 
 
91
from sphinx.util.compat import Directive
 
92
 
 
93
class ImplementationDetail(Directive):
 
94
 
 
95
    has_content = True
 
96
    required_arguments = 0
 
97
    optional_arguments = 1
 
98
    final_argument_whitespace = True
 
99
 
 
100
    def run(self):
 
101
        pnode = nodes.compound(classes=['impl-detail'])
 
102
        content = self.content
 
103
        add_text = nodes.strong('CPython implementation detail:',
 
104
                                'CPython implementation detail:')
 
105
        if self.arguments:
 
106
            n, m = self.state.inline_text(self.arguments[0], self.lineno)
 
107
            pnode.append(nodes.paragraph('', '', *(n + m)))
 
108
        self.state.nested_parse(content, self.content_offset, pnode)
 
109
        if pnode.children and isinstance(pnode[0], nodes.paragraph):
 
110
            pnode[0].insert(0, add_text)
 
111
            pnode[0].insert(1, nodes.Text(' '))
 
112
        else:
 
113
            pnode.insert(0, nodes.paragraph('', '', add_text))
 
114
        return [pnode]
 
115
 
 
116
 
 
117
# Support for documenting decorators
 
118
 
 
119
from sphinx import addnodes
 
120
from sphinx.domains.python import PyModulelevel, PyClassmember
 
121
 
 
122
class PyDecoratorMixin(object):
 
123
    def handle_signature(self, sig, signode):
 
124
        ret = super(PyDecoratorMixin, self).handle_signature(sig, signode)
 
125
        signode.insert(0, addnodes.desc_addname('@', '@'))
 
126
        return ret
 
127
 
 
128
    def needs_arglist(self):
 
129
        return False
 
130
 
 
131
class PyDecoratorFunction(PyDecoratorMixin, PyModulelevel):
 
132
    def run(self):
 
133
        # a decorator function is a function after all
 
134
        self.name = 'py:function'
 
135
        return PyModulelevel.run(self)
 
136
 
 
137
class PyDecoratorMethod(PyDecoratorMixin, PyClassmember):
 
138
    def run(self):
 
139
        self.name = 'py:method'
 
140
        return PyClassmember.run(self)
 
141
 
 
142
 
 
143
# Support for documenting version of removal in deprecations
 
144
 
 
145
from sphinx.locale import versionlabels
 
146
from sphinx.util.compat import Directive
 
147
 
 
148
versionlabels['deprecated-removed'] = \
 
149
    'Deprecated since version %s, will be removed in version %s'
 
150
 
 
151
class DeprecatedRemoved(Directive):
 
152
    has_content = True
 
153
    required_arguments = 2
 
154
    optional_arguments = 1
 
155
    final_argument_whitespace = True
 
156
    option_spec = {}
 
157
 
 
158
    def run(self):
 
159
        node = addnodes.versionmodified()
 
160
        node.document = self.state.document
 
161
        node['type'] = 'deprecated-removed'
 
162
        version = (self.arguments[0], self.arguments[1])
 
163
        node['version'] = version
 
164
        if len(self.arguments) == 3:
 
165
            inodes, messages = self.state.inline_text(self.arguments[2],
 
166
                                                      self.lineno+1)
 
167
            node.extend(inodes)
 
168
            if self.content:
 
169
                self.state.nested_parse(self.content, self.content_offset, node)
 
170
            ret = [node] + messages
 
171
        else:
 
172
            ret = [node]
 
173
        env = self.state.document.settings.env
 
174
        env.note_versionchange('deprecated', version[0], node, self.lineno)
 
175
        return ret
 
176
 
 
177
 
 
178
# Support for including Misc/NEWS
 
179
 
 
180
import re
 
181
import codecs
 
182
 
 
183
issue_re = re.compile('([Ii])ssue #([0-9]+)')
 
184
whatsnew_re = re.compile(r"(?im)^what's new in (.*?)\??$")
 
185
 
 
186
class MiscNews(Directive):
 
187
    has_content = False
 
188
    required_arguments = 1
 
189
    optional_arguments = 0
 
190
    final_argument_whitespace = False
 
191
    option_spec = {}
 
192
 
 
193
    def run(self):
 
194
        fname = self.arguments[0]
 
195
        source = self.state_machine.input_lines.source(
 
196
            self.lineno - self.state_machine.input_offset - 1)
 
197
        source_dir = path.dirname(path.abspath(source))
 
198
        fpath = path.join(source_dir, fname)
 
199
        self.state.document.settings.record_dependencies.add(fpath)
 
200
        try:
 
201
            fp = codecs.open(fpath, encoding='utf-8')
 
202
            try:
 
203
                content = fp.read()
 
204
            finally:
 
205
                fp.close()
 
206
        except Exception:
 
207
            text = 'The NEWS file is not available.'
 
208
            node = nodes.strong(text, text)
 
209
            return [node]
 
210
        content = issue_re.sub(r'`\1ssue #\2 <http://bugs.python.org/\2>`__',
 
211
                               content)
 
212
        content = whatsnew_re.sub(r'\1', content)
 
213
        # remove first 3 lines as they are the main heading
 
214
        lines = ['.. default-role:: obj', ''] + content.splitlines()[3:]
 
215
        self.state_machine.insert_input(lines, fname)
 
216
        return []
 
217
 
 
218
 
 
219
# Support for building "topic help" for pydoc
 
220
 
 
221
pydoc_topic_labels = [
 
222
    'assert', 'assignment', 'atom-identifiers', 'atom-literals',
 
223
    'attribute-access', 'attribute-references', 'augassign', 'binary',
 
224
    'bitwise', 'bltin-code-objects', 'bltin-ellipsis-object',
 
225
    'bltin-null-object', 'bltin-type-objects', 'booleans',
 
226
    'break', 'callable-types', 'calls', 'class', 'comparisons', 'compound',
 
227
    'context-managers', 'continue', 'conversions', 'customization', 'debugger',
 
228
    'del', 'dict', 'dynamic-features', 'else', 'exceptions', 'execmodel',
 
229
    'exprlists', 'floating', 'for', 'formatstrings', 'function', 'global',
 
230
    'id-classes', 'identifiers', 'if', 'imaginary', 'import', 'in', 'integers',
 
231
    'lambda', 'lists', 'naming', 'nonlocal', 'numbers', 'numeric-types',
 
232
    'objects', 'operator-summary', 'pass', 'power', 'raise', 'return',
 
233
    'sequence-types', 'shifting', 'slicings', 'specialattrs', 'specialnames',
 
234
    'string-methods', 'strings', 'subscriptions', 'truth', 'try', 'types',
 
235
    'typesfunctions', 'typesmapping', 'typesmethods', 'typesmodules',
 
236
    'typesseq', 'typesseq-mutable', 'unary', 'while', 'with', 'yield'
 
237
]
 
238
 
 
239
from os import path
 
240
from time import asctime
 
241
from pprint import pformat
 
242
from docutils.io import StringOutput
 
243
from docutils.utils import new_document
 
244
 
 
245
from sphinx.builders import Builder
 
246
from sphinx.writers.text import TextWriter
 
247
 
 
248
 
 
249
class PydocTopicsBuilder(Builder):
 
250
    name = 'pydoc-topics'
 
251
 
 
252
    def init(self):
 
253
        self.topics = {}
 
254
 
 
255
    def get_outdated_docs(self):
 
256
        return 'all pydoc topics'
 
257
 
 
258
    def get_target_uri(self, docname, typ=None):
 
259
        return ''  # no URIs
 
260
 
 
261
    def write(self, *ignored):
 
262
        writer = TextWriter(self)
 
263
        for label in self.status_iterator(pydoc_topic_labels,
 
264
                                          'building topics... ',
 
265
                                          length=len(pydoc_topic_labels)):
 
266
            if label not in self.env.domaindata['std']['labels']:
 
267
                self.warn('label %r not in documentation' % label)
 
268
                continue
 
269
            docname, labelid, sectname = self.env.domaindata['std']['labels'][label]
 
270
            doctree = self.env.get_and_resolve_doctree(docname, self)
 
271
            document = new_document('<section node>')
 
272
            document.append(doctree.ids[labelid])
 
273
            destination = StringOutput(encoding='utf-8')
 
274
            writer.write(document, destination)
 
275
            self.topics[label] = writer.output.encode('utf-8')
 
276
 
 
277
    def finish(self):
 
278
        f = open(path.join(self.outdir, 'topics.py'), 'w')
 
279
        try:
 
280
            f.write('# -*- coding: utf-8 -*-\n')
 
281
            f.write('# Autogenerated by Sphinx on %s\n' % asctime())
 
282
            f.write('topics = ' + pformat(self.topics) + '\n')
 
283
        finally:
 
284
            f.close()
 
285
 
 
286
 
 
287
# Support for checking for suspicious markup
 
288
 
 
289
import suspicious
 
290
 
 
291
 
 
292
# Support for documenting Opcodes
 
293
 
 
294
import re
 
295
 
 
296
opcode_sig_re = re.compile(r'(\w+(?:\+\d)?)(?:\s*\((.*)\))?')
 
297
 
 
298
def parse_opcode_signature(env, sig, signode):
 
299
    """Transform an opcode signature into RST nodes."""
 
300
    m = opcode_sig_re.match(sig)
 
301
    if m is None:
 
302
        raise ValueError
 
303
    opname, arglist = m.groups()
 
304
    signode += addnodes.desc_name(opname, opname)
 
305
    if arglist is not None:
 
306
        paramlist = addnodes.desc_parameterlist()
 
307
        signode += paramlist
 
308
        paramlist += addnodes.desc_parameter(arglist, arglist)
 
309
    return opname.strip()
 
310
 
 
311
 
 
312
# Support for documenting pdb commands
 
313
 
 
314
pdbcmd_sig_re = re.compile(r'([a-z()!]+)\s*(.*)')
 
315
 
 
316
# later...
 
317
#pdbargs_tokens_re = re.compile(r'''[a-zA-Z]+  |  # identifiers
 
318
#                                   [.,:]+     |  # punctuation
 
319
#                                   [\[\]()]   |  # parens
 
320
#                                   \s+           # whitespace
 
321
#                                   ''', re.X)
 
322
 
 
323
def parse_pdb_command(env, sig, signode):
 
324
    """Transform a pdb command signature into RST nodes."""
 
325
    m = pdbcmd_sig_re.match(sig)
 
326
    if m is None:
 
327
        raise ValueError
 
328
    name, args = m.groups()
 
329
    fullname = name.replace('(', '').replace(')', '')
 
330
    signode += addnodes.desc_name(name, name)
 
331
    if args:
 
332
        signode += addnodes.desc_addname(' '+args, ' '+args)
 
333
    return fullname
 
334
 
 
335
 
 
336
def setup(app):
 
337
    app.add_role('issue', issue_role)
 
338
    app.add_role('source', source_role)
 
339
    app.add_directive('impl-detail', ImplementationDetail)
 
340
    app.add_directive('deprecated-removed', DeprecatedRemoved)
 
341
    app.add_builder(PydocTopicsBuilder)
 
342
    app.add_builder(suspicious.CheckSuspiciousMarkupBuilder)
 
343
    app.add_description_unit('opcode', 'opcode', '%s (opcode)',
 
344
                             parse_opcode_signature)
 
345
    app.add_description_unit('pdbcommand', 'pdbcmd', '%s (pdb command)',
 
346
                             parse_pdb_command)
 
347
    app.add_description_unit('2to3fixer', '2to3fixer', '%s (2to3 fixer)')
 
348
    app.add_directive_to_domain('py', 'decorator', PyDecoratorFunction)
 
349
    app.add_directive_to_domain('py', 'decoratormethod', PyDecoratorMethod)
 
350
    app.add_directive('miscnews', MiscNews)