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 types
import posixpath
from os import path
from cStringIO import StringIO
from docutils import nodes
from docutils.parsers.rst import directives, roles
import sphinx
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 domains
from sphinx.domains import all_domains
from sphinx.builders import BUILTIN_BUILDERS
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.tags import Tags
from sphinx.util.compat import Directive, directive_dwim
@ -50,6 +52,7 @@ events = {
}
CONFIG_FILENAME = 'conf.py'
ENV_PICKLE_FILENAME = 'environment.pickle'
class Sphinx(object):
@ -62,6 +65,7 @@ class Sphinx(object):
self._listeners = {}
self.builderclasses = BUILTIN_BUILDERS.copy()
self.builder = None
self.env = None
self.srcdir = srcdir
self.confdir = confdir
@ -104,8 +108,62 @@ class Sphinx(object):
# now that we know all config values, collect them from conf.py
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:
print >>status, 'No builder selected, using default: html'
print >>self._status, 'No builder selected, using default: html'
buildername = 'html'
if buildername not in self.builderclasses:
raise SphinxError('Builder name %s not registered' % buildername)
@ -116,9 +174,7 @@ class Sphinx(object):
mod, cls = builderclass
builderclass = getattr(
__import__('sphinx.builders.' + mod, None, None, [cls]), cls)
self.builder = builderclass(self, freshenv=freshenv)
self.builder.tags = self.tags
self.builder.tags.add(self.builder.format)
self.builder = builderclass(self)
self.emit('builder-inited')
def build(self, all_files, filenames):
@ -300,9 +356,9 @@ class Sphinx(object):
roles.register_local_role(name, role)
def add_domain(self, domain):
if domain.name in domains:
if domain.name in all_domains:
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='',
parse_node=None, ref_nodeclass=None):
@ -311,7 +367,7 @@ class Sphinx(object):
directives.register_directive(directivename,
directive_dwim(GenericDesc))
# XXX support more options?
role_func = XRefRole('', innernodeclass=ref_nodeclass)
role_func = XRefRole(innernodeclass=ref_nodeclass)
roles.register_local_role(rolename, role_func)
def add_crossref_type(self, directivename, rolename, indextemplate='',
@ -319,7 +375,7 @@ class Sphinx(object):
additional_xref_types[directivename] = (rolename, indextemplate, None)
directives.register_directive(directivename, directive_dwim(Target))
# XXX support more options
role_func = XRefRole('', innernodeclass=ref_nodeclass)
role_func = XRefRole(innernodeclass=ref_nodeclass)
roles.register_local_role(rolename, role_func)
def add_transform(self, transform):

View File

@ -15,9 +15,7 @@ from os import path
from docutils import nodes
from sphinx import package_dir, locale
from sphinx.util import SEP, relative_uri
from sphinx.environment import BuildEnvironment
from sphinx.util.console import bold, purple, darkgreen, term_width_line
# side effect: registers roles and directives
@ -25,9 +23,6 @@ from sphinx import roles
from sphinx import directives
ENV_PICKLE_FILENAME = 'environment.pickle'
class Builder(object):
"""
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
format = ''
def __init__(self, app, env=None, freshenv=False):
def __init__(self, app):
self.env = app.env
self.srcdir = app.srcdir
self.confdir = app.confdir
self.outdir = app.outdir
@ -50,18 +46,13 @@ class Builder(object):
self.warn = app.warn
self.info = app.info
self.config = app.config
self.load_i18n()
self.tags = app.tags
self.tags.add(self.format)
# images that need to be copied over (source -> dest)
self.images = {}
# if None, this is set in load_env()
self.env = env
self.freshenv = freshenv
self.init()
self.load_env()
# helper methods
@ -167,50 +158,6 @@ class Builder(object):
# 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):
"""Build all source files."""
self.build(None, summary='all source files', method='all')
@ -290,6 +237,7 @@ class Builder(object):
if updated_docnames:
# save the environment
from sphinx.application import ENV_PICKLE_FILENAME
self.info(bold('pickling environment... '), nonl=True)
self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME))
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.search import js_index
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.util.console import bold
from sphinx.writers.html import HTMLWriter, HTMLTranslator, \
@ -239,7 +240,9 @@ class StandaloneHTMLBuilder(Builder):
rellinks = []
if self.config.html_use_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'),
'M', _('modules')))
@ -404,12 +407,13 @@ class StandaloneHTMLBuilder(Builder):
# 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
modules = sorted(((mn, (self.get_relative_uri('modindex', fn) +
'#module-' + mn, sy, pl, dep))
for (mn, (fn, sy, pl, dep)) in
self.env.modules.iteritems()),
moduleindex.iteritems()),
key=lambda x: x[0].lower())
# collect all platforms
platforms = set()
@ -709,14 +713,15 @@ 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('# 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 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)))
finally:
f.close()
self.info('done')

View File

@ -204,7 +204,10 @@ class DescDirective(Directive):
pass
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.indexnode = addnodes.index(entries=[])
@ -366,7 +369,7 @@ class DefaultDomain(Directive):
def run(self):
env = self.state.document.settings.env
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

View File

@ -103,75 +103,6 @@ class TocTree(Directive):
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):
"""
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('module', directive_dwim(Module))
directives.register_directive('currentmodule', directive_dwim(CurrentModule))
directives.register_directive('sectionauthor', directive_dwim(Author))
directives.register_directive('moduleauthor', directive_dwim(Author))
directives.register_directive('program', directive_dwim(Program))

View File

@ -4,17 +4,23 @@
~~~~~~~~~~~~~~
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.
: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.util import make_refnode
from sphinx.util.compat import Directive
class Domain(object):
@ -23,6 +29,68 @@ class Domain(object):
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:
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
py_sig_re = re.compile(
@ -146,7 +214,15 @@ class PythonDesc(DescDirective):
signode['ids'].append(fullname)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
self.env.note_descref(fullname, self.desctype, self.lineno)
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)
if indextext:
@ -286,6 +362,75 @@ class ClassmemberDesc(PythonDesc):
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):
def process_link(self, env, pnode, has_explicit_title, title, target):
pnode['modname'] = env.currmodule
@ -321,18 +466,105 @@ class PythonDomain(Domain):
'classmethod': ClassmemberDesc,
'staticmethod': ClassmemberDesc,
'attribute': ClassmemberDesc,
'module': PyModule,
'currentmodule': PyCurrentModule,
}
roles = {
'data': PyXRefRole('py'),
'exc': PyXRefRole('py'),
'func': PyXRefRole('py', True),
'class': PyXRefRole('py'),
'const': PyXRefRole('py'),
'attr': PyXRefRole('py'),
'meth': PyXRefRole('py', True),
'mod': PyXRefRole('py'),
'obj': PyXRefRole('py'),
'data': PyXRefRole(),
'exc': PyXRefRole(),
'func': PyXRefRole(fix_parens=True),
'class': PyXRefRole(),
'const': PyXRefRole(),
'attr': PyXRefRole(),
'meth': PyXRefRole(fix_parens=True),
'mod': PyXRefRole(),
'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
@ -370,7 +602,7 @@ class CDesc(DescDirective):
if part[0] in string.ascii_letters+'_' and \
part not in self.stopwords:
pnode = addnodes.pending_xref(
'', reftype='ctype', reftarget=part,
'', refdomain='c', reftype='type', reftarget=part,
modname=None, classname=None)
pnode += tnode
node += pnode
@ -451,7 +683,14 @@ class CDesc(DescDirective):
signode['ids'].append(name)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
self.env.note_descref(name, self.desctype, self.lineno)
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:
@ -470,16 +709,33 @@ class CDomain(Domain):
'var': CDesc,
}
roles = {
'member': XRefRole('c'),
'macro': XRefRole('c'),
'func' : XRefRole('c', True),
'data': XRefRole('c'),
'type': XRefRole('c'),
'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
domains = {
all_domains = {
'py': PythonDomain,
'c': CDomain,
}

View File

@ -43,9 +43,8 @@ from docutils.transforms.parts import ContentsFilter
from sphinx import addnodes
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.domains import domains
from sphinx.directives import additional_xref_types
orig_role_function = roles.role
@ -214,9 +213,9 @@ class BuildEnvironment:
env = pickle.load(picklefile)
finally:
picklefile.close()
env.config.values = config.values
if env.version != ENV_VERSION:
raise IOError('env version not current')
env.config.values = config.values
return env
def topickle(self, filename):
@ -225,6 +224,8 @@ class BuildEnvironment:
self.set_warnfunc(None)
values = self.config.values
del self.config.values
domains = self.domains
del self.domains
# first write to a temporary file, so that if dumping fails,
# the existing environment won't be overwritten
picklefile = open(filename + '.tmp', 'wb')
@ -241,6 +242,7 @@ class BuildEnvironment:
picklefile.close()
movefile(filename + '.tmp', filename)
# reset attributes
self.domains = domains
self.config.values = values
self.set_warnfunc(warnfunc)
@ -254,6 +256,9 @@ class BuildEnvironment:
# the application object; only set while update() runs
self.app = None
# all the registered domains, set by the application
self.domains = {}
# the docutils settings for building
self.settings = default_settings.copy()
self.settings['env'] = self
@ -292,10 +297,10 @@ class BuildEnvironment:
self.glob_toctrees = set() # docnames that have :glob: 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
self.descrefs = {} # fullname -> docname, desctype
self.modules = {} # modname -> docname, synopsis,
# platform, deprecated
self.labels = {} # labelname -> docname, labelid, sectionname
self.anonlabels = {} # labelname -> docname, labelid
self.progoptions = {} # (program, name) -> docname, labelid
@ -363,12 +368,6 @@ class BuildEnvironment:
fnset.discard(docname)
if not fnset:
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():
if fn == docname:
del self.labels[labelname]
@ -382,6 +381,10 @@ class BuildEnvironment:
new = [change for change in changes if change[1] != docname]
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):
"""
Return the filename for the document name.
@ -561,6 +564,7 @@ class BuildEnvironment:
# remove all inventory entries for that file
if app:
app.emit('env-purge-doc', self, docname)
self.clear_doc(docname)
if src_path is None:
@ -600,18 +604,20 @@ class BuildEnvironment:
return data
# 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
def directive(directive_name, language_module, document):
directive_name = directive_name.lower()
if ':' in directive_name:
domain_name, directive_name = directive_name.split(':', 1)
if domain_name in domains:
domain = domains[domain_name]
if directive_name in domain.directives:
return domain.directives[directive_name], []
if domain_name in self.domains:
domain = self.domains[domain_name]
directive = domain.directive(directive_name)
if directive is not None:
return directive, []
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:
return directive, []
return orig_directive_function(directive_name, language_module,
@ -619,14 +625,16 @@ class BuildEnvironment:
directives.directive = directive
def role(role_name, language_module, lineno, reporter):
role_name = role_name.lower()
if ':' in role_name:
domain_name, role_name = role_name.split(':', 1)
if domain_name in domains:
domain = domains[domain_name]
if role_name in domain.roles:
return domain.roles[role_name], []
if domain_name in self.domains:
domain = self.domains[domain_name]
role = domain.role(role_name)
if role is not None:
return role, []
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:
return role, []
return orig_role_function(role_name, language_module,
@ -678,6 +686,7 @@ class BuildEnvironment:
self.docname = None
self.currmodule = None
self.currclass = None
self.default_domain = None
self.gloss_entries = set()
if save_parsed:
@ -987,18 +996,6 @@ class BuildEnvironment:
# -------
# 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):
self.progoptions[self.currprogram, optname] = (self.docname, labelid)
@ -1196,11 +1193,8 @@ class BuildEnvironment:
docname, refnode['refuri']) + refnode['anchorname']
return newnode
descroles = frozenset(('data', 'exc', 'func', 'class', 'const',
'attr', 'obj', 'meth', 'cfunc', 'cmember',
'cdata', 'ctype', 'cmacro'))
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())
@ -1213,7 +1207,15 @@ class BuildEnvironment:
target = node['reftarget']
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']:
# reference to anonymous label; the reference uses
# the supplied link caption
@ -1278,13 +1280,8 @@ class BuildEnvironment:
#self.warn(fromdocname, 'unknown keyword: %s' % target)
newnode = contnode
else:
newnode = nodes.reference('', '')
if docname == fromdocname:
newnode['refid'] = labelid
else:
newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname) + '#' + labelid
newnode.append(contnode)
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)
elif typ == 'option':
progname = node['refprogram']
docname, labelid = self.progoptions.get((progname, target),
@ -1292,13 +1289,8 @@ class BuildEnvironment:
if not docname:
newnode = contnode
else:
newnode = nodes.reference('', '')
if docname == fromdocname:
newnode['refid'] = labelid
else:
newnode['refuri'] = builder.get_relative_uri(
fromdocname, docname) + '#' + labelid
newnode.append(contnode)
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)
elif typ in reftarget_roles:
docname, labelid = self.reftargets.get((typ, target),
('', ''))
@ -1313,62 +1305,19 @@ class BuildEnvironment:
node.line)
newnode = contnode
else:
newnode = nodes.reference('', '')
if docname == fromdocname:
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)
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)
else:
raise RuntimeError('unknown xfileref node encountered: %s'
% 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:
newnode = contnode
if newnode:
node.replace_self(newnode)
node.replace_self(newnode or contnode)
for node in doctree.traverse(addnodes.only):
try:
@ -1592,52 +1541,3 @@ class BuildEnvironment:
# the master file is not included anywhere ;)
continue
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):
# Fetch all the info from the header files
c_objects = self.env.domaindata['c']['objects']
for filename in self.c_sourcefiles:
undoc = []
f = open(filename, 'r')
@ -88,7 +89,7 @@ class CoverageBuilder(Builder):
match = regex.match(line)
if match:
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, ()):
if exp.match(name):
break
@ -116,7 +117,10 @@ class CoverageBuilder(Builder):
op.close()
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
for exp in self.mod_ignorexps:
if exp.match(mod_name):
@ -151,7 +155,7 @@ class CoverageBuilder(Builder):
full_name = '%s.%s' % (mod_name, name)
if inspect.isfunction(obj):
if full_name not in self.env.descrefs:
if full_name not in objects:
for exp in self.fun_ignorexps:
if exp.match(name):
break
@ -162,7 +166,7 @@ class CoverageBuilder(Builder):
if exp.match(name):
break
else:
if full_name not in self.env.descrefs:
if full_name not in objects:
# not documented at all
classes[name] = []
continue
@ -176,7 +180,7 @@ class CoverageBuilder(Builder):
continue
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)
if attrs:

View File

@ -94,7 +94,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
# make the paths into loaders
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 []
self.environment = SandboxedEnvironment(loader=self,
extensions=extensions)
@ -102,7 +102,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
self.environment.globals['debug'] = contextfunction(pformat)
self.environment.globals['accesskey'] = contextfunction(accesskey)
if use_i18n:
self.environment.install_gettext_translations(builder.translator)
self.environment.install_gettext_translations(builder.app.translator)
def render(self, template, context):
return self.environment.get_template(template).render(context)

View File

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

View File

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

View File

@ -396,8 +396,10 @@ def movefile(source, dest):
def copyfile(source, dest):
"""Copy a file and its modification times, if possible."""
shutil.copyfile(source, dest)
try: shutil.copystat(source, dest)
except shutil.Error: pass
try:
shutil.copystat(source, dest)
except shutil.Error:
pass
def copy_static_entry(source, target, builder, context={}):
@ -435,6 +437,23 @@ def split_explicit_title(text):
else:
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
# traverse() is called so many times during a build that it saves
# on average 20-25% overall build time!
@ -464,8 +483,7 @@ def _new_traverse(self, condition=None,
return self._old_traverse(condition, include_self,
descend, siblings, ascend)
import docutils.nodes
docutils.nodes.Node._old_traverse = docutils.nodes.Node.traverse
docutils.nodes.Node._all_traverse = _all_traverse
docutils.nodes.Node._fast_traverse = _fast_traverse
docutils.nodes.Node.traverse = _new_traverse
nodes.Node._old_traverse = nodes.Node.traverse
nodes.Node._all_traverse = _all_traverse
nodes.Node._fast_traverse = _fast_traverse
nodes.Node.traverse = _new_traverse

View File

@ -43,15 +43,15 @@ Testing description units
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

View File

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