refactor: todo: Add TodoDomain to collect todo nodes

This commit is contained in:
Takeshi KOMIYA 2019-06-02 23:14:11 +09:00
parent 0441ab0350
commit 9abb4820b1
3 changed files with 67 additions and 14 deletions

View File

@ -10,6 +10,10 @@ Incompatible changes
Deprecated Deprecated
---------- ----------
* ``sphinx.ext.todo.merge_info()``
* ``sphinx.ext.todo.process_todos()``
* ``sphinx.ext.todo.purge_todos()``
Features added Features added
-------------- --------------

View File

@ -26,6 +26,21 @@ The following is a list of deprecated interfaces.
- (will be) Removed - (will be) Removed
- Alternatives - Alternatives
* - ``sphinx.ext.todo.merge_info()``
- 2.2
- 4.0
- ``sphinx.ext.todo.TodoDomain``
* - ``sphinx.ext.todo.process_todos()``
- 2.2
- 4.0
- ``sphinx.ext.todo.TodoDomain``
* - ``sphinx.ext.todo.purge_todos()``
- 2.2
- 4.0
- ``sphinx.ext.todo.TodoDomain``
* - ``sphinx.builders.latex.LaTeXBuilder.apply_transforms()`` * - ``sphinx.builders.latex.LaTeXBuilder.apply_transforms()``
- 2.1 - 2.1
- 4.0 - 4.0

View File

