This commit is contained in:
Armin Ronacher 2009-07-14 11:48:33 +02:00
commit ec24ba6864
14 changed files with 485 additions and 360 deletions

View File

@ -14,19 +14,21 @@
import sys import sys
import types import types
import posixpath import posixpath
from os import path
from cStringIO import StringIO from cStringIO import StringIO
from docutils import nodes from docutils import nodes
from docutils.parsers.rst import directives, roles from docutils.parsers.rst import directives, roles
import sphinx import sphinx
from sphinx import package_dir, locale
from sphinx.roles import XRefRole from sphinx.roles import XRefRole
from sphinx.config import Config from sphinx.config import Config
from sphinx.errors import SphinxError, SphinxWarning, ExtensionError from sphinx.errors import SphinxError, SphinxWarning, ExtensionError
from sphinx.domains import domains from sphinx.domains import all_domains
from sphinx.builders import BUILTIN_BUILDERS from sphinx.builders import BUILTIN_BUILDERS
from sphinx.directives import GenericDesc, Target, additional_xref_types from sphinx.directives import GenericDesc, Target, additional_xref_types
from sphinx.environment import SphinxStandaloneReader from sphinx.environment import BuildEnvironment, SphinxStandaloneReader
from sphinx.util import pycompat from sphinx.util import pycompat
from sphinx.util.tags import Tags from sphinx.util.tags import Tags
from sphinx.util.compat import Directive, directive_dwim from sphinx.util.compat import Directive, directive_dwim
@ -50,6 +52,7 @@ events = {
} }
CONFIG_FILENAME = 'conf.py' CONFIG_FILENAME = 'conf.py'
ENV_PICKLE_FILENAME = 'environment.pickle'
class Sphinx(object): class Sphinx(object):
@ -62,6 +65,7 @@ class Sphinx(object):
self._listeners = {} self._listeners = {}
self.builderclasses = BUILTIN_BUILDERS.copy() self.builderclasses = BUILTIN_BUILDERS.copy()
self.builder = None self.builder = None
self.env = None
self.srcdir = srcdir self.srcdir = srcdir
self.confdir = confdir self.confdir = confdir
@ -104,8 +108,62 @@ class Sphinx(object):
# now that we know all config values, collect them from conf.py # now that we know all config values, collect them from conf.py
self.config.init_values() self.config.init_values()
# set up translation infrastructure
self._init_i18n()
# set up the build environment
self._init_env(freshenv)
# set up the builder
self._init_builder(buildername)
def _init_i18n(self):
"""
Load translated strings from the configured localedirs if
enabled in the configuration.
"""
if self.config.language is not None:
self.info(bold('loading translations [%s]... ' %
self.config.language), nonl=True)
locale_dirs = [None, path.join(package_dir, 'locale')] + \
[path.join(self.srcdir, x) for x in self.config.locale_dirs]
else:
locale_dirs = []
self.translator, has_translation = locale.init(locale_dirs,
self.config.language)
if self.config.language is not None:
if has_translation:
self.info('done')
else:
self.info('locale not available')
def _init_env(self, freshenv):
if freshenv:
self.env = BuildEnvironment(self.srcdir, self.doctreedir,
self.config)
self.env.find_files(self.config)
for domain in all_domains.keys():
self.env.domains[domain] = all_domains[domain](self.env)
else:
try:
self.info(bold('loading pickled environment... '), nonl=True)
self.env = BuildEnvironment.frompickle(self.config,
path.join(self.doctreedir, ENV_PICKLE_FILENAME))
self.env.domains = {}
for domain in all_domains.keys():
# this can raise if the data version doesn't fit
self.env.domains[domain] = all_domains[domain](self.env)
self.info('done')
except Exception, err:
if type(err) is IOError and err.errno == 2:
self.info('not yet created')
else:
self.info('failed: %s' % err)
return self._init_env(freshenv=True)
self.env.set_warnfunc(self.warn)
def _init_builder(self, buildername):
if buildername is None: if buildername is None:
print >>status, 'No builder selected, using default: html' print >>self._status, 'No builder selected, using default: html'
buildername = 'html' buildername = 'html'
if buildername not in self.builderclasses: if buildername not in self.builderclasses:
raise SphinxError('Builder name %s not registered' % buildername) raise SphinxError('Builder name %s not registered' % buildername)
@ -116,9 +174,7 @@ class Sphinx(object):
mod, cls = builderclass mod, cls = builderclass
builderclass = getattr( builderclass = getattr(
__import__('sphinx.builders.' + mod, None, None, [cls]), cls) __import__('sphinx.builders.' + mod, None, None, [cls]), cls)
self.builder = builderclass(self, freshenv=freshenv) self.builder = builderclass(self)
self.builder.tags = self.tags
self.builder.tags.add(self.builder.format)
self.emit('builder-inited') self.emit('builder-inited')
def build(self, all_files, filenames): def build(self, all_files, filenames):
@ -300,9 +356,9 @@ class Sphinx(object):
roles.register_local_role(name, role) roles.register_local_role(name, role)
def add_domain(self, domain): def add_domain(self, domain):
if domain.name in domains: if domain.name in all_domains:
raise ExtensionError('domain %s already registered' % domain.name) raise ExtensionError('domain %s already registered' % domain.name)
domains[domain.name] = domain all_domains[domain.name] = domain
def add_description_unit(self, directivename, rolename, indextemplate='', def add_description_unit(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None): parse_node=None, ref_nodeclass=None):
@ -311,7 +367,7 @@ class Sphinx(object):
directives.register_directive(directivename, directives.register_directive(directivename,
directive_dwim(GenericDesc)) directive_dwim(GenericDesc))
# XXX support more options? # XXX support more options?
role_func = XRefRole('', innernodeclass=ref_nodeclass) role_func = XRefRole(innernodeclass=ref_nodeclass)
roles.register_local_role(rolename, role_func) roles.register_local_role(rolename, role_func)
def add_crossref_type(self, directivename, rolename, indextemplate='', def add_crossref_type(self, directivename, rolename, indextemplate='',
@ -319,7 +375,7 @@ class Sphinx(object):
additional_xref_types[directivename] = (rolename, indextemplate, None) additional_xref_types[directivename] = (rolename, indextemplate, None)
directives.register_directive(directivename, directive_dwim(Target)) directives.register_directive(directivename, directive_dwim(Target))
# XXX support more options # XXX support more options
role_func = XRefRole('', innernodeclass=ref_nodeclass) role_func = XRefRole(innernodeclass=ref_nodeclass)
roles.register_local_role(rolename, role_func) roles.register_local_role(rolename, role_func)
def add_transform(self, transform): def add_transform(self, transform):

View File

@ -15,9 +15,7 @@ from os import path
from docutils import nodes from docutils import nodes
from sphinx import package_dir, locale
from sphinx.util import SEP, relative_uri from sphinx.util import SEP, relative_uri
from sphinx.environment import BuildEnvironment
from sphinx.util.console import bold, purple, darkgreen, term_width_line from sphinx.util.console import bold, purple, darkgreen, term_width_line
# side effect: registers roles and directives # side effect: registers roles and directives
@ -25,9 +23,6 @@ from sphinx import roles
from sphinx import directives from sphinx import directives
ENV_PICKLE_FILENAME = 'environment.pickle'
class Builder(object): class Builder(object):
""" """
Builds target formats from the reST sources. Builds target formats from the reST sources.
@ -38,7 +33,8 @@ class Builder(object):
# builder's output format, or '' if no document output is produced # builder's output format, or '' if no document output is produced
format = '' format = ''
def __init__(self, app, env=None, freshenv=False): def __init__(self, app):
self.env = app.env
self.srcdir = app.srcdir self.srcdir = app.srcdir
self.confdir = app.confdir self.confdir = app.confdir
self.outdir = app.outdir self.outdir = app.outdir
@ -50,18 +46,13 @@ class Builder(object):
self.warn = app.warn self.warn = app.warn
self.info = app.info self.info = app.info
self.config = app.config self.config = app.config
self.tags = app.tags
self.load_i18n() self.tags.add(self.format)
# images that need to be copied over (source -> dest) # images that need to be copied over (source -> dest)
self.images = {} self.images = {}
# if None, this is set in load_env()
self.env = env
self.freshenv = freshenv
self.init() self.init()
self.load_env()
# helper methods # helper methods
@ -167,50 +158,6 @@ class Builder(object):
# build methods # build methods
def load_i18n(self):
"""
Load translated strings from the configured localedirs if
enabled in the configuration.
"""
if self.config.language is not None:
self.info(bold('loading translations [%s]... ' %
self.config.language), nonl=True)
locale_dirs = [None, path.join(package_dir, 'locale')] + \
[path.join(self.srcdir, x) for x in self.config.locale_dirs]
else:
locale_dirs = []
self.translator, has_translation = locale.init(locale_dirs,
self.config.language)
if self.config.language is not None:
if has_translation:
self.info('done')
else:
self.info('locale not available')
def load_env(self):
"""Set up the build environment."""
if self.env:
return
if not self.freshenv:
try:
self.info(bold('loading pickled environment... '), nonl=True)
self.env = BuildEnvironment.frompickle(self.config,
path.join(self.doctreedir, ENV_PICKLE_FILENAME))
self.info('done')
except Exception, err:
if type(err) is IOError and err.errno == 2:
self.info('not found')
else:
self.info('failed: %s' % err)
self.env = BuildEnvironment(self.srcdir, self.doctreedir,
self.config)
self.env.find_files(self.config)
else:
self.env = BuildEnvironment(self.srcdir, self.doctreedir,
self.config)
self.env.find_files(self.config)
self.env.set_warnfunc(self.warn)
def build_all(self): def build_all(self):
"""Build all source files.""" """Build all source files."""
self.build(None, summary='all source files', method='all') self.build(None, summary='all source files', method='all')
@ -290,6 +237,7 @@ class Builder(object):
if updated_docnames: if updated_docnames:
# save the environment # save the environment
from sphinx.application import ENV_PICKLE_FILENAME
self.info(bold('pickling environment... '), nonl=True) self.info(bold('pickling environment... '), nonl=True)
self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME)) self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME))
self.info('done') self.info('done')

