Convert builtin directives to classes. Refactor desc_directive so that it is easier to subclass.

This commit is contained in:
Georg Brandl 2009-02-18 19:55:57 +01:00
parent 8bd4e8e398
commit 044d42953d
4 changed files with 1208 additions and 1019 deletions

View File

@ -57,8 +57,7 @@ import sphinx
from sphinx.roles import xfileref_role, innernodetypes from sphinx.roles import xfileref_role, innernodetypes
from sphinx.config import Config from sphinx.config import Config
from sphinx.builders import BUILTIN_BUILDERS from sphinx.builders import BUILTIN_BUILDERS
from sphinx.directives import desc_directive, target_directive, \ from sphinx.directives import GenericDesc, Target, additional_xref_types
additional_xref_types
from sphinx.environment import SphinxStandaloneReader from sphinx.environment import SphinxStandaloneReader
from sphinx.util.compat import Directive, directive_dwim from sphinx.util.compat import Directive, directive_dwim
from sphinx.util.console import bold from sphinx.util.console import bold
@ -314,7 +313,7 @@ class Sphinx(object):
parse_node=None, ref_nodeclass=None): parse_node=None, ref_nodeclass=None):
additional_xref_types[directivename] = (rolename, indextemplate, additional_xref_types[directivename] = (rolename, indextemplate,
parse_node) parse_node)
directives.register_directive(directivename, desc_directive) directives.register_directive(directivename, GenericDesc)
roles.register_canonical_role(rolename, xfileref_role) roles.register_canonical_role(rolename, xfileref_role)
if ref_nodeclass is not None: if ref_nodeclass is not None:
innernodetypes[rolename] = ref_nodeclass innernodetypes[rolename] = ref_nodeclass
@ -322,7 +321,7 @@ class Sphinx(object):
def add_crossref_type(self, directivename, rolename, indextemplate='', def add_crossref_type(self, directivename, rolename, indextemplate='',
ref_nodeclass=None): ref_nodeclass=None):
additional_xref_types[directivename] = (rolename, indextemplate, None) additional_xref_types[directivename] = (rolename, indextemplate, None)
directives.register_directive(directivename, target_directive) directives.register_directive(directivename, Target)
roles.register_canonical_role(rolename, xfileref_role) roles.register_canonical_role(rolename, xfileref_role)
if ref_nodeclass is not None: if ref_nodeclass is not None:
innernodetypes[rolename] = ref_nodeclass innernodetypes[rolename] = ref_nodeclass

View File

