1 # -*- coding: utf-8 -*-
6 Handlers for additional ReST directives.
8 :copyright: 2007 by Georg Brandl.
9 :license: Python license.
16 from docutils
import nodes
17 from docutils
.parsers
.rst
import directives
, roles
18 from docutils
.parsers
.rst
.directives
import admonitions
20 from . import addnodes
22 # ------ index markup --------------------------------------------------------------
25 'single', 'pair', 'triple', 'quadruple',
26 'module', 'keyword', 'operator', 'object', 'exception', 'statement', 'builtin',
29 def index_directive(name
, arguments
, options
, content
, lineno
,
30 content_offset
, block_text
, state
, state_machine
):
31 arguments
= arguments
[0].split('\n')
32 env
= state
.document
.settings
.env
33 targetid
= 'index-%s' % env
.index_num
35 targetnode
= nodes
.target('', '', ids
=[targetid
])
36 state
.document
.note_explicit_target(targetnode
)
37 indexnode
= addnodes
.index()
38 indexnode
['entries'] = arguments
39 for entry
in arguments
:
41 type, string
= entry
.split(':', 1)
42 env
.note_index_entry(type.strip(), string
.strip(),
43 targetid
, string
.strip())
46 return [indexnode
, targetnode
]
48 index_directive
.arguments
= (1, 0, 1)
49 directives
.register_directive('index', index_directive
)
51 # ------ information units ---------------------------------------------------------
53 def desc_index_text(desctype
, currmodule
, name
):
54 if desctype
== 'function':
56 return '%s() (built-in function)' % name
57 return '%s() (in module %s)' % (name
, currmodule
)
58 elif desctype
== 'data':
60 return '%s (built-in variable)' % name
61 return '%s (in module %s)' % (name
, currmodule
)
62 elif desctype
== 'class':
63 return '%s (class in %s)' % (name
, currmodule
)
64 elif desctype
== 'exception':
66 elif desctype
== 'method':
68 clsname
, methname
= name
.rsplit('.', 1)
71 return '%s() (in module %s)' % (name
, currmodule
)
75 return '%s() (%s.%s method)' % (methname
, currmodule
, clsname
)
77 return '%s() (%s method)' % (methname
, clsname
)
78 elif desctype
== 'attribute':
80 clsname
, attrname
= name
.rsplit('.', 1)
83 return '%s (in module %s)' % (name
, currmodule
)
87 return '%s (%s.%s attribute)' % (attrname
, currmodule
, clsname
)
89 return '%s (%s attribute)' % (attrname
, clsname
)
90 elif desctype
== 'opcode':
91 return '%s (opcode)' % name
92 elif desctype
== 'cfunction':
93 return '%s (C function)' % name
94 elif desctype
== 'cmember':
95 return '%s (C member)' % name
96 elif desctype
== 'cmacro':
97 return '%s (C macro)' % name
98 elif desctype
== 'ctype':
99 return '%s (C type)' % name
100 elif desctype
== 'cvar':
101 return '%s (C variable)' % name
103 raise ValueError("unhandled descenv: %s" % desctype
)
106 # ------ functions to parse a Python or C signature and create desc_* nodes.
108 py_sig_re
= re
.compile(r
'''^([\w.]*\.)? # class names
109 (\w+) \s* # thing name
110 (?: \((.*)\) )? $ # optionally arguments
113 py_paramlist_re
= re
.compile(r
'([\[\],])') # split at '[', ']' and ','
115 def parse_py_signature(signode
, sig
, desctype
, currclass
):
117 Transform a python signature into RST nodes. Returns (signode, fullname).
118 Return the fully qualified name of the thing.
120 If inside a class, the current class name is handled intelligently:
121 * it is stripped from the displayed name if present
122 * it is added to the full name (return value) if not present
124 m
= py_sig_re
.match(sig
)
125 if m
is None: raise ValueError
126 classname
, name
, arglist
= m
.groups()
129 if classname
and classname
.startswith(currclass
):
130 fullname
= classname
+ name
131 classname
= classname
[len(currclass
):].lstrip('.')
133 fullname
= currclass
+ '.' + classname
+ name
135 fullname
= currclass
+ '.' + name
137 fullname
= classname
+ name
if classname
else name
140 signode
+= addnodes
.desc_classname(classname
, classname
)
141 signode
+= addnodes
.desc_name(name
, name
)
143 if desctype
in ('function', 'method'):
144 # for callables, add an empty parameter list
145 signode
+= addnodes
.desc_parameterlist()
147 signode
+= addnodes
.desc_parameterlist()
149 stack
= [signode
[-1]]
150 arglist
= arglist
.replace('`', '').replace(r
'\ ', '') # remove markup
151 for token
in py_paramlist_re
.split(arglist
):
153 opt
= addnodes
.desc_optional()
158 except IndexError: raise ValueError
159 elif not token
or token
== ',' or token
.isspace():
162 token
= token
.strip()
163 stack
[-1] += addnodes
.desc_parameter(token
, token
)
164 if len(stack
) != 1: raise ValueError
168 c_sig_re
= re
.compile(
169 r
'''^([^(]*?) # return type
170 (\w+) \s* # thing name
171 (?: \((.*)\) )? $ # optionally arguments
173 c_funcptr_sig_re
= re
.compile(
174 r
'''^([^(]+?) # return type
175 (\( [^()]+ \)) \s* # name in parentheses
176 \( (.*) \) $ # arguments
179 # RE to split at word boundaries
180 wsplit_re
= re
.compile(r
'(\W+)')
182 # These C types aren't described in the reference, so don't try to create
183 # a cross-reference to them
184 stopwords
= set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct'))
186 def parse_c_type(node
, ctype
):
187 # add cross-ref nodes for all words
188 for part
in filter(None, wsplit_re
.split(ctype
)):
189 tnode
= nodes
.Text(part
, part
)
190 if part
[0] in string
.letters
+'_' and part
not in stopwords
:
191 pnode
= addnodes
.pending_xref(
192 '', reftype
='ctype', reftarget
=part
, modname
=None, classname
=None)
198 def parse_c_signature(signode
, sig
, desctype
):
199 """Transform a C-language signature into RST nodes."""
200 # first try the function pointer signature regex, it's more specific
201 m
= c_funcptr_sig_re
.match(sig
)
203 m
= c_sig_re
.match(sig
)
205 raise ValueError('no match')
206 rettype
, name
, arglist
= m
.groups()
208 parse_c_type(signode
, rettype
)
209 signode
+= addnodes
.desc_name(name
, name
)
211 if desctype
== 'cfunction':
212 # for functions, add an empty parameter list
213 signode
+= addnodes
.desc_parameterlist()
216 paramlist
= addnodes
.desc_parameterlist()
217 arglist
= arglist
.replace('`', '').replace('\\ ', '') # remove markup
218 # this messes up function pointer types, but not too badly ;)
219 args
= arglist
.split(',')
222 param
= addnodes
.desc_parameter('', '', noemph
=True)
224 ctype
, argname
= arg
.rsplit(' ', 1)
226 # no argument name given, only the type
227 parse_c_type(param
, arg
)
229 parse_c_type(param
, ctype
)
230 param
+= nodes
.emphasis(' '+argname
, ' '+argname
)
236 opcode_sig_re
= re
.compile(r
'(\w+(?:\+\d)?)\s*\((.*)\)')
238 def parse_opcode_signature(signode
, sig
, desctype
):
239 """Transform an opcode signature into RST nodes."""
240 m
= opcode_sig_re
.match(sig
)
241 if m
is None: raise ValueError
242 opname
, arglist
= m
.groups()
243 signode
+= addnodes
.desc_name(opname
, opname
)
244 paramlist
= addnodes
.desc_parameterlist()
246 paramlist
+= addnodes
.desc_parameter(arglist
, arglist
)
247 return opname
.strip()
250 def add_refcount_annotation(env
, node
, name
):
251 """Add a reference count annotation. Return None."""
252 entry
= env
.refcounts
.get(name
)
255 elif entry
.result_type
not in ("PyObject*", "PyVarObject*"):
257 rc
= 'Return value: '
258 if entry
.result_refs
is None:
261 rc
+= ("New" if entry
.result_refs
else "Borrowed") + " reference."
262 node
+= addnodes
.refcount(rc
, rc
)
265 def desc_directive(desctype
, arguments
, options
, content
, lineno
,
266 content_offset
, block_text
, state
, state_machine
):
267 env
= state
.document
.settings
.env
268 node
= addnodes
.desc()
269 node
['desctype'] = desctype
271 noindex
= ('noindex' in options
)
272 signatures
= map(lambda s
: s
.strip(), arguments
[0].split('\n'))
274 for i
, sig
in enumerate(signatures
):
275 # add a signature node for each signature in the current unit
276 # and add a reference target for it
278 signode
= addnodes
.desc_signature(sig
, '')
279 signode
['first'] = False
282 if desctype
in ('function', 'data', 'class', 'exception',
283 'method', 'attribute'):
284 name
= parse_py_signature(signode
, sig
, desctype
, env
.currclass
)
285 elif desctype
in ('cfunction', 'cmember', 'cmacro', 'ctype', 'cvar'):
286 name
= parse_c_signature(signode
, sig
, desctype
)
287 elif desctype
== 'opcode':
288 name
= parse_opcode_signature(signode
, sig
, desctype
)
290 # describe: use generic fallback
292 except ValueError, err
:
294 signode
+= addnodes
.desc_name(sig
, sig
)
295 continue # we don't want an index entry here
296 # only add target and index entry if this is the first description of the
297 # function name in this desc block
298 if not noindex
and name
not in names
:
299 fullname
= (env
.currmodule
+ '.' if env
.currmodule
else '') + name
301 if fullname
not in state
.document
.ids
:
302 signode
['names'].append(fullname
)
303 signode
['ids'].append(fullname
)
304 signode
['first'] = (not names
)
305 state
.document
.note_explicit_target(signode
)
306 env
.note_descref(fullname
, desctype
)
309 env
.note_index_entry('single',
310 desc_index_text(desctype
, env
.currmodule
, name
),
313 subnode
= addnodes
.desc_content()
314 if desctype
== 'cfunction':
315 add_refcount_annotation(env
, subnode
, name
)
316 # needed for automatic qualification of members
317 if desctype
== 'class' and names
:
318 env
.currclass
= names
[0]
319 # needed for association of version{added,changed} directives
321 env
.currdesc
= names
[0]
322 state
.nested_parse(content
, content_offset
, subnode
)
323 if desctype
== 'class':
329 desc_directive
.content
= 1
330 desc_directive
.arguments
= (1, 0, 1)
331 desc_directive
.options
= {'noindex': directives
.flag
}
353 for name
in desctypes
:
354 directives
.register_directive(name
, desc_directive
)
357 # ------ versionadded/versionchanged -----------------------------------------------
359 def version_directive(name
, arguments
, options
, content
, lineno
,
360 content_offset
, block_text
, state
, state_machine
):
361 node
= addnodes
.versionmodified()
363 node
['version'] = arguments
[0]
364 if len(arguments
) == 2:
365 inodes
, messages
= state
.inline_text(arguments
[1], lineno
+1)
368 state
.nested_parse(content
, content_offset
, node
)
369 ret
= [node
] + messages
372 env
= state
.document
.settings
.env
373 env
.note_versionchange(node
['type'], node
['version'], node
)
376 version_directive
.arguments
= (1, 1, 1)
377 version_directive
.content
= 1
379 directives
.register_directive('deprecated', version_directive
)
380 directives
.register_directive('versionadded', version_directive
)
381 directives
.register_directive('versionchanged', version_directive
)
384 # ------ see also ------------------------------------------------------------------
386 def seealso_directive(name
, arguments
, options
, content
, lineno
,
387 content_offset
, block_text
, state
, state_machine
):
388 rv
= admonitions
.make_admonition(
389 addnodes
.seealso
, name
, ['See also:'], options
, content
,
390 lineno
, content_offset
, block_text
, state
, state_machine
)
393 seealso_directive
.content
= 1
394 seealso_directive
.arguments
= (0, 0, 0)
395 directives
.register_directive('seealso', seealso_directive
)
398 # ------ production list (for the reference) ---------------------------------------
400 def productionlist_directive(name
, arguments
, options
, content
, lineno
,
401 content_offset
, block_text
, state
, state_machine
):
402 env
= state
.document
.settings
.env
403 node
= addnodes
.productionlist()
407 # use token as the default role while in production list
408 roles
._roles
[''] = roles
._role
_registry
['token']
409 for rule
in arguments
[0].split('\n'):
410 if i
== 0 and ':' not in rule
:
415 name
, tokens
= rule
.split(':', 1)
418 subnode
= addnodes
.production()
419 subnode
['tokenname'] = name
.strip()
420 if subnode
['tokenname']:
421 idname
= 'grammar-token-%s' % subnode
['tokenname']
422 if idname
not in state
.document
.ids
:
423 subnode
['ids'].append(idname
)
424 state
.document
.note_implicit_target(subnode
, subnode
)
425 env
.note_token(subnode
['tokenname'])
426 inodes
, imessages
= state
.inline_text(tokens
, lineno
+i
)
427 subnode
.extend(inodes
)
428 messages
.extend(imessages
)
431 return [node
] + messages
433 productionlist_directive
.content
= 0
434 productionlist_directive
.arguments
= (1, 0, 1)
435 directives
.register_directive('productionlist', productionlist_directive
)
437 # ------ section metadata ----------------------------------------------------------
439 def module_directive(name
, arguments
, options
, content
, lineno
,
440 content_offset
, block_text
, state
, state_machine
):
441 env
= state
.document
.settings
.env
442 modname
= arguments
[0].strip()
443 env
.currmodule
= modname
444 env
.note_module(modname
, options
.get('synopsis', ''), options
.get('platform', ''))
446 targetnode
= nodes
.target('', '', ids
=['module-' + modname
])
447 state
.document
.note_explicit_target(targetnode
)
448 ret
.append(targetnode
)
449 if 'platform' in options
:
450 node
= nodes
.paragraph()
451 node
+= nodes
.emphasis('Platforms: ', 'Platforms: ')
452 node
+= nodes
.Text(options
['platform'], options
['platform'])
454 # the synopsis isn't printed; in fact, it is only used in the modindex currently
455 env
.note_index_entry('single', '%s (module)' % modname
, 'module-' + modname
,
459 module_directive
.arguments
= (1, 0, 0)
460 module_directive
.options
= {'platform': lambda x
: x
,
461 'synopsis': lambda x
: x
}
462 directives
.register_directive('module', module_directive
)
465 def author_directive(name
, arguments
, options
, content
, lineno
,
466 content_offset
, block_text
, state
, state_machine
):
467 # The author directives aren't included in the built document
470 author_directive
.arguments
= (1, 0, 1)
471 directives
.register_directive('sectionauthor', author_directive
)
472 directives
.register_directive('moduleauthor', author_directive
)
475 # ------ toctree directive ---------------------------------------------------------
477 def toctree_directive(name
, arguments
, options
, content
, lineno
,
478 content_offset
, block_text
, state
, state_machine
):
479 env
= state
.document
.settings
.env
480 dirname
= path
.dirname(env
.filename
)
482 subnode
= addnodes
.toctree()
483 includefiles
= filter(None, content
)
484 # absolutize filenames
485 includefiles
= map(lambda x
: path
.normpath(path
.join(dirname
, x
)), includefiles
)
486 subnode
['includefiles'] = includefiles
487 subnode
['maxdepth'] = options
.get('maxdepth', -1)
490 toctree_directive
.content
= 1
491 toctree_directive
.options
= {'maxdepth': int}
492 directives
.register_directive('toctree', toctree_directive
)
495 # ------ centered directive ---------------------------------------------------------
497 def centered_directive(name
, arguments
, options
, content
, lineno
,
498 content_offset
, block_text
, state
, state_machine
):
501 subnode
= addnodes
.centered()
502 inodes
, messages
= state
.inline_text(arguments
[0], lineno
)
503 subnode
.extend(inodes
)
504 return [subnode
] + messages
506 centered_directive
.arguments
= (1, 0, 1)
507 directives
.register_directive('centered', centered_directive
)
510 # ------ highlightlanguage directive ------------------------------------------------
512 def highlightlang_directive(name
, arguments
, options
, content
, lineno
,
513 content_offset
, block_text
, state
, state_machine
):
514 return [addnodes
.highlightlang(lang
=arguments
[0].strip())]
516 highlightlang_directive
.content
= 0
517 highlightlang_directive
.arguments
= (1, 0, 0)
518 directives
.register_directive('highlightlang',
519 highlightlang_directive
)