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