diff options
Diffstat (limited to 'sphinx')
| -rw-r--r-- | sphinx/builders/__init__.py | 5 | ||||
| -rw-r--r-- | sphinx/builders/gettext.py (renamed from sphinx/builders/intl.py) | 5 | ||||
| -rw-r--r-- | sphinx/builders/websupport.py | 1 | ||||
| -rw-r--r-- | sphinx/domains/__init__.py | 2 | ||||
| -rw-r--r-- | sphinx/domains/cpp.py | 6 | ||||
| -rw-r--r-- | sphinx/domains/python.py | 11 | ||||
| -rw-r--r-- | sphinx/domains/std.py | 45 | ||||
| -rw-r--r-- | sphinx/environment.py | 122 | ||||
| -rw-r--r-- | sphinx/ext/autodoc.py | 33 | ||||
| -rw-r--r-- | sphinx/quickstart.py | 9 | ||||
| -rw-r--r-- | sphinx/roles.py | 6 | ||||
| -rw-r--r-- | sphinx/util/inspect.py | 34 | ||||
| -rw-r--r-- | sphinx/versioning.py | 3 |
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): |
