summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2009-07-05 12:24:27 +0200
committerGeorg Brandl <georg@python.org>2009-07-05 12:24:27 +0200
commit79aeac18027dc955cddf4657944aa7bd2661b47d (patch)
tree8a6a70cd7ffedbb03b6830f08548d072f77d96db
parent9b7ad337720fd49371ca9757b5f928fd9eeb6722 (diff)
downloadsphinx-79aeac18027dc955cddf4657944aa7bd2661b47d.tar.gz
Move domain-specific code around a bit; builtin domains are now completely in sphinx.domains. Test suite does not pass yet.
-rw-r--r--doc/markup/desc.rst4
-rw-r--r--sphinx/application.py8
-rw-r--r--sphinx/directives/desc.py414
-rw-r--r--sphinx/domains.py467
-rw-r--r--sphinx/environment.py80
-rw-r--r--sphinx/roles.py210
-rw-r--r--tests/root/markup.txt4
7 files changed, 573 insertions, 614 deletions
diff --git a/doc/markup/desc.rst b/doc/markup/desc.rst
index ec8ede37..4cfba05f 100644
--- a/doc/markup/desc.rst
+++ b/doc/markup/desc.rst
@@ -119,8 +119,8 @@ The directives are:
Describes a "simple" C macro. Simple macros are macros which are used
for code expansion, but which do not take arguments so cannot be described as
functions. This is not to be used for simple constant definitions. Examples
- of its use in the Python documentation include :cmacro:`PyObject_HEAD` and
- :cmacro:`Py_BEGIN_ALLOW_THREADS`.
+ of its use in the Python documentation include :c:macro:`PyObject_HEAD` and
+ :c:macro:`Py_BEGIN_ALLOW_THREADS`.
.. directive:: .. ctype:: name
diff --git a/sphinx/application.py b/sphinx/application.py
index 9b6cf36f..5e34232c 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -20,7 +20,7 @@ from docutils import nodes
from docutils.parsers.rst import directives, roles
import sphinx
-from sphinx.roles import make_xref_role, simple_link_func
+from sphinx.roles import XRefRole
from sphinx.config import Config
from sphinx.errors import SphinxError, SphinxWarning, ExtensionError
from sphinx.domains import domains
@@ -310,14 +310,16 @@ class Sphinx(object):
parse_node)
directives.register_directive(directivename,
directive_dwim(GenericDesc))
- role_func = make_xref_role(simple_link_func, innernodeclass=ref_nodeclass)
+ # XXX support more options?
+ role_func = XRefRole('', innernodeclass=ref_nodeclass)
roles.register_local_role(rolename, role_func)
def add_crossref_type(self, directivename, rolename, indextemplate='',
ref_nodeclass=None):
additional_xref_types[directivename] = (rolename, indextemplate, None)
directives.register_directive(directivename, directive_dwim(Target))
- role_func = make_xref_role(simple_link_func, innernodeclass=ref_nodeclass)
+ # XXX support more options
+ role_func = XRefRole('', innernodeclass=ref_nodeclass)
roles.register_local_role(rolename, role_func)
def add_transform(self, transform):
diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py
index 666397c3..dfd71382 100644
--- a/sphinx/directives/desc.py
+++ b/sphinx/directives/desc.py
@@ -14,7 +14,6 @@ from docutils import nodes
from docutils.parsers.rst import directives
from sphinx import addnodes
-from sphinx.domains import Domain, domains
from sphinx.locale import l_
from sphinx.util import ws_re
from sphinx.util.compat import Directive, directive_dwim
@@ -33,39 +32,10 @@ def _is_only_paragraph(node):
return False
-# 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)
-
-py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ','
-
-# 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'\\(?=[^\\])')
@@ -278,360 +248,6 @@ class DescDirective(Directive):
return [self.indexnode, node]
-class PythonDesc(DescDirective):
- """
- Description of a general Python object.
- """
-
- def get_signature_prefix(self, sig):
- """
- May return a prefix to put before the object name in the signature.
- """
- return ''
-
- def needs_arglist(self):
- """
- May return true if an empty argument list is to be generated even if
- the document contains none.
- """
- return False
-
- def parse_signature(self, sig, signode):
- """
- Transform a Python signature into RST nodes.
- Returns (fully qualified name of the thing, classname if any).
-
- If inside a class, the current class name is handled intelligently:
- * it is stripped from the displayed name if present
- * it is added to the full name (return value) if not present
- """
- m = py_sig_re.match(sig)
- if m is None:
- raise ValueError
- classname, name, arglist, retann = m.groups()
-
- if self.env.currclass:
- add_module = False
- if classname and classname.startswith(self.env.currclass):
- fullname = classname + name
- # class name is given again in the signature
- classname = classname[len(self.env.currclass):].lstrip('.')
- elif classname:
- # class name is given in the signature, but different
- # (shouldn't happen)
- fullname = self.env.currclass + '.' + classname + name
- else:
- # class name is not given in the signature
- fullname = self.env.currclass + '.' + name
- else:
- add_module = True
- fullname = classname and classname + name or name
-
- prefix = self.get_signature_prefix(sig)
- if prefix:
- signode += addnodes.desc_annotation(prefix, prefix)
-
- if classname:
- signode += addnodes.desc_addname(classname, classname)
- # exceptions are a special case, since they are documented in the
- # 'exceptions' module.
- elif add_module and self.env.config.add_module_names:
- modname = self.options.get('module', self.env.currmodule)
- if modname and modname != 'exceptions':
- nodetext = modname + '.'
- signode += addnodes.desc_addname(nodetext, nodetext)
-
- signode += addnodes.desc_name(name, name)
- if not arglist:
- if self.needs_arglist():
- # for callables, add an empty parameter list
- signode += addnodes.desc_parameterlist()
- if retann:
- signode += addnodes.desc_returns(retann, retann)
- return fullname, classname
- signode += addnodes.desc_parameterlist()
-
- stack = [signode[-1]]
- for token in py_paramlist_re.split(arglist):
- if token == '[':
- opt = addnodes.desc_optional()
- stack[-1] += opt
- stack.append(opt)
- elif token == ']':
- try:
- stack.pop()
- except IndexError:
- raise ValueError
- elif not token or token == ',' or token.isspace():
- pass
- else:
- token = token.strip()
- stack[-1] += addnodes.desc_parameter(token, token)
- if len(stack) != 1:
- raise ValueError
- if retann:
- signode += addnodes.desc_returns(retann, retann)
- return fullname, classname
-
- def get_index_text(self, modname, name):
- """
- Return the text for the index entry of the object.
- """
- raise NotImplementedError('must be implemented in subclasses')
-
- def add_target_and_index(self, name_cls, sig, signode):
- modname = self.options.get('module', self.env.currmodule)
- fullname = (modname and modname + '.' or '') + name_cls[0]
- # note target
- if fullname not in self.state.document.ids:
- signode['names'].append(fullname)
- signode['ids'].append(fullname)
- signode['first'] = (not self.names)
- self.state.document.note_explicit_target(signode)
- self.env.note_descref(fullname, self.desctype, self.lineno)
-
- indextext = self.get_index_text(modname, name_cls)
- if indextext:
- self.indexnode['entries'].append(('single', indextext,
- fullname, fullname))
-
- def before_content(self):
- # needed for automatic qualification of members (reset in subclasses)
- self.clsname_set = False
-
- def after_content(self):
- if self.clsname_set:
- self.env.currclass = None
-
-
-class ModulelevelDesc(PythonDesc):
- """
- Description of an object on module level (functions, data).
- """
-
- def needs_arglist(self):
- return self.desctype == 'function'
-
- 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):
- """
- Description of a class-like object (classes, interfaces, exceptions).
- """
-
- def get_signature_prefix(self, sig):
- return self.desctype + ' '
-
- 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):
- """
- Description of a class member (methods, attributes).
- """
-
- def needs_arglist(self):
- return self.desctype.endswith('method')
-
- def get_signature_prefix(self, sig):
- if self.desctype == 'staticmethod':
- return 'static '
- elif self.desctype == 'classmethod':
- return 'classmethod '
- return ''
-
- 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):
- """
- Description of a C language object.
- """
-
- # 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
- for part in filter(None, wsplit_re.split(ctype)):
- tnode = nodes.Text(part, part)
- if part[0] in string.ascii_letters+'_' and \
- part not in self.stopwords:
- pnode = addnodes.pending_xref(
- '', reftype='ctype', reftarget=part,
- modname=None, classname=None)
- pnode += tnode
- node += pnode
- else:
- node += tnode
-
- def parse_signature(self, sig, signode):
- """Transform a C (or 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)
- if not arglist:
- if self.desctype == 'cfunction':
- # for functions, add an empty parameter list
- signode += addnodes.desc_parameterlist()
- if const:
- signode += addnodes.desc_addname(const, const)
- return name
-
- paramlist = addnodes.desc_parameterlist()
- arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup
- # this messes up function pointer types, but not too badly ;)
- args = arglist.split(',')
- for arg in args:
- arg = arg.strip()
- param = addnodes.desc_parameter('', '', noemph=True)
- try:
- ctype, argname = arg.rsplit(' ', 1)
- except ValueError:
- # no argument name given, only the type
- self._parse_type(param, arg)
- else:
- self._parse_type(param, ctype)
- param += nodes.emphasis(' '+argname, ' '+argname)
- paramlist += param
- signode += paramlist
- if const:
- signode += addnodes.desc_addname(const, const)
- return name
-
- def get_index_text(self, 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 ''
-
- def add_target_and_index(self, name, sig, signode):
- # note target
- if name not in self.state.document.ids:
- signode['names'].append(name)
- signode['ids'].append(name)
- signode['first'] = (not self.names)
- self.state.document.note_explicit_target(signode)
- self.env.note_descref(name, self.desctype, self.lineno)
-
- indextext = self.get_index_text(name)
- if indextext:
- self.indexnode['entries'].append(('single', indextext, name, name))
-
-
class CmdoptionDesc(DescDirective):
"""
Description of a command-line option (.. cmdoption).
@@ -768,33 +384,3 @@ directives.register_directive('default-domain', directive_dwim(DefaultDomain))
directives.register_directive('describe', directive_dwim(DescDirective))
directives.register_directive('cmdoption', directive_dwim(CmdoptionDesc))
directives.register_directive('envvar', directive_dwim(GenericDesc))
-
-
-class PythonDomain(Domain):
- name = 'py'
- label = 'Python'
- directives = {
- 'function': ModulelevelDesc,
- 'data': ModulelevelDesc,
- 'class': ClasslikeDesc,
- 'exception': ClasslikeDesc,
- 'method': ClassmemberDesc,
- 'classmethod': ClassmemberDesc,
- 'staticmethod': ClassmemberDesc,
- 'attribute': ClassmemberDesc,
- }
-
-class CDomain(Domain):
- name = 'c'
- label = 'C'
- directives = {
- 'function': CDesc,
- 'member': CDesc,
- 'macro': CDesc,
- 'type': CDesc,
- 'var': CDesc,
- }
-
-
-domains['py'] = PythonDomain
-domains['c'] = CDomain
diff --git a/sphinx/domains.py b/sphinx/domains.py
index 3b31bd5a..9fa41090 100644
--- a/sphinx/domains.py
+++ b/sphinx/domains.py
@@ -10,11 +10,476 @@
:license: BSD, see LICENSE for details.
"""
+import re
+
+from sphinx import addnodes
+from sphinx.roles import XRefRole
+from sphinx.directives import DescDirective
+
+
class Domain(object):
name = ''
directives = {}
roles = {}
label = ''
+
+# 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)
+
+py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ','
+
+
+class PythonDesc(DescDirective):
+ """
+ Description of a general Python object.
+ """
+
+ def get_signature_prefix(self, sig):
+ """
+ May return a prefix to put before the object name in the signature.
+ """
+ return ''
+
+ def needs_arglist(self):
+ """
+ May return true if an empty argument list is to be generated even if
+ the document contains none.
+ """
+ return False
+
+ def parse_signature(self, sig, signode):
+ """
+ Transform a Python signature into RST nodes.
+ Returns (fully qualified name of the thing, classname if any).
+
+ If inside a class, the current class name is handled intelligently:
+ * it is stripped from the displayed name if present
+ * it is added to the full name (return value) if not present
+ """
+ m = py_sig_re.match(sig)
+ if m is None:
+ raise ValueError
+ classname, name, arglist, retann = m.groups()
+
+ if self.env.currclass:
+ add_module = False
+ if classname and classname.startswith(self.env.currclass):
+ fullname = classname + name
+ # class name is given again in the signature
+ classname = classname[len(self.env.currclass):].lstrip('.')
+ elif classname:
+ # class name is given in the signature, but different
+ # (shouldn't happen)
+ fullname = self.env.currclass + '.' + classname + name
+ else:
+ # class name is not given in the signature
+ fullname = self.env.currclass + '.' + name
+ else:
+ add_module = True
+ fullname = classname and classname + name or name
+
+ prefix = self.get_signature_prefix(sig)
+ if prefix:
+ signode += addnodes.desc_annotation(prefix, prefix)
+
+ if classname:
+ signode += addnodes.desc_addname(classname, classname)
+ # exceptions are a special case, since they are documented in the
+ # 'exceptions' module.
+ elif add_module and self.env.config.add_module_names:
+ modname = self.options.get('module', self.env.currmodule)
+ if modname and modname != 'exceptions':
+ nodetext = modname + '.'
+ signode += addnodes.desc_addname(nodetext, nodetext)
+
+ signode += addnodes.desc_name(name, name)
+ if not arglist:
+ if self.needs_arglist():
+ # for callables, add an empty parameter list
+ signode += addnodes.desc_parameterlist()
+ if retann:
+ signode += addnodes.desc_returns(retann, retann)
+ return fullname, classname
+ signode += addnodes.desc_parameterlist()
+
+ stack = [signode[-1]]
+ for token in py_paramlist_re.split(arglist):
+ if token == '[':
+ opt = addnodes.desc_optional()
+ stack[-1] += opt
+ stack.append(opt)
+ elif token == ']':
+ try:
+ stack.pop()
+ except IndexError:
+ raise ValueError
+ elif not token or token == ',' or token.isspace():
+ pass
+ else:
+ token = token.strip()
+ stack[-1] += addnodes.desc_parameter(token, token)
+ if len(stack) != 1:
+ raise ValueError
+ if retann:
+ signode += addnodes.desc_returns(retann, retann)
+ return fullname, classname
+
+ def get_index_text(self, modname, name):
+ """
+ Return the text for the index entry of the object.
+ """
+ raise NotImplementedError('must be implemented in subclasses')
+
+ def add_target_and_index(self, name_cls, sig, signode):
+ modname = self.options.get('module', self.env.currmodule)
+ fullname = (modname and modname + '.' or '') + name_cls[0]
+ # note target
+ if fullname not in self.state.document.ids:
+ signode['names'].append(fullname)
+ signode['ids'].append(fullname)
+ signode['first'] = (not self.names)
+ self.state.document.note_explicit_target(signode)
+ self.env.note_descref(fullname, self.desctype, self.lineno)
+
+ indextext = self.get_index_text(modname, name_cls)
+ if indextext:
+ self.indexnode['entries'].append(('single', indextext,
+ fullname, fullname))
+
+ def before_content(self):
+ # needed for automatic qualification of members (reset in subclasses)
+ self.clsname_set = False
+
+ def after_content(self):
+ if self.clsname_set:
+ self.env.currclass = None
+
+
+class ModulelevelDesc(PythonDesc):
+ """
+ Description of an object on module level (functions, data).
+ """
+
+ def needs_arglist(self):
+ return self.desctype == 'function'
+
+ 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):
+ """
+ Description of a class-like object (classes, interfaces, exceptions).
+ """
+
+ def get_signature_prefix(self, sig):
+ return self.desctype + ' '
+
+ 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):
+ """
+ Description of a class member (methods, attributes).
+ """
+
+ def needs_arglist(self):
+ return self.desctype.endswith('method')
+
+ def get_signature_prefix(self, sig):
+ if self.desctype == 'staticmethod':
+ return 'static '
+ elif self.desctype == 'classmethod':
+ return 'classmethod '
+ return ''
+
+ 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 PyXRefRole(XRefRole):
+ def process_link(self, env, pnode, has_explicit_title, title, target):
+ pnode['modname'] = env.currmodule
+ pnode['classname'] = env.currclass
+ if not has_explicit_title:
+ title = title.lstrip('.') # only has a meaning for the target
+ 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:]
+ # if the first character is a dot, search more specific namespaces first
+ # else search builtins first
+ if target[0:1] == '.':
+ target = target[1:]
+ pnode['refspecific'] = True
+ return title, target
+
+
+class PythonDomain(Domain):
+ """Python language domain."""
+ name = 'py'
+ label = 'Python'
+ directives = {
+ 'function': ModulelevelDesc,
+ 'data': ModulelevelDesc,
+ 'class': ClasslikeDesc,
+ 'exception': ClasslikeDesc,
+ 'method': ClassmemberDesc,
+ 'classmethod': ClassmemberDesc,
+ 'staticmethod': ClassmemberDesc,
+ 'attribute': ClassmemberDesc,
+ }
+ roles = {
+ 'data': PyXRefRole('py'),
+ 'exc': PyXRefRole('py'),
+ 'func': PyXRefRole('py', True),
+ 'class': PyXRefRole('py'),
+ 'const': PyXRefRole('py'),
+ 'attr': PyXRefRole('py'),
+ 'meth': PyXRefRole('py', True),
+ 'mod': PyXRefRole('py'),
+ 'obj': PyXRefRole('py'),
+ }
+
+
+# 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++ 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*\)$')
+
+
+class CDesc(DescDirective):
+ """
+ Description of a C language object.
+ """
+
+ # 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
+ for part in filter(None, wsplit_re.split(ctype)):
+ tnode = nodes.Text(part, part)
+ if part[0] in string.ascii_letters+'_' and \
+ part not in self.stopwords:
+ pnode = addnodes.pending_xref(
+ '', reftype='ctype', reftarget=part,
+ modname=None, classname=None)
+ pnode += tnode
+ node += pnode
+ else:
+ node += tnode
+
+ def parse_signature(self, sig, signode):
+ """Transform a C (or 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)
+ if not arglist:
+ if self.desctype == 'cfunction':
+ # for functions, add an empty parameter list
+ signode += addnodes.desc_parameterlist()
+ if const:
+ signode += addnodes.desc_addname(const, const)
+ return name
+
+ paramlist = addnodes.desc_parameterlist()
+ arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup
+ # this messes up function pointer types, but not too badly ;)
+ args = arglist.split(',')
+ for arg in args:
+ arg = arg.strip()
+ param = addnodes.desc_parameter('', '', noemph=True)
+ try:
+ ctype, argname = arg.rsplit(' ', 1)
+ except ValueError:
+ # no argument name given, only the type
+ self._parse_type(param, arg)
+ else:
+ self._parse_type(param, ctype)
+ param += nodes.emphasis(' '+argname, ' '+argname)
+ paramlist += param
+ signode += paramlist
+ if const:
+ signode += addnodes.desc_addname(const, const)
+ return name
+
+ def get_index_text(self, 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 ''
+
+ def add_target_and_index(self, name, sig, signode):
+ # note target
+ if name not in self.state.document.ids:
+ signode['names'].append(name)
+ signode['ids'].append(name)
+ signode['first'] = (not self.names)
+ self.state.document.note_explicit_target(signode)
+ self.env.note_descref(name, self.desctype, self.lineno)
+
+ indextext = self.get_index_text(name)
+ if indextext:
+ self.indexnode['entries'].append(('single', indextext, name, name))
+
+
+class CDomain(Domain):
+ """C language domain."""
+ name = 'c'
+ label = 'C'
+ directives = {
+ 'function': CDesc,
+ 'member': CDesc,
+ 'macro': CDesc,
+ 'type': CDesc,
+ 'var': CDesc,
+ }
+ roles = {
+ 'member': XRefRole('c'),
+ 'macro': XRefRole('c'),
+ 'func' : XRefRole('c', True),
+ 'data': XRefRole('c'),
+ 'type': XRefRole('c'),
+ }
+
+
# this contains all registered domains
-domains = {}
+domains = {
+ 'py': PythonDomain,
+ 'c': CDomain,
+}
diff --git a/sphinx/environment.py b/sphinx/environment.py
index d505229b..9393b6ff 100644
--- a/sphinx/environment.py
+++ b/sphinx/environment.py
@@ -48,6 +48,7 @@ from sphinx.errors import SphinxError
from sphinx.domains import domains
from sphinx.directives import additional_xref_types
+orig_role_function = roles.role
orig_directive_function = directives.directive
default_settings = {
@@ -617,6 +618,21 @@ class BuildEnvironment:
document)
directives.directive = directive
+ def role(role_name, language_module, lineno, reporter):
+ if ':' in role_name:
+ domain_name, role_name = role_name.split(':', 1)
+ if domain_name in domains:
+ domain = domains[domain_name]
+ if role_name in domain.roles:
+ return domain.roles[role_name], []
+ elif self.default_domain is not None:
+ role = self.default_domain.roles.get(role_name)
+ if role is not None:
+ return role, []
+ return orig_role_function(role_name, language_module,
+ lineno, reporter)
+ roles.role = role
+
# publish manually
pub = Publisher(reader=SphinxStandaloneReader(),
writer=SphinxDummyWriter(),
@@ -1625,67 +1641,3 @@ class BuildEnvironment:
if newname is None:
return None, None
return newname, self.descrefs[newname]
-
- def find_keyword(self, keyword, avoid_fuzzy=False, cutoff=0.6, n=20):
- """
- Find keyword matches for a keyword. If there's an exact match,
- just return it, else return a list of fuzzy matches if avoid_fuzzy
- isn't True.
-
- Keywords searched are: first modules, then descrefs.
-
- Returns: None if nothing found
- (type, docname, anchorname) if exact match found
- list of (quality, type, docname, anchorname, description)
- if fuzzy
- """
-
- if keyword in self.modules:
- docname, title, system, deprecated = self.modules[keyword]
- return 'module', docname, 'module-' + keyword
- if keyword in self.descrefs:
- docname, ref_type = self.descrefs[keyword]
- return ref_type, docname, keyword
- # special cases
- if '.' not in keyword:
- # exceptions are documented in the exceptions module
- if 'exceptions.'+keyword in self.descrefs:
- docname, ref_type = self.descrefs['exceptions.'+keyword]
- return ref_type, docname, 'exceptions.'+keyword
- # special methods are documented as object methods
- if 'object.'+keyword in self.descrefs:
- docname, ref_type = self.descrefs['object.'+keyword]
- return ref_type, docname, 'object.'+keyword
-
- if avoid_fuzzy:
- return
-
- # find fuzzy matches
- s = difflib.SequenceMatcher()
- s.set_seq2(keyword.lower())
-
- def possibilities():
- for title, (fn, desc, _, _) in self.modules.iteritems():
- yield ('module', fn, 'module-'+title, desc)
- for title, (fn, desctype) in self.descrefs.iteritems():
- yield (desctype, fn, title, '')
-
- def dotsearch(string):
- parts = string.lower().split('.')
- for idx in xrange(0, len(parts)):
- yield '.'.join(parts[idx:])
-
- result = []
- for type, docname, title, desc in possibilities():
- best_res = 0
- for part in dotsearch(title):
- s.set_seq1(part)
- if s.real_quick_ratio() >= cutoff and \
- s.quick_ratio() >= cutoff and \
- s.ratio() >= cutoff and \
- s.ratio() > best_res:
- best_res = s.ratio()
- if best_res:
- result.append((best_res, type, docname, title, desc))
-
- return heapq.nlargest(n, result)
diff --git a/sphinx/roles.py b/sphinx/roles.py
index e59cf070..74f933b2 100644
--- a/sphinx/roles.py
+++ b/sphinx/roles.py
@@ -94,36 +94,6 @@ roles.register_local_role('pep', indexmarkup_role)
roles.register_local_role('rfc', indexmarkup_role)
-def make_xref_role(link_func, nodeclass=None, innernodeclass=None):
- if nodeclass is None:
- nodeclass = addnodes.pending_xref
- if innernodeclass is None:
- innernodeclass = nodes.literal
-
- def func(typ, rawtext, text, lineno, inliner, options={}, content=[]):
- env = inliner.document.settings.env
- if not typ:
- typ = env.config.default_role
- else:
- typ = typ.lower()
- text = utils.unescape(text)
- # if the first character is a bang, don't cross-reference at all
- if text[0:1] == '!':
- text = _fix_parens(typ, text[1:], env)
- return [innernodeclass(rawtext, text, classes=['xref'])], []
- # we want a cross-reference, create the reference node
- pnode = nodeclass(rawtext, reftype=typ, refcaption=False,
- modname=env.currmodule, classname=env.currclass)
- # we may need the line number for warnings
- pnode.line = lineno
- has_explicit_title, title, target = split_explicit_title(text)
- target, title = link_func(env, pnode, has_explicit_title, title, target)
- pnode['reftarget'] = target
- pnode += innernodeclass(rawtext, title, classes=['xref'])
- return [pnode], []
- return func
-
-
def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
return [nodes.emphasis(
rawtext,
@@ -161,112 +131,92 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
return [addnodes.abbreviation(abbr, abbr, explanation=expl)], []
-def normalize_func_parens(env, has_explicit_title, title, target):
- if has_explicit_title:
- if title.endswith('()'):
- # remove parentheses
- title = title[:-2]
- if env.config.add_function_parentheses:
- # add them back to all occurrences if configured
- title += '()'
- # remove parentheses from the target too
- if target.endswith('()'):
- target = target[:-2]
- return target, title
-
+# -- generic cross-reference roles ---------------------------------------------
+
+class XRefRole(object):
+ nodeclass = addnodes.pending_xref
+ innernodeclass = nodes.literal
+
+ def __init__(self, domain_name, fix_parens=False, lowercase=False,
+ nodeclass=None, innernodeclass=None):
+ self.domain_name = domain_name
+ self.fix_parens = fix_parens
+ if nodeclass is not None:
+ self.nodeclass = nodeclass
+ if innernodeclass is not None:
+ self.innernodeclass = innernodeclass
+
+ def process_link(self, env, pnode, has_explicit_title, title, target):
+ return title, ws_re.sub(' ', target)
+
+ def normalize_func_parens(self, env, has_explicit_title, title, target):
+ if not has_explicit_title:
+ if title.endswith('()'):
+ # remove parentheses
+ title = title[:-2]
+ if env.config.add_function_parentheses:
+ # add them back to all occurrences if configured
+ title += '()'
+ # remove parentheses from the target too
+ if target.endswith('()'):
+ target = target[:-2]
+ return title, target
+
+ def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]):
+ env = inliner.document.settings.env
+ if not typ:
+ typ = env.config.default_role
+ else:
+ typ = typ.lower()
+ if ":" in typ:
+ domain, role = typ.split(":", 1)
+ else:
+ domain, role = self.domain_name, typ
+ text = utils.unescape(text)
+ # if the first character is a bang, don't cross-reference at all
+ if text[0:1] == '!':
+ if self.fix_parens:
+ text, _ = self.normalize_func_parens(env, False, text[1:], "")
+ return [self.innernodeclass(rawtext, text, classes=['xref'])], []
+ # split title and target in role content
+ has_explicit_title, title, target = split_explicit_title(text)
+ # we want a cross-reference, create the reference node
+ pnode = self.nodeclass(rawtext, reftype=role, refdomain=domain,
+ refcaption=has_explicit_title)
+ # we may need the line number for warnings
+ pnode.line = lineno
+ title, target = self.process_link(env, pnode, has_explicit_title, title, target)
+ pnode['reftarget'] = target
+ pnode += self.innernodeclass(rawtext, title, classes=['xref'])
+ return [pnode], []
-def generic_link_func(env, pnode, has_explicit_title, title, target):
- if has_explicit_title:
- pnode['refcaption'] = True
- return target, title
+class OptionXRefRole(XRefRole):
+ innernodeclass = addnodes.literal_emphasis
-def pyref_link_func(env, pnode, has_explicit_title, title, target):
- if has_explicit_title:
- pnode['refcaption'] = True
- # fix-up parentheses in link title
- else:
- title = title.lstrip('.') # only has a meaning for the target
- 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:]
- # if the first character is a dot, search more specific namespaces first
- # else search builtins first
- if target[0:1] == '.':
- target = target[1:]
- pnode['refspecific'] = True
- return target, title
-
-
-def pyref_callable_link_func(env, pnode, has_explicit_title, title, target):
- target, title = pyref_link_func(env, pnode, has_explicit_title, title, target)
- target, title = normalize_func_parens(env, has_explicit_title, title, target)
- return target, title
-
-
-def option_link_func(env, pnode, has_explicit_title, title, target):
- program = env.currprogram
- if not has_explicit_title:
- if ' ' in title and not (title.startswith('/') or
- title.startswith('-')):
- program, target = re.split(' (?=-|--|/)', title, 1)
+ def process_link(self, env, pnode, has_explicit_title, title, target):
+ program = env.currprogram
+ if not has_explicit_title:
+ if ' ' in title and not (title.startswith('/') or
+ title.startswith('-')):
+ program, target = re.split(' (?=-|--|/)', title, 1)
+ program = ws_re.sub('-', program)
+ target = target.strip()
+ elif ' ' in target:
+ program, target = re.split(' (?=-|--|/)', target, 1)
program = ws_re.sub('-', program)
- target = target.strip()
- elif ' ' in target:
- program, target = re.split(' (?=-|--|/)', target, 1)
- program = ws_re.sub('-', program)
- pnode['refprogram'] = program
- return target, title
-
-
-def simple_link_func(env, pnode, has_explicit_title, title, target):
- # normalize all whitespace to avoid referencing problems
- target = ws_re.sub(' ', target)
- return target, title
-
-
-def lowercase_link_func(env, pnode, has_explicit_title, title, target):
- target, title = simple_link_func(env, pnode, has_explicit_title, title, target)
- return target.lower(), title
+ pnode['refprogram'] = program
+ return title, target
-def cfunc_link_func(env, pnode, has_explicit_title, title, target):
- return normalize_func_parens(env, has_explicit_title, title, target)
-
-
-generic_pyref_role = make_xref_role(pyref_link_func)
-callable_pyref_role = make_xref_role(pyref_callable_link_func)
-simple_xref_role = make_xref_role(simple_link_func)
-
specific_docroles = {
- 'data': generic_pyref_role,
- 'exc': generic_pyref_role,
- 'func': callable_pyref_role,
- 'class': generic_pyref_role,
- 'const': generic_pyref_role,
- 'attr': generic_pyref_role,
- 'meth': callable_pyref_role,
- 'mod': generic_pyref_role,
- 'obj': generic_pyref_role,
-
- 'keyword': simple_xref_role,
- 'ref': make_xref_role(lowercase_link_func, None, nodes.emphasis),
- 'token': simple_xref_role,
- 'term': make_xref_role(lowercase_link_func, None, nodes.emphasis),
- 'option': make_xref_role(option_link_func, None, addnodes.literal_emphasis),
- 'doc': simple_xref_role,
- 'download': make_xref_role(simple_link_func, addnodes.download_reference),
-
- 'cmember': simple_xref_role,
- 'cmacro': simple_xref_role,
- 'cfunc' : make_xref_role(cfunc_link_func),
- 'cdata': simple_xref_role,
- 'ctype': simple_xref_role,
+ 'keyword': XRefRole(''),
+ 'ref': XRefRole('', lowercase=True, innernodeclass=nodes.emphasis),
+ 'token': XRefRole(''),
+ 'term': XRefRole('', lowercase=True, innernodeclass=nodes.emphasis),
+ 'option': OptionXRefRole('', innernodeclass=addnodes.literal_emphasis),
+ 'doc': XRefRole(''),
+ 'download': XRefRole('', nodeclass=addnodes.download_reference),
'menuselection': menusel_role,
'file': emph_literal_role,
@@ -276,3 +226,5 @@ specific_docroles = {
for rolename, func in specific_docroles.iteritems():
roles.register_local_role(rolename, func)
+
+
diff --git a/tests/root/markup.txt b/tests/root/markup.txt
index 32b037ee..1e5b7eba 100644
--- a/tests/root/markup.txt
+++ b/tests/root/markup.txt
@@ -182,7 +182,8 @@ Testing öäü...
Object markup
-------------
-:cfunc:`CFunction`.
+:c:func:`CFunction`. :c:func:`!malloc`.
+
Only directive
--------------
@@ -207,3 +208,4 @@ Only directive
.. rubric:: Footnotes
.. [#] Like footnotes.
+