More refactoring for language-independent domain support.

* Renamed "desc"ription unit to "object" wherever possible.
* Added standard x-ref types to a StandardDomain which is always consulted.
* Split domains module into a subpackage.
* Removed additional_xref_types in favor of new directive classes in StandardDomain.
* Implemented x-ref inventory version 2, for all object types.
* Added env.doc_read_data which is for temporary data stored while reading.
* Minimally updated extension tutorial.
* Started to implement changes to interactive search.
* Test suite passes again.
This commit is contained in:
Georg Brandl
2009-09-07 22:52:26 +02:00
parent 0d029eeb9c
commit fa7f8812cd
28 changed files with 1086 additions and 845 deletions

View File

@@ -201,8 +201,7 @@ The ``todo`` directive function looks like this::
def run(self):
env = self.state.document.settings.env
targetid = "todo-%s" % env.index_num
env.index_num += 1
targetid = "todo-%s" % env.new_serialno('todo')
targetnode = nodes.target('', '', ids=[targetid])
ad = make_admonition(todo, self.name, [_('Todo')], self.options,
@@ -225,9 +224,10 @@ to the build environment instance using ``self.state.document.settings.env``.
Then, to act as a link target (from the todolist), the todo directive needs to
return a target node in addition to the todo node. The target ID (in HTML, this
will be the anchor name) is generated by using ``env.index_num`` which is
persistent between directive calls and therefore leads to unique target names.
The target node is instantiated without any text (the first two arguments).
will be the anchor name) is generated by using ``env.new_serialno`` which is
returns a new integer directive on each call and therefore leads to unique
target names. The target node is instantiated without any text (the first two
arguments).
An admonition is created using a standard docutils function (wrapped in Sphinx
for docutils cross-version compatibility). The first argument gives the node

View File

@@ -14,7 +14,7 @@ from docutils import nodes
# index markup
class index(nodes.Invisible, nodes.Inline, nodes.TextElement): pass
# description units (classdesc, funcdesc etc.)
# domain-specific object description units (class, function etc.)
# parent node for signature and content
class desc(nodes.Admonition, nodes.Element): pass

View File

@@ -25,9 +25,9 @@ from sphinx import package_dir, locale
from sphinx.roles import XRefRole
from sphinx.config import Config
from sphinx.errors import SphinxError, SphinxWarning, ExtensionError
from sphinx.domains import all_domains
from sphinx.domains import ObjType, all_domains
from sphinx.domains.std import GenericObject, Target, StandardDomain
from sphinx.builders import BUILTIN_BUILDERS
from sphinx.directives import GenericDesc, Target, additional_xref_types
from sphinx.environment import BuildEnvironment, SphinxStandaloneReader
from sphinx.util import pycompat # imported for side-effects
from sphinx.util.tags import Tags
@@ -361,6 +361,7 @@ class Sphinx(object):
def add_domain(self, domain):
# XXX needs to be documented
# XXX what about subclassing and overriding?
if domain.name in all_domains:
raise ExtensionError('domain %s already registered' % domain.name)
all_domains[domain.name] = domain
@@ -377,23 +378,31 @@ class Sphinx(object):
raise ExtensionError('domain %s not yet registered' % domain)
all_domains[domain].roles[name] = role
def add_description_unit(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None):
additional_xref_types[directivename] = (rolename, indextemplate,
parse_node)
directives.register_directive(directivename,
directive_dwim(GenericDesc))
def add_object_type(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None):
StandardDomain.object_types[directivename] = \
ObjType(directivename, rolename)
# create a subclass of GenericObject as the new directive
new_directive = type(directivename, (GenericObject, object),
{'indextemplate': indextemplate,
'parse_node': staticmethod(parse_node)})
StandardDomain.directives[directivename] = new_directive
# XXX support more options?
role_func = XRefRole(innernodeclass=ref_nodeclass)
roles.register_local_role(rolename, role_func)
StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
# backwards compatible alias
add_description_unit = add_object_type
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))
# XXX support more options
role_func = XRefRole(innernodeclass=ref_nodeclass)
roles.register_local_role(rolename, role_func)
StandardDomain.object_types[directivename] = \
ObjType(directivename, rolename)
# create a subclass of Target as the new directive
new_directive = type(directivename, (Target, object),
{'indextemplate': indextemplate})
StandardDomain.directives[directivename] = new_directive
# XXX support more options?
StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass)
def add_transform(self, transform):
SphinxStandaloneReader.transforms.append(transform)

View File

@@ -713,15 +713,13 @@ class StandaloneHTMLBuilder(Builder):
self.info(bold('dumping object inventory... '), nonl=True)
f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w')
try:
# XXX inventory version 2
f.write('# Sphinx inventory version 1\n')
f.write('# Sphinx inventory version 2\n')
f.write('# Project: %s\n' % self.config.project.encode('utf-8'))
f.write('# Version: %s\n' % self.config.version)
#for modname, info in self.env.modules.iteritems():
# f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0])))
#for refname, (docname, desctype) in self.env.descrefs.iteritems():
# f.write('%s %s %s\n' % (refname, desctype,
# self.get_target_uri(docname)))
for domain in self.env.domains.itervalues():
for name, type, docname, anchor, prio in domain.get_objects():
f.write('%s %s:%s %s %s\n' % (name, domain, type, prio,
self.get_target_uri(docname) + '#' + anchor))
finally:
f.close()
self.info('done')

View File

@@ -15,7 +15,6 @@ from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.locale import l_
from sphinx.util import ws_re
from sphinx.util.compat import Directive, directive_dwim
@@ -32,15 +31,11 @@ def _is_only_paragraph(node):
return False
# RE for option descriptions
option_desc_re = re.compile(
r'((?:/|-|--)[-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)')
# RE to strip backslash escapes
strip_backslash_re = re.compile(r'\\(?=[^\\])')
class DescDirective(Directive):
class ObjectDescription(Directive):
"""
Directive to describe a class, function or similar object. Not used
directly, but subclassed to add custom behavior.
@@ -133,10 +128,12 @@ class DescDirective(Directive):
nfield += nfieldname
node = nfieldname
if typ in self.doc_fields_with_linked_arg:
# XXX currmodule/currclass
node = addnodes.pending_xref(
obj, reftype='obj', refcaption=False,
reftarget=obj, modname=self.env.currmodule,
classname=self.env.currclass)
reftarget=obj)
#, modname=self.env.currmodule
#, classname=self.env.currclass
nfieldname += node
node += nodes.Text(obj, obj)
nfield += nodes.field_body()
@@ -205,15 +202,17 @@ class DescDirective(Directive):
def run(self):
if ':' in self.name:
self.domain, self.desctype = self.name.split(':', 1)
self.domain, self.objtype = self.name.split(':', 1)
else:
self.domain, self.desctype = '', self.name
self.domain, self.objtype = '', 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['domain'] = self.domain
# 'desctype' is a backwards compatible attribute
node['objtype'] = node['desctype'] = self.objtype
node['noindex'] = noindex = ('noindex' in self.options)
self.names = []
@@ -242,117 +241,16 @@ class DescDirective(Directive):
node.append(contentnode)
if self.names:
# needed for association of version{added,changed} directives
self.env.currdesc = self.names[0]
self.env.doc_read_data['object'] = 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.env.doc_read_data['object'] = None
self.after_content()
return [self.indexnode, node]
class CmdoptionDesc(DescDirective):
"""
Description of a command-line option (.. cmdoption).
"""
def parse_signature(self, sig, signode):
"""Transform an option description into RST nodes."""
count = 0
firstname = ''
for m in option_desc_re.finditer(sig):
optname, args = m.groups()
if count:
signode += addnodes.desc_addname(', ', ', ')
signode += addnodes.desc_name(optname, optname)
signode += addnodes.desc_addname(args, args)
if not count:
firstname = optname
count += 1
if not firstname:
raise ValueError
return firstname
def add_target_and_index(self, name, sig, signode):
targetname = name.replace('/', '-')
if self.env.currprogram:
targetname = '-' + self.env.currprogram + targetname
targetname = 'cmdoption' + targetname
signode['ids'].append(targetname)
self.state.document.note_explicit_target(signode)
self.indexnode['entries'].append(
('pair', _('%scommand line option; %s') %
((self.env.currprogram and
self.env.currprogram + ' ' or ''), sig),
targetname, targetname))
self.env.note_progoption(name, targetname)
class GenericDesc(DescDirective):
"""
A generic x-ref directive registered with Sphinx.add_description_unit().
"""
def parse_signature(self, sig, signode):
parse_node = additional_xref_types[self.desctype][2]
if parse_node:
name = parse_node(self.env, sig, signode)
else:
signode.clear()
signode += addnodes.desc_name(sig, sig)
# normalize whitespace like XRefRole does
name = ws_re.sub('', sig)
return name
def add_target_and_index(self, name, sig, signode):
rolename, indextemplate = additional_xref_types[self.desctype][:2]
targetname = '%s-%s' % (rolename, name)
signode['ids'].append(targetname)
self.state.document.note_explicit_target(signode)
if indextemplate:
indexentry = indextemplate % (name,)
indextype = 'single'
colon = indexentry.find(':')
if colon != -1:
indextype = indexentry[:colon].strip()
indexentry = indexentry[colon+1:].strip()
self.indexnode['entries'].append((indextype, indexentry,
targetname, targetname))
self.env.note_reftarget(rolename, name, targetname)
class Target(Directive):
"""
Generic target for user-defined cross-reference types.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
def run(self):
env = self.state.document.settings.env
rolename, indextemplate, foo = additional_xref_types[self.name]
# normalize whitespace in fullname like XRefRole does
fullname = ws_re.sub('', self.arguments[0].strip())
targetname = '%s-%s' % (rolename, fullname)
node = nodes.target('', '', ids=[targetname])
self.state.document.note_explicit_target(node)
ret = [node]
if indextemplate:
indexentry = indextemplate % (fullname,)
indextype = 'single'
colon = indexentry.find(':')
if colon != -1:
indextype = indexentry[:colon].strip()
indexentry = indexentry[colon+1:].strip()
inode = addnodes.index(entries=[(indextype, indexentry,
targetname, targetname)])
ret.insert(0, inode)
env.note_reftarget(rolename, fullname, targetname)
return ret
# backwards compatible old name
DescDirective = ObjectDescription
class DefaultDomain(Directive):
@@ -372,18 +270,7 @@ class DefaultDomain(Directive):
env.default_domain = env.domains.get(domain_name)
# Note: the target directive is not registered here, it is used by the
# 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', l_('environment variable; %s'), None),
}
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))
directives.register_directive('describe', directive_dwim(ObjectDescription))
# new, more consistent, name
directives.register_directive('object', directive_dwim(ObjectDescription))

