1# -*- coding: utf-8 -*- 2""" 3 pyspecific.py 4 ~~~~~~~~~~~~~ 5 6 Sphinx extension with Python doc-specific markup. 7 8 :copyright: 2008-2014 by Georg Brandl. 9 :license: Python license. 10""" 11 12ISSUE_URI = 'https://bugs.python.org/issue%s' 13SOURCE_URI = 'https://github.com/python/cpython/tree/2.7/%s' 14 15from docutils import nodes, utils 16from docutils.parsers.rst import Directive 17 18from sphinx.util import status_iterator 19from sphinx.util.nodes import split_explicit_title 20from sphinx.writers.html import HTMLTranslator 21from sphinx.writers.latex import LaTeXTranslator 22from sphinx.writers.text import TextTranslator 23 24# monkey-patch reST parser to disable alphabetic and roman enumerated lists 25from docutils.parsers.rst.states import Body 26Body.enum.converters['loweralpha'] = \ 27 Body.enum.converters['upperalpha'] = \ 28 Body.enum.converters['lowerroman'] = \ 29 Body.enum.converters['upperroman'] = lambda x: None 30 31# monkey-patch HTML and LaTeX translators to keep doctest blocks in the 32# doctest docs themselves 33orig_visit_literal_block = HTMLTranslator.visit_literal_block 34def new_visit_literal_block(self, node): 35 meta = self.builder.env.metadata[self.builder.current_docname] 36 old_trim_doctest_flags = self.highlighter.trim_doctest_flags 37 if 'keepdoctest' in meta: 38 self.highlighter.trim_doctest_flags = False 39 try: 40 orig_visit_literal_block(self, node) 41 finally: 42 self.highlighter.trim_doctest_flags = old_trim_doctest_flags 43 44HTMLTranslator.visit_literal_block = new_visit_literal_block 45 46orig_depart_literal_block = LaTeXTranslator.depart_literal_block 47def new_depart_literal_block(self, node): 48 meta = self.builder.env.metadata[self.curfilestack[-1]] 49 old_trim_doctest_flags = self.highlighter.trim_doctest_flags 50 if 'keepdoctest' in meta: 51 self.highlighter.trim_doctest_flags = False 52 try: 53 orig_depart_literal_block(self, node) 54 finally: 55 self.highlighter.trim_doctest_flags = old_trim_doctest_flags 56 57LaTeXTranslator.depart_literal_block = new_depart_literal_block 58 59# Support for marking up and linking to bugs.python.org issues 60 61def issue_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 62 issue = utils.unescape(text) 63 text = 'bpo-'+ issue 64 refnode = nodes.reference(text, text, refuri=ISSUE_URI % issue) 65 return [refnode], [] 66 67 68# Support for linking to Python source files easily 69 70def source_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): 71 has_t, title, target = split_explicit_title(text) 72 title = utils.unescape(title) 73 target = utils.unescape(target) 74 refnode = nodes.reference(title, title, refuri=SOURCE_URI % target) 75 return [refnode], [] 76 77 78# Support for marking up implementation details 79 80class ImplementationDetail(Directive): 81 82 has_content = True 83 required_arguments = 0 84 optional_arguments = 1 85 final_argument_whitespace = True 86 87 def run(self): 88 pnode = nodes.compound(classes=['impl-detail']) 89 content = self.content 90 add_text = nodes.strong('CPython implementation detail:', 91 'CPython implementation detail:') 92 if self.arguments: 93 n, m = self.state.inline_text(self.arguments[0], self.lineno) 94 pnode.append(nodes.paragraph('', '', *(n + m))) 95 self.state.nested_parse(content, self.content_offset, pnode) 96 if pnode.children and isinstance(pnode[0], nodes.paragraph): 97 pnode[0].insert(0, add_text) 98 pnode[0].insert(1, nodes.Text(' ')) 99 else: 100 pnode.insert(0, nodes.paragraph('', '', add_text)) 101 return [pnode] 102 103 104# Support for documenting decorators 105 106from sphinx import addnodes 107from sphinx.domains.python import PyModulelevel, PyClassmember 108 109class PyDecoratorMixin(object): 110 def handle_signature(self, sig, signode): 111 ret = super(PyDecoratorMixin, self).handle_signature(sig, signode) 112 signode.insert(0, addnodes.desc_addname('@', '@')) 113 return ret 114 115 def needs_arglist(self): 116 return False 117 118class PyDecoratorFunction(PyDecoratorMixin, PyModulelevel): 119 def run(self): 120 # a decorator function is a function after all 121 self.name = 'py:function' 122 return PyModulelevel.run(self) 123 124class PyDecoratorMethod(PyDecoratorMixin, PyClassmember): 125 def run(self): 126 self.name = 'py:method' 127 return PyClassmember.run(self) 128 129 130# Support for building "topic help" for pydoc 131 132pydoc_topic_labels = [ 133 'assert', 'assignment', 'atom-identifiers', 'atom-literals', 134 'attribute-access', 'attribute-references', 'augassign', 'binary', 135 'bitwise', 'bltin-code-objects', 'bltin-ellipsis-object', 136 'bltin-file-objects', 'bltin-null-object', 'bltin-type-objects', 'booleans', 137 'break', 'callable-types', 'calls', 'class', 'comparisons', 'compound', 138 'context-managers', 'continue', 'conversions', 'customization', 'debugger', 139 'del', 'dict', 'dynamic-features', 'else', 'exceptions', 'exec', 'execmodel', 140 'exprlists', 'floating', 'for', 'formatstrings', 'function', 'global', 141 'id-classes', 'identifiers', 'if', 'imaginary', 'import', 'in', 'integers', 142 'lambda', 'lists', 'naming', 'numbers', 'numeric-types', 143 'objects', 'operator-summary', 'pass', 'power', 'print', 'raise', 'return', 144 'sequence-types', 'shifting', 'slicings', 'specialattrs', 'specialnames', 145 'string-methods', 'strings', 'subscriptions', 'truth', 'try', 'types', 146 'typesfunctions', 'typesmapping', 'typesmethods', 'typesmodules', 147 'typesseq', 'typesseq-mutable', 'unary', 'while', 'with', 'yield' 148] 149 150from os import path 151from time import asctime 152from pprint import pformat 153from docutils.io import StringOutput 154from docutils.utils import new_document 155 156from sphinx.builders import Builder 157from sphinx.writers.text import TextWriter 158 159 160class PydocTopicsBuilder(Builder): 161 name = 'pydoc-topics' 162 163 default_translator_class = TextTranslator 164 165 def init(self): 166 self.topics = {} 167 self.secnumbers = {} 168 169 def get_outdated_docs(self): 170 return 'all pydoc topics' 171 172 def get_target_uri(self, docname, typ=None): 173 return '' # no URIs 174 175 def write(self, *ignored): 176 writer = TextWriter(self) 177 for label in status_iterator(pydoc_topic_labels, 178 'building topics... ', 179 length=len(pydoc_topic_labels)): 180 if label not in self.env.domaindata['std']['labels']: 181 self.warn('label %r not in documentation' % label) 182 continue 183 docname, labelid, sectname = self.env.domaindata['std']['labels'][label] 184 doctree = self.env.get_and_resolve_doctree(docname, self) 185 document = new_document('<section node>') 186 document.append(doctree.ids[labelid]) 187 destination = StringOutput(encoding='utf-8') 188 writer.write(document, destination) 189 self.topics[label] = writer.output 190 191 def finish(self): 192 f = open(path.join(self.outdir, 'topics.py'), 'wb') 193 try: 194 f.write('# -*- coding: utf-8 -*-\n'.encode('utf-8')) 195 f.write(('# Autogenerated by Sphinx on %s\n' % asctime()).encode('utf-8')) 196 f.write(('topics = ' + pformat(self.topics) + '\n').encode('utf-8')) 197 finally: 198 f.close() 199 200 201# Support for checking for suspicious markup 202 203import suspicious 204 205 206# Support for documenting Opcodes 207 208import re 209 210opcode_sig_re = re.compile(r'(\w+(?:\+\d)?)(?:\s*\((.*)\))?') 211 212def parse_opcode_signature(env, sig, signode): 213 """Transform an opcode signature into RST nodes.""" 214 m = opcode_sig_re.match(sig) 215 if m is None: 216 raise ValueError 217 opname, arglist = m.groups() 218 signode += addnodes.desc_name(opname, opname) 219 if arglist is not None: 220 paramlist = addnodes.desc_parameterlist() 221 signode += paramlist 222 paramlist += addnodes.desc_parameter(arglist, arglist) 223 return opname.strip() 224 225 226# Support for documenting pdb commands 227 228pdbcmd_sig_re = re.compile(r'([a-z()!]+)\s*(.*)') 229 230# later... 231#pdbargs_tokens_re = re.compile(r'''[a-zA-Z]+ | # identifiers 232# [.,:]+ | # punctuation 233# [\[\]()] | # parens 234# \s+ # whitespace 235# ''', re.X) 236 237def parse_pdb_command(env, sig, signode): 238 """Transform a pdb command signature into RST nodes.""" 239 m = pdbcmd_sig_re.match(sig) 240 if m is None: 241 raise ValueError 242 name, args = m.groups() 243 fullname = name.replace('(', '').replace(')', '') 244 signode += addnodes.desc_name(name, name) 245 if args: 246 signode += addnodes.desc_addname(' '+args, ' '+args) 247 return fullname 248 249 250def setup(app): 251 app.add_role('issue', issue_role) 252 app.add_role('source', source_role) 253 app.add_directive('impl-detail', ImplementationDetail) 254 app.add_builder(PydocTopicsBuilder) 255 app.add_builder(suspicious.CheckSuspiciousMarkupBuilder) 256 app.add_description_unit('opcode', 'opcode', '%s (opcode)', 257 parse_opcode_signature) 258 app.add_description_unit('pdbcommand', 'pdbcmd', '%s (pdb command)', 259 parse_pdb_command) 260 app.add_description_unit('2to3fixer', '2to3fixer', '%s (2to3 fixer)') 261 app.add_directive_to_domain('py', 'decorator', PyDecoratorFunction) 262 app.add_directive_to_domain('py', 'decoratormethod', PyDecoratorMethod) 263 return {'version': '1.0', 'parallel_read_safe': True} 264