View File

@ -33,7 +33,8 @@ from sphinx.util import SEP, os_path, relative_uri, ensuredir, \
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.search import js_index from sphinx.search import js_index
from sphinx.theming import Theme from sphinx.theming import Theme
from sphinx.builders import Builder, ENV_PICKLE_FILENAME from sphinx.builders import Builder
from sphinx.application import ENV_PICKLE_FILENAME
from sphinx.highlighting import PygmentsBridge from sphinx.highlighting import PygmentsBridge
from sphinx.util.console import bold from sphinx.util.console import bold
from sphinx.writers.html import HTMLWriter, HTMLTranslator, \ from sphinx.writers.html import HTMLWriter, HTMLTranslator, \
@ -239,7 +240,9 @@ class StandaloneHTMLBuilder(Builder):
rellinks = [] rellinks = []
if self.config.html_use_index: if self.config.html_use_index:
rellinks.append(('genindex', _('General Index'), 'I', _('index'))) rellinks.append(('genindex', _('General Index'), 'I', _('index')))
if self.config.html_use_modindex and self.env.modules: # XXX generalization of modindex?
if self.config.html_use_modindex and \
self.env.domaindata['py']['modules']:
rellinks.append(('modindex', _('Global Module Index'), rellinks.append(('modindex', _('Global Module Index'),
'M', _('modules'))) 'M', _('modules')))
@ -404,12 +407,13 @@ class StandaloneHTMLBuilder(Builder):
# the global module index # the global module index
if self.config.html_use_modindex and self.env.modules: moduleindex = self.env.domaindata['py']['modules']
if self.config.html_use_modindex and moduleindex:
# the sorted list of all modules, for the global module index # the sorted list of all modules, for the global module index
modules = sorted(((mn, (self.get_relative_uri('modindex', fn) + modules = sorted(((mn, (self.get_relative_uri('modindex', fn) +
'#module-' + mn, sy, pl, dep)) '#module-' + mn, sy, pl, dep))
for (mn, (fn, sy, pl, dep)) in for (mn, (fn, sy, pl, dep)) in
self.env.modules.iteritems()), moduleindex.iteritems()),
key=lambda x: x[0].lower()) key=lambda x: x[0].lower())
# collect all platforms # collect all platforms
platforms = set() platforms = set()
@ -709,14 +713,15 @@ class StandaloneHTMLBuilder(Builder):
self.info(bold('dumping object inventory... '), nonl=True) self.info(bold('dumping object inventory... '), nonl=True)
f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w') f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w')
try: try:
# XXX inventory version 2
f.write('# Sphinx inventory version 1\n') f.write('# Sphinx inventory version 1\n')
f.write('# Project: %s\n' % self.config.project.encode('utf-8')) f.write('# Project: %s\n' % self.config.project.encode('utf-8'))
f.write('# Version: %s\n' % self.config.version) f.write('# Version: %s\n' % self.config.version)
for modname, info in self.env.modules.iteritems(): #for modname, info in self.env.modules.iteritems():
f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0]))) # f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0])))
for refname, (docname, desctype) in self.env.descrefs.iteritems(): #for refname, (docname, desctype) in self.env.descrefs.iteritems():
f.write('%s %s %s\n' % (refname, desctype, # f.write('%s %s %s\n' % (refname, desctype,
self.get_target_uri(docname))) # self.get_target_uri(docname)))
finally: finally:
f.close() f.close()
self.info('done') self.info('done')