View File

@@ -136,27 +136,6 @@ class Author(Directive):
return [para] + messages
class Program(Directive):
"""
Directive to name the program for which options are documented.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
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':
env.currprogram = None
else:
env.currprogram = program
return []
class Index(Directive):
"""
Directive to add entries to the index.
@@ -175,8 +154,7 @@ class Index(Directive):
def run(self):
arguments = self.arguments[0].split('\n')
env = self.state.document.settings.env
targetid = 'index-%s' % env.index_num
env.index_num += 1
targetid = 'index-%s' % env.new_serialno('index')
targetnode = nodes.target('', '', ids=[targetid])
self.state.document.note_explicit_target(targetnode)
indexnode = addnodes.index()
@@ -231,6 +209,7 @@ class VersionChange(Directive):
else:
ret = [node]
env = self.state.document.settings.env
# XXX replace? what with?
env.note_versionchange(node['type'], node['version'], node, self.lineno)
return ret
@@ -261,66 +240,6 @@ class SeeAlso(Directive):
return ret
token_re = re.compile('`([a-z_][a-z0-9_]*)`')
def token_xrefs(text, env):
retnodes = []
pos = 0
for m in token_re.finditer(text):
if m.start() > pos:
txt = text[pos:m.start()]
retnodes.append(nodes.Text(txt, txt))
refnode = addnodes.pending_xref(m.group(1))
refnode['reftype'] = 'token'
refnode['reftarget'] = m.group(1)
refnode['modname'] = env.currmodule
refnode['classname'] = env.currclass
refnode += nodes.literal(m.group(1), m.group(1), classes=['xref'])
retnodes.append(refnode)
pos = m.end()
if pos < len(text):
retnodes.append(nodes.Text(text[pos:], text[pos:]))
return retnodes
class ProductionList(Directive):
"""
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()
messages = []
i = 0
for rule in self.arguments[0].split('\n'):
if i == 0 and ':' not in rule:
# production group
continue
i += 1
try:
name, tokens = rule.split(':', 1)
except ValueError:
break
subnode = addnodes.production()
subnode['tokenname'] = name.strip()
if subnode['tokenname']:
idname = 'grammar-token-%s' % subnode['tokenname']
if idname not in self.state.document.ids:
subnode['ids'].append(idname)
self.state.document.note_implicit_target(subnode, subnode)
env.note_reftarget('token', subnode['tokenname'], idname)
subnode.extend(token_xrefs(tokens, env))
node.append(subnode)
return [node] + messages
class TabularColumns(Directive):
"""
Directive to give an explicit tabulary column definition to LaTeX.
@@ -338,57 +257,6 @@ class TabularColumns(Directive):
return [node]
class Glossary(Directive):
"""
Directive to create a glossary with cross-reference targets
for :term: roles.
"""
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'sorted': directives.flag,
}
def run(self):
env = self.state.document.settings.env
node = addnodes.glossary()
node.document = self.state.document
self.state.nested_parse(self.content, self.content_offset, node)
# the content should be definition lists
dls = [child for child in node
if isinstance(child, nodes.definition_list)]
# now, extract definition terms to enable cross-reference creation
new_dl = nodes.definition_list()
new_dl['classes'].append('glossary')
items = []
for dl in dls:
for li in dl.children:
if not li.children or not isinstance(li[0], nodes.term):
continue
termtext = li.children[0].astext()
new_id = 'term-' + nodes.make_id(termtext)
if new_id in env.gloss_entries:
new_id = 'term-' + str(len(env.gloss_entries))
env.gloss_entries.add(new_id)
li[0]['names'].append(new_id)
li[0]['ids'].append(new_id)
env.note_reftarget('term', termtext.lower(), new_id)
# add an index entry too
indexnode = addnodes.index()
indexnode['entries'] = [('single', termtext, new_id, termtext)]
li.insert(0, indexnode)
items.append((termtext, li))
if 'sorted' in self.options:
items.sort(key=lambda x: x[0].lower())
new_dl.extend(item[1] for item in items)
node.children = [new_dl]
return [node]
class Centered(Directive):
"""
Directive to create a centered line of bold text.
@@ -494,22 +362,19 @@ class Only(Directive):
directives.register_directive('toctree', directive_dwim(TocTree))
directives.register_directive('sectionauthor', directive_dwim(Author))
directives.register_directive('moduleauthor', directive_dwim(Author))
directives.register_directive('program', directive_dwim(Program))
directives.register_directive('index', directive_dwim(Index))
directives.register_directive('deprecated', directive_dwim(VersionChange))
directives.register_directive('versionadded', directive_dwim(VersionChange))
directives.register_directive('versionchanged', directive_dwim(VersionChange))
directives.register_directive('seealso', directive_dwim(SeeAlso))
directives.register_directive('productionlist', directive_dwim(ProductionList))
directives.register_directive('tabularcolumns', directive_dwim(TabularColumns))
directives.register_directive('glossary', directive_dwim(Glossary))
directives.register_directive('centered', directive_dwim(Centered))
directives.register_directive('acks', directive_dwim(Acks))
directives.register_directive('hlist', directive_dwim(HList))
directives.register_directive('only', directive_dwim(Only))
# register the standard rst class directive under a different name
# only for backwards compatibility now
try:
# docutils 0.4
from docutils.parsers.rst.directives.misc import class_directive

176
sphinx/domains/__init__.py Normal file
View File

@@ -0,0 +1,176 @@
# -*- coding: utf-8 -*-
"""
sphinx.domains
~~~~~~~~~~~~~~
Support for domains, which are groupings of description directives
and roles describing e.g. constructs of one programming language.
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
class ObjType(object):
"""
XXX add docstring
"""
known_attrs = {
'search': True,
}
def __init__(self, lname, *roles, **attrs):
self.lname = lname
self.roles = roles
self.attrs = self.known_attrs.copy()
self.attrs.update(attrs)
class Domain(object):
"""
A Domain is meant to be a group of "object" description directives for
objects of a similar nature, and corresponding roles to create references to
them. Examples would be Python modules, classes, functions etc., elements
of a templating language, Sphinx roles and directives, etc.
Each domain has a separate storage for information about existing objects
and how to reference them in `data`, which must be a dictionary. It also
must implement several functions that expose the object information in a
uniform way to parts of Sphinx that allow the user to reference or search
for objects in a domain-agnostic way.
About `self.data`: since all object and cross-referencing information is
stored on a BuildEnvironment instance, the `domain.data` object is also
stored in the `env.domaindata` dict under the key `domain.name`. Before the
build process starts, every active domain is instantiated and given the
environment object; the `domaindata` dict must then either be nonexistent or
a dictionary whose 'version' key is equal to the domain class'
`data_version` attribute. Otherwise, `IOError` is raised and the pickled
environment is discarded.
"""
#: domain name: should be short, but unique
name = ''
#: domain label: longer, more descriptive (used in messages)
label = ''
#: type (usually directive) name -> ObjType instance
object_types = {}
#: directive name -> directive class
directives = {}
#: role name -> role callable
roles = {}
#: data value for a fresh environment
initial_data = {}
#: data version
data_version = 0
def __init__(self, env):
self.env = env
if self.name not in env.domaindata:
assert isinstance(self.initial_data, dict)
new_data = self.initial_data.copy()
new_data['version'] = self.data_version
self.data = env.domaindata[self.name] = new_data
else:
self.data = env.domaindata[self.name]
if self.data['version'] != self.data_version:
raise IOError('data of %r domain out of date' % self.label)
self._role_cache = {}
self._directive_cache = {}
self._role2type = {}
for name, obj in self.object_types.iteritems():
for rolename in obj.roles:
self._role2type[rolename] = name
self.object_type = self._role2type.get # XXX necessary?
def role(self, name):
"""
Return a role adapter function that always gives the registered
role its full name ('domain:name') as the first argument.
"""
if name in self._role_cache:
return self._role_cache[name]
if name not in self.roles:
return None
fullname = '%s:%s' % (self.name, name)
def role_adapter(typ, rawtext, text, lineno, inliner,
options={}, content=[]):
return self.roles[name](fullname, rawtext, text, lineno,
inliner, options, content)
self._role_cache[name] = role_adapter
return role_adapter
def directive(self, name):
"""
Return a directive adapter class that always gives the registered
directive its full name ('domain:name') as ``self.name``.
"""
if name in self._directive_cache:
return self._directive_cache[name]
if name not in self.directives:
return None
fullname = '%s:%s' % (self.name, name)
# XXX what about function-style directives?
BaseDirective = self.directives[name]
class DirectiveAdapter(BaseDirective):
def run(self):
self.name = fullname
return BaseDirective.run(self)
self._directive_cache[name] = DirectiveAdapter
return DirectiveAdapter
# methods that should be overwritten
def clear_doc(self, docname):
"""
Remove traces of a document in the domain-specific inventories.
"""
pass
# XXX way for individual docs to override methods in an existing domain?
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
"""
Resolve the ``pending_xref`` *node* with the given *typ* and *target*.
This method should return a new node, to replace the xref node,
containing the *contnode* which is the markup content of the
cross-reference.
If no resolution can be found, None can be returned; the xref node will
then given to the 'missing-reference' event, and if that yields no
resolution, replaced by *contnode*.
The method can also raise `sphinx.environment.NoUri` to suppress the
'missing-reference' event being emitted.
"""
pass
def get_objects(self):
"""
Return an iterable of "object descriptions", which are tuples with
five items:
* `name` -- fully qualified name
* `type` -- object type, a key in ``self.object_types``
* `docname` -- the document where it is to be found
* `anchor` -- the anchor name for the object
* `priority` -- how "important" the object is; an integer
(XXX assign meanings)
"""
return []
from sphinx.domains.c import CDomain
from sphinx.domains.std import StandardDomain
from sphinx.domains.python import PythonDomain
# this contains all registered domains
all_domains = {
'std': StandardDomain,
'py': PythonDomain,
'c': CDomain,
}

