Merge pull request #6433 from tk0miya/refactor_todo2

refactor todo extension with Domain
This commit is contained in:
Takeshi KOMIYA 2019-06-15 23:12:03 +09:00 committed by GitHub
commit d2f0c83568
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 155 additions and 27 deletions

View File

@ -14,6 +14,10 @@ Deprecated
``sphinx.ext.autosummary.generate.generate_autosummary_docs()`` ``sphinx.ext.autosummary.generate.generate_autosummary_docs()``
* ``sphinx.ext.autosummary.generate._simple_info()`` * ``sphinx.ext.autosummary.generate._simple_info()``
* ``sphinx.ext.autosummary.generate._simple_warn()`` * ``sphinx.ext.autosummary.generate._simple_warn()``
* ``sphinx.ext.todo.merge_info()``
* ``sphinx.ext.todo.process_todo_nodes()``
* ``sphinx.ext.todo.process_todos()``
* ``sphinx.ext.todo.purge_todos()``
Features added Features added
-------------- --------------

View File

@ -42,6 +42,26 @@ The following is a list of deprecated interfaces.
- 4.0 - 4.0
- ``logging.warning()`` - ``logging.warning()``
* - ``sphinx.ext.todo.merge_info()``
- 2.2
- 4.0
- ``sphinx.ext.todo.TodoDomain``
* - ``sphinx.ext.todo.process_todo_nodes()``
- 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
@ -122,19 +157,82 @@ class TodoList(SphinxDirective):
return [todolist('')] return [todolist('')]
class TodoListProcessor:
def __init__(self, app, doctree, docname):
# type: (Sphinx, nodes.document, str) -> None
self.builder = app.builder
self.config = app.config
self.env = app.env
self.domain = cast(TodoDomain, app.env.get_domain('todo'))
self.process(doctree, docname)
def process(self, doctree, docname):
# type: (nodes.document, str) -> None
todos = sum(self.domain.todos.values(), [])
for node in doctree.traverse(todolist):
if not self.config.todo_include_todos:
node.parent.remove(node)
continue
if node.get('ids'):
content = [nodes.target()] # type: List[nodes.Element]
else:
content = []
for todo in todos:
# Create a copy of the todo node
new_todo = todo.deepcopy()
new_todo['ids'].clear()
# (Recursively) resolve references in the todo content
self.env.resolve_references(new_todo, todo['docname'], self.builder) # type: ignore # NOQA
content.append(new_todo)
todo_ref = self.create_todo_reference(todo, docname)
content.append(todo_ref)
node.replace_self(content)
def create_todo_reference(self, todo, docname):
# type: (todo_node, str) -> nodes.paragraph
if self.config.todo_link_only:
description = _('<<original entry>>')
else:
description = (_('(The <<original entry>> is located in %s, line %d.)') %
(todo.source, todo.line))
prefix = description[:description.find('<<')]
suffix = description[description.find('>>') + 2:]
para = nodes.paragraph(classes=['todo-source'])
para += nodes.Text(prefix, prefix)
# Create a reference
linktext = nodes.emphasis(_('original entry'), _('original entry'))
reference = nodes.reference('', '', linktext, internal=True)
try:
reference['refuri'] = self.builder.get_relative_uri(docname, todo['docname'])
reference['refuri'] += '#' + todo['ids'][0]
except NoUri:
# ignore if no URI can be determined, e.g. for LaTeX output
pass
para += reference
para += nodes.Text(suffix, suffix)
return para
def process_todo_nodes(app, doctree, fromdocname): def process_todo_nodes(app, doctree, fromdocname):
# type: (Sphinx, nodes.document, str) -> None # type: (Sphinx, nodes.document, str) -> None
node = None # type: nodes.Element """Replace all todolist nodes with a list of the collected todos.
if not app.config['todo_include_todos']: Augment each todo with a backlink to the original location.
for node in doctree.traverse(todo_node): """
node.parent.remove(node) warnings.warn('process_todo_nodes() is deprecated.', RemovedInSphinx40Warning)
# Replace all todolist nodes with a list of the collected todos. domain = cast(TodoDomain, app.env.get_domain('todo'))
# Augment each todo with a backlink to the original location. todos = sum(domain.todos.values(), [])
env = app.builder.env
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'):
@ -146,14 +244,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:]
@ -163,17 +261,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) # type: ignore # NOQA
app.builder)
# Insert into the todolist # Insert into the todolist
content.append(todo_entry) content.append(todo_entry)
@ -184,6 +282,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
@ -192,6 +291,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'):
@ -201,7 +301,10 @@ def merge_info(app, env, docnames, other):
def visit_todo_node(self, node): def visit_todo_node(self, node):
# type: (HTMLTranslator, todo_node) -> None # type: (HTMLTranslator, todo_node) -> None
if self.config.todo_include_todos:
self.visit_admonition(node) self.visit_admonition(node)
else:
raise nodes.SkipNode
def depart_todo_node(self, node): def depart_todo_node(self, node):
@ -211,11 +314,14 @@ def depart_todo_node(self, node):
def latex_visit_todo_node(self, node): def latex_visit_todo_node(self, node):
# type: (LaTeXTranslator, todo_node) -> None # type: (LaTeXTranslator, todo_node) -> None
if self.config.todo_include_todos:
self.body.append('\n\\begin{sphinxadmonition}{note}{') self.body.append('\n\\begin{sphinxadmonition}{note}{')
self.body.append(self.hypertarget_to(node)) self.body.append(self.hypertarget_to(node))
title_node = cast(nodes.title, node[0]) title_node = cast(nodes.title, node[0])
self.body.append('%s:}' % title_node.astext().translate(tex_escape_map)) self.body.append('%s:}' % title_node.astext().translate(tex_escape_map))
node.pop(0) node.pop(0)
else:
raise nodes.SkipNode
def latex_depart_todo_node(self, node): def latex_depart_todo_node(self, node):
@ -240,12 +346,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', TodoListProcessor)
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
} }