View File

@ -204,7 +204,10 @@ class DescDirective(Directive):
pass pass
def run(self): def run(self):
self.desctype = self.name if ':' in self.name:
self.domain, self.desctype = self.name.split(':', 1)
else:
self.domain, self.desctype = '', self.name
self.env = self.state.document.settings.env self.env = self.state.document.settings.env
self.indexnode = addnodes.index(entries=[]) self.indexnode = addnodes.index(entries=[])
@ -366,7 +369,7 @@ class DefaultDomain(Directive):
def run(self): def run(self):
env = self.state.document.settings.env env = self.state.document.settings.env
domain_name = arguments[0] domain_name = arguments[0]
env.default_domain = domains.get(domain_name) env.default_domain = env.domains.get(domain_name)
# Note: the target directive is not registered here, it is used by the # Note: the target directive is not registered here, it is used by the

View File

@ -103,75 +103,6 @@ class TocTree(Directive):
return ret return ret
class Module(Directive):
"""
Directive to mark description of a new module.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'platform': lambda x: x,
'synopsis': lambda x: x,
'noindex': directives.flag,
'deprecated': directives.flag,
}
def run(self):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
noindex = 'noindex' in self.options
env.currmodule = modname
env.note_module(modname, self.options.get('synopsis', ''),
self.options.get('platform', ''),
'deprecated' in self.options)
modulenode = addnodes.module()
modulenode['modname'] = modname
modulenode['synopsis'] = self.options.get('synopsis', '')
targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
self.state.document.note_explicit_target(targetnode)
ret = [modulenode, targetnode]
if 'platform' in self.options:
platform = self.options['platform']
modulenode['platform'] = platform
node = nodes.paragraph()
node += nodes.emphasis('', _('Platforms: '))
node += nodes.Text(platform, platform)
ret.append(node)
# the synopsis isn't printed; in fact, it is only used in the
# modindex currently
if not noindex:
indextext = _('%s (module)') % modname
inode = addnodes.index(entries=[('single', indextext,
'module-' + modname, modname)])
ret.insert(0, inode)
return ret
class CurrentModule(Directive):
"""
This directive is just to tell Sphinx that we're documenting
stuff in module foo, but links to module foo won't lead here.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
if modname == 'None':
env.currmodule = None
else:
env.currmodule = modname
return []
class Author(Directive): class Author(Directive):
""" """
Directive to give the name of the author of the current document Directive to give the name of the author of the current document
@ -559,8 +490,6 @@ class Only(Directive):
directives.register_directive('toctree', directive_dwim(TocTree)) directives.register_directive('toctree', directive_dwim(TocTree))
directives.register_directive('module', directive_dwim(Module))
directives.register_directive('currentmodule', directive_dwim(CurrentModule))
directives.register_directive('sectionauthor', directive_dwim(Author)) directives.register_directive('sectionauthor', directive_dwim(Author))
directives.register_directive('moduleauthor', directive_dwim(Author)) directives.register_directive('moduleauthor', directive_dwim(Author))
directives.register_directive('program', directive_dwim(Program)) directives.register_directive('program', directive_dwim(Program))

View File

