diff --git a/CHANGES b/CHANGES index 5b7550bcc..f48956201 100644 --- a/CHANGES +++ b/CHANGES @@ -72,6 +72,10 @@ Features added * math: Add hyperlink marker to each equations in HTML output * Add new theme ``nonav`` that doesn't include any navigation links. This is for any help generator like qthelp. +* #2680: `sphinx.ext.todo` now emits warnings if `todo_emit_warnings` enabled. + Also, it emits an additional event named `todo-defined` to handle the TODO + entries in 3rd party extensions. + Bugs fixed ---------- diff --git a/doc/ext/todo.rst b/doc/ext/todo.rst index a809bc665..09abfa9b8 100644 --- a/doc/ext/todo.rst +++ b/doc/ext/todo.rst @@ -34,6 +34,13 @@ There is also an additional config value: If this is ``True``, :rst:dir:`todo` and :rst:dir:`todolist` produce output, else they produce nothing. The default is ``False``. +.. confval:: todo_emit_warnings + + If this is ``True``, :rst:dir:`todo` emits a warning for each TODO entries. + The default is ``False``. + + .. versionadded:: 1.5 + .. confval:: todo_link_only If this is ``True``, :rst:dir:`todolist` produce output without file path and line, @@ -41,3 +48,11 @@ There is also an additional config value: .. versionadded:: 1.4 +autodoc provides the following an additional event: + +.. event:: todo-defined (app, node) + + .. versionadded:: 1.5 + + Emitted when a todo is defined. *node* is the defined ``sphinx.ext.todo.todo_node`` + node. diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index a853ec93c..f3b526ce6 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -70,6 +70,8 @@ def process_todos(app, doctree): if not hasattr(env, 'todo_all_todos'): env.todo_all_todos = [] for node in doctree.traverse(todo_node): + app.emit('todo-defined', node) + try: targetnode = node.parent[node.parent.index(node) - 1] if not isinstance(targetnode, nodes.target): @@ -86,6 +88,9 @@ def process_todos(app, doctree): 'target': targetnode, }) + if env.config.todo_emit_warnings: + env.warn_node("TODO entry found: %s" % node[1].astext(), node) + class TodoList(Directive): """ @@ -187,8 +192,10 @@ def depart_todo_node(self, node): def setup(app): + app.add_event('todo-defined') app.add_config_value('todo_include_todos', False, 'html') app.add_config_value('todo_link_only', False, 'html') + app.add_config_value('todo_emit_warnings', False, 'html') app.add_node(todolist) app.add_node(todo_node, diff --git a/tests/roots/test-ext-todo/bar.rst b/tests/roots/test-ext-todo/bar.rst new file mode 100644 index 000000000..6804a68c1 --- /dev/null +++ b/tests/roots/test-ext-todo/bar.rst @@ -0,0 +1,4 @@ +bar +=== + +.. todo:: todo in bar diff --git a/tests/roots/test-ext-todo/conf.py b/tests/roots/test-ext-todo/conf.py new file mode 100644 index 000000000..c67a86c5a --- /dev/null +++ b/tests/roots/test-ext-todo/conf.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +extensions = ['sphinx.ext.todo'] +master_doc = 'index' diff --git a/tests/roots/test-ext-todo/foo.rst b/tests/roots/test-ext-todo/foo.rst new file mode 100644 index 000000000..269199977 --- /dev/null +++ b/tests/roots/test-ext-todo/foo.rst @@ -0,0 +1,4 @@ +foo +=== + +.. todo:: todo in foo diff --git a/tests/roots/test-ext-todo/index.rst b/tests/roots/test-ext-todo/index.rst new file mode 100644 index 000000000..6b95f73fd --- /dev/null +++ b/tests/roots/test-ext-todo/index.rst @@ -0,0 +1,9 @@ +test for sphinx.ext.todo +======================== + +.. toctree:: + + foo + bar + +.. todolist:: diff --git a/tests/test_ext_todo.py b/tests/test_ext_todo.py new file mode 100644 index 000000000..269a8a2be --- /dev/null +++ b/tests/test_ext_todo.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +""" + test_ext_todo + ~~~~~~~~~~~~~ + + Test sphinx.ext.todo extension. + + :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +from util import with_app + + +@with_app('html', testroot='ext-todo', freshenv=True, + confoverrides={'todo_include_todos': True, 'todo_emit_warnings': True}) +def test_todo(app, status, warning): + todos = [] + + def on_todo_defined(app, node): + todos.append(node) + + app.connect('todo-defined', on_todo_defined) + app.builder.build_all() + + # check todolist + content = (app.outdir / 'index.html').text() + html = ('
Todo
\n' + 'todo in foo
') + assert re.search(html, content, re.S) + + html = ('Todo
\n' + 'todo in bar
') + assert re.search(html, content, re.S) + + # check todo + content = (app.outdir / 'foo.html').text() + html = ('Todo
\n' + 'todo in foo
') + assert re.search(html, content, re.S) + + # check emitted warnings + assert 'WARNING: TODO entry found: todo in foo' in warning.getvalue() + assert 'WARNING: TODO entry found: todo in bar' in warning.getvalue() + + # check handled event + assert len(todos) == 2 + assert set(todo[1].astext() for todo in todos) == set(['todo in foo', 'todo in bar']) + + +@with_app('html', testroot='ext-todo', freshenv=True, + confoverrides={'todo_include_todos': False, 'todo_emit_warnings': True}) +def test_todo_not_included(app, status, warning): + todos = [] + + def on_todo_defined(app, node): + todos.append(node) + + app.connect('todo-defined', on_todo_defined) + app.builder.build_all() + + # check todolist + content = (app.outdir / 'index.html').text() + html = ('Todo
\n' + 'todo in foo
') + assert not re.search(html, content, re.S) + + html = ('Todo
\n' + 'todo in bar
') + assert not re.search(html, content, re.S) + + # check todo + content = (app.outdir / 'foo.html').text() + html = ('Todo
\n' + 'todo in foo
') + assert not re.search(html, content, re.S) + + # check emitted warnings + assert 'WARNING: TODO entry found: todo in foo' in warning.getvalue() + assert 'WARNING: TODO entry found: todo in bar' in warning.getvalue() + + # check handled event + assert len(todos) == 2 + assert set(todo[1].astext() for todo in todos) == set(['todo in foo', 'todo in bar'])