Commits

Łukasz Langa committed 0d9c1d6 Merge

getargspec moved to sphinx.util.inspect

Comments (0)

Files changed (19)

 
 * Added a Texinfo builder.
 
+* Incompatibility: The :rst:dir:`py:module` directive doesn't output
+  its ``platform`` option value anymore.  (It was the only thing that
+  the directive did output, and therefore quite inconsistent.)
+
 * Added i18n support for content, a ``gettext`` builder and
   related utilities.
 
 Release 1.0.7 (in development)
 ==============================
 
+* #572: Show warnings by default when reference labels cannot be
+  found.
+
+* #536: Include line number when complaining about missing reference
+  targets in nitpicky mode.
+
 * #590: Fix inline display of graphviz diagrams in LaTeX output.
 
 * #589: Build using app.build() in setup command.
 
 PAPEROPT_a4      = -D latex_paper_size=a4
 PAPEROPT_letter  = -D latex_paper_size=letter
-ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) \
-                $(SPHINXOPTS) $(O) .
+ALLSPHINXOPTS    = -d _build/doctrees $(PAPEROPT_$(PAPER)) \
+                   $(SPHINXOPTS) $(O) .
+I18NSPHINXOPTS   = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(O) .
 
 .PHONY: help clean html dirhtml singlehtml text man pickle json htmlhelp \
 	qthelp devhelp epub latex latexpdf changes linkcheck doctest
 	@echo "pdflatex finished; the PDF files are in _build/latex."
 
 gettext:
-	$(SPHINXBUILD) -b gettext $(ALLSPHINXOPTS) _build/locale
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) _build/locale
 	@echo
 	@echo "Build finished. The message catalogs are in _build/locale."
 
 
    .. versionadded:: 0.5
 
-.. module:: sphinx.builders.intl
+.. module:: sphinx.builders.gettext
 .. class:: MessageCatalogBuilder
 
    This builder produces gettext-style message catalos.  Each top-level file or
 way to do that.
 
 After Sphinx successfully ran the
-:class:`~sphinx.builders.intl.MessageCatalogBuilder` you will find a collection
+:class:`~sphinx.builders.gettext.MessageCatalogBuilder` you will find a collection
 of ``.pot`` files in your output directory.  These are **catalog templates**
 and contain messages in your original language *only*.
 
   *nosidebar*.
 
 * **pyramid** -- A theme from the Pyramid web framework project, designed by
-  Blais Laflamme.  THere are currently no options beyond *nosidebar*.
+  Blaise Laflamme.  THere are currently no options beyond *nosidebar*.
 
 * **haiku** -- A theme without sidebar inspired by the `Haiku OS user guide
   <http://www.haiku-os.org/docs/userguide/en/contents.html>`_.  The following

sphinx/builders/__init__.py

     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
     'changes':    ('changes', 'ChangesBuilder'),
     'linkcheck':  ('linkcheck', 'CheckExternalLinksBuilder'),
     'websupport': ('websupport', 'WebSupportBuilder'),
-    'gettext':    ('intl', 'MessageCatalogBuilder'),
+    'gettext':    ('gettext', 'MessageCatalogBuilder'),
 }

sphinx/builders/gettext.py

+# -*- coding: utf-8 -*-
+"""
+    sphinx.builders.gettext
+    ~~~~~~~~~~~~~~~~~~~~~~~
+
+    The MessageCatalogBuilder class.
+
+    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+"""
+
+from os import path
+from codecs import open
+from datetime import datetime
+from collections import defaultdict
+
+from docutils import nodes
+
+from sphinx.builders import Builder
+from sphinx.util.nodes import extract_messages
+from sphinx.util.osutil import SEP, copyfile
+from sphinx.util.console import darkgreen
+
+POHEADER = ur"""
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) %(copyright)s
+# This file is distributed under the same license as the %(project)s package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: %(version)s\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: %(ctime)s\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+"""[1:]
+
+
+class I18nBuilder(Builder):
+    """
+    General i18n builder.
+    """
+    name = 'i18n'
+    versioning_method = 'text'
+
+    def init(self):
+        Builder.init(self)
+        self.catalogs = defaultdict(dict)
+
+    def get_target_uri(self, docname, typ=None):
+        return ''
+
+    def get_outdated_docs(self):
+        return self.env.found_docs
+
+    def prepare_writing(self, docnames):
+        return
+
+    def write_doc(self, docname, doctree):
+        catalog = self.catalogs[docname.split(SEP, 1)[0]]
+
+        for node, msg in extract_messages(doctree):
+            catalog.setdefault(node.uid, msg)
+
+
+class MessageCatalogBuilder(I18nBuilder):
+    """
+    Builds gettext-style message catalogs (.pot files).
+    """
+    name = 'gettext'
+
+    def finish(self):
+        I18nBuilder.finish(self)
+        data = dict(
+            version = self.config.version,
+            copyright = self.config.copyright,
+            project = self.config.project,
+            # XXX should supply tz
+            ctime = datetime.now().strftime('%Y-%m-%d %H:%M%z'),
+        )
+        for section, messages in self.status_iterator(
+                self.catalogs.iteritems(), "writing message catalogs... ",
+                lambda (section, _):darkgreen(section), len(self.catalogs)):
+
+            pofn = path.join(self.outdir, section + '.pot')
+            pofile = open(pofn, 'w', encoding='utf-8')
+            try:
+                pofile.write(POHEADER % data)
+                for uid, message in messages.iteritems():
+                    # message contains *one* line of text ready for translation
+                    message = message.replace(u'\\', ur'\\'). \
+                                      replace(u'"', ur'\"')
+                    pomsg = u'#%s\nmsgid "%s"\nmsgstr ""\n\n' % (uid, message)
+                    pofile.write(pomsg)
+            finally:
+                pofile.close()