203
sphinx/domains/c.py Normal file
View File

@@ -0,0 +1,203 @@
# -*- coding: utf-8 -*-
"""
sphinx.domains.c
~~~~~~~~~~~~~~~~
The C language domain.
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import string
from docutils import nodes
from docutils.parsers.rst import directives
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 import make_refnode
from sphinx.util.compat import Directive
# 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 CObject(ObjectDescription):
"""
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(
'', refdomain='c', reftype='type', 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.objtype == '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.objtype == 'cfunction':
return _('%s (C function)') % name
elif self.objtype == 'cmember':
return _('%s (C member)') % name
elif self.objtype == 'cmacro':
return _('%s (C macro)') % name
elif self.objtype == 'ctype':
return _('%s (C type)') % name
elif self.objtype == '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)
inv = self.env.domaindata['c']['objects']
if name in inv:
self.env.warn(
self.env.docname,
'duplicate C object description of %s, ' % name +
'other instance in ' + self.env.doc2path(inv[name][0]),
self.lineno)
inv[name] = (self.env.docname, self.objtype)
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'
object_types = {
'function': ObjType(l_('C function'), 'func'),
'member': ObjType(l_('C member'), 'member'),
'macro': ObjType(l_('C macro'), 'macro'),
'type': ObjType(l_('C type'), 'type'),
'var': ObjType(l_('C variable'), 'data'),
}
directives = {
'function': CObject,
'member': CObject,
'macro': CObject,
'type': CObject,
'var': CObject,
}
roles = {
'func' : XRefRole(fix_parens=True),
'member': XRefRole(),
'macro': XRefRole(),
'data': XRefRole(),
'type': XRefRole(),
}
initial_data = {
'objects': {}, # fullname -> docname, objtype
}
def clear_doc(self, docname):
for fullname, (fn, _) in 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:
return None
obj = self.data[target]
return make_refnode(builder, fromdocname, obj[0], contnode, target)
def get_objects(self):
for refname, (docname, type) in self.data['objects'].iteritems():
yield (refname, type, docname, refname, 0)

View File

@@ -1,135 +1,28 @@
# -*- coding: utf-8 -*-
"""
sphinx.domains
~~~~~~~~~~~~~~
sphinx.domains.python
~~~~~~~~~~~~~~~~~~~~~
Support for domains, which are groupings of description directives
and roles describing e.g. constructs of one programming language.
The Python domain.
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
import string
from docutils import nodes
from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.roles import XRefRole
from sphinx.directives import DescDirective
from sphinx.locale import l_
from sphinx.domains import Domain, ObjType
from sphinx.directives import ObjectDescription
from sphinx.util import make_refnode
from sphinx.util.compat import Directive
class Domain(object):
"""
A Domain is meant to be a group of "object" description directives for
objects of a similar nature, and corresponding roles to create references to
them. Examples would be Python modules, classes, functions etc., elements
of a templating language, Sphinx roles and directives, etc.
Each domain has a separate storage for information about existing objects
and how to reference them in `data`, which must be a dictionary. It also
must implement several functions that expose the object information in a
uniform way to parts of Sphinx that allow the user to reference or search
for objects in a domain-agnostic way.
About `self.data`: since all object and cross-referencing information is
stored on a BuildEnvironment instance, the `domain.data` object is also
stored in the `env.domaindata` dict under the key `domain.name`. Before the
build process starts, every active domain is instantiated and given the
environment object; the `domaindata` dict must then either be nonexistent or
a dictionary whose 'version' key is equal to the domain class'
`data_version` attribute. Otherwise, `IOError` is raised and the pickled
environment is discarded.
"""
name = ''
directives = {}
roles = {}
label = ''
# data value for a fresh environment
initial_data = {}
# data version
data_version = 0
def __init__(self, env):
self.env = env
if self.name not in env.domaindata:
assert isinstance(self.initial_data, dict)
new_data = self.initial_data.copy()
new_data['version'] = self.data_version
self.data = env.domaindata[self.name] = new_data
else:
self.data = env.domaindata[self.name]
if self.data['version'] != self.data_version:
raise IOError('data of %r domain out of date' % self.label)
self._role_cache = {}
self._directive_cache = {}
def clear_doc(self, docname):
"""
Remove traces of a document in the domain-specific inventories.
"""
pass
def role(self, name):
"""
Return a role adapter function that always gives the registered
role its full name ('domain:name') as the first argument.
"""
if name in self._role_cache:
return self._role_cache[name]
if name not in self.roles:
return None
fullname = '%s:%s' % (self.name, name)
def role_adapter(typ, rawtext, text, lineno, inliner,
options={}, content=[]):
return self.roles[name](fullname, rawtext, text, lineno,
inliner, options, content)
self._role_cache[name] = role_adapter
return role_adapter
def directive(self, name):
"""
Return a directive adapter class that always gives the registered
directive its full name ('domain:name') as ``self.name``.
"""
if name in self._directive_cache:
return self._directive_cache[name]
if name not in self.directives:
return None
fullname = '%s:%s' % (self.name, name)
# XXX what about function-style directives?
BaseDirective = self.directives[name]
class DirectiveAdapter(BaseDirective):
def run(self):
self.name = fullname
return BaseDirective.run(self)
self._directive_cache[name] = DirectiveAdapter
return DirectiveAdapter
def resolve_xref(self, typ, target, node, contnode):
"""
Resolve the ``pending_xref`` *node* with the given *typ* and *target*.
This method should return a new node, to replace the xref node,
containing the *contnode* which is the markup content of the
cross-reference.
If no resolution can be found, None can be returned; the xref node will
then given to the 'missing-reference' event, and if that yields no
resolution, replaced by *contnode*.
The method can also raise `sphinx.environment.NoUri` to suppress the
'missing-reference' event being emitted.
"""
pass
# REs for Python signatures
py_sig_re = re.compile(
r'''^ ([\w.]*\.)? # class name(s)
@@ -142,7 +35,7 @@ py_sig_re = re.compile(
py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ','
class PythonDesc(DescDirective):
class PyObject(ObjectDescription):
"""
Description of a general Python object.
"""
@@ -174,19 +67,20 @@ class PythonDesc(DescDirective):
raise ValueError
classname, name, arglist, retann = m.groups()
if self.env.currclass:
currclass = self.env.doc_read_data.get('py_class')
if currclass:
add_module = False
if classname and classname.startswith(self.env.currclass):
if classname and classname.startswith(currclass):
fullname = classname + name
# class name is given again in the signature
classname = classname[len(self.env.currclass):].lstrip('.')
classname = classname[len(currclass):].lstrip('.')
elif classname:
# class name is given in the signature, but different
# (shouldn't happen)
fullname = self.env.currclass + '.' + classname + name
fullname = currclass + '.' + classname + name
else:
# class name is not given in the signature
fullname = self.env.currclass + '.' + name
fullname = currclass + '.' + name
else:
add_module = True
fullname = classname and classname + name or name
@@ -200,7 +94,8 @@ class PythonDesc(DescDirective):
# 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)
modname = self.options.get(
'module', self.env.doc_read_data.get('py_module'))
if modname and modname != 'exceptions':
nodetext = modname + '.'
signode += addnodes.desc_addname(nodetext, nodetext)
@@ -244,7 +139,8 @@ class PythonDesc(DescDirective):
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)
modname = self.options.get(
'module', self.env.doc_read_data.get('py_module'))
fullname = (modname and modname + '.' or '') + name_cls[0]
# note target
if fullname not in self.state.document.ids:
@@ -260,7 +156,7 @@ class PythonDesc(DescDirective):
'other instance in ' +
self.env.doc2path(objects[fullname][0]),
self.lineno)
objects[fullname] = (self.env.docname, self.desctype)
objects[fullname] = (self.env.docname, self.objtype)
indextext = self.get_index_text(modname, name_cls)
if indextext:
@@ -273,23 +169,23 @@ class PythonDesc(DescDirective):
def after_content(self):
if self.clsname_set:
self.env.currclass = None
self.env.doc_read_data['py_class'] = None
class ModulelevelDesc(PythonDesc):
class PyModulelevel(PyObject):
"""
Description of an object on module level (functions, data).
"""
def needs_arglist(self):
return self.desctype == 'function'
return self.objtype == 'function'
def get_index_text(self, modname, name_cls):
if self.desctype == 'function':
if self.objtype == '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':
elif self.objtype == 'data':
if not modname:
return _('%s (built-in variable)') % name_cls[0]
return _('%s (in module %s)') % (name_cls[0], modname)
@@ -297,50 +193,50 @@ class ModulelevelDesc(PythonDesc):
return ''
class ClasslikeDesc(PythonDesc):
class PyClasslike(PyObject):
"""
Description of a class-like object (classes, interfaces, exceptions).
"""
def get_signature_prefix(self, sig):
return self.desctype + ' '
return self.objtype + ' '
def get_index_text(self, modname, name_cls):
if self.desctype == 'class':
if self.objtype == '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':
elif self.objtype == 'exception':
return name_cls[0]
else:
return ''
def before_content(self):
PythonDesc.before_content(self)
PyObject.before_content(self)
if self.names:
self.env.currclass = self.names[0][0]
self.env.doc_read_data['py_class'] = self.names[0][0]
self.clsname_set = True
class ClassmemberDesc(PythonDesc):
class PyClassmember(PyObject):
"""
Description of a class member (methods, attributes).
"""
def needs_arglist(self):
return self.desctype.endswith('method')
return self.objtype.endswith('method')
def get_signature_prefix(self, sig):
if self.desctype == 'staticmethod':
if self.objtype == 'staticmethod':
return 'static '
elif self.desctype == 'classmethod':
elif self.objtype == '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':
if self.objtype == 'method':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
@@ -352,7 +248,7 @@ class ClassmemberDesc(PythonDesc):
return _('%s() (%s.%s method)') % (methname, modname, clsname)
else:
return _('%s() (%s method)') % (methname, clsname)
elif self.desctype == 'staticmethod':
elif self.objtype == 'staticmethod':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
@@ -365,7 +261,7 @@ class ClassmemberDesc(PythonDesc):
clsname)
else:
return _('%s() (%s static method)') % (methname, clsname)
elif self.desctype == 'classmethod':
elif self.objtype == 'classmethod':
try:
clsname, methname = name.rsplit('.', 1)
except ValueError:
@@ -378,7 +274,7 @@ class ClassmemberDesc(PythonDesc):
clsname)
else:
return _('%s() (%s class method)') % (methname, clsname)
elif self.desctype == 'attribute':
elif self.objtype == 'attribute':
try:
clsname, attrname = name.rsplit('.', 1)
except ValueError:
@@ -394,9 +290,10 @@ class ClassmemberDesc(PythonDesc):
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('.')
PyObject.before_content(self)
lastname = self.names and self.names[-1][1]
if lastname and not self.env.doc_read_data.get('py_class'):
self.env.doc_read_data['py_class'] = lastname.strip('.')
self.clsname_set = True
@@ -420,7 +317,7 @@ class PyModule(Directive):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
noindex = 'noindex' in self.options
env.currmodule = modname
env.doc_read_data['py_module'] = modname
env.domaindata['py']['modules'][modname] = \
(env.docname, self.options.get('synopsis', ''),
self.options.get('platform', ''), 'deprecated' in self.options)
@@ -463,16 +360,16 @@ class PyCurrentModule(Directive):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
if modname == 'None':
env.currmodule = None
env.doc_read_data['py_module'] = None
else:
env.currmodule = modname
env.doc_read_data['py_module'] = modname
return []
class PyXRefRole(XRefRole):
def process_link(self, env, pnode, has_explicit_title, title, target):
pnode['modname'] = env.currmodule
pnode['classname'] = env.currclass
pnode['modname'] = env.doc_read_data.get('py_module')
pnode['classname'] = env.doc_read_data.get('py_class')
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
@@ -495,31 +392,41 @@ class PythonDomain(Domain):
"""Python language domain."""
name = 'py'
label = 'Python'
object_types = {
'function': ObjType(l_('function'), 'func', 'obj'),
'data': ObjType(l_('data'), 'data', 'obj'),
'class': ObjType(l_('class'), 'class', 'obj'),
'exception': ObjType(l_('exception'), 'exc', 'obj'),
'method': ObjType(l_('method'), 'meth', 'obj'),
'attribute': ObjType(l_('attribute'), 'attr', 'obj'),
'module': ObjType(l_('module'), 'mod', 'obj'),
}
directives = {
'function': ModulelevelDesc,
'data': ModulelevelDesc,
'class': ClasslikeDesc,
'exception': ClasslikeDesc,
'method': ClassmemberDesc,
'classmethod': ClassmemberDesc,
'staticmethod': ClassmemberDesc,
'attribute': ClassmemberDesc,
'module': PyModule,
'function': PyModulelevel,
'data': PyModulelevel,
'class': PyClasslike,
'exception': PyClasslike,
'method': PyClassmember,
'classmethod': PyClassmember,
'staticmethod': PyClassmember,
'attribute': PyClassmember,
'module': PyModule,
'currentmodule': PyCurrentModule,
}
roles = {
'data': PyXRefRole(),
'exc': PyXRefRole(),
'func': PyXRefRole(fix_parens=True),
'data': PyXRefRole(),
'exc': PyXRefRole(),
'func': PyXRefRole(fix_parens=True),
'class': PyXRefRole(),
'const': PyXRefRole(),
'attr': PyXRefRole(),
'meth': PyXRefRole(fix_parens=True),
'mod': PyXRefRole(),
'obj': PyXRefRole(),
'attr': PyXRefRole(),
'meth': PyXRefRole(fix_parens=True),
'mod': PyXRefRole(),
'obj': PyXRefRole(),
}
initial_data = {
'objects': {}, # fullname -> docname, desctype
'objects': {}, # fullname -> docname, objtype
'modules': {}, # modname -> docname, synopsis, platform, deprecated
}
@@ -531,10 +438,10 @@ class PythonDomain(Domain):
if fn == docname:
del self.data['modules'][modname]
def find_desc(self, env, modname, classname, name, type, searchorder=0):
def find_obj(self, env, modname, classname, name, type, searchorder=0):
"""
Find a Python object description for "name", perhaps using the given
module and/or classname.
Find a Python object for "name", perhaps using the given module and/or
classname.
"""
# skip parens
if name[-2:] == '()':
@@ -592,188 +499,19 @@ class PythonDomain(Domain):
return make_refnode(builder, fromdocname, docname,
'module-' + target, contnode, title)
else:
modname = node['modname']
clsname = node['classname']
modname = node.get('modname')
clsname = node.get('classname')
searchorder = node.hasattr('refspecific') and 1 or 0
name, desc = self.find_desc(env, modname, clsname,
target, typ, searchorder)
if not desc:
name, obj = self.find_obj(env, modname, clsname,
target, typ, searchorder)
if not obj:
return None
else:
return make_refnode(builder, fromdocname, desc[0], name,
return make_refnode(builder, fromdocname, obj[0], name,
contnode, name)
# 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(
'', refdomain='c', reftype='type', 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)
inv = self.env.domaindata['c']['objects']
if name in inv:
self.env.warn(
self.env.docname,
'duplicate C object description of %s, ' % name +
'other instance in ' + self.env.doc2path(inv[name][0]),
self.lineno)
inv[name] = (self.env.docname, self.desctype)
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(),
'macro': XRefRole(),
'func' : XRefRole(fix_parens=True),
'data': XRefRole(),
'type': XRefRole(),
}
initial_data = {
'objects': {}, # fullname -> docname, desctype
}
def clear_doc(self, docname):
for fullname, (fn, _) in 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:
return None
desc = self.data[target]
return make_refnode(builder, fromdocname, desc[0], contnode, target)
# this contains all registered domains
all_domains = {
'py': PythonDomain,
'c': CDomain,
}
def get_objects(self):
for modname, info in self.data['modules'].iteritems():
yield (modname, 'module', info[0], 'module-' + modname, 1)
for refname, (docname, type) in self.data['objects'].iteritems():
yield (refname, type, docname, refname, 0)

361
sphinx/domains/std.py Normal file
View File

@@ -0,0 +1,361 @@
# -*- coding: utf-8 -*-
"""
sphinx.domains.std
~~~~~~~~~~~~~~~~~~
The standard domain.
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import re
from docutils import nodes
from docutils.parsers.rst import directives
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 import make_refnode, ws_re
from sphinx.util.compat import Directive
# RE for option descriptions
option_desc_re = re.compile(
r'((?:/|-|--)[-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)')
class GenericObject(ObjectDescription):
"""
A generic x-ref directive registered with Sphinx.add_description_unit().
"""
indextemplate = ''
parse_node = None
def parse_signature(self, sig, signode):
if self.parse_node:
name = self.parse_node(self.env, sig, signode)
else:
signode.clear()
signode += addnodes.desc_name(sig, sig)
# normalize whitespace like XRefRole does
name = ws_re.sub('', sig)
return name
def add_target_and_index(self, name, sig, signode):
targetname = '%s-%s' % (self.objtype, name)
signode['ids'].append(targetname)
self.state.document.note_explicit_target(signode)
if self.indextemplate:
colon = self.indextemplate.find(':')
if colon != -1:
indextype = self.indextemplate[:colon].strip()
indexentry = self.indextemplate[colon+1:].strip() % (name,)
else:
indextype = 'single'
indexentry = self.indextemplate % (name,)
self.indexnode['entries'].append((indextype, indexentry,
targetname, targetname))
self.env.domaindata['std']['objects'][self.objtype, name] = \
self.env.docname, targetname
class EnvVar(GenericObject):
indextemplate = l_('environment variable; %s')
class Target(Directive):
"""
Generic target for user-defined cross-reference types.
"""
indextemplate = ''
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {}
def run(self):
env = self.state.document.settings.env
# normalize whitespace in fullname like XRefRole does
fullname = ws_re.sub('', self.arguments[0].strip())
targetname = '%s-%s' % (self.name, fullname)
node = nodes.target('', '', ids=[targetname])
self.state.document.note_explicit_target(node)
ret = [node]
if self.indextemplate:
indexentry = self.indextemplate % (fullname,)
indextype = 'single'
colon = indexentry.find(':')
if colon != -1:
indextype = indexentry[:colon].strip()
indexentry = indexentry[colon+1:].strip()
inode = addnodes.index(entries=[(indextype, indexentry,
targetname, targetname)])
ret.insert(0, inode)
env.domaindata['std']['objects'][self.name, fullname] = \
env.docname, targetname
return ret
class Cmdoption(ObjectDescription):
"""
Description of a command-line option (.. cmdoption).
"""
def parse_signature(self, sig, signode):
"""Transform an option description into RST nodes."""
count = 0
firstname = ''
for m in option_desc_re.finditer(sig):
optname, args = m.groups()
if count:
signode += addnodes.desc_addname(', ', ', ')
signode += addnodes.desc_name(optname, optname)
signode += addnodes.desc_addname(args, args)
if not count:
firstname = optname
count += 1
if not firstname:
raise ValueError
return firstname
def add_target_and_index(self, name, sig, signode):
targetname = name.replace('/', '-')
currprogram = self.env.doc_read_data.get('std_program')
if currprogram:
targetname = '-' + currprogram + targetname
targetname = 'cmdoption' + targetname
signode['ids'].append(targetname)
self.state.document.note_explicit_target(signode)
self.indexnode['entries'].append(
('pair', _('%scommand line option; %s') %
((currprogram and currprogram + ' ' or ''), sig),
targetname, targetname))
self.env.domaindata['std']['progoptions'][currprogram, name] = \
self.env.docname, targetname
class Program(Directive):
"""
Directive to name the program for which options are documented.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
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':
env.doc_read_data['std_program'] = None
else:
env.doc_read_data['std_program'] = program
return []
class OptionXRefRole(XRefRole):
innernodeclass = addnodes.literal_emphasis
def process_link(self, env, pnode, has_explicit_title, title, target):
program = env.doc_read_data.get('std_program')
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)
pnode['refprogram'] = program
return title, target
class Glossary(Directive):
"""
Directive to create a glossary with cross-reference targets
for :term: roles.
"""
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'sorted': directives.flag,
}
def run(self):
env = self.state.document.settings.env
objects = env.domaindata['std']['objects']
gloss_entries = env.doc_read_data.setdefault('gloss_entries', set())
node = addnodes.glossary()
node.document = self.state.document
self.state.nested_parse(self.content, self.content_offset, node)
# the content should be definition lists
dls = [child for child in node
if isinstance(child, nodes.definition_list)]
# now, extract definition terms to enable cross-reference creation
new_dl = nodes.definition_list()
new_dl['classes'].append('glossary')
items = []
for dl in dls:
for li in dl.children:
if not li.children or not isinstance(li[0], nodes.term):
continue
termtext = li.children[0].astext()
new_id = 'term-' + nodes.make_id(termtext)
if new_id in gloss_entries:
new_id = 'term-' + str(len(gloss_entries))
gloss_entries.add(new_id)
li[0]['names'].append(new_id)
li[0]['ids'].append(new_id)
objects['term', termtext.lower()] = env.docname, new_id
# add an index entry too
indexnode = addnodes.index()
indexnode['entries'] = [('single', termtext, new_id, termtext)]
li.insert(0, indexnode)
items.append((termtext, li))
if 'sorted' in self.options:
items.sort(key=lambda x: x[0].lower())
new_dl.extend(item[1] for item in items)
node.children = [new_dl]
return [node]
token_re = re.compile('`([a-z_][a-z0-9_]*)`')
def token_xrefs(text):
retnodes = []
pos = 0
for m in token_re.finditer(text):
if m.start() > pos:
txt = text[pos:m.start()]
retnodes.append(nodes.Text(txt, txt))
refnode = addnodes.pending_xref(
m.group(1), reftype='token', refdomain='std', reftarget=m.group(1))
refnode += nodes.literal(m.group(1), m.group(1), classes=['xref'])
retnodes.append(refnode)
pos = m.end()
if pos < len(text):
retnodes.append(nodes.Text(text[pos:], text[pos:]))
return retnodes
class ProductionList(Directive):
"""
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
objects = env.domaindata['std']['objects']
node = addnodes.productionlist()
messages = []
i = 0
for rule in self.arguments[0].split('\n'):
if i == 0 and ':' not in rule:
# production group
continue
i += 1
try:
name, tokens = rule.split(':', 1)
except ValueError:
break
subnode = addnodes.production()
subnode['tokenname'] = name.strip()
if subnode['tokenname']:
idname = 'grammar-token-%s' % subnode['tokenname']
if idname not in self.state.document.ids:
subnode['ids'].append(idname)
self.state.document.note_implicit_target(subnode, subnode)
objects['token', subnode['tokenname']] = env.docname, idname
subnode.extend(token_xrefs(tokens))
node.append(subnode)
return [node] + messages
class StandardDomain(Domain):
"""
Domain for all objects that don't fit into another domain or are added
via the application interface.
"""
name = 'std'
label = 'Default'
object_types = {
'term': ObjType(l_('glossary term'), 'term', search=False),
'token': ObjType(l_('grammar token'), 'token', search=False),
'envvar': ObjType(l_('environment variable'), 'envvar'),
'cmdoption': ObjType(l_('program option'), 'option'),
}
directives = {
'program': Program,
'cmdoption': Cmdoption,
'envvar': EnvVar,
'glossary': Glossary,
'productionlist': ProductionList,
}
roles = {
'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis),
'envvar': XRefRole(), # XXX add index entries
'token': XRefRole(),
'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis),
}
initial_data = {
'progoptions': {}, # (program, name) -> docname, labelid
'objects': {}, # (type, name) -> docname, labelid
}
def clear_doc(self, docname):
for key, (fn, _) in self.data['progoptions'].items():
if fn == docname:
del self.data['progoptions'][key]
for key, (fn, _) in self.data['objects'].items():
if fn == docname:
del self.data['objects'][key]
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
if typ == 'option':
progname = node['refprogram']
docname, labelid = self.data['progoptions'].get((progname, target),
('', ''))
if not docname:
return None
else:
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
else:
docname, labelid = self.data['objects'].get((typ, target),
('', ''))
if not docname:
if typ == 'term':
env.warn(fromdocname, 'term not in glossary: %s' % target,
node.line)
return None
else:
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def get_objects(self):
return [] # XXX implement

View File

@@ -39,7 +39,6 @@ from sphinx import addnodes
from sphinx.util import movefile, get_matching_docs, SEP, ustrftime, \
docname_join, FilenameUniqDict, url_re, make_refnode
from sphinx.errors import SphinxError, ExtensionError
from sphinx.directives import additional_xref_types
orig_role_function = roles.role
orig_directive_function = directives.directive
@@ -56,7 +55,7 @@ default_settings = {
# This is increased every time an environment attribute is added
# or changed to properly invalidate pickle files.
ENV_VERSION = 31
ENV_VERSION = 32
default_substitutions = set([
@@ -292,14 +291,12 @@ class BuildEnvironment:
self.numbered_toctrees = set() # docnames that have :numbered: toctrees
# domain-specific inventories, here to be pickled
self.domaindata = {} # domainname -> domain-specific object
self.domaindata = {} # domainname -> domain-specific dict
# X-ref target inventory
self.labels = {} # labelname -> docname, labelid, sectionname
self.anonlabels = {} # labelname -> docname, labelid
self.progoptions = {} # (program, name) -> docname, labelid
self.reftargets = {} # (type, name) -> docname, labelid
# type: term, token, envvar, citation
self.citations = {} # citation name -> docname, labelid
# Other inventories
self.indexentries = {} # docname -> list of
@@ -311,15 +308,8 @@ class BuildEnvironment:
self.images = FilenameUniqDict()
self.dlfiles = FilenameUniqDict()
# These are set while parsing a file
self.docname = None # current document name
# XXX remove currmodule and currclass from here
self.currmodule = None # current module name
self.currclass = None # current class name
self.currdesc = None # current descref name
self.currprogram = None # current program name
self.index_num = 0 # autonumber for index targets
self.gloss_entries = set() # existing definition labels
# temporary data storage while reading a document
self.doc_read_data = {}
# Some magically present labels
def add_magic_label(name, description):
@@ -366,12 +356,9 @@ class BuildEnvironment:
for labelname, (fn, _, _) in self.labels.items():
if fn == docname:
del self.labels[labelname]
for key, (fn, _) in self.reftargets.items():
for key, (fn, _) in self.citations.items():
if fn == docname:
del self.reftargets[key]
for key, (fn, _) in self.progoptions.items():
if fn == docname:
del self.progoptions[key]
del self.citations[key]
for version, changes in self.versionchanges.items():
new = [change for change in changes if change[1] != docname]
changes[:] = new
@@ -574,7 +561,7 @@ class BuildEnvironment:
self.warn(docname, 'default role %s not found' %
self.config.default_role)
self.docname = docname
self.doc_read_data['docname'] = docname
self.settings['input_encoding'] = self.config.source_encoding
self.settings['trim_footnote_reference_space'] = \
self.config.trim_footnote_reference_space
@@ -603,7 +590,9 @@ class BuildEnvironment:
# monkey-patch, so that domain directives take precedence
def directive(directive_name, language_module, document):
"""Lookup a directive."""
directive_name = directive_name.lower()
# explicit domain given?
if ':' in directive_name:
domain_name, directive_name = directive_name.split(':', 1)
if domain_name in self.domains:
@@ -611,16 +600,25 @@ class BuildEnvironment:
directive = domain.directive(directive_name)
if directive is not None:
return directive, []
# else look in the default domain
elif self.default_domain is not None:
directive = self.default_domain.directive(directive_name)
if directive is not None:
return directive, []
# always look in the std domain
# (XXX or register them in the docutils namespace?)
directive = self.domains['std'].directive(directive_name)
if directive is not None:
return directive, []
# last, look in the default docutils namespace
return orig_directive_function(directive_name, language_module,
document)
directives.directive = directive
def role(role_name, language_module, lineno, reporter):
"""Lookup a role name."""
role_name = role_name.lower()
# explicit domain given?
if ':' in role_name:
domain_name, role_name = role_name.split(':', 1)
if domain_name in self.domains:
@@ -628,10 +626,16 @@ class BuildEnvironment:
role = domain.role(role_name)
if role is not None:
return role, []
# else look in the default domain
elif self.default_domain is not None:
role = self.default_domain.role(role_name)
if role is not None:
return role, []
# always look in the std domain
role = self.domains['std'].role(role_name)
if role is not None:
return role, []
# last, look in the default docutils namespace
return orig_role_function(role_name, language_module,
lineno, reporter)
roles.role = role
@@ -678,11 +682,8 @@ class BuildEnvironment:
metanode.__class__ = addnodes.meta
# cleanup
self.docname = None
self.currmodule = None
self.currclass = None
self.doc_read_data.clear()
self.default_domain = None
self.gloss_entries = set()
if save_parsed:
# save the parsed doctree
@@ -699,6 +700,33 @@ class BuildEnvironment:
else:
return doctree
# utilities to use while reading a document
@property
def docname(self):
"""Backwards compatible alias."""
return self.doc_read_data['docname']
@property
def currmodule(self):
"""Backwards compatible alias."""
return self.doc_read_data.get('py_module')
@property
def currclass(self):
"""Backwards compatible alias."""
return self.doc_read_data.get('py_class')
def new_serialno(self, category=''):
"""Return a serial number, e.g. for index entry targets."""
key = category + 'serialno'
cur = self.doc_read_data.get(key, 0)
self.doc_read_data[key] = cur + 1
return cur
def note_dependency(self, filename):
self.dependencies.setdefault(self.docname, set()).add(filename)
def filter_messages(self, doctree):
"""
Filter system messages from a doctree.
@@ -887,11 +915,11 @@ class BuildEnvironment:
def note_citations_from(self, docname, document):
for node in document.traverse(nodes.citation):
label = node[0].astext()
if ('citation', label) in self.reftargets:
if label in self.citations:
self.warn(docname, 'duplicate citation %s, ' % label +
'other instance in %s' % self.doc2path(
self.reftargets['citation', label][0]), node.line)
self.reftargets['citation', label] = (docname, node['ids'][0])
self.citations[label][0]), node.line)
self.citations[label] = (docname, node['ids'][0])
def note_toctree(self, docname, toctreenode):
"""Note a TOC tree directive in a document and gather information about
@@ -988,24 +1016,14 @@ class BuildEnvironment:
if result is not None:
return result
# -------
# these are called from docutils directives and therefore use self.docname
#
def note_progoption(self, optname, labelid):
self.progoptions[self.currprogram, optname] = (self.docname, labelid)
def note_reftarget(self, type, name, labelid):
self.reftargets[type, name] = (self.docname, labelid)
# XXX remove
def note_versionchange(self, type, version, node, lineno):
self.versionchanges.setdefault(version, []).append(
(type, self.docname, lineno, self.currmodule, self.currdesc,
(type, self.docname, lineno,
self.doc_read_data.get('py_module'),
self.doc_read_data.get('object'),
node.astext()))
def note_dependency(self, filename):
self.dependencies.setdefault(self.docname, set()).add(filename)
# -------
def get_domain(self, domainname):
"""Return the domain instance with the specified name.
Raises an ExtensionError if the domain is not registered."""
@@ -1197,11 +1215,6 @@ class BuildEnvironment:
return newnode
def resolve_references(self, doctree, fromdocname, builder):
# XXX remove this
reftarget_roles = set(('token', 'term', 'citation'))
# add all custom xref types too
reftarget_roles.update(i[0] for i in additional_xref_types.values())
for node in doctree.traverse(addnodes.pending_xref):
contnode = node[0].deepcopy()
newnode = None
@@ -1218,6 +1231,7 @@ class BuildEnvironment:
raise NoUri
newnode = domain.resolve_xref(self, fromdocname, builder,
typ, target, node, contnode)
# really hardwired reference types
elif typ == 'ref':
if node['refcaption']:
# reference to anonymous label; the reference uses
@@ -1276,37 +1290,21 @@ class BuildEnvironment:
newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname)
newnode.append(innernode)
elif typ == 'citation':
docname, labelid = self.citations.get(target, ('', ''))
if not docname:
self.warn(fromdocname,
'citation not found: %s' % target, node.line)
newnode = None
else:
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)
elif typ == 'keyword':
# keywords are referenced by named labels
# keywords are oddballs: they are referenced by named labels
docname, labelid, _ = self.labels.get(target, ('','',''))
if not docname:
#self.warn(fromdocname, 'unknown keyword: %s' % target)
newnode = contnode
else:
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)
elif typ == 'option':
progname = node['refprogram']
docname, labelid = self.progoptions.get((progname, target),
('', ''))
if not docname:
newnode = contnode
else:
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)
elif typ in reftarget_roles:
docname, labelid = self.reftargets.get((typ, target),
('', ''))
if not docname:
if typ == 'term':
self.warn(fromdocname,
'term not in glossary: %s' % target,
node.line)
elif typ == 'citation':
self.warn(fromdocname,
'citation not found: %s' % target,
node.line)
newnode = contnode
newnode = None
else:
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)

