summaryrefslogtreecommitdiff
path: root/sphinx
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx')
-rw-r--r--sphinx/builders/__init__.py5
-rw-r--r--sphinx/builders/gettext.py (renamed from sphinx/builders/intl.py)5
-rw-r--r--sphinx/builders/websupport.py1
-rw-r--r--sphinx/domains/__init__.py2
-rw-r--r--sphinx/domains/cpp.py6
-rw-r--r--sphinx/domains/python.py11
-rw-r--r--sphinx/domains/std.py45
-rw-r--r--sphinx/environment.py122
-rw-r--r--sphinx/ext/autodoc.py33
-rw-r--r--sphinx/quickstart.py9
-rw-r--r--sphinx/roles.py6
-rw-r--r--sphinx/util/inspect.py34
-rw-r--r--sphinx/versioning.py3
13 files changed, 162 insertions, 120 deletions
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index 33954033..5240a1c7 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -31,9 +31,12 @@ class Builder(object):
name = ''
# builder's output format, or '' if no document output is produced
format = ''
+ # doctree versioning method
+ versioning_method = 'none'
def __init__(self, app):
self.env = app.env
+ self.env.set_versioning_method(self.versioning_method)
self.srcdir = app.srcdir
self.confdir = app.confdir
self.outdir = app.outdir
@@ -330,5 +333,5 @@ BUILTIN_BUILDERS = {
'changes': ('changes', 'ChangesBuilder'),
'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'),
'websupport': ('websupport', 'WebSupportBuilder'),
- 'gettext': ('intl', 'MessageCatalogBuilder'),
+ 'gettext': ('gettext', 'MessageCatalogBuilder'),
}
diff --git a/sphinx/builders/intl.py b/sphinx/builders/gettext.py
index 74ba03b5..1ff92360 100644
--- a/sphinx/builders/intl.py
+++ b/sphinx/builders/gettext.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
- sphinx.builders.intl
- ~~~~~~~~~~~~~~~~~~~~
+ sphinx.builders.gettext
+ ~~~~~~~~~~~~~~~~~~~~~~~
The MessageCatalogBuilder class.
@@ -48,6 +48,7 @@ class I18nBuilder(Builder):
General i18n builder.
"""
name = 'i18n'
+ versioning_method = 'text'
def init(self):
Builder.init(self)
diff --git a/sphinx/builders/websupport.py b/sphinx/builders/websupport.py
index e8f6aef3..b7757309 100644
--- a/sphinx/builders/websupport.py
+++ b/sphinx/builders/websupport.py
@@ -26,6 +26,7 @@ class WebSupportBuilder(PickleHTMLBuilder):
Builds documents for the web support package.
"""
name = 'websupport'
+ versioning_method = 'commentable'
def init(self):
PickleHTMLBuilder.init(self)
diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py
index d6faa127..c48568eb 100644
--- a/sphinx/domains/__init__.py
+++ b/sphinx/domains/__init__.py
@@ -131,6 +131,8 @@ class Domain(object):
roles = {}
#: a list of Index subclasses
indices = []
+ #: role name -> a warning message if reference is missing
+ dangling_warnings = {}
#: data value for a fresh environment
initial_data = {}
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 4e40dde7..5465c91f 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -1090,13 +1090,15 @@ class CPPDomain(Domain):
contnode, name)
parser = DefinitionParser(target)
- # XXX: warn?
try:
expr = parser.parse_type().get_name()
parser.skip_ws()
if not parser.eof or expr is None:
- return None
+ raise DefinitionError('')
except DefinitionError:
+ refdoc = node.get('refdoc', fromdocname)
+ env.warn(refdoc, 'unparseable C++ definition: %r' % target,
+ node.line)
return None
parent = node['cpp:parent']
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 0dbd883c..2387f1e0 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -419,15 +419,8 @@ class PyModule(Directive):
targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
self.state.document.note_explicit_target(targetnode)
ret = [targetnode]
- # XXX this behavior of the module directive is a mess...
- if 'platform' in self.options:
- platform = self.options['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
+ # the platform and synopsis aren't printed; in fact, they are only used
+ # in the modindex currently
if not noindex:
indextext = _('%s (module)') % modname
inode = addnodes.index(entries=[('single', indextext,
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 7dcec616..3fb9e753 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -411,11 +411,13 @@ class StandardDomain(Domain):
# links to tokens in grammar productions
'token': XRefRole(),
# links to terms in glossary
- 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis),
+ 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis,
+ warn_dangling=True),
# links to headings or arbitrary labels
- 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis),
+ 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis,
+ warn_dangling=True),
# links to labels, without a different title
- 'keyword': XRefRole(),
+ 'keyword': XRefRole(warn_dangling=True),
}
initial_data = {
@@ -433,6 +435,13 @@ class StandardDomain(Domain):
},
}
+ dangling_warnings = {
+ 'term': 'term not in glossary: %(target)s',
+ 'ref': 'undefined label: %(target)s (if the link has no caption '
+ 'the label must precede a section header)',
+ 'keyword': 'unknown keyword: %(target)s',
+ }
+
def clear_doc(self, docname):
for key, (fn, _) in self.data['progoptions'].items():
if fn == docname:
@@ -490,27 +499,16 @@ class StandardDomain(Domain):
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
if typ == 'ref':
- #refdoc = node.get('refdoc', fromdocname)
if node['refexplicit']:
# reference to anonymous label; the reference uses
# the supplied link caption
docname, labelid = self.data['anonlabels'].get(target, ('',''))
sectname = node.astext()
- # XXX warn somehow if not resolved by intersphinx
- #if not docname:
- # env.warn(refdoc, 'undefined label: %s' %
- # target, node.line)
else:
# reference to named label; the final node will
# contain the section name after the label
docname, labelid, sectname = self.data['labels'].get(target,
('','',''))
- # XXX warn somehow if not resolved by intersphinx
- #if not docname:
- # env.warn(refdoc,
- # 'undefined label: %s' % target + ' -- if you '
- # 'don\'t give a link caption the label must '
- # 'precede a section header.', node.line)
if not docname:
return None
newnode = nodes.reference('', '', internal=True)
@@ -534,20 +532,17 @@ class StandardDomain(Domain):
# keywords are oddballs: they are referenced by named labels
docname, labelid, _ = self.data['labels'].get(target, ('','',''))
if not docname:
- #env.warn(refdoc, 'unknown keyword: %s' % target)
return None
- else:
- return make_refnode(builder, fromdocname, docname,
- labelid, contnode)
+ return make_refnode(builder, fromdocname, docname,
+ labelid, contnode)
elif typ == 'option':
progname = node['refprogram']
docname, labelid = self.data['progoptions'].get((progname, target),
('', ''))
if not docname:
return None
- else:
- return make_refnode(builder, fromdocname, docname,
- labelid, contnode)
+ return make_refnode(builder, fromdocname, docname,
+ labelid, contnode)
else:
objtypes = self.objtypes_for_role(typ) or []
for objtype in objtypes:
@@ -557,13 +552,9 @@ class StandardDomain(Domain):
else:
docname, labelid = '', ''
if not docname:
- if typ == 'term':
- env.warn(node.get('refdoc', fromdocname),
- 'term not in glossary: %s' % target, node.line)
return None
- else:
- return make_refnode(builder, fromdocname, docname,
- labelid, contnode)
+ return make_refnode(builder, fromdocname, docname,
+ labelid, contnode)
def get_objects(self):
for (prog, option), info in self.data['progoptions'].iteritems():
diff --git a/sphinx/environment.py b/sphinx/environment.py
index 59debcd8..1f8e00f5 100644
--- a/sphinx/environment.py
+++ b/sphinx/environment.py
@@ -43,6 +43,7 @@ from sphinx.util.nodes import clean_astext, make_refnode, extract_messages
from sphinx.util.osutil import movefile, SEP, ustrftime
from sphinx.util.matching import compile_matchers
from sphinx.util.pycompat import all, class_types
+from sphinx.util.websupport import is_commentable
from sphinx.errors import SphinxError, ExtensionError
from sphinx.locale import _, init as init_locale
from sphinx.versioning import add_uids, merge_doctrees
@@ -68,7 +69,7 @@ default_settings = {
# This is increased every time an environment attribute is added
# or changed to properly invalidate pickle files.
-ENV_VERSION = 39
+ENV_VERSION = 40
default_substitutions = set([
@@ -79,6 +80,12 @@ default_substitutions = set([
dummy_reporter = Reporter('', 4, 4)
+versioning_conditions = {
+ 'none': False,
+ 'text': nodes.TextElement,
+ 'commentable': is_commentable,
+}
+
class WarningStream(object):
def __init__(self, warnfunc):
@@ -182,7 +189,8 @@ class CitationReferences(Transform):
for citnode in self.document.traverse(nodes.citation_reference):
cittext = citnode.astext()
refnode = addnodes.pending_xref(cittext, reftype='citation',
- reftarget=cittext)
+ reftarget=cittext, refwarn=True)
+ refnode.line = citnode.line or citnode.parent.line
refnode += nodes.Text('[' + cittext + ']')
citnode.parent.replace(citnode, refnode)
@@ -313,6 +321,9 @@ class BuildEnvironment:
self.srcdir = srcdir
self.config = config
+ # the method of doctree versioning; see set_versioning_method
+ self.versioning_condition = None
+
# the application object; only set while update() runs
self.app = None
@@ -380,6 +391,23 @@ class BuildEnvironment:
self._warnfunc = func
self.settings['warning_stream'] = WarningStream(func)
+ def set_versioning_method(self, method):
+ """This sets the doctree versioning method for this environment.
+
+ Versioning methods are a builder property; only builders with the same
+ versioning method can share the same doctree directory. Therefore, we
+ raise an exception if the user tries to use an environment with an
+ incompatible versioning method.
+ """
+ if method not in versioning_conditions:
+ raise ValueError('invalid versioning method: %r' % method)
+ condition = versioning_conditions[method]
+ if self.versioning_condition not in (None, condition):
+ raise SphinxError('This environment is incompatible with the '
+ 'selected builder, please choose another '
+ 'doctree directory.')
+ self.versioning_condition = condition
+
def warn(self, docname, msg, lineno=None):
# strange argument order is due to backwards compatibility
self._warnfunc(msg, (docname, lineno))
@@ -754,25 +782,24 @@ class BuildEnvironment:
# store time of build, for outdated files detection
self.all_docs[docname] = time.time()
- # get old doctree
- old_doctree_path = self.doc2path(docname, self.doctreedir, '.doctree')
- try:
- f = open(old_doctree_path, 'rb')
+ if self.versioning_condition:
+ # get old doctree
try:
- old_doctree = pickle.load(f)
- finally:
- f.close()
- old_doctree.settings.env = self
- old_doctree.reporter = Reporter(self.doc2path(docname), 2, 5,
- stream=WarningStream(self._warnfunc))
- except EnvironmentError:
- old_doctree = None
-
- # add uids for versioning
- if old_doctree is None:
- list(add_uids(doctree, nodes.TextElement))
- else:
- list(merge_doctrees(old_doctree, doctree, nodes.TextElement))
+ f = open(self.doc2path(docname,
+ self.doctreedir, '.doctree'), 'rb')
+ try:
+ old_doctree = pickle.load(f)
+ finally:
+ f.close()
+ except EnvironmentError:
+ old_doctree = None
+
+ # add uids for versioning
+ if old_doctree is None:
+ list(add_uids(doctree, self.versioning_condition))
+ else:
+ list(merge_doctrees(
+ old_doctree, doctree, self.versioning_condition))
# make it picklable
doctree.reporter = None
@@ -1385,10 +1412,10 @@ class BuildEnvironment:
typ = node['reftype']
target = node['reftarget']
refdoc = node.get('refdoc', fromdocname)
- warned = False
+ domain = None
try:
- if node.has_key('refdomain') and node['refdomain']:
+ if 'refdomain' in node and node['refdomain']:
# let the domain try to resolve the reference
try:
domain = self.domains[node['refdomain']]
@@ -1401,11 +1428,7 @@ class BuildEnvironment:
# directly reference to document by source name;
# can be absolute or relative
docname = docname_join(refdoc, target)
- if docname not in self.all_docs:
- self.warn(refdoc,
- 'unknown document: %s' % docname, node.line)
- warned = True
- else:
+ if docname in self.all_docs:
if node['refexplicit']:
# reference with explicit title
caption = node.astext()
@@ -1418,11 +1441,7 @@ class BuildEnvironment:
newnode.append(innernode)
elif typ == 'citation':
docname, labelid = self.citations.get(target, ('', ''))
- if not docname:
- self.warn(refdoc,
- 'citation not found: %s' % target, node.line)
- warned = True
- else:
+ if docname:
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)
# no new node found? try the missing-reference event
@@ -1430,16 +1449,40 @@ class BuildEnvironment:
newnode = builder.app.emit_firstresult(
'missing-reference', self, node, contnode)
# still not found? warn if in nit-picky mode
- if newnode is None and not warned and self.config.nitpicky:
- self.warn(refdoc,
- 'reference target not found: %stype %s, target %s'
- % (node.get('refdomain') and
- 'domain %s, ' % node['refdomain'] or '',
- typ, target))
+ if newnode is None:
+ self._warn_missing_reference(
+ fromdocname, typ, target, node, domain)
except NoUri:
newnode = contnode
node.replace_self(newnode or contnode)
+ # remove only-nodes that do not belong to our builder
+ self.process_only_nodes(doctree, fromdocname, builder)
+
+ # allow custom references to be resolved
+ builder.app.emit('doctree-resolved', doctree, fromdocname)
+
+ def _warn_missing_reference(self, fromdoc, typ, target, node, domain):
+ warn = node.get('refwarn')
+ if self.config.nitpicky:
+ warn = True # XXX process exceptions here
+ if not warn:
+ return
+ refdoc = node.get('refdoc', fromdoc)
+ if domain and typ in domain.dangling_warnings:
+ msg = domain.dangling_warnings[typ]
+ elif typ == 'doc':
+ msg = 'unknown document: %(target)s'
+ elif typ == 'citation':
+ msg = 'citation not found: %(target)s'
+ elif node.get('refdomain', 'std') != 'std':
+ msg = '%s:%s reference target not found: %%(target)s' % \
+ (node['refdomain'], typ)
+ else:
+ msg = '%s reference target not found: %%(target)s' % typ
+ self.warn(refdoc, msg % {'target': target}, node.line)
+
+ def process_only_nodes(self, doctree, fromdocname, builder):
for node in doctree.traverse(addnodes.only):
try:
ret = builder.tags.eval_condition(node['expr'])
@@ -1455,9 +1498,6 @@ class BuildEnvironment:
# if there is a target node before the only node
node.replace_self(nodes.comment())
- # allow custom references to be resolved
- builder.app.emit('doctree-resolved', doctree, fromdocname)
-
def assign_section_numbers(self):
"""Assign a section number to each heading under a numbered toctree."""
# a list of all docnames whose section numbers changed
diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py
index 7ea3dd80..928a943f 100644
--- a/sphinx/ext/autodoc.py
+++ b/sphinx/ext/autodoc.py
@@ -27,7 +27,8 @@ from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.application import ExtensionError
from sphinx.util.nodes import nested_parse_with_titles
from sphinx.util.compat import Directive
-from sphinx.util.inspect import isdescriptor, safe_getmembers, safe_getattr
+from sphinx.util.inspect import (getargspec, isdescriptor, safe_getmembers, \
+ safe_getattr)
from sphinx.util.pycompat import base_exception, class_types
from sphinx.util.docstrings import prepare_docstring
@@ -1284,36 +1285,6 @@ def add_documenter(cls):
AutoDirective._registry[cls.objtype] = cls
-if sys.version_info >= (2, 5):
- from functools import partial
- def getargspec(func):
- """Like inspect.getargspec but supports functools.partial as well."""
- if inspect.ismethod(func):
- func = func.im_func
- parts = 0, ()
- if type(func) is partial:
- parts = len(func.args), func.keywords.keys()
- func = func.func
- if not inspect.isfunction(func):
- raise TypeError('{!r} is not a Python function'.format(func))
- args, varargs, varkw = inspect.getargs(func.func_code)
- func_defaults = func.func_defaults
- if func_defaults:
- func_defaults = list(func_defaults)
- if parts[0]:
- args = args[parts[0]:]
- if parts[1]:
- for arg in parts[1]:
- i = args.index(arg) - len(args)
- del args[i]
- try:
- del func_defaults[i]
- except IndexError:
- pass
- return inspect.ArgSpec(args, varargs, varkw, func_defaults)
-else:
- getargspec = inspect.getargspec
-
def setup(app):
app.add_autodocumenter(ModuleDocumenter)
app.add_autodocumenter(ClassDocumenter)
diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py
index 4d7e2db3..0818ad0a 100644
--- a/sphinx/quickstart.py
+++ b/sphinx/quickstart.py
@@ -361,6 +361,8 @@ PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) \
$(SPHINXOPTS) %(rsrcdir)s
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) %(rsrcdir)s
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp \
epub latex latexpdf text man changes linkcheck doctest gettext
@@ -483,7 +485,7 @@ info:
\t@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
-\t$(SPHINXBUILD) -b gettext $(ALLSPHINXOPTS) $(BUILDDIR)/locale
+\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
\t@echo
\t@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
@@ -514,8 +516,10 @@ if "%%SPHINXBUILD%%" == "" (
)
set BUILDDIR=%(rbuilddir)s
set ALLSPHINXOPTS=-d %%BUILDDIR%%/doctrees %%SPHINXOPTS%% %(rsrcdir)s
+set I18NSPHINXOPTS=%%SPHINXOPTS%% %(rsrcdir)s
if NOT "%%PAPER%%" == "" (
\tset ALLSPHINXOPTS=-D latex_paper_size=%%PAPER%% %%ALLSPHINXOPTS%%
+\tset I18NSPHINXOPTS=-D latex_paper_size=%%PAPER%% %%I18NSPHINXOPTS%%
)
if "%%1" == "" goto help
@@ -659,7 +663,7 @@ if "%%1" == "texinfo" (
)
if "%%1" == "gettext" (
-\t%%SPHINXBUILD%% -b gettext %%ALLSPHINXOPTS%% %%BUILDDIR%%/locale
+\t%%SPHINXBUILD%% -b gettext %%I18NSPHINXOPTS%% %%BUILDDIR%%/locale
\tif errorlevel 1 exit /b 1
\techo.
\techo.Build finished. The message catalogs are in %%BUILDDIR%%/locale.
@@ -991,4 +995,3 @@ def main(argv=sys.argv):
print
print '[Interrupted.]'
return
-
diff --git a/sphinx/roles.py b/sphinx/roles.py
index 1d791f6d..30743914 100644
--- a/sphinx/roles.py
+++ b/sphinx/roles.py
@@ -69,9 +69,10 @@ class XRefRole(object):
innernodeclass = nodes.literal
def __init__(self, fix_parens=False, lowercase=False,
- nodeclass=None, innernodeclass=None):
+ nodeclass=None, innernodeclass=None, warn_dangling=False):
self.fix_parens = fix_parens
self.lowercase = lowercase
+ self.warn_dangling = warn_dangling
if nodeclass is not None:
self.nodeclass = nodeclass
if innernodeclass is not None:
@@ -133,6 +134,7 @@ class XRefRole(object):
refnode += self.innernodeclass(rawtext, title, classes=classes)
# we also need the source document
refnode['refdoc'] = env.docname
+ refnode['refwarn'] = self.warn_dangling
# result_nodes allow further modification of return values
return self.result_nodes(inliner.document, env, refnode, is_ref=True)
@@ -298,7 +300,7 @@ specific_docroles = {
# links to download references
'download': XRefRole(nodeclass=addnodes.download_reference),
# links to documents
- 'doc': XRefRole(),
+ 'doc': XRefRole(warn_dangling=True),
'pep': indexmarkup_role,
'rfc': indexmarkup_role,
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index a0574003..c136393f 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -9,6 +9,40 @@
:license: BSD, see LICENSE for details.
"""
+inspect = __import__('inspect')
+import sys
+
+if sys.version_info >= (2, 5):
+ from functools import partial
+ def getargspec(func):
+ """Like inspect.getargspec but supports functools.partial as well."""
+ if inspect.ismethod(func):
+ func = func.im_func
+ parts = 0, ()
+ if type(func) is partial:
+ parts = len(func.args), func.keywords.keys()
+ func = func.func
+ if not inspect.isfunction(func):
+ raise TypeError('%r is not a Python function' % func)
+ args, varargs, varkw = inspect.getargs(func.func_code)
+ func_defaults = func.func_defaults
+ if func_defaults:
+ func_defaults = list(func_defaults)
+ if parts[0]:
+ args = args[parts[0]:]
+ if parts[1]:
+ for arg in parts[1]:
+ i = args.index(arg) - len(args)
+ del args[i]
+ try:
+ del func_defaults[i]
+ except IndexError:
+ pass
+ return inspect.ArgSpec(args, varargs, varkw, func_defaults)
+else:
+ getargspec = inspect.getargspec
+
+
def isdescriptor(x):
"""Check if the object is some kind of descriptor."""
for item in '__get__', '__set__', '__delete__':
diff --git a/sphinx/versioning.py b/sphinx/versioning.py
index f50f80b0..74355efe 100644
--- a/sphinx/versioning.py
+++ b/sphinx/versioning.py
@@ -11,7 +11,6 @@
"""
from uuid import uuid4
from operator import itemgetter
-from collections import defaultdict
from sphinx.util.pycompat import product, zip_longest
@@ -49,7 +48,7 @@ def merge_doctrees(old, new, condition):
new_iter = new.traverse(condition)
old_nodes = []
new_nodes = []
- ratios = defaultdict(list)
+ ratios = {}
seen = set()
# compare the nodes each doctree in order
for old_node, new_node in zip_longest(old_iter, new_iter):