@ -16,103 +16,133 @@ from docutils.parsers.rst import directives
from sphinx import addnodes from sphinx import addnodes
from sphinx.util import parselinenos from sphinx.util import parselinenos
from sphinx.util.compat import Directive
# ------ highlight directive --------------------------------------------------- class Highlight(Directive):
"""
Directive to set the highlighting language for code blocks, as well
as the threshold for line numbers.
"""
def highlightlang_directive(name, arguments, options, content, lineno, has_content = False
content_offset, block_text, state, state_machine): required_arguments = 1
if 'linenothreshold' in options: optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'linenothreshold': directives.unchanged,
}
def run(self):
if 'linenothreshold' in self.options:
try: try:
linenothreshold = int(options['linenothreshold']) linenothreshold = int(self.options['linenothreshold'])
except Exception: except Exception:
linenothreshold = 10 linenothreshold = 10
else: else:
linenothreshold = sys.maxint linenothreshold = sys.maxint
return [addnodes.highlightlang(lang=arguments[0].strip(), return [addnodes.highlightlang(lang=self.arguments[0].strip(),
linenothreshold=linenothreshold)] linenothreshold=linenothreshold)]
highlightlang_directive.content = 0
highlightlang_directive.arguments = (1, 0, 0)
highlightlang_directive.options = {'linenothreshold': directives.unchanged}
directives.register_directive('highlight', highlightlang_directive)
# old name
directives.register_directive('highlightlang', highlightlang_directive)
class CodeBlock(Directive):
"""
Directive for a code block with special highlighting or line numbering
settings.
"""
# ------ code-block directive -------------------------------------------------- has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'linenos': directives.flag,
}
def codeblock_directive(name, arguments, options, content, lineno, def run(self):
content_offset, block_text, state, state_machine): code = u'\n'.join(self.content)
code = u'\n'.join(content)
literal = nodes.literal_block(code, code) literal = nodes.literal_block(code, code)
literal['language'] = arguments[0] literal['language'] = self.arguments[0]
literal['linenos'] = 'linenos' in options literal['linenos'] = 'linenos' in self.options
return [literal] return [literal]
codeblock_directive.content = 1
codeblock_directive.arguments = (1, 0, 0)
codeblock_directive.options = {'linenos': directives.flag}
directives.register_directive('code-block', codeblock_directive)
directives.register_directive('sourcecode', codeblock_directive)
class LiteralInclude(Directive):
"""
Like ``.. include:: :literal:``, but only warns if the include file is
not found, and does not raise errors. Also has several options for
selecting what to include.
"""
# ------ literalinclude directive ---------------------------------------------- has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'linenos': directives.flag,
'language': directives.unchanged_required,
'encoding': directives.encoding,
'pyobject': directives.unchanged_required,
'lines': directives.unchanged_required,
'start-after': directives.unchanged_required,
'end-before': directives.unchanged_required,
}
def literalinclude_directive(name, arguments, options, content, lineno, def run(self):
content_offset, block_text, state, state_machine): document = self.state.document
"""Like .. include:: :literal:, but only warns if the include file is filename = self.arguments[0]
not found.""" if not document.settings.file_insertion_enabled:
if not state.document.settings.file_insertion_enabled: return [document.reporter.warning('File insertion disabled',
return [state.document.reporter.warning('File insertion disabled', line=self.lineno)]
line=lineno)] env = document.settings.env
env = state.document.settings.env rel_fn = filename
rel_fn = arguments[0] sourcename = self.state_machine.input_lines.source(
source_dir = path.dirname(path.abspath(state_machine.input_lines.source( self.lineno - self.state_machine.input_offset - 1)
lineno - state_machine.input_offset - 1))) source_dir = path.dirname(path.abspath(sourcename))
fn = path.normpath(path.join(source_dir, rel_fn)) fn = path.normpath(path.join(source_dir, rel_fn))
if 'pyobject' in options and 'lines' in options: if 'pyobject' in self.options and 'lines' in self.options:
return [state.document.reporter.warning( return [document.reporter.warning(
'Cannot use both "pyobject" and "lines" options', line=lineno)] 'Cannot use both "pyobject" and "lines" options',
line=self.lineno)]
encoding = options.get('encoding', env.config.source_encoding) encoding = self.options.get('encoding', env.config.source_encoding)
try: try:
f = codecs.open(fn, 'rU', encoding) f = codecs.open(fn, 'rU', encoding)
lines = f.readlines() lines = f.readlines()
f.close() f.close()
except (IOError, OSError): except (IOError, OSError):
return [state.document.reporter.warning( return [document.reporter.warning(
'Include file %r not found or reading it failed' % arguments[0], 'Include file %r not found or reading it failed' % filename,
line=lineno)] line=self.lineno)]
except UnicodeError: except UnicodeError:
return [state.document.reporter.warning( return [document.reporter.warning(
'Encoding %r used for reading included file %r seems to ' 'Encoding %r used for reading included file %r seems to '
'be wrong, try giving an :encoding: option' % 'be wrong, try giving an :encoding: option' %
(encoding, arguments[0]))] (encoding, filename))]
objectname = options.get('pyobject') objectname = self.options.get('pyobject')
if objectname is not None: if objectname is not None:
from sphinx.pycode import ModuleAnalyzer from sphinx.pycode import ModuleAnalyzer
analyzer = ModuleAnalyzer.for_file(fn, '') analyzer = ModuleAnalyzer.for_file(fn, '')
tags = analyzer.find_tags() tags = analyzer.find_tags()
if objectname not in tags: if objectname not in tags:
return [state.document.reporter.warning( return [document.reporter.warning(
'Object named %r not found in include file %r' % 'Object named %r not found in include file %r' %
(objectname, arguments[0]), line=lineno)] (objectname, filename), line=self.lineno)]
else: else:
lines = lines[tags[objectname][1] - 1 : tags[objectname][2] - 1] lines = lines[tags[objectname][1]-1 : tags[objectname][2]-1]
linespec = options.get('lines') linespec = self.options.get('lines')
if linespec is not None: if linespec is not None:
try: try:
linelist = parselinenos(linespec, len(lines)) linelist = parselinenos(linespec, len(lines))
except ValueError, err: except ValueError, err:
return [state.document.reporter.warning(str(err), line=lineno)] return [document.reporter.warning(str(err), line=self.lineno)]
lines = [lines[i] for i in linelist] lines = [lines[i] for i in linelist]
startafter = options.get('start-after') startafter = self.options.get('start-after')
endbefore = options.get('end-before') endbefore = self.options.get('end-before')
if startafter is not None or endbefore is not None: if startafter is not None or endbefore is not None:
use = not startafter use = not startafter
res = [] res = []
@ -129,22 +159,16 @@ def literalinclude_directive(name, arguments, options, content, lineno,
text = ''.join(lines) text = ''.join(lines)
retnode = nodes.literal_block(text, text, source=fn) retnode = nodes.literal_block(text, text, source=fn)
retnode.line = 1 retnode.line = 1
if options.get('language', ''): if self.options.get('language', ''):
retnode['language'] = options['language'] retnode['language'] = self.options['language']
if 'linenos' in options: if 'linenos' in self.options:
retnode['linenos'] = True retnode['linenos'] = True
state.document.settings.env.note_dependency(rel_fn) document.settings.env.note_dependency(rel_fn)
return [retnode] return [retnode]
literalinclude_directive.options = {
'linenos': directives.flag, directives.register_directive('highlight', Highlight)
'language': directives.unchanged_required, directives.register_directive('highlightlang', Highlight) # old name
'encoding': directives.encoding, directives.register_directive('code-block', CodeBlock)
'pyobject': directives.unchanged_required, directives.register_directive('sourcecode', CodeBlock)
'lines': directives.unchanged_required, directives.register_directive('literalinclude', LiteralInclude)
'start-after': directives.unchanged_required,
'end-before': directives.unchanged_required,
}
literalinclude_directive.content = 0
literalinclude_directive.arguments = (1, 0, 0)
directives.register_directive('literalinclude', literalinclude_directive)

View File

@ -15,92 +15,76 @@ from docutils.parsers.rst import directives
from sphinx import addnodes from sphinx import addnodes
from sphinx.util import ws_re from sphinx.util import ws_re
from sphinx.util.compat import Directive
# ------ information units ----------------------------------------------------- def _is_only_paragraph(node):
"""True if the node only contains one paragraph (and system messages)."""
def desc_index_text(desctype, module, name, add_modules): if len(node) == 0:
if desctype == 'function': return False
if not module: elif len(node) > 1:
return _('%s() (built-in function)') % name for subnode in node[1:]:
return _('%s() (in module %s)') % (name, module) if not isinstance(subnode, nodes.system_message):
elif desctype == 'data': return False
if not module: if isinstance(node[0], nodes.paragraph):
return _('%s (built-in variable)') % name return True
return _('%s (in module %s)') % (name, module) return False
elif desctype == 'class':
if not module:
return _('%s (built-in class)') % name
return _('%s (class in %s)') % (name, module)
elif desctype == 'exception':
return name
elif desctype == 'method':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
if module:
return _('%s() (in module %s)') % (name, module)
else:
return '%s()' % name
if module and add_modules:
return _('%s() (%s.%s method)') % (methname, module, clsname)
else:
return _('%s() (%s method)') % (methname, clsname)
elif desctype == 'staticmethod':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
if module:
return _('%s() (in module %s)') % (name, module)
else:
return '%s()' % name
if module and add_modules:
return _('%s() (%s.%s static method)') % (methname, module, clsname)
else:
return _('%s() (%s static method)') % (methname, clsname)
elif desctype == 'classmethod':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
if module:
return '%s() (in module %s)' % (name, module)
else:
return '%s()' % name
if module:
return '%s() (%s.%s class method)' % (methname, module, clsname)
else:
return '%s() (%s class method)' % (methname, clsname)
elif desctype == 'attribute':
try:
clsname, attrname = name.rsplit('.', 1)
except ValueError:
if module:
return _('%s (in module %s)') % (name, module)
else:
return name
if module and add_modules:
return _('%s (%s.%s attribute)') % (attrname, module, clsname)
else:
return _('%s (%s attribute)') % (attrname, clsname)
elif desctype == 'cfunction':
return _('%s (C function)') % name
elif desctype == 'cmember':
return _('%s (C member)') % name
elif desctype == 'cmacro':
return _('%s (C macro)') % name
elif desctype == 'ctype':
return _('%s (C type)') % name
elif desctype == 'cvar':
return _('%s (C variable)') % name
else:
raise ValueError('unhandled descenv: %s' % desctype)
# ------ make field lists (like :param foo:) in desc bodies prettier # REs for Python signatures
py_sig_re = re.compile(
r'''^ ([\w.]*\.)? # class name(s)
(\w+) \s* # thing name
(?: \((.*)\) # optional: arguments
(?:\s* -> \s* (.*))? # return annotation
)? $ # and nothing more
''', re.VERBOSE)
_ = lambda x: x # make gettext extraction in constants possible py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ','
doc_fields_with_arg = { # REs for C signatures
c_sig_re = re.compile(
r'''^([^(]*?) # return type
([\w:]+) \s* # thing name (colon allowed for C++ class names)
(?: \((.*)\) )? # optionally arguments
(\s+const)? $ # const specifier
''', re.VERBOSE)
c_funcptr_sig_re = re.compile(
r'''^([^(]+?) # return type
(\( [^()]+ \)) \s* # name in parentheses
\( (.*) \) # arguments
(\s+const)? $ # const specifier
''', re.VERBOSE)
c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$')
# RE for option descriptions
option_desc_re = re.compile(
r'((?:/|-|--)[-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)')
# RE to split at word boundaries
wsplit_re = re.compile(r'(\W+)')
# RE to strip backslash escapes
strip_backslash_re = re.compile(r'\\(?=[^\\])')
class DescDirective(Directive):
"""
Directive to describe a class, function or similar object.
"""
has_content = True
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {
'noindex': directives.flag,
'module': directives.unchanged,
}
_ = lambda x: x # make gettext extraction in constants possible
doc_fields_with_arg = {
'param': '%param', 'param': '%param',
'parameter': '%param', 'parameter': '%param',
'arg': '%param', 'arg': '%param',
@ -118,33 +102,17 @@ doc_fields_with_arg = {
'cvar': 'Variable', 'cvar': 'Variable',
'returns': _('Returns'), 'returns': _('Returns'),
'return': 'Returns', 'return': 'Returns',
} }
doc_fields_with_linked_arg = ('raises', 'raise', 'exception', 'except') doc_fields_with_linked_arg = ('raises', 'raise', 'exception', 'except')
doc_fields_without_arg = { doc_fields_without_arg = {
'returns': 'Returns', 'returns': 'Returns',
'return': 'Returns', 'return': 'Returns',
'rtype': _('Return type'), 'rtype': _('Return type'),
} }
del _ def handle_doc_fields(self, node):
def _is_only_paragraph(node):
# determine if the node only contains one paragraph (and system messages)
if len(node) == 0:
return False
elif len(node) > 1:
for subnode in node[1:]:
if not isinstance(subnode, nodes.system_message):
return False
if isinstance(node[0], nodes.paragraph):
return True
return False
def handle_doc_fields(node, env):
# don't traverse, only handle field lists that are immediate children # don't traverse, only handle field lists that are immediate children
for child in node.children: for child in node.children:
if not isinstance(child, nodes.field_list): if not isinstance(child, nodes.field_list):
@ -158,7 +126,7 @@ def handle_doc_fields(node, env):
fname, fbody = field fname, fbody = field
try: try:
typ, obj = fname.astext().split(None, 1) typ, obj = fname.astext().split(None, 1)
typdesc = _(doc_fields_with_arg[typ]) typdesc = _(self.doc_fields_with_arg[typ])
if _is_only_paragraph(fbody): if _is_only_paragraph(fbody):
children = fbody.children[0].children children = fbody.children[0].children
else: else:
@ -179,8 +147,9 @@ def handle_doc_fields(node, env):
elif typdesc == '%type': elif typdesc == '%type':
typenodes = fbody.children typenodes = fbody.children
if _is_only_paragraph(fbody): if _is_only_paragraph(fbody):
typenodes = [nodes.Text(' (')] + \ typenodes = ([nodes.Text(' (')] +
typenodes[0].children + [nodes.Text(')')] typenodes[0].children +
[nodes.Text(')')])
param_types[obj] = typenodes param_types[obj] = typenodes
else: else:
fieldname = typdesc + ' ' fieldname = typdesc + ' '
@ -188,12 +157,11 @@ def handle_doc_fields(node, env):
nfieldname = nodes.field_name(fieldname, fieldname) nfieldname = nodes.field_name(fieldname, fieldname)
nfield += nfieldname nfield += nfieldname
node = nfieldname node = nfieldname
if typ in doc_fields_with_linked_arg: if typ in self.doc_fields_with_linked_arg:
node = addnodes.pending_xref(obj, reftype='obj', node = addnodes.pending_xref(
refcaption=False, obj, reftype='obj', refcaption=False,
reftarget=obj, reftarget=obj, modname=self.env.currmodule,
modname=env.currmodule, classname=self.env.currclass)
classname=env.currclass)
nfieldname += node nfieldname += node
node += nodes.Text(obj, obj) node += nodes.Text(obj, obj)
nfield += nodes.field_body() nfield += nodes.field_body()
@ -202,7 +170,7 @@ def handle_doc_fields(node, env):
except (KeyError, ValueError): except (KeyError, ValueError):
fnametext = fname.astext() fnametext = fname.astext()
try: try:
typ = _(doc_fields_without_arg[fnametext]) typ = _(self.doc_fields_without_arg[fnametext])
except KeyError: except KeyError:
# at least capitalize the field name # at least capitalize the field name
typ = fnametext.capitalize() typ = fnametext.capitalize()
@ -224,20 +192,70 @@ def handle_doc_fields(node, env):
param_nodes[param][1:1] = type param_nodes[param][1:1] = type
child.replace_self(new_list) child.replace_self(new_list)
def get_signatures(self):
# remove backslashes to support (dummy) escapes; helps Vim highlighting
return [strip_backslash_re.sub('', sig.strip())
for sig in self.arguments[0].split('\n')]
# ------ functions to parse a Python or C signature and create desc_* nodes. def parse_signature(self, sig, signode):
raise ValueError # must be implemented in subclasses
py_sig_re = re.compile( def add_target_and_index(self, name, sig, signode):
r'''^ ([\w.]*\.)? # class name(s) return # do nothing by default
(\w+) \s* # thing name
(?: \((.*)\) # optional: arguments
(?:\s* -> \s* (.*))? # return annotation
)? $ # and nothing more
''', re.VERBOSE)
py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' def before_content(self):
pass
def parse_py_signature(signode, sig, desctype, module, env): def after_content(self):
pass
def run(self):
self.desctype = self.name
self.env = self.state.document.settings.env
self.indexnode = addnodes.index(entries=[])
node = addnodes.desc()
node.document = self.state.document
node['desctype'] = self.desctype
node['noindex'] = noindex = ('noindex' in self.options)
self.names = []
signatures = self.get_signatures()
for i, sig in enumerate(signatures):
# add a signature node for each signature in the current unit
# and add a reference target for it
signode = addnodes.desc_signature(sig, '')
signode['first'] = False
node.append(signode)
try:
# name can also be a tuple, e.g. (classname, objname)
name = self.parse_signature(sig, signode)
except ValueError, err:
# signature parsing failed
signode.clear()
signode += addnodes.desc_name(sig, sig)
continue # we don't want an index entry here
if not noindex and name not in self.names:
# only add target and index entry if this is the first
# description of the object with this name in this desc block
self.names.append(name)
self.add_target_and_index(name, sig, signode)
contentnode = addnodes.desc_content()
node.append(contentnode)
if self.names:
# needed for association of version{added,changed} directives
self.env.currdesc = self.names[0]
self.before_content()
self.state.nested_parse(self.content, self.content_offset, contentnode)
self.handle_doc_fields(contentnode)
self.env.currdesc = None
self.after_content()
return [self.indexnode, node]
class PythonDesc(DescDirective):
def parse_signature(self, sig, signode):
""" """
Transform a python signature into RST nodes. Transform a python signature into RST nodes.
Return (fully qualified name of the thing, classname if any). Return (fully qualified name of the thing, classname if any).
@ -251,40 +269,44 @@ def parse_py_signature(signode, sig, desctype, module, env):
raise ValueError raise ValueError
classname, name, arglist, retann = m.groups() classname, name, arglist, retann = m.groups()
if env.currclass: if self.env.currclass:
add_module = False add_module = False
if classname and classname.startswith(env.currclass): if classname and classname.startswith(self.env.currclass):
fullname = classname + name fullname = classname + name
# class name is given again in the signature # class name is given again in the signature
classname = classname[len(env.currclass):].lstrip('.') classname = classname[len(self.env.currclass):].lstrip('.')
elif classname: elif classname:
# class name is given in the signature, but different # class name is given in the signature, but different
# (shouldn't happen) # (shouldn't happen)
fullname = env.currclass + '.' + classname + name fullname = self.env.currclass + '.' + classname + name
else: else:
# class name is not given in the signature # class name is not given in the signature
fullname = env.currclass + '.' + name fullname = self.env.currclass + '.' + name
else: else:
add_module = True add_module = True
fullname = classname and classname + name or name fullname = classname and classname + name or name
if desctype == 'staticmethod': # XXX!
if self.desctype == 'staticmethod':
signode += addnodes.desc_annotation('static ', 'static ') signode += addnodes.desc_annotation('static ', 'static ')
elif desctype == 'classmethod': elif self.desctype == 'classmethod':
signode += addnodes.desc_annotation('classmethod ', 'classmethod ') signode += addnodes.desc_annotation('classmethod ', 'classmethod ')
if classname: if classname:
signode += addnodes.desc_addname(classname, classname) signode += addnodes.desc_addname(classname, classname)
# exceptions are a special case, since they are documented in the # exceptions are a special case, since they are documented in the
# 'exceptions' module. # 'exceptions' module.
elif add_module and env.config.add_module_names and \ elif add_module and self.env.config.add_module_names:
module and module != 'exceptions': modname = self.options.get('module', self.env.currmodule)
nodetext = module + '.' if modname and modname != 'exceptions':
nodetext = modname + '.'
signode += addnodes.desc_addname(nodetext, nodetext) signode += addnodes.desc_addname(nodetext, nodetext)
signode += addnodes.desc_name(name, name) signode += addnodes.desc_name(name, name)
if not arglist: if not arglist:
if desctype in ('function', 'method', 'staticmethod', 'classmethod'): # XXX!
if self.desctype in ('function', 'method',
'staticmethod', 'classmethod'):
# for callables, add an empty parameter list # for callables, add an empty parameter list
signode += addnodes.desc_parameterlist() signode += addnodes.desc_parameterlist()
if retann: if retann:
@ -314,33 +336,142 @@ def parse_py_signature(signode, sig, desctype, module, env):
signode += addnodes.desc_returns(retann, retann) signode += addnodes.desc_returns(retann, retann)
return fullname, classname return fullname, classname
def get_index_text(self, modname, name):
raise NotImplementedError('must be implemented in subclasses')
c_sig_re = re.compile( def add_target_and_index(self, name_cls, sig, signode):
r'''^([^(]*?) # return type modname = self.options.get('module', self.env.currmodule)
([\w:]+) \s* # thing name (colon allowed for C++ class names) fullname = (modname and modname + '.' or '') + name_cls[0]
(?: \((.*)\) )? # optionally arguments # note target
(\s+const)? $ # const specifier if fullname not in self.state.document.ids:
''', re.VERBOSE) signode['names'].append(fullname)
c_funcptr_sig_re = re.compile( signode['ids'].append(fullname)
r'''^([^(]+?) # return type signode['first'] = (not self.names)
(\( [^()]+ \)) \s* # name in parentheses self.state.document.note_explicit_target(signode)
\( (.*) \) # arguments self.env.note_descref(fullname, self.desctype, self.lineno)
(\s+const)? $ # const specifier
''', re.VERBOSE)
c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$')
# RE to split at word boundaries indextext = self.get_index_text(modname, name_cls)
wsplit_re = re.compile(r'(\W+)') if indextext:
self.indexnode['entries'].append(('single', indextext,
fullname, fullname))
# These C types aren't described in the reference, so don't try to create def before_content(self):
# a cross-reference to them # needed for automatic qualification of members (reset in subclasses)
stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct')) self.clsname_set = False
def parse_c_type(node, ctype): def after_content(self):
if self.clsname_set:
self.env.currclass = None
class ModulelevelDesc(PythonDesc):
def get_index_text(self, modname, name_cls):
if self.desctype == 'function':
if not modname:
return _('%s() (built-in function)') % name_cls[0]
return _('%s() (in module %s)') % (name_cls[0], modname)
elif self.desctype == 'data':
if not modname:
return _('%s (built-in variable)') % name_cls[0]
return _('%s (in module %s)') % (name_cls[0], modname)
else:
return ''
class ClasslikeDesc(PythonDesc):
def get_index_text(self, modname, name_cls):
if self.desctype == 'class':
if not modname:
return _('%s (built-in class)') % name_cls[0]
return _('%s (class in %s)') % (name_cls[0], modname)
elif self.desctype == 'exception':
return name_cls[0]
else:
return ''
def before_content(self):
PythonDesc.before_content(self)
if self.names:
self.env.currclass = self.names[0][0]
self.clsname_set = True
class ClassmemberDesc(PythonDesc):
def get_index_text(self, modname, name_cls):
name, cls = name_cls
add_modules = self.env.config.add_module_names
if self.desctype == 'method':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
if modname:
return _('%s() (in module %s)') % (name, modname)
else:
return '%s()' % name
if modname and add_modules:
return _('%s() (%s.%s method)') % (methname, modname, clsname)
else:
return _('%s() (%s method)') % (methname, clsname)
elif self.desctype == 'staticmethod':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
if modname:
return _('%s() (in module %s)') % (name, modname)
else:
return '%s()' % name
if modname and add_modules:
return _('%s() (%s.%s static method)') % (methname, modname,
clsname)
else:
return _('%s() (%s static method)') % (methname, clsname)
elif self.desctype == 'classmethod':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
if modname:
return '%s() (in module %s)' % (name, modname)
else:
return '%s()' % name
if modname:
return '%s() (%s.%s class method)' % (methname, modname,
clsname)
else:
return '%s() (%s class method)' % (methname, clsname)
elif self.desctype == 'attribute':
try:
clsname, attrname = name.rsplit('.', 1)
except ValueError:
if modname:
return _('%s (in module %s)') % (name, modname)
else:
return name
if modname and add_modules:
return _('%s (%s.%s attribute)') % (attrname, modname, clsname)
else:
return _('%s (%s attribute)') % (attrname, clsname)
else:
return ''
def before_content(self):
PythonDesc.before_content(self)
if self.names and self.names[-1][1] and not self.env.currclass:
self.env.currclass = self.names[-1][1].strip('.')
self.clsname_set = True
class CDesc(DescDirective):
# These C types aren't described anywhere, so don't try to create
# a cross-reference to them
stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct'))
def _parse_type(self, node, ctype):
# add cross-ref nodes for all words # add cross-ref nodes for all words
for part in filter(None, wsplit_re.split(ctype)): for part in filter(None, wsplit_re.split(ctype)):
tnode = nodes.Text(part, part) tnode = nodes.Text(part, part)
if part[0] in string.ascii_letters+'_' and part not in stopwords: if part[0] in string.ascii_letters+'_' and \
part not in self.stopwords:
pnode = addnodes.pending_xref( pnode = addnodes.pending_xref(
'', reftype='ctype', reftarget=part, '', reftype='ctype', reftarget=part,
modname=None, classname=None) modname=None, classname=None)
@ -349,7 +480,7 @@ def parse_c_type(node, ctype):
else: else:
node += tnode node += tnode
def parse_c_signature(signode, sig, desctype): def parse_signature(self, sig, signode):
"""Transform a C (or C++) signature into RST nodes.""" """Transform a C (or C++) signature into RST nodes."""
# first try the function pointer signature regex, it's more specific # first try the function pointer signature regex, it's more specific
m = c_funcptr_sig_re.match(sig) m = c_funcptr_sig_re.match(sig)
@ -360,7 +491,7 @@ def parse_c_signature(signode, sig, desctype):
rettype, name, arglist, const = m.groups() rettype, name, arglist, const = m.groups()
signode += addnodes.desc_type('', '') signode += addnodes.desc_type('', '')
parse_c_type(signode[-1], rettype) self._parse_type(signode[-1], rettype)
try: try:
classname, funcname = name.split('::', 1) classname, funcname = name.split('::', 1)
classname += '::' classname += '::'
@ -374,7 +505,7 @@ def parse_c_signature(signode, sig, desctype):
if m: if m:
name = m.group(1) name = m.group(1)
if not arglist: if not arglist:
if desctype == 'cfunction': if self.desctype == 'cfunction':
# for functions, add an empty parameter list # for functions, add an empty parameter list
signode += addnodes.desc_parameterlist() signode += addnodes.desc_parameterlist()
return name return name
@ -390,9 +521,9 @@ def parse_c_signature(signode, sig, desctype):
ctype, argname = arg.rsplit(' ', 1) ctype, argname = arg.rsplit(' ', 1)
except ValueError: except ValueError:
# no argument name given, only the type # no argument name given, only the type
parse_c_type(param, arg) self._parse_type(param, arg)
else: else:
parse_c_type(param, ctype) self._parse_type(param, ctype)
param += nodes.emphasis(' '+argname, ' '+argname) param += nodes.emphasis(' '+argname, ' '+argname)
paramlist += param paramlist += param
signode += paramlist signode += paramlist
@ -400,11 +531,30 @@ def parse_c_signature(signode, sig, desctype):
signode += addnodes.desc_addname(const, const) signode += addnodes.desc_addname(const, const)
return name return name
def get_index_text(self, modname, name):
if self.desctype == 'cfunction':
return _('%s (C function)') % name
elif self.desctype == 'cmember':
return _('%s (C member)') % name
elif self.desctype == 'cmacro':
return _('%s (C macro)') % name
elif self.desctype == 'ctype':
return _('%s (C type)') % name
elif self.desctype == 'cvar':
return _('%s (C variable)') % name
else:
return ''
option_desc_re = re.compile( # just copy that one
r'((?:/|-|--)[-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') add_target_and_index = PythonDesc.__dict__['add_target_and_index']
def parse_option_desc(signode, sig):
class CmdoptionDesc(DescDirective):
"""
A command-line option (.. cmdoption).
"""
def parse_signature(self, sig, signode):
"""Transform an option description into RST nodes.""" """Transform an option description into RST nodes."""
count = 0 count = 0
firstname = '' firstname = ''
@ -421,186 +571,73 @@ def parse_option_desc(signode, sig):
raise ValueError raise ValueError
return firstname return firstname
def add_target_and_index(self, name, sig, signode):
strip_backslash_re = re.compile(r'\\(?=[^\\])') targetname = name.replace('/', '-')
if self.env.currprogram:
def desc_directive(desctype, arguments, options, content, lineno, targetname = '-' + self.env.currprogram + targetname
content_offset, block_text, state, state_machine):
env = state.document.settings.env
inode = addnodes.index(entries=[])
node = addnodes.desc()
node.document = state.document
node['desctype'] = desctype
noindex = ('noindex' in options)
node['noindex'] = noindex
# remove backslashes to support (dummy) escapes; helps Vim's highlighting
signatures = map(lambda s: strip_backslash_re.sub('', s.strip()),
arguments[0].split('\n'))
names = []
clsname = None
module = options.get('module', env.currmodule)
for i, sig in enumerate(signatures):
# add a signature node for each signature in the current unit
# and add a reference target for it
sig = sig.strip()
signode = addnodes.desc_signature(sig, '')
signode['first'] = False
node.append(signode)
try:
if desctype in ('function', 'data', 'class', 'exception',
'method', 'staticmethod', 'classmethod',
'attribute'):
name, clsname = parse_py_signature(signode, sig,
desctype, module, env)
elif desctype in ('cfunction', 'cmember', 'cmacro',
'ctype', 'cvar'):
name = parse_c_signature(signode, sig, desctype)
elif desctype == 'cmdoption':
optname = parse_option_desc(signode, sig)
if not noindex:
targetname = optname.replace('/', '-')
if env.currprogram:
targetname = '-' + env.currprogram + targetname
targetname = 'cmdoption' + targetname targetname = 'cmdoption' + targetname
signode['ids'].append(targetname) signode['ids'].append(targetname)
state.document.note_explicit_target(signode) self.state.document.note_explicit_target(signode)
inode['entries'].append( self.indexnode['entries'].append(
('pair', _('%scommand line option; %s') % ('pair', _('%scommand line option; %s') %
((env.currprogram and env.currprogram + ' ' or ''), ((self.env.currprogram and
sig), self.env.currprogram + ' ' or ''), sig),
targetname, targetname)) targetname, targetname))
env.note_progoption(optname, targetname) self.env.note_progoption(name, targetname)
continue
elif desctype == 'describe':
signode.clear() class GenericDesc(DescDirective):
signode += addnodes.desc_name(sig, sig) """
continue A generic x-ref directive registered with Sphinx.add_description_unit().
else: """
# another registered generic x-ref directive
rolename, indextemplate, parse_node = \ def parse_signature(self, sig, signode):
additional_xref_types[desctype] parse_node = additional_xref_types[self.desctype][2]
if parse_node: if parse_node:
fullname = parse_node(env, sig, signode) name = parse_node(self.env, sig, signode)
else: else:
signode.clear() signode.clear()
signode += addnodes.desc_name(sig, sig) signode += addnodes.desc_name(sig, sig)
# normalize whitespace like xfileref_role does # normalize whitespace like xfileref_role does
fullname = ws_re.sub('', sig) name = ws_re.sub('', sig)
if not noindex: return name
targetname = '%s-%s' % (rolename, fullname)
def add_target_and_index(self, name, sig, signode):
rolename, indextemplate, _ = additional_xref_types[self.desctype]
targetname = '%s-%s' % (rolename, name)
signode['ids'].append(targetname) signode['ids'].append(targetname)
state.document.note_explicit_target(signode) self.state.document.note_explicit_target(signode)
if indextemplate: if indextemplate:
indexentry = _(indextemplate) % (fullname,) indexentry = _(indextemplate) % (name,)
indextype = 'single' indextype = 'single'
colon = indexentry.find(':') colon = indexentry.find(':')
if colon != -1: if colon != -1:
indextype = indexentry[:colon].strip() indextype = indexentry[:colon].strip()
indexentry = indexentry[colon+1:].strip() indexentry = indexentry[colon+1:].strip()
inode['entries'].append((indextype, indexentry, self.indexnode['entries'].append((indextype, indexentry,
targetname, targetname)) targetname, targetname))
env.note_reftarget(rolename, fullname, targetname) self.env.note_reftarget(rolename, name, targetname)
# don't use object indexing below
continue
except ValueError, err:
# signature parsing failed
signode.clear()
signode += addnodes.desc_name(sig, sig)
continue # we don't want an index entry here
# only add target and index entry if this is the first description
# of the function name in this desc block
if not noindex and name not in names:
fullname = (module and module + '.' or '') + name
# note target
if fullname not in state.document.ids:
signode['names'].append(fullname)
signode['ids'].append(fullname)
signode['first'] = (not names)
state.document.note_explicit_target(signode)
env.note_descref(fullname, desctype, lineno)
names.append(name)
indextext = desc_index_text(desctype, module, name,
env.config.add_module_names)
inode['entries'].append(('single', indextext, fullname, fullname))
subnode = addnodes.desc_content()
node.append(subnode)
# needed for automatic qualification of members
clsname_set = False
if desctype in ('class', 'exception') and names:
env.currclass = names[0]
clsname_set = True
elif desctype in ('method', 'staticmethod', 'classmethod',
'attribute') and clsname and not env.currclass:
env.currclass = clsname.strip('.')
clsname_set = True
# needed for association of version{added,changed} directives
if names:
env.currdesc = names[0]
state.nested_parse(content, content_offset, subnode)
handle_doc_fields(subnode, env)
if clsname_set:
env.currclass = None
env.currdesc = None
return [inode, node]
desc_directive.content = 1
desc_directive.arguments = (1, 0, 1)
desc_directive.options = {'noindex': directives.flag,
'module': directives.unchanged}
desctypes = [
# the Python ones
'function',
'data',
'class',
'method',
'classmethod',
'staticmethod',
'attribute',
'exception',
# the C ones
'cfunction',
'cmember',
'cmacro',
'ctype',
'cvar',
# for command line options
'cmdoption',
# the generic one
'describe',
'envvar',
]
for _name in desctypes:
directives.register_directive(_name, desc_directive)
_ = lambda x: x
# Generic cross-reference types; they can be registered in the application;
# the directives are either desc_directive or target_directive
additional_xref_types = {
# directive name: (role name, index text, function to parse the desc node)
'envvar': ('envvar', _('environment variable; %s'), None),
}
del _
# ------ target ---------------------------------------------------------------- class Target(Directive):
"""
Generic target for user-defined cross-reference types.
"""
def target_directive(targettype, arguments, options, content, lineno, has_content = False
content_offset, block_text, state, state_machine): required_arguments = 1
"""Generic target for user-defined cross-reference types.""" optional_arguments = 0
env = state.document.settings.env final_argument_whitespace = True
rolename, indextemplate, foo = additional_xref_types[targettype] option_spec = {}
def run(self):
env = self.state.document.settings.env
rolename, indextemplate, foo = additional_xref_types[self.name]
# normalize whitespace in fullname like xfileref_role does # normalize whitespace in fullname like xfileref_role does
fullname = ws_re.sub('', arguments[0].strip()) fullname = ws_re.sub('', self.arguments[0].strip())
targetname = '%s-%s' % (rolename, fullname) targetname = '%s-%s' % (rolename, fullname)
node = nodes.target('', '', ids=[targetname]) node = nodes.target('', '', ids=[targetname])
state.document.note_explicit_target(node) self.state.document.note_explicit_target(node)
ret = [node] ret = [node]
if indextemplate: if indextemplate:
indexentry = indextemplate % (fullname,) indexentry = indextemplate % (fullname,)
@ -615,8 +652,37 @@ def target_directive(targettype, arguments, options, content, lineno,
env.note_reftarget(rolename, fullname, targetname) env.note_reftarget(rolename, fullname, targetname)
return ret return ret
target_directive.content = 0 # Note: the target directive is not registered here, it is used by the
target_directive.arguments = (1, 0, 1) # application when registering additional xref types.
# note, the target directive is not registered here, it is used by the _ = lambda x: x
# application when registering additional xref types
# Generic cross-reference types; they can be registered in the application;
# the directives are either desc_directive or target_directive.
additional_xref_types = {
# directive name: (role name, index text, function to parse the desc node)
'envvar': ('envvar', _('environment variable; %s'), None),
}
del _
directives.register_directive('describe', DescDirective)
directives.register_directive('function', ModulelevelDesc)
directives.register_directive('data', ModulelevelDesc)
directives.register_directive('class', ClasslikeDesc)
directives.register_directive('exception', ClasslikeDesc)
directives.register_directive('method', ClassmemberDesc)
directives.register_directive('classmethod', ClassmemberDesc)
directives.register_directive('staticmethod', ClassmemberDesc)
directives.register_directive('attribute', ClassmemberDesc)
directives.register_directive('cfunction', CDesc)
directives.register_directive('cmember', CDesc)
directives.register_directive('cmacro', CDesc)
directives.register_directive('ctype', CDesc)
directives.register_directive('cvar', CDesc)
directives.register_directive('cmdoption', CmdoptionDesc)
directives.register_directive('envvar', GenericDesc)

View File

@ -15,16 +15,29 @@ from docutils.parsers.rst import directives
from sphinx import addnodes from sphinx import addnodes
from sphinx.locale import pairindextypes from sphinx.locale import pairindextypes
from sphinx.util import patfilter, ws_re, caption_ref_re, url_re, docname_join from sphinx.util import patfilter, ws_re, caption_ref_re, url_re, docname_join
from sphinx.util.compat import make_admonition from sphinx.util.compat import Directive, make_admonition
# ------ the TOC tree ---------------------------------------------------------- class TocTree(Directive):
"""
Directive to notify Sphinx about the hierarchical structure of the docs,
and to include a table-of-contents like tree in the current document.
"""
def toctree_directive(name, arguments, options, content, lineno, has_content = True
content_offset, block_text, state, state_machine): required_arguments = 0
env = state.document.settings.env optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'maxdepth': int,
'glob': directives.flag,
'hidden': directives.flag,
}
def run(self):
env = self.state.document.settings.env
suffix = env.config.source_suffix suffix = env.config.source_suffix
glob = 'glob' in options glob = 'glob' in self.options
ret = [] ret = []
# (title, ref) pairs, where ref may be a document, or an external link, # (title, ref) pairs, where ref may be a document, or an external link,
@ -35,11 +48,11 @@ def toctree_directive(name, arguments, options, content, lineno,
all_docnames = env.found_docs.copy() all_docnames = env.found_docs.copy()
# don't add the currently visited file in catch-all patterns # don't add the currently visited file in catch-all patterns
all_docnames.remove(env.docname) all_docnames.remove(env.docname)
for entry in content: for entry in self.content:
if not entry: if not entry:
continue continue
if not glob: if not glob:
# look for explicit titles and documents ("Some Title <document>"). # look for explicit titles ("Some Title <document>")
m = caption_ref_re.match(entry) m = caption_ref_re.match(entry)
if m: if m:
ref = m.group(2) ref = m.group(2)
@ -56,9 +69,9 @@ def toctree_directive(name, arguments, options, content, lineno,
if url_re.match(ref) or ref == 'self': if url_re.match(ref) or ref == 'self':
entries.append((title, ref)) entries.append((title, ref))
elif docname not in env.found_docs: elif docname not in env.found_docs:
ret.append(state.document.reporter.warning( ret.append(self.state.document.reporter.warning(
'toctree references unknown document %r' % docname, 'toctree references unknown document %r' % docname,
line=lineno)) line=self.lineno))
else: else:
entries.append((title, docname)) entries.append((title, docname))
includefiles.append(docname) includefiles.append(docname)
@ -70,47 +83,56 @@ def toctree_directive(name, arguments, options, content, lineno,
entries.append((None, docname)) entries.append((None, docname))
includefiles.append(docname) includefiles.append(docname)
if not docnames: if not docnames:
ret.append(state.document.reporter.warning( ret.append(self.state.document.reporter.warning(
'toctree glob pattern %r didn\'t match any documents' 'toctree glob pattern %r didn\'t match any documents'
% entry, line=lineno)) % entry, line=self.lineno))
subnode = addnodes.toctree() subnode = addnodes.toctree()
subnode['parent'] = env.docname subnode['parent'] = env.docname
subnode['entries'] = entries subnode['entries'] = entries
subnode['includefiles'] = includefiles subnode['includefiles'] = includefiles
subnode['maxdepth'] = options.get('maxdepth', -1) subnode['maxdepth'] = self.options.get('maxdepth', -1)
subnode['glob'] = glob subnode['glob'] = glob
subnode['hidden'] = 'hidden' in options subnode['hidden'] = 'hidden' in self.options
ret.append(subnode) ret.append(subnode)
return ret return ret
toctree_directive.content = 1
toctree_directive.options = {'maxdepth': int, 'glob': directives.flag,
'hidden': directives.flag}
directives.register_directive('toctree', toctree_directive)
class Module(Directive):
"""
Directive to mark description of a new module.
"""
# ------ section metadata ------------------------------------------------------ has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'platform': lambda x: x,
'synopsis': lambda x: x,
'noindex': directives.flag,
'deprecated': directives.flag,
}
def module_directive(name, arguments, options, content, lineno, def run(self):
content_offset, block_text, state, state_machine): env = self.state.document.settings.env
env = state.document.settings.env modname = self.arguments[0].strip()
modname = arguments[0].strip() noindex = 'noindex' in self.options
noindex = 'noindex' in options
env.currmodule = modname env.currmodule = modname
env.note_module(modname, options.get('synopsis', ''), env.note_module(modname, self.options.get('synopsis', ''),
options.get('platform', ''), self.options.get('platform', ''),
'deprecated' in options) 'deprecated' in self.options)
modulenode = addnodes.module() modulenode = addnodes.module()
modulenode['modname'] = modname modulenode['modname'] = modname
modulenode['synopsis'] = options.get('synopsis', '') modulenode['synopsis'] = self.options.get('synopsis', '')
targetnode = nodes.target('', '', ids=['module-' + modname]) targetnode = nodes.target('', '', ids=['module-' + modname])
state.document.note_explicit_target(targetnode) self.state.document.note_explicit_target(targetnode)
ret = [modulenode, targetnode] ret = [modulenode, targetnode]
if 'platform' in options: if 'platform' in self.options:
modulenode['platform'] = options['platform'] platform = self.options['platform']
modulenode['platform'] = platform
node = nodes.paragraph() node = nodes.paragraph()
node += nodes.emphasis('', _('Platforms: ')) node += nodes.emphasis('', _('Platforms: '))
node += nodes.Text(options['platform'], options['platform']) node += nodes.Text(platform, platform)
ret.append(node) ret.append(node)
# the synopsis isn't printed; in fact, it is only used in the # the synopsis isn't printed; in fact, it is only used in the
# modindex currently # modindex currently
@ -121,83 +143,104 @@ def module_directive(name, arguments, options, content, lineno,
ret.insert(0, inode) ret.insert(0, inode)
return ret return ret
module_directive.arguments = (1, 0, 0)
module_directive.options = {'platform': lambda x: x,
'synopsis': lambda x: x,
'noindex': directives.flag,
'deprecated': directives.flag}
directives.register_directive('module', module_directive)
class CurrentModule(Directive):
"""
This directive is just to tell Sphinx that we're documenting
stuff in module foo, but links to module foo won't lead here.
"""
def currentmodule_directive(name, arguments, options, content, lineno, has_content = False
content_offset, block_text, state, state_machine): required_arguments = 1
# This directive is just to tell people that we're documenting optional_arguments = 0
# stuff in module foo, but links to module foo won't lead here. final_argument_whitespace = False
env = state.document.settings.env option_spec = {}
modname = arguments[0].strip()
def run(self):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
if modname == 'None': if modname == 'None':
env.currmodule = None env.currmodule = None
else: else:
env.currmodule = modname env.currmodule = modname
return [] return []
currentmodule_directive.arguments = (1, 0, 0)
directives.register_directive('currentmodule', currentmodule_directive)
class Author(Directive):
"""
Directive to give the name of the author of the current document
or section. Shown in the output only if the show_authors option is on.
"""
def author_directive(name, arguments, options, content, lineno, has_content = False
content_offset, block_text, state, state_machine): required_arguments = 1
# Show authors only if the show_authors option is on optional_arguments = 0
env = state.document.settings.env final_argument_whitespace = True
option_spec = {}
def run(self):
env = self.state.document.settings.env
if not env.config.show_authors: if not env.config.show_authors:
return [] return []
para = nodes.paragraph() para = nodes.paragraph()
emph = nodes.emphasis() emph = nodes.emphasis()
para += emph para += emph
if name == 'sectionauthor': if self.name == 'sectionauthor':
text = _('Section author: ') text = _('Section author: ')
elif name == 'moduleauthor': elif self.name == 'moduleauthor':
text = _('Module author: ') text = _('Module author: ')
else: else:
text = _('Author: ') text = _('Author: ')
emph += nodes.Text(text, text) emph += nodes.Text(text, text)
inodes, messages = state.inline_text(arguments[0], lineno) inodes, messages = self.state.inline_text(self.arguments[0],
self.lineno)
emph.extend(inodes) emph.extend(inodes)
return [para] + messages return [para] + messages
author_directive.arguments = (1, 0, 1)
directives.register_directive('sectionauthor', author_directive)
directives.register_directive('moduleauthor', author_directive)
class Program(Directive):
"""
Directive to name the program for which options are documented.
"""
def program_directive(name, arguments, options, content, lineno, has_content = False
content_offset, block_text, state, state_machine): required_arguments = 1
env = state.document.settings.env optional_arguments = 0
program = ws_re.sub('-', arguments[0].strip()) final_argument_whitespace = True
option_spec = {}
def run(self):
env = self.state.document.settings.env
program = ws_re.sub('-', self.arguments[0].strip())
if program == 'None': if program == 'None':
env.currprogram = None env.currprogram = None
else: else:
env.currprogram = program env.currprogram = program
return [] return []
program_directive.arguments = (1, 0, 1)
directives.register_directive('program', program_directive)
class Index(Directive):
"""
Directive to add entries to the index.
"""
# ------ index markup ---------------------------------------------------------- has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
indextypes = [ indextypes = [
'single', 'pair', 'triple', 'single', 'pair', 'triple',
] ]
def index_directive(name, arguments, options, content, lineno, def run(self):
content_offset, block_text, state, state_machine): arguments = self.arguments[0].split('\n')
arguments = arguments[0].split('\n') env = self.state.document.settings.env
env = state.document.settings.env
targetid = 'index-%s' % env.index_num targetid = 'index-%s' % env.index_num
env.index_num += 1 env.index_num += 1
targetnode = nodes.target('', '', ids=[targetid]) targetnode = nodes.target('', '', ids=[targetid])
state.document.note_explicit_target(targetnode) self.state.document.note_explicit_target(targetnode)
indexnode = addnodes.index() indexnode = addnodes.index()
indexnode['entries'] = ne = [] indexnode['entries'] = ne = []
for entry in arguments: for entry in arguments:
@ -209,7 +252,7 @@ def index_directive(name, arguments, options, content, lineno,
ne.append(('pair', value, targetid, value)) ne.append(('pair', value, targetid, value))
break break
else: else:
for type in indextypes: for type in self.indextypes:
if entry.startswith(type+':'): if entry.startswith(type+':'):
value = entry[len(type)+1:].strip() value = entry[len(type)+1:].strip()
ne.append((type, value, targetid, value)) ne.append((type, value, targetid, value))
@ -223,58 +266,62 @@ def index_directive(name, arguments, options, content, lineno,
ne.append(('single', value, targetid, value)) ne.append(('single', value, targetid, value))
return [indexnode, targetnode] return [indexnode, targetnode]
index_directive.arguments = (1, 0, 1)
directives.register_directive('index', index_directive)
# ------ versionadded/versionchanged ------------------------------------------- class VersionChange(Directive):
"""
Directive to describe a change/addition/deprecation in a specific version.
"""
def version_directive(name, arguments, options, content, lineno, has_content = True
content_offset, block_text, state, state_machine): required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
option_spec = {}
def run(self):
node = addnodes.versionmodified() node = addnodes.versionmodified()
node.document = state.document node.document = self.state.document
node['type'] = name node['type'] = self.name
node['version'] = arguments[0] node['version'] = self.arguments[0]
if len(arguments) == 2: if len(self.arguments) == 2:
inodes, messages = state.inline_text(arguments[1], lineno+1) inodes, messages = self.state.inline_text(self.arguments[1],
self.lineno+1)
node.extend(inodes) node.extend(inodes)
if content: if self.content:
state.nested_parse(content, content_offset, node) self.state.nested_parse(self.content, self.content_offset, node)
ret = [node] + messages ret = [node] + messages
else: else:
ret = [node] ret = [node]
env = state.document.settings.env env = self.state.document.settings.env
env.note_versionchange(node['type'], node['version'], node, lineno) env.note_versionchange(node['type'], node['version'], node, self.lineno)
return ret return ret
version_directive.arguments = (1, 1, 1)
version_directive.content = 1
directives.register_directive('deprecated', version_directive) class SeeAlso(Directive):
directives.register_directive('versionadded', version_directive) """
directives.register_directive('versionchanged', version_directive) An admonition mentioning things to look at as reference.
"""
has_content = True
required_arguments = 0
optional_arguments = 1
final_argument_whitespace = True
option_spec = {}
# ------ see also -------------------------------------------------------------- def run(self):
def seealso_directive(name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
ret = make_admonition( ret = make_admonition(
addnodes.seealso, name, [_('See also')], options, content, addnodes.seealso, self.name, [_('See also')], self.options,
lineno, content_offset, block_text, state, state_machine) self.content, self.lineno, self.content_offset, self.block_text,
if arguments: self.state, self.state_machine)
argnodes, msgs = state.inline_text(arguments[0], lineno) if self.arguments:
argnodes, msgs = self.state.inline_text(self.arguments[0],
self.lineno)
para = nodes.paragraph() para = nodes.paragraph()
para += argnodes para += argnodes
para += msgs para += msgs
ret[0].insert(1, para) ret[0].insert(1, para)
return ret return ret
seealso_directive.content = 1
seealso_directive.arguments = (0, 1, 1)
directives.register_directive('seealso', seealso_directive)
# ------ production list (for the reference) -----------------------------------
token_re = re.compile('`([a-z_]+)`') token_re = re.compile('`([a-z_]+)`')
@ -297,14 +344,24 @@ def token_xrefs(text, env):
retnodes.append(nodes.Text(text[pos:], text[pos:])) retnodes.append(nodes.Text(text[pos:], text[pos:]))
return retnodes return retnodes
def productionlist_directive(name, arguments, options, content, lineno, class ProductionList(Directive):
content_offset, block_text, state, state_machine): """
env = state.document.settings.env Directive to list grammar productions.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
def run(self):
env = self.state.document.settings.env
node = addnodes.productionlist() node = addnodes.productionlist()
messages = [] messages = []
i = 0 i = 0
for rule in arguments[0].split('\n'): for rule in self.arguments[0].split('\n'):
if i == 0 and ':' not in rule: if i == 0 and ':' not in rule:
# production group # production group
continue continue
@ -317,31 +374,36 @@ def productionlist_directive(name, arguments, options, content, lineno,
subnode['tokenname'] = name.strip() subnode['tokenname'] = name.strip()
if subnode['tokenname']: if subnode['tokenname']:
idname = 'grammar-token-%s' % subnode['tokenname'] idname = 'grammar-token-%s' % subnode['tokenname']
if idname not in state.document.ids: if idname not in self.state.document.ids:
subnode['ids'].append(idname) subnode['ids'].append(idname)
state.document.note_implicit_target(subnode, subnode) self.state.document.note_implicit_target(subnode, subnode)
env.note_reftarget('token', subnode['tokenname'], idname) env.note_reftarget('token', subnode['tokenname'], idname)
subnode.extend(token_xrefs(tokens, env)) subnode.extend(token_xrefs(tokens, env))
node.append(subnode) node.append(subnode)
return [node] + messages return [node] + messages
productionlist_directive.content = 0
productionlist_directive.arguments = (1, 0, 1)
directives.register_directive('productionlist', productionlist_directive)
class Glossary(Directive):
"""
Directive to create a glossary with cross-reference targets
for :term: roles.
"""
# ------ glossary directive ---------------------------------------------------- has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def glossary_directive(name, arguments, options, content, lineno, def run(self):
content_offset, block_text, state, state_machine): env = self.state.document.settings.env
"""Glossary with cross-reference targets for :term: roles."""
env = state.document.settings.env
node = addnodes.glossary() node = addnodes.glossary()
node.document = state.document node.document = self.state.document
state.nested_parse(content, content_offset, node) self.state.nested_parse(self.content, self.content_offset, node)
# the content should be definition lists # the content should be definition lists
dls = [child for child in node if isinstance(child, nodes.definition_list)] dls = [child for child in node
if isinstance(child, nodes.definition_list)]
# now, extract definition terms to enable cross-reference creation # now, extract definition terms to enable cross-reference creation
for dl in dls: for dl in dls:
dl['classes'].append('glossary') dl['classes'].append('glossary')
@ -355,60 +417,80 @@ def glossary_directive(name, arguments, options, content, lineno,
env.gloss_entries.add(new_id) env.gloss_entries.add(new_id)
li[0]['names'].append(new_id) li[0]['names'].append(new_id)
li[0]['ids'].append(new_id) li[0]['ids'].append(new_id)
state.document.settings.env.note_reftarget('term', termtext.lower(), env.note_reftarget('term', termtext.lower(), new_id)
new_id)
# add an index entry too # add an index entry too
indexnode = addnodes.index() indexnode = addnodes.index()
indexnode['entries'] = [('single', termtext, new_id, termtext)] indexnode['entries'] = [('single', termtext, new_id, termtext)]
li.insert(0, indexnode) li.insert(0, indexnode)
return [node] return [node]
glossary_directive.content = 1
glossary_directive.arguments = (0, 0, 0)
directives.register_directive('glossary', glossary_directive)
class Centered(Directive):
"""
Directive to create a centered line of bold text.
"""
# ------ miscellaneous markup -------------------------------------------------- has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
def centered_directive(name, arguments, options, content, lineno, def run(self):
content_offset, block_text, state, state_machine): if not self.arguments:
if not arguments:
return [] return []
subnode = addnodes.centered() subnode = addnodes.centered()
inodes, messages = state.inline_text(arguments[0], lineno) inodes, messages = self.state.inline_text(self.arguments[0],
self.lineno)
subnode.extend(inodes) subnode.extend(inodes)
return [subnode] + messages return [subnode] + messages
centered_directive.arguments = (1, 0, 1)
directives.register_directive('centered', centered_directive)
def acks_directive(name, arguments, options, content, lineno, class Acks(Directive):
content_offset, block_text, state, state_machine): """
Directive for a list of names.
"""
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
node = addnodes.acks() node = addnodes.acks()
node.document = state.document node.document = self.state.document
state.nested_parse(content, content_offset, node) self.state.nested_parse(self.content, self.content_offset, node)
if len(node.children) != 1 or not isinstance(node.children[0], if len(node.children) != 1 or not isinstance(node.children[0],
nodes.bullet_list): nodes.bullet_list):
return [state.document.reporter.warning('.. acks content is not a list', return [self.state.document.reporter.warning(
line=lineno)] '.. acks content is not a list', line=self.lineno)]
return [node] return [node]
acks_directive.content = 1
acks_directive.arguments = (0, 0, 0)
directives.register_directive('acks', acks_directive)
class HList(Directive):
"""
Directive for a list that gets compacted horizontally.
"""
def hlist_directive(name, arguments, options, content, lineno, has_content = True
content_offset, block_text, state, state_machine): required_arguments = 0
ncolumns = options.get('columns', 2) optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'columns': int,
}
def run(self):
ncolumns = self.options.get('columns', 2)
node = nodes.paragraph() node = nodes.paragraph()
node.document = state.document node.document = self.state.document
state.nested_parse(content, content_offset, node) self.state.nested_parse(self.content, self.content_offset, node)
if len(node.children) != 1 or not isinstance(node.children[0], if len(node.children) != 1 or not isinstance(node.children[0],
nodes.bullet_list): nodes.bullet_list):
return [state.document.reporter.warning( return [self.state.document.reporter.warning(
'.. hlist content is not a list', line=lineno)] '.. hlist content is not a list', line=self.lineno)]
fulllist = node.children[0] fulllist = node.children[0]
# create a hlist node where the items are distributed # create a hlist node where the items are distributed
npercol, nmore = divmod(len(fulllist), ncolumns) npercol, nmore = divmod(len(fulllist), ncolumns)
@ -423,23 +505,41 @@ def hlist_directive(name, arguments, options, content, lineno,
newnode += col newnode += col
return [newnode] return [newnode]
hlist_directive.content = 1
hlist_directive.arguments = (0, 0, 0)
hlist_directive.options = {'columns': int}
directives.register_directive('hlist', hlist_directive)
class TabularColumns(Directive):
"""
Directive to give an explicit tabulary column definition to LaTeX.
"""
def tabularcolumns_directive(name, arguments, options, content, lineno, has_content = False
content_offset, block_text, state, state_machine): required_arguments = 1
# support giving explicit tabulary column definition to latex optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
def run(self):
node = addnodes.tabular_col_spec() node = addnodes.tabular_col_spec()
node['spec'] = arguments[0] node['spec'] = self.arguments[0]
return [node] return [node]
tabularcolumns_directive.content = 0
tabularcolumns_directive.arguments = (1, 0, 1)
directives.register_directive('tabularcolumns', tabularcolumns_directive)
directives.register_directive('toctree', TocTree)
directives.register_directive('module', Module)
directives.register_directive('currentmodule', CurrentModule)
directives.register_directive('sectionauthor', Author)
directives.register_directive('moduleauthor', Author)
directives.register_directive('program', Program)
directives.register_directive('index', Index)
directives.register_directive('deprecated', VersionChange)
directives.register_directive('versionadded', VersionChange)
directives.register_directive('versionchanged', VersionChange)
directives.register_directive('seealso', SeeAlso)
directives.register_directive('productionlist', ProductionList)
directives.register_directive('glossary', Glossary)
directives.register_directive('centered', Centered)
directives.register_directive('acks', Acks)
directives.register_directive('hlist', HList)
directives.register_directive('tabularcolumns', TabularColumns)
# register the standard rst class directive under a different name # register the standard rst class directive under a different name