View File

@@ -546,9 +546,9 @@ class Documenter(object):
do all members, else those given by *self.options.members*.
"""
# set current namespace for finding members
self.env.autodoc_current_module = self.modname
self.env.doc_read_data['autodoc_module'] = self.modname
if self.objpath:
self.env.autodoc_current_class = self.objpath[0]
self.env.doc_read_data['autodoc_class'] = self.objpath[0]
want_all = all_members or self.options.inherited_members or \
self.options.members is ALL
@@ -589,8 +589,8 @@ class Documenter(object):
check_module=members_check_module)
# reset current objects
self.env.autodoc_current_module = None
self.env.autodoc_current_class = None
self.env.doc_read_data['autodoc_module'] = None
self.env.doc_read_data['autodoc_class'] = None
def generate(self, more_content=None, real_modname=None,
check_module=False, all_members=False):
@@ -748,11 +748,10 @@ class ModuleLevelDocumenter(Documenter):
else:
# if documenting a toplevel object without explicit module,
# it can be contained in another auto directive ...
if hasattr(self.env, 'autodoc_current_module'):
modname = self.env.autodoc_current_module
modname = self.env.doc_read_data.get('autodoc_module')
# ... or in the scope of a module directive
if not modname:
modname = self.env.currmodule
modname = self.env.doc_read_data.get('py_module')
# ... else, it stays None, which means invalid
return modname, parents + [base]
@@ -771,21 +770,20 @@ class ClassLevelDocumenter(Documenter):
# if documenting a class-level object without path,
# there must be a current class, either from a parent
# auto directive ...
if hasattr(self.env, 'autodoc_current_class'):
mod_cls = self.env.autodoc_current_class
mod_cls = self.env.doc_read_data.get('autodoc_class')
# ... or from a class directive
if mod_cls is None:
mod_cls = self.env.currclass
mod_cls = self.env.doc_read_data.get('py_class')
# ... if still None, there's no way to know
if mod_cls is None:
return None, []
modname, cls = rpartition(mod_cls, '.')
parents = [cls]
# if the module name is still missing, get it like above
if not modname and hasattr(self.env, 'autodoc_current_module'):
modname = self.env.autodoc_current_module
if not modname:
modname = self.env.currmodule
modname = self.env.doc_read_data.get('autodoc_module')
if not modname:
modname = self.env.doc_read_data.get('py_module')
# ... else, it stays None, which means invalid
return modname, parents + [base]
@@ -1082,7 +1080,7 @@ class AutoDirective(Directive):
# record all filenames as dependencies -- this will at least
# partially make automatic invalidation possible
for fn in self.filename_set:
self.env.note_dependency(fn)
self.state.document.settings.record_dependencies.add(fn)
# use a custom reporter that correctly assigns lines to source
# filename/description and lineno

View File

@@ -228,8 +228,9 @@ class Autosummary(Directive):
env = self.state.document.settings.env
prefixes = ['']
if env.currmodule:
prefixes.insert(0, env.currmodule)
currmodule = env.doc_read_data.get('py_module')
if currmodule:
prefixes.insert(0, currmodule)
items = []

View File

@@ -283,7 +283,8 @@ class InheritanceDiagram(Directive):
# Create a graph starting with the list of classes
try:
graph = InheritanceGraph(class_names, env.currmodule)
graph = InheritanceGraph(class_names,
env.doc_read_data.get('py_module'))
except InheritanceException, err:
return [node.document.reporter.warning(err.args[0],
line=self.lineno)]

View File

@@ -3,17 +3,17 @@
sphinx.ext.intersphinx
~~~~~~~~~~~~~~~~~~~~~~
Insert links to Python objects documented in remote Sphinx documentation.
Insert links to objects documented in remote Sphinx documentation.
This works as follows:
* Each Sphinx HTML build creates a file named "objects.inv" that contains
a mapping from Python identifiers to URIs relative to the HTML set's root.
* Each Sphinx HTML build creates a file named "objects.inv" that contains a
mapping from object names to URIs relative to the HTML set's root.
* Projects using the Intersphinx extension can specify links to such mapping
files in the `intersphinx_mapping` config value. The mapping will then be
used to resolve otherwise missing references to Python objects into links
to the other documentation.
used to resolve otherwise missing references to objects into links to the
other documentation.
* By default, the mapping file is assumed to be at the same location as the
rest of the documentation; however, the location of the mapping file can
@@ -41,12 +41,44 @@ if hasattr(urllib2, 'HTTPSHandler'):
urllib2.install_opener(urllib2.build_opener(*handlers))
def fetch_inventory_v1(f, uri, join):
invdata = {}
line = f.next()
projname = line.rstrip()[11:].decode('utf-8')
line = f.next()
version = line.rstrip()[11:]
for line in f:
name, type, location = line.rstrip().split(None, 2)
location = join(uri, location)
# version 1 did not add anchors to the location
if type == 'mod':
type = 'py:module'
location += '#module-' + name
else:
location += '#' + name
invdata.setdefault(type, {})[name] = (projname, version, location)
return invdata
def fetch_inventory_v2(f, uri, join):
invdata = {}
line = f.next()
projname = line.rstrip()[11:].decode('utf-8')
line = f.next()
version = line.rstrip()[11:]
for line in f:
name, type, prio, location = line.rstrip().split(None, 3)
location = join(uri, location)
invdata.setdefault(type, {})[name] = (projname, version, location)
return invdata
def fetch_inventory(app, uri, inv):
"""Fetch, parse and return an intersphinx inventory file."""
invdata = {}
# both *uri* (base URI of the links to generate) and *inv* (actual
# location of the inventory file) can be local or remote URIs
localuri = uri.find('://') == -1
join = localuri and path.join or posixpath.join
try:
if inv.find('://') != -1:
f = urllib2.urlopen(inv)
@@ -57,24 +89,18 @@ def fetch_inventory(app, uri, inv):
'%s: %s' % (inv, err.__class__, err))
return
try:
line = f.next()
if line.rstrip() != '# Sphinx inventory version 1':
line = f.next().rstrip()
if line == '# Sphinx inventory version 1':
invdata = fetch_inventory_v1(f, uri, join)
elif line == '# Sphinx inventory version 2':
invdata = fetch_inventory_v2(f, uri, join)
else:
f.close()
raise ValueError('unknown or unsupported inventory version')
line = f.next()
projname = line.rstrip()[11:].decode('utf-8')
line = f.next()
version = line.rstrip()[11:]
for line in f:
name, type, location = line.rstrip().split(None, 2)
if localuri:
location = path.join(uri, location)
else:
location = posixpath.join(uri, location)
invdata[name] = (type, projname, version, location)
f.close()
except Exception, err:
app.warn('intersphinx inventory %r not readable due to '
'%s: %s' % (inv, err.__class__, err))
'%s: %s' % (inv, err.__class__.__name__, err))
else:
return invdata
@@ -111,27 +137,29 @@ def load_mappings(app):
def missing_reference(app, env, node, contnode):
"""Attempt to resolve a missing reference via intersphinx references."""
type = node['reftype']
domain = node['refdomain']
fulltype = domain + ':' + type
target = node['reftarget']
if type == 'mod':
type, proj, version, uri = env.intersphinx_inventory.get(target,
('','','',''))
if type != 'mod':
return None
target = 'module-' + target # for link anchor
else:
if target[-2:] == '()':
target = target[:-2]
target = target.rstrip(' *')
# special case: exceptions and object methods
if type == 'exc' and '.' not in target and \
'exceptions.' + target in env.intersphinx_inventory:
target = 'exceptions.' + target
elif type in ('func', 'meth') and '.' not in target and \
'object.' + target in env.intersphinx_inventory:
target = 'object.' + target
if target not in env.intersphinx_inventory:
return None
type, proj, version, uri = env.intersphinx_inventory[target]
if type not in env.intersphinx_inventory:
return
if target not in env.intersphinx_inventory[type]:
return
proj, version, uri = env.intersphinx_inventory[type][target]
# XXX
if target[-2:] == '()':
target = target[:-2]
target = target.rstrip(' *')
# special case: exceptions and object methods
if type == 'exc' and '.' not in target and \
'exceptions.' + target in env.intersphinx_inventory:
target = 'exceptions.' + target
elif type in ('func', 'meth') and '.' not in target and \
'object.' + target in env.intersphinx_inventory:
target = 'object.' + target
if target not in env.intersphinx_inventory:
return None
type, proj, version, uri = env.intersphinx_inventory[target]
# print "Intersphinx hit:", target, uri
newnode = nodes.reference('', '')
newnode['refuri'] = uri + '#' + target

View File

@@ -69,7 +69,7 @@ class Refcounts(dict):
def add_refcount_annotations(self, app, doctree):
for node in doctree.traverse(addnodes.desc_content):
par = node.parent
if par['desctype'] != 'cfunction':
if par['domain'] != 'c' or par['objtype'] != 'function':
continue
if not par[0].has_key('names') or not par[0]['names']:
continue

View File

@@ -34,9 +34,7 @@ class Todo(Directive):
def run(self):
env = self.state.document.settings.env
targetid = "todo-%s" % env.index_num
env.index_num += 1
targetid = 'index-%s' % env.new_serialno('index')
targetnode = nodes.target('', '', ids=[targetid])
ad = make_admonition(todo_node, self.name, [_('Todo')], self.options,

View File

@@ -41,7 +41,7 @@ for rolename, nodeclass in generic_docroles.iteritems():
# -- generic cross-reference roles ---------------------------------------------
class XRefRole(object):
"""XXX add docstring"""
"""XXX add docstrings"""
nodeclass = addnodes.pending_xref
innernodeclass = nodes.literal
@@ -101,24 +101,6 @@ class XRefRole(object):
return [pnode], []
class OptionXRefRole(XRefRole):
innernodeclass = addnodes.literal_emphasis
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)
pnode['refprogram'] = program
return title, target
_EnvvarXrefRole = XRefRole()
def indexmarkup_role(typ, rawtext, etext, lineno, inliner,
@@ -129,11 +111,11 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner,
else:
typ = typ.lower()
text = utils.unescape(etext)
targetid = 'index-%s' % env.index_num
env.index_num += 1
targetid = 'index-%s' % env.new_serialno('index')
indexnode = addnodes.index()
targetnode = nodes.target('', '', ids=[targetid])
inliner.document.note_explicit_target(targetnode)
# XXX remove
if typ == 'envvar':
indexnode['entries'] = [('single', text, targetid, text),
('single', _('environment variable; %s') % text,
@@ -214,9 +196,6 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
specific_docroles = {
'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),
@@ -233,7 +212,7 @@ for rolename, func in specific_docroles.iteritems():
roles.register_local_role(rolename, func)
# compatibility alias
# backwards compatibility alias
def xfileref_role(*args, **kwds):
warnings.warn('xfileref_role is deprecated, use XRefRole',
DeprecationWarning, stacklevel=2)

View File

@@ -119,8 +119,8 @@ class IndexBuilder(object):
self._titles = {}
# stemmed word -> set(filenames)
self._mapping = {}
# desctypes -> index
self._desctypes = {}
# objtype -> index
self._objtypes = {}
def load(self, stream, format):
"""Reconstruct from frozen data."""
@@ -138,7 +138,7 @@ class IndexBuilder(object):
self._mapping[k] = set([index2fn[v]])
else:
self._mapping[k] = set(index2fn[i] for i in v)
# no need to load keywords/desctypes
# no need to load keywords/objtypes
def dump(self, stream, format):
"""Dump the frozen index to a stream."""
@@ -157,7 +157,7 @@ class IndexBuilder(object):
def get_descrefs(self, fn2index):
rv = {}
dt = self._desctypes
dt = self._objtypes
# XXX implement search capability
return rv
for fullname, (doc, desctype) in self.env.descrefs.iteritems():
@@ -193,9 +193,10 @@ class IndexBuilder(object):
filenames=filenames,
titles=titles,
terms=self.get_terms(fn2index),
descrefs=self.get_descrefs(fn2index),
modules=self.get_modules(fn2index),
desctypes=dict((v, k) for (k, v) in self._desctypes.items()),
# XXX
#descrefs=self.get_descrefs(fn2index),
#modules=self.get_modules(fn2index),
objtypes=dict((v, k) for (k, v) in self._objtypes.items()),
)
def prune(self, filenames):

View File

@@ -330,9 +330,8 @@ var Search = {
var filenames = this._index.filenames;
var titles = this._index.titles;
var terms = this._index.terms;
var descrefs = this._index.descrefs;
var modules = this._index.modules;
var desctypes = this._index.desctypes;
var objects = this._index.objects;
var objtypes = this._index.objtypes;
var fileMap = {};
var files = null;
var objectResults = [];
@@ -341,6 +340,7 @@ var Search = {
// lookup as object
if (object != null) {
// XXX must be adapted
for (var module in modules) {
if (module.indexOf(object) > -1) {
fn = modules[module];
@@ -348,12 +348,12 @@ var Search = {
objectResults.push([filenames[fn], module, '#module-'+module, descr]);
}
}
for (var prefix in descrefs) {
for (var name in descrefs[prefix]) {
for (var prefix in objects) {
for (var name in objects[prefix]) {
var fullname = (prefix ? prefix + '.' : '') + name;
if (fullname.toLowerCase().indexOf(object) > -1) {
match = descrefs[prefix][name];
descr = desctypes[match[1]] + _(', in ') + titles[match[0]];
match = objects[prefix][name];
descr = objtypes[match[1]] + _(', in ') + titles[match[0]];
objectResults.push([filenames[match[0]], fullname, '#'+fullname, descr]);
}
}

View File

@@ -61,7 +61,7 @@ class HTMLTranslator(BaseTranslator):
self.add_permalinks = builder.config.html_add_permalinks
def visit_desc(self, node):
self.body.append(self.starttag(node, 'dl', CLASS=node['desctype']))
self.body.append(self.starttag(node, 'dl', CLASS=node['objtype']))
def depart_desc(self, node):
self.body.append('</dl>\n\n')
@@ -69,7 +69,7 @@ class HTMLTranslator(BaseTranslator):
# the id is set automatically
self.body.append(self.starttag(node, 'dt'))
# anchor for per-desc interactive data
if node.parent['desctype'] != 'describe' \
if node.parent['objtype'] != 'describe' \
and node['ids'] and node['first']:
self.body.append('<!--[%s]-->' % node['ids'][0])
def depart_desc_signature(self, node):

View File

@@ -123,7 +123,7 @@ class Table(object):
class Desc(object):
def __init__(self, node):
self.env = LaTeXTranslator.desc_map.get(node['desctype'], 'describe')
self.env = LaTeXTranslator.desc_map.get(node['objtype'], 'describe')
self.type = self.cls = self.name = self.params = \
self.annotation = self.returns = ''
self.count = 0
@@ -462,7 +462,7 @@ class LaTeXTranslator(nodes.NodeVisitor):
def depart_desc_signature(self, node):
d = self.descstack[-1]
d.cls = d.cls.rstrip('.')
if node.parent['desctype'] != 'describe' and node['ids']:
if node.parent['objtype'] != 'describe' and node['ids']:
hyper = '\\hypertarget{%s}{}' % node['ids'][0]
else:
hyper = ''

View File

@@ -174,8 +174,8 @@ class TextTranslator(nodes.NodeVisitor):
def visit_desc_signature(self, node):
self.new_state(0)
if node.parent['desctype'] in ('class', 'exception'):
self.add_text('%s ' % node.parent['desctype'])
if node.parent['objtype'] in ('class', 'exception'):
self.add_text('%s ' % node.parent['objtype'])
def depart_desc_signature(self, node):
# XXX: wrap signatures in a way that makes sense
self.end_state(wrap=False, end=None)

View File

@@ -16,7 +16,7 @@ Contents:
subdir/includes
includes
markup
desc
objects
bom
math
autodoc

View File

@@ -97,28 +97,28 @@ def test_parse_name():
verify('function', 'util.raises', ('util', ['raises'], None, None))
verify('function', 'util.raises(exc) -> None',
('util', ['raises'], 'exc', 'None'))
directive.env.autodoc_current_module = 'util'
directive.env.doc_read_data['autodoc_module'] = 'util'
verify('function', 'raises', ('util', ['raises'], None, None))
directive.env.autodoc_current_module = None
directive.env.currmodule = 'util'
del directive.env.doc_read_data['autodoc_module']
directive.env.doc_read_data['py_module'] = 'util'
verify('function', 'raises', ('util', ['raises'], None, None))
verify('class', 'TestApp', ('util', ['TestApp'], None, None))
# for members
directive.env.currmodule = 'foo'
directive.env.doc_read_data['py_module'] = 'foo'
verify('method', 'util.TestApp.cleanup',
('util', ['TestApp', 'cleanup'], None, None))
directive.env.currmodule = 'util'
directive.env.currclass = 'Foo'
directive.env.autodoc_current_class = 'TestApp'
directive.env.doc_read_data['py_module'] = 'util'
directive.env.doc_read_data['py_class'] = 'Foo'
directive.env.doc_read_data['autodoc_class'] = 'TestApp'
verify('method', 'cleanup', ('util', ['TestApp', 'cleanup'], None, None))
verify('method', 'TestApp.cleanup',
('util', ['TestApp', 'cleanup'], None, None))
# and clean up
directive.env.currmodule = None
directive.env.currclass = None
directive.env.autodoc_current_class = None
del directive.env.doc_read_data['py_module']
del directive.env.doc_read_data['py_class']
del directive.env.doc_read_data['autodoc_class']
def test_format_signature():
@@ -353,7 +353,7 @@ def test_generate():
'function', 'util.foobar', more_content=None)
# test auto and given content mixing
directive.env.currmodule = 'test_autodoc'
directive.env.doc_read_data['py_module'] = 'test_autodoc'
assert_result_contains(' Function.', 'method', 'Class.meth')
add_content = ViewList()
add_content.append('Content.', '', 0)
@@ -428,7 +428,7 @@ def test_generate():
'class', 'Outer', all_members=True)
# test generation for C modules (which have no source file)
directive.env.currmodule = 'time'
directive.env.doc_read_data['py_module'] = 'time'
assert_processes([('function', 'time.asctime')], 'function', 'asctime')
assert_processes([('function', 'time.asctime')], 'function', 'asctime')

View File

@@ -111,11 +111,11 @@ HTML_XPATH = {
".//li/tt/em/span[@class='pre']": '^i$',
".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8',
".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1',
".//a[@href='desc.html#envvar-HOME']/tt/span[@class='pre']": 'HOME',
".//a[@href='objects.html#envvar-HOME']/tt/span[@class='pre']": 'HOME',
".//a[@href='#with']/tt/span[@class='pre']": '^with$',
".//a[@href='#grammar-token-try_stmt']/tt/span": '^statement$',
".//a[@href='subdir/includes.html']/em": 'Including in subdir',
".//a[@href='desc.html#cmdoption-python-c']/em": 'Python -c option',
".//a[@href='objects.html#cmdoption-python-c']/em": 'Python -c option',
# abbreviations
".//abbr[@title='abbreviation']": '^abbr$',
# version stuff
@@ -141,13 +141,13 @@ HTML_XPATH = {
".//p": 'In both.',
".//p": 'Always present',
},
'desc.html': {
'objects.html': {
".//dt[@id='mod.Cls.meth1']": '',
".//dt[@id='errmod.Error']": '',
".//a[@href='#mod.Cls']": '',
".//dl[@class='userdesc']": '',
".//dt[@id='userdescrole-myobj']": '',
".//a[@href='#userdescrole-myobj']": '',
".//dt[@id='userdesc-myobj']": '',
".//a[@href='#userdesc-myobj']": '',
".//span[@class='pre']": 'CFunction()',
},
'contents.html': {

View File

@@ -95,7 +95,7 @@ def test_object_inventory():
refs = env.domaindata['py']['objects']
assert 'func_without_module' in refs
assert refs['func_without_module'] == ('desc', 'function')
assert refs['func_without_module'] == ('objects', 'function')
assert 'func_without_module2' in refs
assert 'mod.func_in_module' in refs
assert 'mod.Cls' in refs
@@ -110,7 +110,7 @@ def test_object_inventory():
assert 'func_noindex' not in refs
assert env.domaindata['py']['modules']['mod'] == \
('desc', 'Module synopsis.', 'UNIX', False)
('objects', 'Module synopsis.', 'UNIX', False)
assert env.domains['py'].data is env.domaindata['py']
assert env.domains['c'].data is env.domaindata['c']