sphinx/builders/intl.py

-# -*- coding: utf-8 -*-
-"""
-    sphinx.builders.intl
-    ~~~~~~~~~~~~~~~~~~~~
-
-    The MessageCatalogBuilder class.
-
-    :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
-    :license: BSD, see LICENSE for details.
-"""
-
-from os import path
-from codecs import open
-from datetime import datetime
-from collections import defaultdict
-
-from docutils import nodes
-
-from sphinx.builders import Builder
-from sphinx.util.nodes import extract_messages
-from sphinx.util.osutil import SEP, copyfile
-from sphinx.util.console import darkgreen
-
-POHEADER = ur"""
-# SOME DESCRIPTIVE TITLE.
-# Copyright (C) %(copyright)s
-# This file is distributed under the same license as the %(project)s package.
-# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
-#
-#, fuzzy
-msgid ""
-msgstr ""
-"Project-Id-Version: %(version)s\n"
-"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: %(ctime)s\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
-"Language-Team: LANGUAGE <LL@li.org>\n"
-"MIME-Version: 1.0\n"
-"Content-Type: text/plain; charset=UTF-8\n"
-"Content-Transfer-Encoding: 8bit\n"
-
-"""[1:]
-
-
-class I18nBuilder(Builder):
-    """
-    General i18n builder.
-    """
-    name = 'i18n'
-
-    def init(self):
-        Builder.init(self)
-        self.catalogs = defaultdict(dict)
-
-    def get_target_uri(self, docname, typ=None):
-        return ''
-
-    def get_outdated_docs(self):
-        return self.env.found_docs
-
-    def prepare_writing(self, docnames):
-        return
-
-    def write_doc(self, docname, doctree):
-        catalog = self.catalogs[docname.split(SEP, 1)[0]]
-
-        for node, msg in extract_messages(doctree):
-            catalog.setdefault(node.uid, msg)
-
-
-class MessageCatalogBuilder(I18nBuilder):
-    """
-    Builds gettext-style message catalogs (.pot files).
-    """
-    name = 'gettext'
-
-    def finish(self):
-        I18nBuilder.finish(self)
-        data = dict(
-            version = self.config.version,
-            copyright = self.config.copyright,
-            project = self.config.project,
-            # XXX should supply tz
-            ctime = datetime.now().strftime('%Y-%m-%d %H:%M%z'),
-        )
-        for section, messages in self.status_iterator(
-                self.catalogs.iteritems(), "writing message catalogs... ",
-                lambda (section, _):darkgreen(section), len(self.catalogs)):
-
-            pofn = path.join(self.outdir, section + '.pot')
-            pofile = open(pofn, 'w', encoding='utf-8')
-            try:
-                pofile.write(POHEADER % data)
-                for uid, message in messages.iteritems():
-                    # message contains *one* line of text ready for translation
-                    message = message.replace(u'\\', ur'\\'). \
-                                      replace(u'"', ur'\"')
-                    pomsg = u'#%s\nmsgid "%s"\nmsgstr ""\n\n' % (uid, message)
-                    pofile.write(pomsg)
-            finally:
-                pofile.close()

sphinx/builders/websupport.py

     Builds documents for the web support package.
     """
     name = 'websupport'
+    versioning_method = 'commentable'
 
     def init(self):
         PickleHTMLBuilder.init(self)

sphinx/domains/__init__.py

     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 = {}

sphinx/domains/cpp.py

                                 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']

sphinx/domains/python.py

         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,

sphinx/domains/std.py

         # 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 = {
         },
     }
 
+    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:
     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)
             # 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:
             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():

sphinx/environment.py

 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
 
 # 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([
 
 dummy_reporter = Reporter('', 4, 4)
 
+versioning_conditions = {
+    'none': False,
+    'text': nodes.TextElement,
+    'commentable': is_commentable,
+}
+
 
 class WarningStream(object):
     def __init__(self, warnfunc):
         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)
 
         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
 
         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))
         # 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
+                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, nodes.TextElement))
-        else:
-            list(merge_doctrees(old_doctree, doctree, nodes.TextElement))
+            # 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
             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']]
                     # 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()
                         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
                     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'])
                     # 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

sphinx/ext/autodoc.py

 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
 
     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)

sphinx/quickstart.py

 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
 \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."
 
 )
 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
 )
 
 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.
         print
         print '[Interrupted.]'
         return
-
     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:
         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)
 
     # 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,

sphinx/util/inspect.py

     :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__':

sphinx/versioning.py

 """
 from uuid import uuid4
 from operator import itemgetter
-from collections import defaultdict
 
 from sphinx.util.pycompat import product, zip_longest
 
     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):