@ -4,17 +4,23 @@
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
Support for domains, which are groupings of description directives Support for domains, which are groupings of description directives
describing e.g. constructs of one programming language. and roles describing e.g. constructs of one programming language.
:copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import re import re
import string
from docutils import nodes
from docutils.parsers.rst import directives
from sphinx import addnodes from sphinx import addnodes
from sphinx.roles import XRefRole from sphinx.roles import XRefRole
from sphinx.directives import DescDirective from sphinx.directives import DescDirective
from sphinx.util import make_refnode
from sphinx.util.compat import Directive
class Domain(object): class Domain(object):
@ -23,6 +29,68 @@ class Domain(object):
roles = {} roles = {}
label = '' 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:
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)
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):
pass
# REs for Python signatures # REs for Python signatures
py_sig_re = re.compile( py_sig_re = re.compile(
@ -146,7 +214,15 @@ class PythonDesc(DescDirective):
signode['ids'].append(fullname) signode['ids'].append(fullname)
signode['first'] = (not self.names) signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode) self.state.document.note_explicit_target(signode)
self.env.note_descref(fullname, self.desctype, self.lineno) objects = self.env.domaindata['py']['objects']
if fullname in objects:
self.env.warn(
self.env.docname,
'duplicate object description of %s, ' % fullname +
'other instance in ' +
self.env.doc2path(objects[fullname][0]),
self.lineno)
objects[fullname] = (self.env.docname, self.desctype)
indextext = self.get_index_text(modname, name_cls) indextext = self.get_index_text(modname, name_cls)
if indextext: if indextext:
@ -286,6 +362,75 @@ class ClassmemberDesc(PythonDesc):
self.clsname_set = True self.clsname_set = True
class PyModule(Directive):
"""
Directive to mark description of a new module.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {
'platform': lambda x: x,
'synopsis': lambda x: x,
'noindex': directives.flag,
'deprecated': directives.flag,
}
def run(self):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
noindex = 'noindex' in self.options
env.currmodule = modname
env.domaindata['py']['modules'][modname] = \
(env.docname, self.options.get('synopsis', ''),
self.options.get('platform', ''), 'deprecated' in self.options)
modulenode = addnodes.module()
modulenode['modname'] = modname
modulenode['synopsis'] = self.options.get('synopsis', '')
targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
self.state.document.note_explicit_target(targetnode)
ret = [modulenode, targetnode]
if 'platform' in self.options:
platform = self.options['platform']
modulenode['platform'] = platform
node = nodes.paragraph()
node += nodes.emphasis('', _('Platforms: '))
node += nodes.Text(platform, platform)
ret.append(node)
# the synopsis isn't printed; in fact, it is only used in the
# modindex currently
if not noindex:
indextext = _('%s (module)') % modname
inode = addnodes.index(entries=[('single', indextext,
'module-' + modname, modname)])
ret.insert(0, inode)
return ret
class PyCurrentModule(Directive):
"""
This directive is just to tell Sphinx that we're documenting
stuff in module foo, but links to module foo won't lead here.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
option_spec = {}
def run(self):
env = self.state.document.settings.env
modname = self.arguments[0].strip()
if modname == 'None':
env.currmodule = None
else:
env.currmodule = modname
return []
class PyXRefRole(XRefRole): class PyXRefRole(XRefRole):
def process_link(self, env, pnode, has_explicit_title, title, target): def process_link(self, env, pnode, has_explicit_title, title, target):
pnode['modname'] = env.currmodule pnode['modname'] = env.currmodule
@ -321,18 +466,105 @@ class PythonDomain(Domain):
'classmethod': ClassmemberDesc, 'classmethod': ClassmemberDesc,
'staticmethod': ClassmemberDesc, 'staticmethod': ClassmemberDesc,
'attribute': ClassmemberDesc, 'attribute': ClassmemberDesc,
'module': PyModule,
'currentmodule': PyCurrentModule,
} }
roles = { roles = {
'data': PyXRefRole('py'), 'data': PyXRefRole(),
'exc': PyXRefRole('py'), 'exc': PyXRefRole(),
'func': PyXRefRole('py', True), 'func': PyXRefRole(fix_parens=True),
'class': PyXRefRole('py'), 'class': PyXRefRole(),
'const': PyXRefRole('py'), 'const': PyXRefRole(),
'attr': PyXRefRole('py'), 'attr': PyXRefRole(),
'meth': PyXRefRole('py', True), 'meth': PyXRefRole(fix_parens=True),
'mod': PyXRefRole('py'), 'mod': PyXRefRole(),
'obj': PyXRefRole('py'), 'obj': PyXRefRole(),
} }
initial_data = {
'objects': {}, # fullname -> docname, desctype
'modules': {}, # modname -> docname, synopsis, platform, deprecated
}
def clear_doc(self, docname):
for fullname, (fn, _) in self.data['objects'].items():
if fn == docname:
del self.data['objects'][fullname]
for modname, (fn, _, _, _) in self.data['modules'].items():
if fn == docname:
del self.data['modules'][modname]
def find_desc(self, env, modname, classname, name, type, searchorder=0):
"""
Find a Python object description for "name", perhaps using the given
module and/or classname.
"""
# skip parens
if name[-2:] == '()':
name = name[:-2]
if not name:
return None, None
objects = self.data['objects']
newname = None
if searchorder == 1:
if modname and classname and \
modname + '.' + classname + '.' + name in objects:
newname = modname + '.' + classname + '.' + name
elif modname and modname + '.' + name in objects:
newname = modname + '.' + name
elif name in objects:
newname = name
else:
if name in objects:
newname = name
elif modname and modname + '.' + name in objects:
newname = modname + '.' + name
elif modname and classname and \
modname + '.' + classname + '.' + name in objects:
newname = modname + '.' + classname + '.' + name
# special case: builtin exceptions have module "exceptions" set
elif type == 'exc' and '.' not in name and \
'exceptions.' + name in objects:
newname = 'exceptions.' + name
# special case: object methods
elif type in ('func', 'meth') and '.' not in name and \
'object.' + name in objects:
newname = 'object.' + name
if newname is None:
return None, None
return newname, objects[newname]
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
if (typ == 'mod' or
typ == 'obj' and target in self.data['modules']):
docname, synopsis, platform, deprecated = \
self.data['modules'].get(target, ('','','', ''))
if not docname:
return None
elif docname == fromdocname:
# don't link to self
return contnode
else:
title = '%s%s%s' % ((platform and '(%s) ' % platform),
synopsis,
(deprecated and ' (deprecated)' or ''))
return make_refnode(builder, fromdocname, docname,
'module-' + target, contnode, title)
else:
modname = node['modname']
clsname = node['classname']
searchorder = node.hasattr('refspecific') and 1 or 0
name, desc = self.find_desc(env, modname, clsname,
target, typ, searchorder)
if not desc:
return None
else:
return make_refnode(builder, fromdocname, desc[0], name,
contnode, name)
# RE to split at word boundaries # RE to split at word boundaries
@ -370,7 +602,7 @@ class CDesc(DescDirective):
if part[0] in string.ascii_letters+'_' and \ if part[0] in string.ascii_letters+'_' and \
part not in self.stopwords: part not in self.stopwords:
pnode = addnodes.pending_xref( pnode = addnodes.pending_xref(
'', reftype='ctype', reftarget=part, '', refdomain='c', reftype='type', reftarget=part,
modname=None, classname=None) modname=None, classname=None)
pnode += tnode pnode += tnode
node += pnode node += pnode
@ -451,7 +683,14 @@ class CDesc(DescDirective):
signode['ids'].append(name) signode['ids'].append(name)
signode['first'] = (not self.names) signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode) self.state.document.note_explicit_target(signode)
self.env.note_descref(name, self.desctype, self.lineno) 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) indextext = self.get_index_text(name)
if indextext: if indextext:
@ -470,16 +709,33 @@ class CDomain(Domain):
'var': CDesc, 'var': CDesc,
} }
roles = { roles = {
'member': XRefRole('c'), 'member': XRefRole(),
'macro': XRefRole('c'), 'macro': XRefRole(),
'func' : XRefRole('c', True), 'func' : XRefRole(fix_parens=True),
'data': XRefRole('c'), 'data': XRefRole(),
'type': XRefRole('c'), '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 # this contains all registered domains
domains = { all_domains = {
'py': PythonDomain, 'py': PythonDomain,
'c': CDomain, 'c': CDomain,
} }

View File

@ -43,9 +43,8 @@ from docutils.transforms.parts import ContentsFilter
from sphinx import addnodes from sphinx import addnodes
from sphinx.util import movefile, get_matching_docs, SEP, ustrftime, \ from sphinx.util import movefile, get_matching_docs, SEP, ustrftime, \
docname_join, FilenameUniqDict, url_re docname_join, FilenameUniqDict, url_re, make_refnode
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.domains import domains
from sphinx.directives import additional_xref_types from sphinx.directives import additional_xref_types
orig_role_function = roles.role orig_role_function = roles.role
@ -214,9 +213,9 @@ class BuildEnvironment:
env = pickle.load(picklefile) env = pickle.load(picklefile)
finally: finally:
picklefile.close() picklefile.close()
env.config.values = config.values
if env.version != ENV_VERSION: if env.version != ENV_VERSION:
raise IOError('env version not current') raise IOError('env version not current')
env.config.values = config.values
return env return env
def topickle(self, filename): def topickle(self, filename):
@ -225,6 +224,8 @@ class BuildEnvironment:
self.set_warnfunc(None) self.set_warnfunc(None)
values = self.config.values values = self.config.values
del self.config.values del self.config.values
domains = self.domains
del self.domains
# first write to a temporary file, so that if dumping fails, # first write to a temporary file, so that if dumping fails,
# the existing environment won't be overwritten # the existing environment won't be overwritten
picklefile = open(filename + '.tmp', 'wb') picklefile = open(filename + '.tmp', 'wb')
@ -241,6 +242,7 @@ class BuildEnvironment:
picklefile.close() picklefile.close()
movefile(filename + '.tmp', filename) movefile(filename + '.tmp', filename)
# reset attributes # reset attributes
self.domains = domains
self.config.values = values self.config.values = values
self.set_warnfunc(warnfunc) self.set_warnfunc(warnfunc)
@ -254,6 +256,9 @@ class BuildEnvironment:
# the application object; only set while update() runs # the application object; only set while update() runs
self.app = None self.app = None
# all the registered domains, set by the application
self.domains = {}
# the docutils settings for building # the docutils settings for building
self.settings = default_settings.copy() self.settings = default_settings.copy()
self.settings['env'] = self self.settings['env'] = self
@ -292,10 +297,10 @@ class BuildEnvironment:
self.glob_toctrees = set() # docnames that have :glob: toctrees self.glob_toctrees = set() # docnames that have :glob: toctrees
self.numbered_toctrees = set() # docnames that have :numbered: toctrees self.numbered_toctrees = set() # docnames that have :numbered: toctrees
# domain-specific inventories, here to be pickled
self.domaindata = {} # domainname -> domain-specific object
# X-ref target inventory # X-ref target inventory
self.descrefs = {} # fullname -> docname, desctype
self.modules = {} # modname -> docname, synopsis,
# platform, deprecated
self.labels = {} # labelname -> docname, labelid, sectionname self.labels = {} # labelname -> docname, labelid, sectionname
self.anonlabels = {} # labelname -> docname, labelid self.anonlabels = {} # labelname -> docname, labelid
self.progoptions = {} # (program, name) -> docname, labelid self.progoptions = {} # (program, name) -> docname, labelid
@ -363,12 +368,6 @@ class BuildEnvironment:
fnset.discard(docname) fnset.discard(docname)
if not fnset: if not fnset:
del self.files_to_rebuild[subfn] del self.files_to_rebuild[subfn]
for fullname, (fn, _) in self.descrefs.items():
if fn == docname:
del self.descrefs[fullname]
for modname, (fn, _, _, _) in self.modules.items():
if fn == docname:
del self.modules[modname]
for labelname, (fn, _, _) in self.labels.items(): for labelname, (fn, _, _) in self.labels.items():
if fn == docname: if fn == docname:
del self.labels[labelname] del self.labels[labelname]
@ -382,6 +381,10 @@ class BuildEnvironment:
new = [change for change in changes if change[1] != docname] new = [change for change in changes if change[1] != docname]
changes[:] = new changes[:] = new
# XXX why does this not work inside the if?
for domain in self.domains.values():
domain.clear_doc(docname)
def doc2path(self, docname, base=True, suffix=None): def doc2path(self, docname, base=True, suffix=None):
""" """
Return the filename for the document name. Return the filename for the document name.
@ -561,6 +564,7 @@ class BuildEnvironment:
# remove all inventory entries for that file # remove all inventory entries for that file
if app: if app:
app.emit('env-purge-doc', self, docname) app.emit('env-purge-doc', self, docname)
self.clear_doc(docname) self.clear_doc(docname)
if src_path is None: if src_path is None:
@ -600,18 +604,20 @@ class BuildEnvironment:
return data return data
# defaults to the global default, but can be re-set in a document # defaults to the global default, but can be re-set in a document
self.default_domain = domains.get(self.config.default_domain) self.default_domain = self.domains.get(self.config.default_domain)
# monkey-patch, so that domain directives take precedence # monkey-patch, so that domain directives take precedence
def directive(directive_name, language_module, document): def directive(directive_name, language_module, document):
directive_name = directive_name.lower()
if ':' in directive_name: if ':' in directive_name:
domain_name, directive_name = directive_name.split(':', 1) domain_name, directive_name = directive_name.split(':', 1)
if domain_name in domains: if domain_name in self.domains:
domain = domains[domain_name] domain = self.domains[domain_name]
if directive_name in domain.directives: directive = domain.directive(directive_name)
return domain.directives[directive_name], [] if directive is not None:
return directive, []
elif self.default_domain is not None: elif self.default_domain is not None:
directive = self.default_domain.directives.get(directive_name) directive = self.default_domain.directive(directive_name)
if directive is not None: if directive is not None:
return directive, [] return directive, []
return orig_directive_function(directive_name, language_module, return orig_directive_function(directive_name, language_module,
@ -619,14 +625,16 @@ class BuildEnvironment:
directives.directive = directive directives.directive = directive
def role(role_name, language_module, lineno, reporter): def role(role_name, language_module, lineno, reporter):
role_name = role_name.lower()
if ':' in role_name: if ':' in role_name:
domain_name, role_name = role_name.split(':', 1) domain_name, role_name = role_name.split(':', 1)
if domain_name in domains: if domain_name in self.domains:
domain = domains[domain_name] domain = self.domains[domain_name]
if role_name in domain.roles: role = domain.role(role_name)
return domain.roles[role_name], [] if role is not None:
return role, []
elif self.default_domain is not None: elif self.default_domain is not None:
role = self.default_domain.roles.get(role_name) role = self.default_domain.role(role_name)
if role is not None: if role is not None:
return role, [] return role, []
return orig_role_function(role_name, language_module, return orig_role_function(role_name, language_module,
@ -678,6 +686,7 @@ class BuildEnvironment:
self.docname = None self.docname = None
self.currmodule = None self.currmodule = None
self.currclass = None self.currclass = None
self.default_domain = None
self.gloss_entries = set() self.gloss_entries = set()
if save_parsed: if save_parsed:
@ -987,18 +996,6 @@ class BuildEnvironment:
# ------- # -------
# these are called from docutils directives and therefore use self.docname # these are called from docutils directives and therefore use self.docname
# #
def note_descref(self, fullname, desctype, line):
if fullname in self.descrefs:
self.warn(self.docname,
'duplicate canonical description name %s, ' % fullname +
'other instance in ' +
self.doc2path(self.descrefs[fullname][0]),
line)
self.descrefs[fullname] = (self.docname, desctype)
def note_module(self, modname, synopsis, platform, deprecated):
self.modules[modname] = (self.docname, synopsis, platform, deprecated)
def note_progoption(self, optname, labelid): def note_progoption(self, optname, labelid):
self.progoptions[self.currprogram, optname] = (self.docname, labelid) self.progoptions[self.currprogram, optname] = (self.docname, labelid)
@ -1196,11 +1193,8 @@ class BuildEnvironment:
docname, refnode['refuri']) + refnode['anchorname'] docname, refnode['refuri']) + refnode['anchorname']
return newnode return newnode
descroles = frozenset(('data', 'exc', 'func', 'class', 'const',
'attr', 'obj', 'meth', 'cfunc', 'cmember',
'cdata', 'ctype', 'cmacro'))
def resolve_references(self, doctree, fromdocname, builder): def resolve_references(self, doctree, fromdocname, builder):
# XXX remove this
reftarget_roles = set(('token', 'term', 'citation')) reftarget_roles = set(('token', 'term', 'citation'))
# add all custom xref types too # add all custom xref types too
reftarget_roles.update(i[0] for i in additional_xref_types.values()) reftarget_roles.update(i[0] for i in additional_xref_types.values())
@ -1213,7 +1207,15 @@ class BuildEnvironment:
target = node['reftarget'] target = node['reftarget']
try: try:
if typ == 'ref': if node.has_key('refdomain'):
# let the domain try to resolve the reference
try:
domain = self.domains[node['refdomain']]
except KeyError:
raise NoUri
newnode = domain.resolve_xref(self, fromdocname, builder,
typ, target, node, contnode)
elif typ == 'ref':
if node['refcaption']: if node['refcaption']:
# reference to anonymous label; the reference uses # reference to anonymous label; the reference uses
# the supplied link caption # the supplied link caption
@ -1278,13 +1280,8 @@ class BuildEnvironment:
#self.warn(fromdocname, 'unknown keyword: %s' % target) #self.warn(fromdocname, 'unknown keyword: %s' % target)
newnode = contnode newnode = contnode
else: else:
newnode = nodes.reference('', '') newnode = make_refnode(builder, fromdocname, docname,
if docname == fromdocname: labelid, contnode)
newnode['refid'] = labelid
else:
newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname) + '#' + labelid
newnode.append(contnode)
elif typ == 'option': elif typ == 'option':
progname = node['refprogram'] progname = node['refprogram']
docname, labelid = self.progoptions.get((progname, target), docname, labelid = self.progoptions.get((progname, target),
@ -1292,13 +1289,8 @@ class BuildEnvironment:
if not docname: if not docname:
newnode = contnode newnode = contnode
else: else:
newnode = nodes.reference('', '') newnode = make_refnode(builder, fromdocname, docname,
if docname == fromdocname: labelid, contnode)
newnode['refid'] = labelid
else:
newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname) + '#' + labelid
newnode.append(contnode)
elif typ in reftarget_roles: elif typ in reftarget_roles:
docname, labelid = self.reftargets.get((typ, target), docname, labelid = self.reftargets.get((typ, target),
('', '')) ('', ''))
@ -1313,62 +1305,19 @@ class BuildEnvironment:
node.line) node.line)
newnode = contnode newnode = contnode
else: else:
newnode = nodes.reference('', '') newnode = make_refnode(builder, fromdocname, docname,
if docname == fromdocname: labelid, contnode)
newnode['refid'] = labelid
else:
newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname, typ) + '#' + labelid
newnode.append(contnode)
elif typ == 'mod' or \
typ == 'obj' and target in self.modules:
docname, synopsis, platform, deprecated = \
self.modules.get(target, ('','','', ''))
if not docname:
newnode = builder.app.emit_firstresult(
'missing-reference', self, node, contnode)
if not newnode:
newnode = contnode
elif docname == fromdocname:
# don't link to self
newnode = contnode
else:
newnode = nodes.reference('', '')
newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname) + '#module-' + target
newnode['reftitle'] = '%s%s%s' % (
(platform and '(%s) ' % platform),
synopsis, (deprecated and ' (deprecated)' or ''))
newnode.append(contnode)
elif typ in self.descroles:
# "descrefs"
modname = node['modname']
clsname = node['classname']
searchorder = node.hasattr('refspecific') and 1 or 0
name, desc = self.find_desc(modname, clsname,
target, typ, searchorder)
if not desc:
newnode = builder.app.emit_firstresult(
'missing-reference', self, node, contnode)
if not newnode:
newnode = contnode
else:
newnode = nodes.reference('', '')
if desc[0] == fromdocname:
newnode['refid'] = name
else:
newnode['refuri'] = (
builder.get_relative_uri(fromdocname, desc[0])
+ '#' + name)
newnode['reftitle'] = name
newnode.append(contnode)
else: else:
raise RuntimeError('unknown xfileref node encountered: %s' raise RuntimeError('unknown xfileref node encountered: %s'
% node) % node)
# no new node found? try the missing-reference event
if newnode is None:
newnode = builder.app.emit_firstresult(
'missing-reference', self, node, contnode)
except NoUri: except NoUri:
newnode = contnode newnode = contnode
if newnode: node.replace_self(newnode or contnode)
node.replace_self(newnode)
for node in doctree.traverse(addnodes.only): for node in doctree.traverse(addnodes.only):
try: try:
@ -1592,52 +1541,3 @@ class BuildEnvironment:
# the master file is not included anywhere ;) # the master file is not included anywhere ;)
continue continue
self.warn(docname, 'document isn\'t included in any toctree') self.warn(docname, 'document isn\'t included in any toctree')
# --------- QUERYING -------------------------------------------------------
def find_desc(self, modname, classname, name, type, searchorder=0):
"""Find a description node matching "name", perhaps using
the given module and/or classname."""
# skip parens
if name[-2:] == '()':
name = name[:-2]
if not name:
return None, None
# don't add module and class names for C things
if type[0] == 'c' and type not in ('class', 'const'):
# skip trailing star and whitespace
name = name.rstrip(' *')
if name in self.descrefs and self.descrefs[name][1][0] == 'c':
return name, self.descrefs[name]
return None, None
newname = None
if searchorder == 1:
if modname and classname and \
modname + '.' + classname + '.' + name in self.descrefs:
newname = modname + '.' + classname + '.' + name
elif modname and modname + '.' + name in self.descrefs:
newname = modname + '.' + name
elif name in self.descrefs:
newname = name
else:
if name in self.descrefs:
newname = name
elif modname and modname + '.' + name in self.descrefs:
newname = modname + '.' + name
elif modname and classname and \
modname + '.' + classname + '.' + name in self.descrefs:
newname = modname + '.' + classname + '.' + name
# special case: builtin exceptions have module "exceptions" set
elif type == 'exc' and '.' not in name and \
'exceptions.' + name in self.descrefs:
newname = 'exceptions.' + name
# special case: object methods
elif type in ('func', 'meth') and '.' not in name and \
'object.' + name in self.descrefs:
newname = 'object.' + name
if newname is None:
return None, None
return newname, self.descrefs[newname]