@ -11,6 +11,7 @@
:license: BSD, see LICENSE for details. :license: BSD, see LICENSE for details.
""" """
import warnings
from typing import cast from typing import cast
from docutils import nodes from docutils import nodes
@ -18,6 +19,8 @@ from docutils.parsers.rst import directives
from docutils.parsers.rst.directives.admonitions import BaseAdmonition from docutils.parsers.rst.directives.admonitions import BaseAdmonition
import sphinx import sphinx
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.domains import Domain
from sphinx.errors import NoUri from sphinx.errors import NoUri
from sphinx.locale import _, __ from sphinx.locale import _, __
from sphinx.util import logging from sphinx.util import logging
@ -69,6 +72,7 @@ class Todo(BaseAdmonition, SphinxDirective):
return [todo] return [todo]
elif isinstance(todo, todo_node): elif isinstance(todo, todo_node):
todo.insert(0, nodes.title(text=_('Todo'))) todo.insert(0, nodes.title(text=_('Todo')))
todo['docname'] = self.env.docname
self.add_name(todo) self.add_name(todo)
self.set_source_info(todo) self.set_source_info(todo)
self.state.document.note_explicit_target(todo) self.state.document.note_explicit_target(todo)
@ -77,8 +81,39 @@ class Todo(BaseAdmonition, SphinxDirective):
raise RuntimeError # never reached here raise RuntimeError # never reached here
class TodoDomain(Domain):
name = 'todo'
label = 'todo'
@property
def todos(self):
# type: () -> Dict[str, List[todo_node]]
return self.data.setdefault('todos', {})
def clear_doc(self, docname):
# type: (str) -> None
self.todos.pop(docname, None)
def merge_domaindata(self, docnames, otherdata):
# type: (List[str], Dict) -> None
for docname in docnames:
self.todos[docname] = otherdata['todos'][docname]
def process_doc(self, env, docname, document):
# type: (BuildEnvironment, str, nodes.document) -> None
todos = self.todos.setdefault(docname, [])
for todo in document.traverse(todo_node):
env.app.emit('todo-defined', todo)
todos.append(todo)
if env.config.todo_emit_warnings:
logger.warning(__("TODO entry found: %s"), todo[1].astext(),
location=todo)
def process_todos(app, doctree): def process_todos(app, doctree):
# type: (Sphinx, nodes.document) -> None # type: (Sphinx, nodes.document) -> None
warnings.warn('process_todos() is deprecated.', RemovedInSphinx40Warning)
# collect all todos in the environment # collect all todos in the environment
# this is not done in the directive itself because it some transformations # this is not done in the directive itself because it some transformations
# must have already been run, e.g. substitutions # must have already been run, e.g. substitutions
@ -127,10 +162,8 @@ def process_todo_nodes(app, doctree, fromdocname):
"""Replace all todolist nodes with a list of the collected todos. """Replace all todolist nodes with a list of the collected todos.
Augment each todo with a backlink to the original location. Augment each todo with a backlink to the original location.
""" """
env = app.builder.env domain = cast(TodoDomain, app.env.get_domain('todo'))
todos = sum(domain.todos.values(), [])
if not hasattr(env, 'todo_all_todos'):
env.todo_all_todos = [] # type: ignore
for node in doctree.traverse(todolist): for node in doctree.traverse(todolist):
if node.get('ids'): if node.get('ids'):
@ -142,14 +175,14 @@ def process_todo_nodes(app, doctree, fromdocname):
node.replace_self(content) node.replace_self(content)
continue continue
for todo_info in env.todo_all_todos: # type: ignore for todo_info in todos:
para = nodes.paragraph(classes=['todo-source']) para = nodes.paragraph(classes=['todo-source'])
if app.config['todo_link_only']: if app.config['todo_link_only']:
description = _('<<original entry>>') description = _('<<original entry>>')
else: else:
description = ( description = (
_('(The <<original entry>> is located in %s, line %d.)') % _('(The <<original entry>> is located in %s, line %d.)') %
(todo_info['source'], todo_info['lineno']) (todo_info.source, todo_info.line)
) )
desc1 = description[:description.find('<<')] desc1 = description[:description.find('<<')]
desc2 = description[description.find('>>') + 2:] desc2 = description[description.find('>>') + 2:]
@ -159,16 +192,17 @@ def process_todo_nodes(app, doctree, fromdocname):
innernode = nodes.emphasis(_('original entry'), _('original entry')) innernode = nodes.emphasis(_('original entry'), _('original entry'))
try: try:
para += make_refnode(app.builder, fromdocname, todo_info['docname'], para += make_refnode(app.builder, fromdocname, todo_info['docname'],
todo_info['target'], innernode) todo_info['ids'][0], innernode)
except NoUri: except NoUri:
# ignore if no URI can be determined, e.g. for LaTeX output # ignore if no URI can be determined, e.g. for LaTeX output
pass pass
para += nodes.Text(desc2, desc2) para += nodes.Text(desc2, desc2)
todo_entry = todo_info['todo'] todo_entry = todo_info.deepcopy()
todo_entry['ids'].clear()
# (Recursively) resolve references in the todo content # (Recursively) resolve references in the todo content
env.resolve_references(todo_entry, todo_info['docname'], app.env.resolve_references(todo_entry, todo_info['docname'],
app.builder) app.builder)
# Insert into the todolist # Insert into the todolist
@ -180,6 +214,7 @@ def process_todo_nodes(app, doctree, fromdocname):
def purge_todos(app, env, docname): def purge_todos(app, env, docname):
# type: (Sphinx, BuildEnvironment, str) -> None # type: (Sphinx, BuildEnvironment, str) -> None
warnings.warn('purge_todos() is deprecated.', RemovedInSphinx40Warning)
if not hasattr(env, 'todo_all_todos'): if not hasattr(env, 'todo_all_todos'):
return return
env.todo_all_todos = [todo for todo in env.todo_all_todos # type: ignore env.todo_all_todos = [todo for todo in env.todo_all_todos # type: ignore
@ -188,6 +223,7 @@ def purge_todos(app, env, docname):
def merge_info(app, env, docnames, other): def merge_info(app, env, docnames, other):
# type: (Sphinx, BuildEnvironment, Iterable[str], BuildEnvironment) -> None # type: (Sphinx, BuildEnvironment, Iterable[str], BuildEnvironment) -> None
warnings.warn('merge_info() is deprecated.', RemovedInSphinx40Warning)
if not hasattr(other, 'todo_all_todos'): if not hasattr(other, 'todo_all_todos'):
return return
if not hasattr(env, 'todo_all_todos'): if not hasattr(env, 'todo_all_todos'):
@ -242,12 +278,10 @@ def setup(app):
app.add_directive('todo', Todo) app.add_directive('todo', Todo)
app.add_directive('todolist', TodoList) app.add_directive('todolist', TodoList)
app.connect('doctree-read', process_todos) app.add_domain(TodoDomain)
app.connect('doctree-resolved', process_todo_nodes) app.connect('doctree-resolved', process_todo_nodes)
app.connect('env-purge-doc', purge_todos)
app.connect('env-merge-info', merge_info)
return { return {
'version': sphinx.__display_version__, 'version': sphinx.__display_version__,
'env_version': 1, 'env_version': 2,
'parallel_read_safe': True 'parallel_read_safe': True
} }