sphinx/sphinx/domains/c.py
Georg Brandl c3eb669f8a Added the any role that can be used to find a cross-reference of
*any* type in *any* domain.  Custom domains should implement the new
`~Domain.resolve_any_xref` method to make this work properly.
2014-09-19 12:59:18 +02:00

296 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""
sphinx.domains.c
~~~~~~~~~~~~~~~~
The C language domain.
:copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import string
from docutils import nodes
from sphinx import addnodes
from sphinx.roles import XRefRole
from sphinx.locale import l_, _
from sphinx.domains import Domain, ObjType
from sphinx.directives import ObjectDescription
from sphinx.util.nodes import make_refnode
from sphinx.util.docfields import Field, TypedField
# RE to split at word boundaries
wsplit_re = re.compile(r'(\W+)')
# REs for C signatures
c_sig_re = re.compile(
r'''^([^(]*?) # return type
([\w:.]+) \s* # thing name (colon allowed for C++)
(?: \((.*)\) )? # 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_arg_sig_re = re.compile(
r'''^\s*([^(,]+?) # return type
\( ([^()]+) \) \s* # name in parentheses
\( (.*) \) # arguments
(\s+const)? # const specifier
\s*(?=$|,) # end with comma or end of string
''', re.VERBOSE)
c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$')
class CObject(ObjectDescription):
"""
Description of a C language object.
"""
doc_field_types = [
TypedField('parameter', label=l_('Parameters'),
names=('param', 'parameter', 'arg', 'argument'),
typerolename='type', typenames=('type',)),
Field('returnvalue', label=l_('Returns'), has_arg=False,
names=('returns', 'return')),
Field('returntype', label=l_('Return type'), has_arg=False,
names=('rtype',)),
]
# These C types aren't described anywhere, so don't try to create
# a cross-reference to them
stopwords = set((
'const', 'void', 'char', 'wchar_t', 'int', 'short',
'long', 'float', 'double', 'unsigned', 'signed', 'FILE',
'clock_t', 'time_t', 'ptrdiff_t', 'size_t', 'ssize_t',
'struct', '_Bool',
))
def _parse_type(self, node, ctype):
# add cross-ref nodes for all words
for part in [_f for _f in wsplit_re.split(ctype) if _f]:
tnode = nodes.Text(part, part)
if part[0] in string.ascii_letters+'_' and \
part not in self.stopwords:
pnode = addnodes.pending_xref(
'', refdomain='c', reftype='type', reftarget=part,
modname=None, classname=None)
pnode += tnode
node += pnode
else:
node += tnode
def _parse_arglist(self, arglist):
while True:
m = c_funcptr_arg_sig_re.match(arglist)
if m:
yield m.group()
arglist = c_funcptr_arg_sig_re.sub('', arglist)
if ',' in arglist:
_, arglist = arglist.split(',', 1)
else:
break
else:
if ',' in arglist:
arg, arglist = arglist.split(',', 1)
yield arg
else:
yield arglist
break
def handle_signature(self, sig, signode):
"""Transform a C signature into RST nodes."""
# first try the function pointer signature regex, it's more specific
m = c_funcptr_sig_re.match(sig)
if m is None:
m = c_sig_re.match(sig)
if m is None:
raise ValueError('no match')
rettype, name, arglist, const = m.groups()
signode += addnodes.desc_type('', '')
self._parse_type(signode[-1], rettype)
try:
classname, funcname = name.split('::', 1)
classname += '::'
signode += addnodes.desc_addname(classname, classname)
signode += addnodes.desc_name(funcname, funcname)
# name (the full name) is still both parts
except ValueError:
signode += addnodes.desc_name(name, name)
# clean up parentheses from canonical name
m = c_funcptr_name_re.match(name)
if m:
name = m.group(1)
typename = self.env.temp_data.get('c:type')
if self.name == 'c:member' and typename:
fullname = typename + '.' + name
else:
fullname = name
if not arglist:
if self.objtype == 'function':
# for functions, add an empty parameter list
signode += addnodes.desc_parameterlist()
if const:
signode += addnodes.desc_addname(const, const)
return fullname
paramlist = addnodes.desc_parameterlist()
arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup
# this messes up function pointer types, but not too badly ;)
for arg in self._parse_arglist(arglist):
arg = arg.strip()
param = addnodes.desc_parameter('', '', noemph=True)
try:
m = c_funcptr_arg_sig_re.match(arg)
if m:
self._parse_type(param, m.group(1) + '(')
param += nodes.emphasis(m.group(2), m.group(2))
self._parse_type(param, ')(' + m.group(3) + ')')
if m.group(4):
param += addnodes.desc_addname(m.group(4), m.group(4))
else:
ctype, argname = arg.rsplit(' ', 1)
self._parse_type(param, ctype)
# separate by non-breaking space in the output
param += nodes.emphasis(' '+argname, u'\xa0'+argname)
except ValueError:
# no argument name given, only the type
self._parse_type(param, arg)
paramlist += param
signode += paramlist
if const:
signode += addnodes.desc_addname(const, const)
return fullname
def get_index_text(self, name):
if self.objtype == 'function':
return _('%s (C function)') % name
elif self.objtype == 'member':
return _('%s (C member)') % name
elif self.objtype == 'macro':
return _('%s (C macro)') % name
elif self.objtype == 'type':
return _('%s (C type)') % name
elif self.objtype == 'var':
return _('%s (C variable)') % name
else:
return ''
def add_target_and_index(self, name, sig, signode):
# for C API items we add a prefix since names are usually not qualified
# by a module name and so easily clash with e.g. section titles
targetname = 'c.' + name
if targetname not in self.state.document.ids:
signode['names'].append(targetname)
signode['ids'].append(targetname)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
inv = self.env.domaindata['c']['objects']
if name in inv:
self.state_machine.reporter.warning(
'duplicate C object description of %s, ' % name +
'other instance in ' + self.env.doc2path(inv[name][0]),
line=self.lineno)
inv[name] = (self.env.docname, self.objtype)
indextext = self.get_index_text(name)
if indextext:
self.indexnode['entries'].append(('single', indextext,
targetname, ''))
def before_content(self):
self.typename_set = False
if self.name == 'c:type':
if self.names:
self.env.temp_data['c:type'] = self.names[0]
self.typename_set = True
def after_content(self):
if self.typename_set:
self.env.temp_data['c:type'] = None
class CXRefRole(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target):
if not has_explicit_title:
target = target.lstrip('~') # only has a meaning for the title
# if the first character is a tilde, don't display the module/class
# parts of the contents
if title[0:1] == '~':
title = title[1:]
dot = title.rfind('.')
if dot != -1:
title = title[dot+1:]
return title, target
class CDomain(Domain):
"""C language domain."""
name = 'c'
label = 'C'
object_types = {
'function': ObjType(l_('function'), 'func'),
'member': ObjType(l_('member'), 'member'),
'macro': ObjType(l_('macro'), 'macro'),
'type': ObjType(l_('type'), 'type'),
'var': ObjType(l_('variable'), 'data'),
}
directives = {
'function': CObject,
'member': CObject,
'macro': CObject,
'type': CObject,
'var': CObject,
}
roles = {
'func' : CXRefRole(fix_parens=True),
'member': CXRefRole(),
'macro': CXRefRole(),
'data': CXRefRole(),
'type': CXRefRole(),
}
initial_data = {
'objects': {}, # fullname -> docname, objtype
}
def clear_doc(self, docname):
for fullname, (fn, _) in list(self.data['objects'].items()):
if fn == docname:
del self.data['objects'][fullname]
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
# strip pointer asterisk
target = target.rstrip(' *')
if target not in self.data['objects']:
return None
obj = self.data['objects'][target]
return make_refnode(builder, fromdocname, obj[0], 'c.' + target,
contnode, target)
def resolve_any_xref(self, env, fromdocname, builder, target,
node, contnode):
# strip pointer asterisk
target = target.rstrip(' *')
if target not in self.data['objects']:
return []
obj = self.data['objects'][target]
return [('c:' + self.role_for_objtype(obj[1]),
make_refnode(builder, fromdocname, obj[0], 'c.' + target,
contnode, target))]
def get_objects(self):
for refname, (docname, type) in list(self.data['objects'].items()):
yield (refname, refname, type, docname, 'c.' + refname, 1)