View File

@ -79,6 +79,7 @@ class CoverageBuilder(Builder):
def build_c_coverage(self): def build_c_coverage(self):
# Fetch all the info from the header files # Fetch all the info from the header files
c_objects = self.env.domaindata['c']['objects']
for filename in self.c_sourcefiles: for filename in self.c_sourcefiles:
undoc = [] undoc = []
f = open(filename, 'r') f = open(filename, 'r')
@ -88,7 +89,7 @@ class CoverageBuilder(Builder):
match = regex.match(line) match = regex.match(line)
if match: if match:
name = match.groups()[0] name = match.groups()[0]
if name not in self.env.descrefs: if name not in c_objects:
for exp in self.c_ignorexps.get(key, ()): for exp in self.c_ignorexps.get(key, ()):
if exp.match(name): if exp.match(name):
break break
@ -116,7 +117,10 @@ class CoverageBuilder(Builder):
op.close() op.close()
def build_py_coverage(self): def build_py_coverage(self):
for mod_name in self.env.modules: objects = self.env.domaindata['py']['objects']
modules = self.env.domaindata['py']['modules']
for mod_name in modules:
ignore = False ignore = False
for exp in self.mod_ignorexps: for exp in self.mod_ignorexps:
if exp.match(mod_name): if exp.match(mod_name):
@ -151,7 +155,7 @@ class CoverageBuilder(Builder):
full_name = '%s.%s' % (mod_name, name) full_name = '%s.%s' % (mod_name, name)
if inspect.isfunction(obj): if inspect.isfunction(obj):
if full_name not in self.env.descrefs: if full_name not in objects:
for exp in self.fun_ignorexps: for exp in self.fun_ignorexps:
if exp.match(name): if exp.match(name):
break break
@ -162,7 +166,7 @@ class CoverageBuilder(Builder):
if exp.match(name): if exp.match(name):
break break
else: else:
if full_name not in self.env.descrefs: if full_name not in objects:
# not documented at all # not documented at all
classes[name] = [] classes[name] = []
continue continue
@ -176,7 +180,7 @@ class CoverageBuilder(Builder):
continue continue
full_attr_name = '%s.%s' % (full_name, attr_name) full_attr_name = '%s.%s' % (full_name, attr_name)
if full_attr_name not in self.env.descrefs: if full_attr_name not in objects:
attrs.append(attr_name) attrs.append(attr_name)
if attrs: if attrs:

View File

@ -94,7 +94,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
# make the paths into loaders # make the paths into loaders
self.loaders = map(SphinxFileSystemLoader, chain) self.loaders = map(SphinxFileSystemLoader, chain)
use_i18n = builder.translator is not None use_i18n = builder.app.translator is not None
extensions = use_i18n and ['jinja2.ext.i18n'] or [] extensions = use_i18n and ['jinja2.ext.i18n'] or []
self.environment = SandboxedEnvironment(loader=self, self.environment = SandboxedEnvironment(loader=self,
extensions=extensions) extensions=extensions)
@ -102,7 +102,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
self.environment.globals['debug'] = contextfunction(pformat) self.environment.globals['debug'] = contextfunction(pformat)
self.environment.globals['accesskey'] = contextfunction(accesskey) self.environment.globals['accesskey'] = contextfunction(accesskey)
if use_i18n: if use_i18n:
self.environment.install_gettext_translations(builder.translator) self.environment.install_gettext_translations(builder.app.translator)
def render(self, template, context): def render(self, template, context):
return self.environment.get_template(template).render(context) return self.environment.get_template(template).render(context)

View File

@ -137,9 +137,8 @@ class XRefRole(object):
nodeclass = addnodes.pending_xref nodeclass = addnodes.pending_xref
innernodeclass = nodes.literal innernodeclass = nodes.literal
def __init__(self, domain_name, fix_parens=False, lowercase=False, def __init__(self, fix_parens=False, lowercase=False,
nodeclass=None, innernodeclass=None): nodeclass=None, innernodeclass=None):
self.domain_name = domain_name
self.fix_parens = fix_parens self.fix_parens = fix_parens
if nodeclass is not None: if nodeclass is not None:
self.nodeclass = nodeclass self.nodeclass = nodeclass
@ -169,10 +168,10 @@ class XRefRole(object):
typ = env.config.default_role typ = env.config.default_role
else: else:
typ = typ.lower() typ = typ.lower()
if ":" in typ: if ':' not in typ:
domain, role = typ.split(":", 1) domain, role = '', typ
else: else:
domain, role = self.domain_name, typ domain, role = typ.split(':', 1)
text = utils.unescape(text) text = utils.unescape(text)
# if the first character is a bang, don't cross-reference at all # if the first character is a bang, don't cross-reference at all
if text[0:1] == '!': if text[0:1] == '!':
@ -212,13 +211,13 @@ class OptionXRefRole(XRefRole):
specific_docroles = { specific_docroles = {
'keyword': XRefRole(''), 'keyword': XRefRole(),
'ref': XRefRole('', lowercase=True, innernodeclass=nodes.emphasis), 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis),
'token': XRefRole(''), 'token': XRefRole(),
'term': XRefRole('', lowercase=True, innernodeclass=nodes.emphasis), 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis),
'option': OptionXRefRole('', innernodeclass=addnodes.literal_emphasis), 'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis),
'doc': XRefRole(''), 'doc': XRefRole(),
'download': XRefRole('', nodeclass=addnodes.download_reference), 'download': XRefRole(nodeclass=addnodes.download_reference),
'menuselection': menusel_role, 'menuselection': menusel_role,
'file': emph_literal_role, 'file': emph_literal_role,

