mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge pull request #6260 from tk0miya/refactor_events
Make EventManager portable
This commit is contained in:
commit
15bc5a32bb
@ -145,7 +145,7 @@ Sphinx core events
|
|||||||
------------------
|
------------------
|
||||||
|
|
||||||
These events are known to the core. The arguments shown are given to the
|
These events are known to the core. The arguments shown are given to the
|
||||||
registered event handlers. Use :meth:`.connect` in an extension's ``setup``
|
registered event handlers. Use :meth:`.Sphinx.connect` in an extension's ``setup``
|
||||||
function (note that ``conf.py`` can also have a ``setup`` function) to connect
|
function (note that ``conf.py`` can also have a ``setup`` function) to connect
|
||||||
handlers to the events. Example:
|
handlers to the events. Example:
|
||||||
|
|
||||||
|
@ -38,3 +38,8 @@ Builder API
|
|||||||
.. automethod:: write_doc
|
.. automethod:: write_doc
|
||||||
.. automethod:: finish
|
.. automethod:: finish
|
||||||
|
|
||||||
|
**Attributes**
|
||||||
|
|
||||||
|
.. attribute:: events
|
||||||
|
|
||||||
|
An :class:`.EventManager` object.
|
||||||
|
@ -27,6 +27,10 @@ Build environment API
|
|||||||
|
|
||||||
Directory for storing pickled doctrees.
|
Directory for storing pickled doctrees.
|
||||||
|
|
||||||
|
.. attribute:: events
|
||||||
|
|
||||||
|
An :class:`.EventManager` object.
|
||||||
|
|
||||||
.. attribute:: found_docs
|
.. attribute:: found_docs
|
||||||
|
|
||||||
A set of all existing docnames.
|
A set of all existing docnames.
|
||||||
|
@ -29,3 +29,9 @@ components (e.g. :class:`.Config`, :class:`.BuildEnvironment` and so on) easily.
|
|||||||
|
|
||||||
.. autoclass:: sphinx.transforms.post_transforms.images.ImageConverter
|
.. autoclass:: sphinx.transforms.post_transforms.images.ImageConverter
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
Utility components
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. autoclass:: sphinx.events.EventManager
|
||||||
|
:members:
|
||||||
|
@ -182,7 +182,7 @@ class Sphinx:
|
|||||||
self.warningiserror = warningiserror
|
self.warningiserror = warningiserror
|
||||||
logging.setup(self, self._status, self._warning)
|
logging.setup(self, self._status, self._warning)
|
||||||
|
|
||||||
self.events = EventManager()
|
self.events = EventManager(self)
|
||||||
|
|
||||||
# keep last few messages for traceback
|
# keep last few messages for traceback
|
||||||
# This will be filled by sphinx.util.logging.LastMessagesWriter
|
# This will be filled by sphinx.util.logging.LastMessagesWriter
|
||||||
@ -249,7 +249,7 @@ class Sphinx:
|
|||||||
|
|
||||||
# now that we know all config values, collect them from conf.py
|
# now that we know all config values, collect them from conf.py
|
||||||
self.config.init_values()
|
self.config.init_values()
|
||||||
self.emit('config-inited', self.config)
|
self.events.emit('config-inited', self.config)
|
||||||
|
|
||||||
# create the project
|
# create the project
|
||||||
self.project = Project(self.srcdir, self.config.source_suffix)
|
self.project = Project(self.srcdir, self.config.source_suffix)
|
||||||
@ -319,7 +319,7 @@ class Sphinx:
|
|||||||
# type: () -> None
|
# type: () -> None
|
||||||
self.builder.set_environment(self.env)
|
self.builder.set_environment(self.env)
|
||||||
self.builder.init()
|
self.builder.init()
|
||||||
self.emit('builder-inited')
|
self.events.emit('builder-inited')
|
||||||
|
|
||||||
# ---- main "build" method -------------------------------------------------
|
# ---- main "build" method -------------------------------------------------
|
||||||
|
|
||||||
@ -360,10 +360,10 @@ class Sphinx:
|
|||||||
envfile = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
|
envfile = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
|
||||||
if path.isfile(envfile):
|
if path.isfile(envfile):
|
||||||
os.unlink(envfile)
|
os.unlink(envfile)
|
||||||
self.emit('build-finished', err)
|
self.events.emit('build-finished', err)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
self.emit('build-finished', None)
|
self.events.emit('build-finished', None)
|
||||||
self.builder.cleanup()
|
self.builder.cleanup()
|
||||||
|
|
||||||
# ---- general extensibility interface -------------------------------------
|
# ---- general extensibility interface -------------------------------------
|
||||||
@ -420,13 +420,7 @@ class Sphinx:
|
|||||||
Return the return values of all callbacks as a list. Do not emit core
|
Return the return values of all callbacks as a list. Do not emit core
|
||||||
Sphinx events in extensions!
|
Sphinx events in extensions!
|
||||||
"""
|
"""
|
||||||
try:
|
return self.events.emit(event, *args)
|
||||||
logger.debug('[app] emitting event: %r%s', event, repr(args)[:100])
|
|
||||||
except Exception:
|
|
||||||
# not every object likes to be repr()'d (think
|
|
||||||
# random stuff coming via autodoc)
|
|
||||||
pass
|
|
||||||
return self.events.emit(event, self, *args)
|
|
||||||
|
|
||||||
def emit_firstresult(self, event, *args):
|
def emit_firstresult(self, event, *args):
|
||||||
# type: (str, Any) -> Any
|
# type: (str, Any) -> Any
|
||||||
@ -436,7 +430,7 @@ class Sphinx:
|
|||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
"""
|
"""
|
||||||
return self.events.emit_firstresult(event, self, *args)
|
return self.events.emit_firstresult(event, *args)
|
||||||
|
|
||||||
# registering addon parts
|
# registering addon parts
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ if False:
|
|||||||
from sphinx.application import Sphinx # NOQA
|
from sphinx.application import Sphinx # NOQA
|
||||||
from sphinx.config import Config # NOQA
|
from sphinx.config import Config # NOQA
|
||||||
from sphinx.environment import BuildEnvironment # NOQA
|
from sphinx.environment import BuildEnvironment # NOQA
|
||||||
|
from sphinx.events import EventManager # NOQA
|
||||||
from sphinx.util.i18n import CatalogInfo # NOQA
|
from sphinx.util.i18n import CatalogInfo # NOQA
|
||||||
from sphinx.util.tags import Tags # NOQA
|
from sphinx.util.tags import Tags # NOQA
|
||||||
|
|
||||||
@ -93,6 +94,7 @@ class Builder:
|
|||||||
|
|
||||||
self.app = app # type: Sphinx
|
self.app = app # type: Sphinx
|
||||||
self.env = None # type: BuildEnvironment
|
self.env = None # type: BuildEnvironment
|
||||||
|
self.events = app.events # type: EventManager
|
||||||
self.config = app.config # type: Config
|
self.config = app.config # type: Config
|
||||||
self.tags = app.tags # type: Tags
|
self.tags = app.tags # type: Tags
|
||||||
self.tags.add(self.format)
|
self.tags.add(self.format)
|
||||||
@ -399,7 +401,7 @@ class Builder:
|
|||||||
added, changed, removed = self.env.get_outdated_files(updated)
|
added, changed, removed = self.env.get_outdated_files(updated)
|
||||||
|
|
||||||
# allow user intervention as well
|
# allow user intervention as well
|
||||||
for docs in self.app.emit('env-get-outdated', self, added, changed, removed):
|
for docs in self.events.emit('env-get-outdated', self, added, changed, removed):
|
||||||
changed.update(set(docs) & self.env.found_docs)
|
changed.update(set(docs) & self.env.found_docs)
|
||||||
|
|
||||||
# if files were added or removed, all documents with globbed toctrees
|
# if files were added or removed, all documents with globbed toctrees
|
||||||
@ -416,13 +418,13 @@ class Builder:
|
|||||||
|
|
||||||
# clear all files no longer present
|
# clear all files no longer present
|
||||||
for docname in removed:
|
for docname in removed:
|
||||||
self.app.emit('env-purge-doc', self.env, docname)
|
self.events.emit('env-purge-doc', self.env, docname)
|
||||||
self.env.clear_doc(docname)
|
self.env.clear_doc(docname)
|
||||||
|
|
||||||
# read all new and changed files
|
# read all new and changed files
|
||||||
docnames = sorted(added | changed)
|
docnames = sorted(added | changed)
|
||||||
# allow changing and reordering the list of docs to read
|
# allow changing and reordering the list of docs to read
|
||||||
self.app.emit('env-before-read-docs', self.env, docnames)
|
self.events.emit('env-before-read-docs', self.env, docnames)
|
||||||
|
|
||||||
# check if we should do parallel or serial read
|
# check if we should do parallel or serial read
|
||||||
if parallel_available and len(docnames) > 5 and self.app.parallel > 1:
|
if parallel_available and len(docnames) > 5 and self.app.parallel > 1:
|
||||||
@ -439,7 +441,7 @@ class Builder:
|
|||||||
raise SphinxError('master file %s not found' %
|
raise SphinxError('master file %s not found' %
|
||||||
self.env.doc2path(self.config.master_doc))
|
self.env.doc2path(self.config.master_doc))
|
||||||
|
|
||||||
for retval in self.app.emit('env-updated', self.env):
|
for retval in self.events.emit('env-updated', self.env):
|
||||||
if retval is not None:
|
if retval is not None:
|
||||||
docnames.extend(retval)
|
docnames.extend(retval)
|
||||||
|
|
||||||
@ -453,7 +455,7 @@ class Builder:
|
|||||||
for docname in status_iterator(docnames, __('reading sources... '), "purple",
|
for docname in status_iterator(docnames, __('reading sources... '), "purple",
|
||||||
len(docnames), self.app.verbosity):
|
len(docnames), self.app.verbosity):
|
||||||
# remove all inventory entries for that file
|
# remove all inventory entries for that file
|
||||||
self.app.emit('env-purge-doc', self.env, docname)
|
self.events.emit('env-purge-doc', self.env, docname)
|
||||||
self.env.clear_doc(docname)
|
self.env.clear_doc(docname)
|
||||||
self.read_doc(docname)
|
self.read_doc(docname)
|
||||||
|
|
||||||
@ -461,7 +463,7 @@ class Builder:
|
|||||||
# type: (List[str], int) -> None
|
# type: (List[str], int) -> None
|
||||||
# clear all outdated docs at once
|
# clear all outdated docs at once
|
||||||
for docname in docnames:
|
for docname in docnames:
|
||||||
self.app.emit('env-purge-doc', self.env, docname)
|
self.events.emit('env-purge-doc', self.env, docname)
|
||||||
self.env.clear_doc(docname)
|
self.env.clear_doc(docname)
|
||||||
|
|
||||||
def read_process(docs):
|
def read_process(docs):
|
||||||
|
@ -653,7 +653,7 @@ class StandaloneHTMLBuilder(Builder):
|
|||||||
def gen_additional_pages(self):
|
def gen_additional_pages(self):
|
||||||
# type: () -> None
|
# type: () -> None
|
||||||
# pages from extensions
|
# pages from extensions
|
||||||
for pagelist in self.app.emit('html-collect-pages'):
|
for pagelist in self.events.emit('html-collect-pages'):
|
||||||
for pagename, context, template in pagelist:
|
for pagename, context, template in pagelist:
|
||||||
self.handle_page(pagename, context, template)
|
self.handle_page(pagename, context, template)
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ if False:
|
|||||||
from sphinx.application import Sphinx # NOQA
|
from sphinx.application import Sphinx # NOQA
|
||||||
from sphinx.builders import Builder # NOQA
|
from sphinx.builders import Builder # NOQA
|
||||||
from sphinx.config import Config # NOQA
|
from sphinx.config import Config # NOQA
|
||||||
|
from sphinx.event import EventManager # NOQA
|
||||||
from sphinx.domains import Domain # NOQA
|
from sphinx.domains import Domain # NOQA
|
||||||
from sphinx.project import Project # NOQA
|
from sphinx.project import Project # NOQA
|
||||||
|
|
||||||
@ -95,6 +96,7 @@ class BuildEnvironment:
|
|||||||
self.srcdir = None # type: str
|
self.srcdir = None # type: str
|
||||||
self.config = None # type: Config
|
self.config = None # type: Config
|
||||||
self.config_status = None # type: int
|
self.config_status = None # type: int
|
||||||
|
self.events = None # type: EventManager
|
||||||
self.project = None # type: Project
|
self.project = None # type: Project
|
||||||
self.version = None # type: Dict[str, str]
|
self.version = None # type: Dict[str, str]
|
||||||
|
|
||||||
@ -190,7 +192,7 @@ class BuildEnvironment:
|
|||||||
# type: () -> Dict
|
# type: () -> Dict
|
||||||
"""Obtains serializable data for pickling."""
|
"""Obtains serializable data for pickling."""
|
||||||
__dict__ = self.__dict__.copy()
|
__dict__ = self.__dict__.copy()
|
||||||
__dict__.update(app=None, domains={}) # clear unpickable attributes
|
__dict__.update(app=None, domains={}, events=None) # clear unpickable attributes
|
||||||
return __dict__
|
return __dict__
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
@ -210,6 +212,7 @@ class BuildEnvironment:
|
|||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
self.doctreedir = app.doctreedir
|
self.doctreedir = app.doctreedir
|
||||||
|
self.events = app.events
|
||||||
self.srcdir = app.srcdir
|
self.srcdir = app.srcdir
|
||||||
self.project = app.project
|
self.project = app.project
|
||||||
self.version = app.registry.get_envversion(app)
|
self.version = app.registry.get_envversion(app)
|
||||||
@ -307,7 +310,7 @@ class BuildEnvironment:
|
|||||||
|
|
||||||
for domainname, domain in self.domains.items():
|
for domainname, domain in self.domains.items():
|
||||||
domain.merge_domaindata(docnames, other.domaindata[domainname])
|
domain.merge_domaindata(docnames, other.domaindata[domainname])
|
||||||
app.emit('env-merge-info', self, docnames, other)
|
self.events.emit('env-merge-info', self, docnames, other)
|
||||||
|
|
||||||
def path2doc(self, filename):
|
def path2doc(self, filename):
|
||||||
# type: (str) -> Optional[str]
|
# type: (str) -> Optional[str]
|
||||||
@ -449,7 +452,7 @@ class BuildEnvironment:
|
|||||||
def check_dependents(self, app, already):
|
def check_dependents(self, app, already):
|
||||||
# type: (Sphinx, Set[str]) -> Iterator[str]
|
# type: (Sphinx, Set[str]) -> Iterator[str]
|
||||||
to_rewrite = [] # type: List[str]
|
to_rewrite = [] # type: List[str]
|
||||||
for docnames in app.emit('env-get-updated', self):
|
for docnames in self.events.emit('env-get-updated', self):
|
||||||
to_rewrite.extend(docnames)
|
to_rewrite.extend(docnames)
|
||||||
for docname in set(to_rewrite):
|
for docname in set(to_rewrite):
|
||||||
if docname not in already:
|
if docname not in already:
|
||||||
@ -597,7 +600,7 @@ class BuildEnvironment:
|
|||||||
self.temp_data = backup
|
self.temp_data = backup
|
||||||
|
|
||||||
# allow custom references to be resolved
|
# allow custom references to be resolved
|
||||||
self.app.emit('doctree-resolved', doctree, docname)
|
self.events.emit('doctree-resolved', doctree, docname)
|
||||||
|
|
||||||
def collect_relations(self):
|
def collect_relations(self):
|
||||||
# type: () -> Dict[str, List[str]]
|
# type: () -> Dict[str, List[str]]
|
||||||
@ -653,4 +656,4 @@ class BuildEnvironment:
|
|||||||
# call check-consistency for all extensions
|
# call check-consistency for all extensions
|
||||||
for domain in self.domains.values():
|
for domain in self.domains.values():
|
||||||
domain.check_consistency()
|
domain.check_consistency()
|
||||||
self.app.emit('env-check-consistency', self)
|
self.events.emit('env-check-consistency', self)
|
||||||
|
@ -10,14 +10,20 @@
|
|||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import warnings
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
|
|
||||||
|
from sphinx.deprecation import RemovedInSphinx40Warning
|
||||||
from sphinx.errors import ExtensionError
|
from sphinx.errors import ExtensionError
|
||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
|
from sphinx.util import logging
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
# For type annotation
|
# For type annotation
|
||||||
from typing import Any, Callable, Dict, List # NOQA
|
from typing import Any, Callable, Dict, List # NOQA
|
||||||
|
from sphinx.application import Sphinx # NOQA
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
# List of all known core events. Maps name to arguments description.
|
# List of all known core events. Maps name to arguments description.
|
||||||
@ -42,20 +48,28 @@ core_events = {
|
|||||||
|
|
||||||
|
|
||||||
class EventManager:
|
class EventManager:
|
||||||
def __init__(self):
|
"""Event manager for Sphinx."""
|
||||||
# type: () -> None
|
|
||||||
|
def __init__(self, app=None):
|
||||||
|
# type: (Sphinx) -> None
|
||||||
|
if app is None:
|
||||||
|
warnings.warn('app argument is required for EventManager.',
|
||||||
|
RemovedInSphinx40Warning)
|
||||||
|
self.app = app
|
||||||
self.events = core_events.copy()
|
self.events = core_events.copy()
|
||||||
self.listeners = defaultdict(OrderedDict) # type: Dict[str, Dict[int, Callable]]
|
self.listeners = defaultdict(OrderedDict) # type: Dict[str, Dict[int, Callable]]
|
||||||
self.next_listener_id = 0
|
self.next_listener_id = 0
|
||||||
|
|
||||||
def add(self, name):
|
def add(self, name):
|
||||||
# type: (str) -> None
|
# type: (str) -> None
|
||||||
|
"""Register a custom Sphinx event."""
|
||||||
if name in self.events:
|
if name in self.events:
|
||||||
raise ExtensionError(__('Event %r already present') % name)
|
raise ExtensionError(__('Event %r already present') % name)
|
||||||
self.events[name] = ''
|
self.events[name] = ''
|
||||||
|
|
||||||
def connect(self, name, callback):
|
def connect(self, name, callback):
|
||||||
# type: (str, Callable) -> int
|
# type: (str, Callable) -> int
|
||||||
|
"""Connect a handler to specific event."""
|
||||||
if name not in self.events:
|
if name not in self.events:
|
||||||
raise ExtensionError(__('Unknown event name: %s') % name)
|
raise ExtensionError(__('Unknown event name: %s') % name)
|
||||||
|
|
||||||
@ -66,18 +80,35 @@ class EventManager:
|
|||||||
|
|
||||||
def disconnect(self, listener_id):
|
def disconnect(self, listener_id):
|
||||||
# type: (int) -> None
|
# type: (int) -> None
|
||||||
|
"""Disconnect a handler."""
|
||||||
for event in self.listeners.values():
|
for event in self.listeners.values():
|
||||||
event.pop(listener_id, None)
|
event.pop(listener_id, None)
|
||||||
|
|
||||||
def emit(self, name, *args):
|
def emit(self, name, *args):
|
||||||
# type: (str, Any) -> List
|
# type: (str, Any) -> List
|
||||||
|
"""Emit a Sphinx event."""
|
||||||
|
try:
|
||||||
|
logger.debug('[app] emitting event: %r%s', name, repr(args)[:100])
|
||||||
|
except Exception:
|
||||||
|
# not every object likes to be repr()'d (think
|
||||||
|
# random stuff coming via autodoc)
|
||||||
|
pass
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for callback in self.listeners[name].values():
|
for callback in self.listeners[name].values():
|
||||||
results.append(callback(*args))
|
if self.app is None:
|
||||||
|
# for compatibility; RemovedInSphinx40Warning
|
||||||
|
results.append(callback(*args))
|
||||||
|
else:
|
||||||
|
results.append(callback(self.app, *args))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def emit_firstresult(self, name, *args):
|
def emit_firstresult(self, name, *args):
|
||||||
# type: (str, Any) -> Any
|
# type: (str, Any) -> Any
|
||||||
|
"""Emit a Sphinx event and returns first result.
|
||||||
|
|
||||||
|
This returns the result of the first handler that doesn't return ``None``.
|
||||||
|
"""
|
||||||
for result in self.emit(name, *args):
|
for result in self.emit(name, *args):
|
||||||
if result is not None:
|
if result is not None:
|
||||||
return result
|
return result
|
||||||
|
@ -403,9 +403,9 @@ class Documenter:
|
|||||||
|
|
||||||
retann = self.retann
|
retann = self.retann
|
||||||
|
|
||||||
result = self.env.app.emit_firstresult(
|
result = self.env.events.emit_firstresult('autodoc-process-signature',
|
||||||
'autodoc-process-signature', self.objtype, self.fullname,
|
self.objtype, self.fullname,
|
||||||
self.object, self.options, args, retann)
|
self.object, self.options, args, retann)
|
||||||
if result:
|
if result:
|
||||||
args, retann = result
|
args, retann = result
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ def process_todos(app, doctree):
|
|||||||
if not hasattr(env, 'todo_all_todos'):
|
if not hasattr(env, 'todo_all_todos'):
|
||||||
env.todo_all_todos = [] # type: ignore
|
env.todo_all_todos = [] # type: ignore
|
||||||
for node in doctree.traverse(todo_node):
|
for node in doctree.traverse(todo_node):
|
||||||
app.emit('todo-defined', node)
|
app.events.emit('todo-defined', node)
|
||||||
|
|
||||||
newnode = node.deepcopy()
|
newnode = node.deepcopy()
|
||||||
newnode['ids'] = []
|
newnode['ids'] = []
|
||||||
|
Loading…
Reference in New Issue
Block a user