From db87ed440c39f08800feb11a5e851674ab949c6b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 23 Sep 2011 09:46:24 +0200 Subject: [PATCH 1/4] Fix #648: Fix line numbers reported in warnings about undefined references. --- CHANGES | 3 ++ sphinx/directives/code.py | 6 ++-- sphinx/directives/other.py | 17 ++++++---- sphinx/domains/c.py | 5 ++- sphinx/domains/cpp.py | 11 +++---- sphinx/domains/javascript.py | 5 ++- sphinx/domains/python.py | 14 ++++---- sphinx/domains/rst.py | 10 +++--- sphinx/domains/std.py | 5 ++- sphinx/environment.py | 63 ++++++++++++++++-------------------- sphinx/ext/doctest.py | 3 +- sphinx/ext/extlinks.py | 8 ++--- sphinx/ext/ifconfig.py | 3 +- sphinx/ext/intersphinx.py | 2 +- sphinx/ext/mathbase.py | 3 +- sphinx/ext/oldcmarkup.py | 4 +-- sphinx/ext/todo.py | 8 +++-- sphinx/roles.py | 4 +-- sphinx/util/nodes.py | 28 ++++++++++++++++ tests/test_build_html.py | 4 +-- 20 files changed, 113 insertions(+), 93 deletions(-) diff --git a/CHANGES b/CHANGES index 6e37d7432..690354a67 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ Release 1.0.8 (Sep 23, 2011) ============================ +* #648: Fix line numbers reported in warnings about undefined + references. + * #696, #666: Fix C++ array definitions and template arguments that are not type names. diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index 6073a7de2..ca2ea61ba 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -17,6 +17,7 @@ from docutils.parsers.rst import Directive, directives from sphinx import addnodes from sphinx.util import parselinenos +from sphinx.util.nodes import set_source_info class Highlight(Directive): @@ -64,7 +65,7 @@ class CodeBlock(Directive): literal = nodes.literal_block(code, code) literal['language'] = self.arguments[0] literal['linenos'] = 'linenos' in self.options - literal.line = self.lineno + set_source_info(self, literal) return [literal] @@ -186,8 +187,7 @@ class LiteralInclude(Directive): if self.options.get('tab-width'): text = text.expandtabs(self.options['tab-width']) retnode = nodes.literal_block(text, text, source=fn) - retnode.line = 1 - retnode.attributes['line_number'] = self.lineno + set_source_info(self, retnode) if self.options.get('language', ''): retnode['language'] = self.options['language'] if 'linenos' in self.options: diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index f59a29e14..606c559bf 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -15,7 +15,7 @@ from docutils.parsers.rst import Directive, directives from sphinx import addnodes from sphinx.locale import pairindextypes, _ from sphinx.util import url_re, docname_join -from sphinx.util.nodes import explicit_title_re +from sphinx.util.nodes import explicit_title_re, set_source_info from sphinx.util.compat import make_admonition from sphinx.util.matching import patfilter @@ -101,6 +101,7 @@ class TocTree(Directive): subnode['hidden'] = 'hidden' in self.options subnode['numbered'] = 'numbered' in self.options subnode['titlesonly'] = 'titlesonly' in self.options + set_source_info(self, subnode) wrappernode = nodes.compound(classes=['toctree-wrapper']) wrappernode.append(subnode) ret.append(wrappernode) @@ -205,6 +206,7 @@ class VersionChange(Directive): def run(self): node = addnodes.versionmodified() node.document = self.state.document + set_source_info(self, node) node['type'] = self.name node['version'] = self.arguments[0] if len(self.arguments) == 2: @@ -217,7 +219,8 @@ class VersionChange(Directive): else: ret = [node] env = self.state.document.settings.env - env.note_versionchange(node['type'], node['version'], node, self.lineno) + # XXX should record node.source as well + env.note_versionchange(node['type'], node['version'], node, node.line) return ret @@ -261,7 +264,7 @@ class TabularColumns(Directive): def run(self): node = addnodes.tabular_col_spec() node['spec'] = self.arguments[0] - node.line = self.lineno + set_source_info(self, node) return [node] @@ -360,7 +363,7 @@ class Only(Directive): def run(self): node = addnodes.only() node.document = self.state.document - node.line = self.lineno + set_source_info(self, node) node['expr'] = self.arguments[0] self.state.nested_parse(self.content, self.content_offset, node, match_titles=1) @@ -376,10 +379,10 @@ class Include(BaseInclude): """ def run(self): - if self.arguments[0].startswith('/') or \ - self.arguments[0].startswith(os.sep): + path = self.arguments[0] + if path.startswith('/') or path.startswith(os.sep): env = self.state.document.settings.env - self.arguments[0] = os.path.join(env.srcdir, self.arguments[0][1:]) + self.arguments[0] = os.path.join(env.srcdir, path[1:]) return BaseInclude.run(self) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 48fbb36f2..253fd345a 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -159,11 +159,10 @@ class CObject(ObjectDescription): self.state.document.note_explicit_target(signode) inv = self.env.domaindata['c']['objects'] if name in inv: - self.env.warn( - self.env.docname, + self.state_machine.reporter.warning( 'duplicate C object description of %s, ' % name + 'other instance in ' + self.env.doc2path(inv[name][0]), - self.lineno) + line=self.lineno) inv[name] = (self.env.docname, self.objtype) indextext = self.get_index_text(name) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index ca984dab8..2f9789fd2 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -965,8 +965,7 @@ class CPPObject(ObjectDescription): rv = self.parse_definition(parser) parser.assert_end() except DefinitionError, e: - self.env.warn(self.env.docname, - e.description, self.lineno) + self.state_machine.reporter.warning(e.description, line=self.lineno) raise ValueError self.describe_signature(signode, rv) @@ -1111,8 +1110,8 @@ class CPPCurrentNamespace(Directive): prefix = parser.parse_type() parser.assert_end() except DefinitionError, e: - self.env.warn(self.env.docname, - e.description, self.lineno) + self.state_machine.reporter.warning(e.description, + line=self.lineno) else: env.temp_data['cpp:prefix'] = prefix return [] @@ -1186,9 +1185,7 @@ class CPPDomain(Domain): if not parser.eof or expr is None: raise DefinitionError('') except DefinitionError: - refdoc = node.get('refdoc', fromdocname) - env.warn(refdoc, 'unparseable C++ definition: %r' % target, - node.line) + env.warn_node('unparseable C++ definition: %r' % target, node) return None parent = node['cpp:parent'] diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index e53eb5fd5..80d25ad0f 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -86,12 +86,11 @@ class JSObject(ObjectDescription): self.state.document.note_explicit_target(signode) objects = self.env.domaindata['js']['objects'] if fullname in objects: - self.env.warn( - self.env.docname, + self.state_machine.reporter.warning( 'duplicate object description of %s, ' % fullname + 'other instance in ' + self.env.doc2path(objects[fullname][0]), - self.lineno) + line=self.lineno) objects[fullname] = self.env.docname, self.objtype indextext = self.get_index_text(objectname, name_obj) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 73f0440ef..e812b6106 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -212,13 +212,12 @@ class PyObject(ObjectDescription): self.state.document.note_explicit_target(signode) objects = self.env.domaindata['py']['objects'] if fullname in objects: - self.env.warn( - self.env.docname, + self.state_machine.reporter.warning( 'duplicate object description of %s, ' % fullname + 'other instance in ' + self.env.doc2path(objects[fullname][0]) + ', use :noindex: for one of them', - self.lineno) + line=self.lineno) objects[fullname] = (self.env.docname, self.objtype) indextext = self.get_index_text(modname, name_cls) @@ -648,11 +647,10 @@ class PythonDomain(Domain): if not matches: return None elif len(matches) > 1: - env.warn(fromdocname, - 'more than one target found for cross-reference ' - '%r: %s' % (target, - ', '.join(match[0] for match in matches)), - node.line) + env.warn_node( + 'more than one target found for cross-reference ' + '%r: %s' % (target, ', '.join(match[0] for match in matches)), + node) name, obj = matches[0] if obj[1] == 'module': diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 6b3e05eec..770a6f6a7 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -38,12 +38,10 @@ class ReSTMarkup(ObjectDescription): objects = self.env.domaindata['rst']['objects'] key = (self.objtype, name) if key in objects: - self.env.warn(self.env.docname, - 'duplicate description of %s %s, ' % - (self.objtype, name) + - 'other instance in ' + - self.env.doc2path(objects[key]), - self.lineno) + self.state_machine.reporter.warning( + 'duplicate description of %s %s, ' % (self.objtype, name) + + 'other instance in ' + self.env.doc2path(objects[key]), + line=self.lineno) objects[key] = self.env.docname indextext = self.get_index_text(self.objtype, name) if indextext: diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index b9da29b86..389134ccc 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -408,9 +408,8 @@ class StandardDomain(Domain): # link and object descriptions continue if name in labels: - env.warn(docname, 'duplicate label %s, ' % name + - 'other instance in ' + env.doc2path(labels[name][0]), - node.line) + env.warn_node('duplicate label %s, ' % name + 'other instance ' + 'in ' + env.doc2path(labels[name][0]), node) anonlabels[name] = docname, labelid if node.tagname == 'section': sectname = clean_astext(node[0]) # node[0] == title node diff --git a/sphinx/environment.py b/sphinx/environment.py index b2fb529fa..465c88255 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -25,7 +25,7 @@ from itertools import izip, groupby from docutils import nodes from docutils.io import FileInput, NullOutput from docutils.core import Publisher -from docutils.utils import Reporter, relative_path +from docutils.utils import Reporter, relative_path, get_source_line from docutils.readers import standalone from docutils.parsers.rst import roles, directives from docutils.parsers.rst.languages import en as english @@ -37,7 +37,7 @@ from docutils.transforms.parts import ContentsFilter from sphinx import addnodes from sphinx.util import url_re, get_matching_docs, docname_join, \ FilenameUniqDict -from sphinx.util.nodes import clean_astext, make_refnode +from sphinx.util.nodes import clean_astext, make_refnode, WarningStream from sphinx.util.osutil import movefile, SEP, ustrftime from sphinx.util.matching import compile_matchers from sphinx.util.pycompat import all @@ -76,14 +76,6 @@ default_substitutions = set([ dummy_reporter = Reporter('', 4, 4) -class WarningStream(object): - def __init__(self, warnfunc): - self.warnfunc = warnfunc - def write(self, text): - if text.strip(): - self.warnfunc(text, None, '') - - class NoUri(Exception): """Raised by get_relative_uri if there is no URI available.""" pass @@ -342,6 +334,9 @@ class BuildEnvironment: # strange argument order is due to backwards compatibility self._warnfunc(msg, (docname, lineno)) + def warn_node(self, msg, node): + self._warnfunc(msg, '%s:%s' % get_source_line(node)) + def clear_doc(self, docname): """Remove all traces of a source file in the inventory.""" if docname in self.all_docs: @@ -792,8 +787,8 @@ class BuildEnvironment: filepath = path.normpath(path.join(docdir, node['reftarget'])) self.dependencies.setdefault(docname, set()).add(filepath) if not os.access(path.join(self.srcdir, filepath), os.R_OK): - self.warn(docname, 'download file not readable: %s' % filepath, - getattr(node, 'line', None)) + self.warn_node('download file not readable: %s' % filepath, + node) continue uniquename = self.dlfiles.add_file(docname, filepath) node['filename'] = uniquename @@ -811,8 +806,7 @@ class BuildEnvironment: node['candidates'] = candidates = {} imguri = node['uri'] if imguri.find('://') != -1: - self.warn(docname, 'nonlocal image URI found: %s' % imguri, - node.line) + self.warn_node('nonlocal image URI found: %s' % imguri, node) candidates['?'] = imguri continue # imgpath is the image path *from srcdir* @@ -838,9 +832,8 @@ class BuildEnvironment: finally: f.close() except (OSError, IOError), err: - self.warn(docname, 'image file %s not ' - 'readable: %s' % (filename, err), - node.line) + self.warn_node('image file %s not readable: %s' % + (filename, err), node) if imgtype: candidates['image/' + imgtype] = new_imgpath else: @@ -850,8 +843,8 @@ class BuildEnvironment: for imgpath in candidates.itervalues(): self.dependencies.setdefault(docname, set()).add(imgpath) if not os.access(path.join(self.srcdir, imgpath), os.R_OK): - self.warn(docname, 'image file not readable: %s' % imgpath, - node.line) + self.warn_node('image file not readable: %s' % imgpath, + node) continue self.images.add_file(docname, imgpath) @@ -974,9 +967,9 @@ class BuildEnvironment: for node in document.traverse(nodes.citation): label = node[0].astext() if label in self.citations: - self.warn(docname, 'duplicate citation %s, ' % label + - 'other instance in %s' % self.doc2path( - self.citations[label][0]), node.line) + self.warn_node('duplicate citation %s, ' % label + + 'other instance in %s' % self.doc2path( + self.citations[label][0]), node) self.citations[label] = (docname, node['ids'][0]) def note_toctree(self, docname, toctreenode): @@ -1243,15 +1236,15 @@ class BuildEnvironment: refnode.children = [nodes.Text(title)] if not toc.children: # empty toc means: no titles will show up in the toctree - self.warn(docname, - 'toctree contains reference to document ' - '%r that doesn\'t have a title: no link ' - 'will be generated' % ref, toctreenode.line) + self.warn_node( + 'toctree contains reference to document %r that ' + 'doesn\'t have a title: no link will be generated' + % ref, toctreenode) except KeyError: # this is raised if the included file does not exist - self.warn(docname, 'toctree contains reference to ' - 'nonexisting document %r' % ref, - toctreenode.line) + self.warn_node( + 'toctree contains reference to nonexisting document %r' + % ref, toctreenode) else: # if titles_only is given, only keep the main title and # sub-toctrees @@ -1332,8 +1325,7 @@ class BuildEnvironment: # 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) + self.warn_node('unknown document: %s' % docname, node) warned = True else: if node['refexplicit']: @@ -1349,8 +1341,7 @@ class BuildEnvironment: elif typ == 'citation': docname, labelid = self.citations.get(target, ('', '')) if not docname: - self.warn(refdoc, - 'citation not found: %s' % target, node.line) + self.warn_node('citation not found: %s' % target, node) warned = True else: newnode = make_refnode(builder, fromdocname, docname, @@ -1370,7 +1361,7 @@ class BuildEnvironment: else: msg = '%s reference target not found: ' \ '%%(target)s' % typ - self.warn(refdoc, msg % {'target': target}, node.line) + self.warn_node(msg % {'target': target}, node) except NoUri: newnode = contnode node.replace_self(newnode or contnode) @@ -1379,8 +1370,8 @@ class BuildEnvironment: try: ret = builder.tags.eval_condition(node['expr']) except Exception, err: - self.warn(fromdocname, 'exception while evaluating only ' - 'directive expression: %s' % err, node.line) + self.warn_node('exception while evaluating only ' + 'directive expression: %s' % err, node) node.replace_self(node.children) else: if ret: diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index dcee09f58..a981b9bc5 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -23,6 +23,7 @@ from docutils import nodes from docutils.parsers.rst import directives from sphinx.builders import Builder +from sphinx.util.nodes import set_source_info from sphinx.util.compat import Directive from sphinx.util.console import bold @@ -63,7 +64,7 @@ class TestDirective(Directive): else: groups = ['default'] node = nodetype(code, code, testnodetype=self.name, groups=groups) - node.line = self.lineno + set_source_info(self, node) if test is not None: # only save if it differs from code node['test'] = test diff --git a/sphinx/ext/extlinks.py b/sphinx/ext/extlinks.py index 910354a29..9f60cdede 100644 --- a/sphinx/ext/extlinks.py +++ b/sphinx/ext/extlinks.py @@ -36,10 +36,10 @@ def make_link_role(base_url, prefix): try: full_url = base_url % part except (TypeError, ValueError): - env = inliner.document.settings.env - env.warn(env.docname, 'unable to expand %s extlink with base ' - 'URL %r, please make sure the base contains \'%%s\' ' - 'exactly once' % (typ, base_url)) + inliner.reporter.warning( + 'unable to expand %s extlink with base URL %r, please make ' + 'sure the base contains \'%%s\' exactly once' + % (typ, base_url), line=lineno) full_url = base_url + part if not has_explicit_title: if prefix is None: diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index 5698ca28c..50c7bbefa 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -22,6 +22,7 @@ from docutils import nodes +from sphinx.util.nodes import set_source_info from sphinx.util.compat import Directive @@ -39,7 +40,7 @@ class IfConfig(Directive): def run(self): node = ifconfig() node.document = self.state.document - node.line = self.lineno + set_source_info(self, node) node['expr'] = self.arguments[0] self.state.nested_parse(self.content, self.content_offset, node, match_titles=1) diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 64a747c9d..4e1ca9809 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -151,7 +151,7 @@ def load_mappings(app): # new format name, (uri, inv) = key, value if not name.isalnum(): - env.warn('intersphinx identifier %r is not alphanumeric' % name) + app.warn('intersphinx identifier %r is not alphanumeric' % name) else: # old format, no name name, uri, inv = None, key, value diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index e7ea82d7c..fc8495845 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -12,6 +12,7 @@ from docutils import nodes, utils from docutils.parsers.rst import directives +from sphinx.util.nodes import set_source_info from sphinx.util.compat import Directive @@ -69,7 +70,7 @@ class MathDirective(Directive): node['nowrap'] = 'nowrap' in self.options node['docname'] = self.state.document.settings.env.docname ret = [node] - node.line = self.lineno + set_source_info(self, node) if hasattr(self, 'src'): node.source = self.src if node['label']: diff --git a/sphinx/ext/oldcmarkup.py b/sphinx/ext/oldcmarkup.py index 2bf9b65d8..cfc95ba48 100644 --- a/sphinx/ext/oldcmarkup.py +++ b/sphinx/ext/oldcmarkup.py @@ -31,7 +31,7 @@ class OldCDirective(Directive): def run(self): env = self.state.document.settings.env if not env.app._oldcmarkup_warned: - env.warn(env.docname, WARNING_MSG, self.lineno) + self.state_machine.reporter.warning(WARNING_MSG, line=self.lineno) env.app._oldcmarkup_warned = True newname = 'c:' + self.name[1:] newdir = env.lookup_domain_element('directive', newname)[0] @@ -45,7 +45,7 @@ def old_crole(typ, rawtext, text, lineno, inliner, options={}, content=[]): if not typ: typ = env.config.default_role if not env.app._oldcmarkup_warned: - env.warn(env.docname, WARNING_MSG) + inliner.reporter.warning(WARNING_MSG, line=lineno) env.app._oldcmarkup_warned = True newtyp = 'c:' + typ[1:] newrole = env.lookup_domain_element('role', newtyp)[0] diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 2ba9d5e1a..c18d70c8d 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -16,6 +16,7 @@ from docutils import nodes from sphinx.locale import _ from sphinx.environment import NoUri +from sphinx.util.nodes import set_source_info from sphinx.util.compat import Directive, make_admonition class todo_node(nodes.Admonition, nodes.Element): pass @@ -41,7 +42,7 @@ class Todo(Directive): ad = make_admonition(todo_node, self.name, [_('Todo')], self.options, self.content, self.lineno, self.content_offset, self.block_text, self.state, self.state_machine) - ad[0].line = self.lineno + set_source_info(self, ad[0]) return [targetnode] + ad @@ -61,6 +62,7 @@ def process_todos(app, doctree): targetnode = None env.todo_all_todos.append({ 'docname': env.docname, + 'source': node.source or env.doc2path(env.docname), 'lineno': node.line, 'todo': node.deepcopy(), 'target': targetnode, @@ -105,9 +107,9 @@ def process_todo_nodes(app, doctree, fromdocname): for todo_info in env.todo_all_todos: para = nodes.paragraph(classes=['todo-source']) - filename = env.doc2path(todo_info['docname'], base=None) description = _('(The <> is located in ' - ' %s, line %d.)') % (filename, todo_info['lineno']) + ' %s, line %d.)') % \ + (todo_info['source'], todo_info['lineno']) desc1 = description[:description.find('<<')] desc2 = description[description.find('>>')+2:] para += nodes.Text(desc1, desc1) diff --git a/sphinx/roles.py b/sphinx/roles.py index 32d02da14..e3d412997 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -18,7 +18,7 @@ from docutils.parsers.rst import roles from sphinx import addnodes from sphinx.locale import _ from sphinx.util import ws_re -from sphinx.util.nodes import split_explicit_title +from sphinx.util.nodes import split_explicit_title, set_role_source_info generic_docroles = { @@ -126,7 +126,7 @@ class XRefRole(object): refnode = self.nodeclass(rawtext, reftype=role, refdomain=domain, refexplicit=has_explicit_title) # we may need the line number for warnings - refnode.line = lineno + set_role_source_info(inliner, lineno, refnode) title, target = self.process_link( env, refnode, has_explicit_title, title, target) # now that the target and title are finally determined, set them diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index fd6a2f839..b7b5073d8 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -13,10 +13,23 @@ import re import types from docutils import nodes +from docutils.statemachine import StateMachine from sphinx import addnodes +class WarningStream(object): + + def __init__(self, warnfunc): + self.warnfunc = warnfunc + self._re = re.compile(r'\((DEBUG|INFO|WARNING|ERROR|SEVERE)/[0-4]\)') + + def write(self, text): + text = text.strip() + if text: + self.warnfunc(self._re.sub(r'\1:', text), None, '') + + # \x00 means the "<" was backslash-escaped explicit_title_re = re.compile(r'^(.+?)\s*(?$', re.DOTALL) caption_ref_re = explicit_title_re # b/w compat alias @@ -91,6 +104,21 @@ def make_refnode(builder, fromdocname, todocname, targetid, child, title=None): node.append(child) return node + +if hasattr(StateMachine, 'get_source_and_line'): + def set_source_info(directive, node): + node.source, node.line = \ + directive.state_machine.get_source_and_line(directive.lineno) + def set_role_source_info(inliner, lineno, node): + node.source, node.line = \ + inliner.reporter.locator(lineno) +else: + # docutils <= 0.6 compatibility + def set_source_info(directive, node): + node.line = directive.lineno + def set_role_source_info(inliner, lineno, node): + node.line = lineno + # 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! diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 638dc4afa..93aa41d41 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -32,12 +32,12 @@ html_warnfile = StringIO() ENV_WARNINGS = """\ %(root)s/autodoc_fodder.py:docstring of autodoc_fodder\\.MarkupError:2: \ -\\(WARNING/2\\) Explicit markup ends without a blank line; unexpected \ +WARNING: Explicit markup ends without a blank line; unexpected \ unindent\\.\\n? %(root)s/images.txt:9: WARNING: image file not readable: foo.png %(root)s/images.txt:23: WARNING: nonlocal image URI found: \ http://www.python.org/logo.png -%(root)s/includes.txt:\\d*: \\(WARNING/2\\) Encoding 'utf-8-sig' used for \ +%(root)s/includes.txt:\\d*: WARNING: Encoding 'utf-8-sig' used for \ reading included file u'.*?wrongenc.inc' seems to be wrong, try giving an \ :encoding: option\\n? %(root)s/includes.txt:4: WARNING: download file not readable: nonexisting.png From 01f45a12c70320cb9a42c133d340210601103a93 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 23 Sep 2011 10:38:18 +0200 Subject: [PATCH 2/4] Fix #727: Fix the links to search results with custom object types. --- CHANGES | 2 ++ sphinx/search.py | 22 ++++++++++++++-------- sphinx/themes/basic/static/searchtools.js | 11 +++++++---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGES b/CHANGES index 690354a67..a8904b31d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0.8 (Sep 23, 2011) ============================ +* #727: Fix the links to search results with custom object types. + * #648: Fix line numbers reported in warnings about undefined references. diff --git a/sphinx/search.py b/sphinx/search.py index 51f997c2d..373c80e37 100644 --- a/sphinx/search.py +++ b/sphinx/search.py @@ -121,7 +121,7 @@ class IndexBuilder(object): self._mapping = {} # objtype -> index self._objtypes = {} - # objtype index -> objname (localized) + # objtype index -> (domain, type, objname (localized)) self._objnames = {} def load(self, stream, format): @@ -160,21 +160,27 @@ class IndexBuilder(object): continue if prio < 0: continue - # XXX splitting at dot is kind of Python specific prefix, name = rpartition(fullname, '.') pdict = rv.setdefault(prefix, {}) try: - i = otypes[domainname, type] + typeindex = otypes[domainname, type] except KeyError: - i = len(otypes) - otypes[domainname, type] = i + typeindex = len(otypes) + otypes[domainname, type] = typeindex otype = domain.object_types.get(type) if otype: # use unicode() to fire translation proxies - onames[i] = unicode(domain.get_type_name(otype)) + onames[typeindex] = (domainname, type, + unicode(domain.get_type_name(otype))) else: - onames[i] = type - pdict[name] = (fn2index[docname], i, prio) + onames[typeindex] = (domainname, type, type) + if anchor == fullname: + shortanchor = '' + elif anchor == type + '-' + fullname: + shortanchor = '-' + else: + shortanchor = anchor + pdict[name] = (fn2index[docname], typeindex, prio, shortanchor) return rv def get_terms(self, fn2index): diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 9308c6d5c..04ce07534 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -363,10 +363,13 @@ var Search = { var fullname = (prefix ? prefix + '.' : '') + name; if (fullname.toLowerCase().indexOf(object) > -1) { match = objects[prefix][name]; - descr = objnames[match[1]] + _(', in ') + titles[match[0]]; - // XXX the generated anchors are not generally correct - // XXX there may be custom prefixes - result = [filenames[match[0]], fullname, '#'+fullname, descr]; + descr = objnames[match[1]][0] + _(', in ') + titles[match[0]]; + anchor = match[3]; + if (anchor == '') + anchor = fullname; + else if (anchor == '-') + anchor = objnames[match[1]][1] + '-' + fullname; + result = [filenames[match[0]], fullname, '#'+anchor, descr]; switch (match[2]) { case 1: objectResults.push(result); break; case 0: importantResults.push(result); break; From 757d0013a70f0df65b353aa4ce9c3355781c35b3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 23 Sep 2011 10:40:18 +0200 Subject: [PATCH 3/4] Fix the ``abbr`` role when the abbreviation has newlines in it. --- CHANGES | 2 ++ sphinx/roles.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index a8904b31d..3b8495e72 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0.8 (Sep 23, 2011) ============================ +* Fix the ``abbr`` role when the abbreviation has newlines in it. + * #727: Fix the links to search results with custom object types. * #648: Fix line numbers reported in warnings about undefined diff --git a/sphinx/roles.py b/sphinx/roles.py index e3d412997..0ef3624ef 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -259,7 +259,7 @@ def emph_literal_role(typ, rawtext, text, lineno, inliner, return [retnode], [] -_abbr_re = re.compile('\((.*)\)$') +_abbr_re = re.compile('\((.*)\)$', re.S) def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): text = utils.unescape(text) From 69f7e0768114fde9ccabc4bf7d56f765743612a6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 23 Sep 2011 10:46:43 +0200 Subject: [PATCH 4/4] Fix #627: Fix tracebacks for AttributeErrors in autosummary generation. --- CHANGES | 2 ++ sphinx/ext/autosummary/generate.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 3b8495e72..6fb83f404 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0.8 (Sep 23, 2011) ============================ +* #627: Fix tracebacks for AttributeErrors in autosummary generation. + * Fix the ``abbr`` role when the abbreviation has newlines in it. * #727: Fix the links to search results with custom object types. diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index b5ff3f4ca..86be0e53a 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -29,6 +29,7 @@ from jinja2.sandbox import SandboxedEnvironment from sphinx.ext.autosummary import import_by_name, get_documenter from sphinx.jinja2glue import BuiltinTemplateLoader from sphinx.util.osutil import ensuredir +from sphinx.util.inspect import safe_getattr def main(argv=sys.argv): usage = """%prog [OPTIONS] SOURCEFILE ...""" @@ -135,10 +136,14 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', template = template_env.get_template('autosummary/base.rst') def get_members(obj, typ, include_public=[]): - items = [ - name for name in dir(obj) - if get_documenter(getattr(obj, name), obj).objtype == typ - ] + items = [] + for name in dir(obj): + try: + documenter = get_documenter(safe_getattr(obj, name), obj) + except AttributeError: + continue + if documenter.objtype == typ: + items.append(name) public = [x for x in items if x in include_public or not x.startswith('_')] return public, items