View File

@ -149,6 +149,8 @@ class IndexBuilder(object):
def get_modules(self, fn2index): def get_modules(self, fn2index):
rv = {} rv = {}
# XXX implement search capability
return rv
for name, (doc, _, _, _) in self.env.modules.iteritems(): for name, (doc, _, _, _) in self.env.modules.iteritems():
if doc in fn2index: if doc in fn2index:
rv[name] = fn2index[doc] rv[name] = fn2index[doc]
@ -157,6 +159,8 @@ class IndexBuilder(object):
def get_descrefs(self, fn2index): def get_descrefs(self, fn2index):
rv = {} rv = {}
dt = self._desctypes dt = self._desctypes
# XXX implement search capability
return rv
for fullname, (doc, desctype) in self.env.descrefs.iteritems(): for fullname, (doc, desctype) in self.env.descrefs.iteritems():
if doc not in fn2index: if doc not in fn2index:
continue continue

View File

@ -396,8 +396,10 @@ def movefile(source, dest):
def copyfile(source, dest): def copyfile(source, dest):
"""Copy a file and its modification times, if possible.""" """Copy a file and its modification times, if possible."""
shutil.copyfile(source, dest) shutil.copyfile(source, dest)
try: shutil.copystat(source, dest) try:
except shutil.Error: pass shutil.copystat(source, dest)
except shutil.Error:
pass
def copy_static_entry(source, target, builder, context={}): def copy_static_entry(source, target, builder, context={}):
@ -435,6 +437,23 @@ def split_explicit_title(text):
else: else:
return False, text, text return False, text, text
from docutils import nodes
def make_refnode(builder, fromdocname, todocname, targetid, child, title=None):
"""Shortcut to create a reference node."""
node = nodes.reference('', '')
if fromdocname == todocname:
node['refid'] = targetid
else:
node['refuri'] = (builder.get_relative_uri(fromdocname, todocname)
+ '#' + targetid)
if title:
node['reftitle'] = title
node.append(child)
return node
# monkey-patch Node.traverse to get more speed # monkey-patch Node.traverse to get more speed
# traverse() is called so many times during a build that it saves # traverse() is called so many times during a build that it saves
# on average 20-25% overall build time! # on average 20-25% overall build time!
@ -464,8 +483,7 @@ def _new_traverse(self, condition=None,
return self._old_traverse(condition, include_self, return self._old_traverse(condition, include_self,
descend, siblings, ascend) descend, siblings, ascend)
import docutils.nodes nodes.Node._old_traverse = nodes.Node.traverse
docutils.nodes.Node._old_traverse = docutils.nodes.Node.traverse nodes.Node._all_traverse = _all_traverse
docutils.nodes.Node._all_traverse = _all_traverse nodes.Node._fast_traverse = _fast_traverse
docutils.nodes.Node._fast_traverse = _fast_traverse nodes.Node.traverse = _new_traverse
docutils.nodes.Node.traverse = _new_traverse

View File

@ -43,15 +43,15 @@ Testing description units
C items C items
======= =======
.. cfunction:: Sphinx_DoSomething() .. c:function:: Sphinx_DoSomething()
.. cmember:: SphinxStruct.member .. c:member:: SphinxStruct.member
.. cmacro:: SPHINX_USE_PYTHON .. c:macro:: SPHINX_USE_PYTHON
.. ctype:: SphinxType .. c:type:: SphinxType
.. cvar:: sphinx_global .. c:var:: sphinx_global
Testing references Testing references

View File

@ -20,8 +20,8 @@ warnings = []
def setup_module(): def setup_module():
global app, env global app, env
app = TestApp(srcdir='(temp)') app = TestApp(srcdir='(temp)', freshenv=True)
env = BuildEnvironment(app.srcdir, app.doctreedir, app.config) env = app.env
env.set_warnfunc(lambda *args: warnings.append(args)) env.set_warnfunc(lambda *args: warnings.append(args))
def teardown_module(): def teardown_module():
@ -51,7 +51,7 @@ def test_images():
tree = env.get_doctree('images') tree = env.get_doctree('images')
app._warning.reset() app._warning.reset()
htmlbuilder = StandaloneHTMLBuilder(app, env) htmlbuilder = StandaloneHTMLBuilder(app)
htmlbuilder.post_process_images(tree) htmlbuilder.post_process_images(tree)
assert "no matching candidate for image URI u'foo.*'" in \ assert "no matching candidate for image URI u'foo.*'" in \
app._warning.content[-1] app._warning.content[-1]
@ -61,7 +61,7 @@ def test_images():
set(['img.png', 'img1.png', 'simg.png', 'svgimg.svg']) set(['img.png', 'img1.png', 'simg.png', 'svgimg.svg'])
app._warning.reset() app._warning.reset()
latexbuilder = LaTeXBuilder(app, env) latexbuilder = LaTeXBuilder(app)
latexbuilder.post_process_images(tree) latexbuilder.post_process_images(tree)
assert "no matching candidate for image URI u'foo.*'" in \ assert "no matching candidate for image URI u'foo.*'" in \
app._warning.content[-1] app._warning.content[-1]
@ -92,7 +92,7 @@ def test_second_update():
assert 'autodoc' not in env.found_docs assert 'autodoc' not in env.found_docs
def test_object_inventory(): def test_object_inventory():
refs = env.descrefs refs = env.domaindata['py']['objects']
assert 'func_without_module' in refs assert 'func_without_module' in refs
assert refs['func_without_module'] == ('desc', 'function') assert refs['func_without_module'] == ('desc', 'function')
@ -109,5 +109,8 @@ def test_object_inventory():
assert 'func_in_module' not in refs assert 'func_in_module' not in refs
assert 'func_noindex' not in refs assert 'func_noindex' not in refs
assert 'mod' in env.modules assert env.domaindata['py']['modules']['mod'] == \
assert env.modules['mod'] == ('desc', 'Module synopsis.', 'UNIX', False) ('desc', 'Module synopsis.', 'UNIX', False)
assert env.domains['py'].data is env.domaindata['py']
assert env.domains['c'].data is env.domaindata['c']