summaryrefslogtreecommitdiff
path: root/sphinx/application.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/application.py')
-rw-r--r--sphinx/application.py877
1 files changed, 712 insertions, 165 deletions
diff --git a/sphinx/application.py b/sphinx/application.py
index 7af1c8d39..9d3d5de9f 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -3,7 +3,7 @@
sphinx.application
~~~~~~~~~~~~~~~~~~
- Sphinx application object.
+ Sphinx application class and extensibility interface.
Gracefully adapted from the TextPress system by Armin.
@@ -17,35 +17,41 @@ import posixpath
import sys
import warnings
from collections import deque
+from inspect import isclass
from os import path
-from docutils import nodes
-from docutils.parsers.rst import directives, roles
-from six import iteritems, itervalues
+from docutils.parsers.rst import Directive, directives, roles
+from six import itervalues
+from six.moves import cPickle as pickle
from six.moves import cStringIO
import sphinx
from sphinx import package_dir, locale
-from sphinx.config import Config
-from sphinx.deprecation import RemovedInSphinx20Warning
+from sphinx.config import Config, check_unicode
+from sphinx.config import CONFIG_FILENAME # NOQA # for compatibility (RemovedInSphinx30)
+from sphinx.deprecation import (
+ RemovedInSphinx20Warning, RemovedInSphinx30Warning, RemovedInSphinx40Warning
+)
from sphinx.environment import BuildEnvironment
-from sphinx.errors import ConfigError, ExtensionError, VersionRequirementError
+from sphinx.errors import ApplicationError, ConfigError, VersionRequirementError
from sphinx.events import EventManager
-from sphinx.extension import verify_required_extensions
from sphinx.locale import __
from sphinx.registry import SphinxComponentRegistry
+from sphinx.util import docutils
from sphinx.util import import_object
from sphinx.util import logging
from sphinx.util import pycompat # noqa: F401
+from sphinx.util.build_phase import BuildPhase
from sphinx.util.console import bold # type: ignore
-from sphinx.util.docutils import is_html5_writer_available, directive_helper
+from sphinx.util.docutils import directive_helper
from sphinx.util.i18n import find_catalog_source_files
-from sphinx.util.osutil import ENOENT, ensuredir, relpath
+from sphinx.util.osutil import abspath, ensuredir, relpath
from sphinx.util.tags import Tags
if False:
# For type annotation
from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Tuple, Type, Union # NOQA
+ from docutils import nodes # NOQA
from docutils.parsers import Parser # NOQA
from docutils.transforms import Transform # NOQA
from sphinx.builders import Builder # NOQA
@@ -54,7 +60,7 @@ if False:
from sphinx.extension import Extension # NOQA
from sphinx.roles import XRefRole # NOQA
from sphinx.theming import Theme # NOQA
- from sphinx.util.typing import RoleFunction # NOQA
+ from sphinx.util.typing import RoleFunction, TitleGetter # NOQA
builtin_extensions = (
'sphinx.builders.applehelp',
@@ -73,6 +79,7 @@ builtin_extensions = (
'sphinx.builders.text',
'sphinx.builders.websupport',
'sphinx.builders.xml',
+ 'sphinx.config',
'sphinx.domains.c',
'sphinx.domains.cpp',
'sphinx.domains.javascript',
@@ -83,8 +90,10 @@ builtin_extensions = (
'sphinx.directives.code',
'sphinx.directives.other',
'sphinx.directives.patches',
+ 'sphinx.extension',
'sphinx.io',
'sphinx.parsers',
+ 'sphinx.registry',
'sphinx.roles',
'sphinx.transforms.post_transforms',
'sphinx.transforms.post_transforms.images',
@@ -101,32 +110,52 @@ builtin_extensions = (
'alabaster',
) # type: Tuple[unicode, ...]
-CONFIG_FILENAME = 'conf.py'
ENV_PICKLE_FILENAME = 'environment.pickle'
logger = logging.getLogger(__name__)
class Sphinx(object):
+ """The main application class and extensibility interface.
+
+ :ivar srcdir: Directory containing source.
+ :ivar confdir: Directory containing ``conf.py``.
+ :ivar doctreedir: Directory for storing pickled doctrees.
+ :ivar outdir: Directory for storing build documents.
+ """
def __init__(self, srcdir, confdir, outdir, doctreedir, buildername,
confoverrides=None, status=sys.stdout, warning=sys.stderr,
freshenv=False, warningiserror=False, tags=None, verbosity=0,
parallel=0):
# type: (unicode, unicode, unicode, unicode, unicode, Dict, IO, IO, bool, bool, List[unicode], int, int) -> None # NOQA
+ self.phase = BuildPhase.INITIALIZATION
self.verbosity = verbosity
self.extensions = {} # type: Dict[unicode, Extension]
self._setting_up_extension = ['?'] # type: List[unicode]
self.builder = None # type: Builder
self.env = None # type: BuildEnvironment
self.registry = SphinxComponentRegistry()
- self.enumerable_nodes = {} # type: Dict[nodes.Node, Tuple[unicode, Callable]] # NOQA
self.html_themes = {} # type: Dict[unicode, unicode]
- self.srcdir = srcdir # type: unicode
+ # validate provided directories
+ self.srcdir = abspath(srcdir) # type: unicode
+ self.outdir = abspath(outdir) # type: unicode
+ self.doctreedir = abspath(doctreedir) # type: unicode
self.confdir = confdir
- self.outdir = outdir
- self.doctreedir = doctreedir
+ if self.confdir: # confdir is optional
+ self.confdir = abspath(self.confdir)
+ if not path.isfile(path.join(self.confdir, 'conf.py')):
+ raise ApplicationError(__("config directory doesn't contain a "
+ "conf.py file (%s)") % confdir)
+
+ if not path.isdir(self.srcdir):
+ raise ApplicationError(__('Cannot find source directory (%s)') %
+ self.srcdir)
+
+ if self.srcdir == self.outdir:
+ raise ApplicationError(__('Source directory and destination '
+ 'directory cannot be identical'))
self.parallel = parallel
@@ -152,17 +181,18 @@ class Sphinx(object):
self.messagelog = deque(maxlen=10) # type: deque
# say hello to the world
- logger.info(bold('Running Sphinx v%s' % sphinx.__display_version__))
+ logger.info(bold(__('Running Sphinx v%s') % sphinx.__display_version__))
# status code for command-line application
self.statuscode = 0
# read config
self.tags = Tags(tags)
- self.config = Config(confdir, CONFIG_FILENAME,
- confoverrides or {}, self.tags)
- self.config.check_unicode()
- # defer checking types until i18n has been initialized
+ if self.confdir is None:
+ self.config = Config({}, confoverrides or {})
+ else:
+ self.config = Config.read(self.confdir, confoverrides or {}, self.tags)
+ check_unicode(self.config)
# initialize some limited config variables before initialize i18n and loading
# extensions
@@ -194,14 +224,13 @@ class Sphinx(object):
self.preload_builder(buildername)
if not path.isdir(outdir):
- logger.info('making output directory...')
+ logger.info(__('making output directory...'))
ensuredir(outdir)
# the config file itself can be an extension
if self.config.setup:
self._setting_up_extension = ['conf.py']
- # py31 doesn't have 'callable' function for below check
- if hasattr(self.config.setup, '__call__'):
+ if callable(self.config.setup):
self.config.setup(self)
else:
raise ConfigError(
@@ -212,9 +241,7 @@ class Sphinx(object):
# now that we know all config values, collect them from conf.py
self.config.init_values()
-
- # check extension versions if requested
- verify_required_extensions(self, self.config.needs_extensions)
+ self.emit('config-inited', self.config)
# check primary_domain if requested
primary_domain = self.config.primary_domain
@@ -223,16 +250,10 @@ class Sphinx(object):
# create the builder
self.builder = self.create_builder(buildername)
- # check all configuration values for permissible types
- self.config.check_types()
- # set up source_parsers
- self._init_source_parsers()
# set up the build environment
self._init_env(freshenv)
# set up the builder
self._init_builder()
- # set up the enumerable nodes
- self._init_enumerable_nodes()
def _init_i18n(self):
# type: () -> None
@@ -240,7 +261,7 @@ class Sphinx(object):
the configuration.
"""
if self.config.language is not None:
- logger.info(bold('loading translations [%s]... ' % self.config.language),
+ logger.info(bold(__('loading translations [%s]... ') % self.config.language),
nonl=True)
user_locale_dirs = [
path.join(self.srcdir, x) for x in self.config.locale_dirs]
@@ -252,25 +273,18 @@ class Sphinx(object):
locale_dirs = [None, path.join(package_dir, 'locale')] + user_locale_dirs # type: ignore # NOQA
else:
locale_dirs = []
- self.translator, has_translation = locale.init(locale_dirs, self.config.language)
+ self.translator, has_translation = locale.init(locale_dirs, self.config.language) # type: ignore # NOQA
if self.config.language is not None:
if has_translation or self.config.language == 'en':
# "en" never needs to be translated
logger.info(__('done'))
else:
- logger.info('not available for built-in messages')
-
- def _init_source_parsers(self):
- # type: () -> None
- for suffix, parser in iteritems(self.config.source_parsers):
- self.add_source_parser(suffix, parser)
- for suffix, parser in iteritems(self.registry.get_source_parsers()):
- if suffix not in self.config.source_suffix and suffix != '*':
- self.config.source_suffix.append(suffix)
+ logger.info(__('not available for built-in messages'))
def _init_env(self, freshenv):
# type: (bool) -> None
- if freshenv:
+ filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
+ if freshenv or not os.path.exists(filename):
self.env = BuildEnvironment(self)
self.env.find_files(self.config, self.builder)
for domain in self.registry.create_domains(self.env):
@@ -278,18 +292,20 @@ class Sphinx(object):
else:
try:
logger.info(bold(__('loading pickled environment... ')), nonl=True)
- filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
- self.env = BuildEnvironment.frompickle(filename, self)
+ with open(filename, 'rb') as f:
+ self.env = pickle.load(f)
+ self.env.app = self
+ self.env.config.values = self.config.values
+ needed, reason = self.env.need_refresh(self)
+ if needed:
+ raise IOError(reason)
self.env.domains = {}
for domain in self.registry.create_domains(self.env):
# this can raise if the data version doesn't fit
self.env.domains[domain.name] = domain
logger.info(__('done'))
except Exception as err:
- if isinstance(err, IOError) and err.errno == ENOENT:
- logger.info(__('not yet created'))
- else:
- logger.info(__('failed: %s'), err)
+ logger.info(__('failed: %s'), err)
self._init_env(freshenv=True)
def preload_builder(self, name):
@@ -310,15 +326,11 @@ class Sphinx(object):
self.builder.init()
self.emit('builder-inited')
- def _init_enumerable_nodes(self):
- # type: () -> None
- for node, settings in iteritems(self.enumerable_nodes):
- self.env.get_domain('std').enumerable_nodes[node] = settings # type: ignore
-
# ---- main "build" method -------------------------------------------------
def build(self, force_all=False, filenames=None):
# type: (bool, List[unicode]) -> None
+ self.phase = BuildPhase.READING
try:
if force_all:
self.builder.compile_all_catalogs()
@@ -334,7 +346,7 @@ class Sphinx(object):
__('succeeded') or __('finished with problems'))
if self._warncount:
logger.info(bold(__('build %s, %s warning.',
- 'build %s, %s warnings.', self._warncount) %
+ 'build %s, %s warnings.', self._warncount) %
(status, self._warncount)))
else:
logger.info(bold(__('build %s.') % status))
@@ -361,10 +373,15 @@ class Sphinx(object):
# type: (unicode, unicode, unicode, unicode) -> None
"""Emit a warning.
- If *location* is given, it should either be a tuple of (docname, lineno)
- or a string describing the location of the warning as well as possible.
+ If *location* is given, it should either be a tuple of (*docname*,
+ *lineno*) or a string describing the location of the warning as well as
+ possible.
- *type* and *subtype* are used to suppress warnings with :confval:`suppress_warnings`.
+ *type* and *subtype* are used to suppress warnings with
+ :confval:`suppress_warnings`.
+
+ .. deprecated:: 1.6
+ Use :mod:`sphinx.util.logging` instead.
"""
warnings.warn('app.warning() is now deprecated. Use sphinx.util.logging instead.',
RemovedInSphinx20Warning)
@@ -376,6 +393,9 @@ class Sphinx(object):
If *nonl* is true, don't emit a newline at the end (which implies that
more info output will follow soon.)
+
+ .. deprecated:: 1.6
+ Use :mod:`sphinx.util.logging` instead.
"""
warnings.warn('app.info() is now deprecated. Use sphinx.util.logging instead.',
RemovedInSphinx20Warning)
@@ -383,21 +403,33 @@ class Sphinx(object):
def verbose(self, message, *args, **kwargs):
# type: (unicode, Any, Any) -> None
- """Emit a verbose informational message."""
+ """Emit a verbose informational message.
+
+ .. deprecated:: 1.6
+ Use :mod:`sphinx.util.logging` instead.
+ """
warnings.warn('app.verbose() is now deprecated. Use sphinx.util.logging instead.',
RemovedInSphinx20Warning)
logger.verbose(message, *args, **kwargs)
def debug(self, message, *args, **kwargs):
# type: (unicode, Any, Any) -> None
- """Emit a debug-level informational message."""
+ """Emit a debug-level informational message.
+
+ .. deprecated:: 1.6
+ Use :mod:`sphinx.util.logging` instead.
+ """
warnings.warn('app.debug() is now deprecated. Use sphinx.util.logging instead.',
RemovedInSphinx20Warning)
logger.debug(message, *args, **kwargs)
def debug2(self, message, *args, **kwargs):
# type: (unicode, Any, Any) -> None
- """Emit a lowlevel debug-level informational message."""
+ """Emit a lowlevel debug-level informational message.
+
+ .. deprecated:: 1.6
+ Use :mod:`sphinx.util.logging` instead.
+ """
warnings.warn('app.debug2() is now deprecated. Use debug() instead.',
RemovedInSphinx20Warning)
logger.debug(message, *args, **kwargs)
@@ -406,35 +438,68 @@ class Sphinx(object):
def setup_extension(self, extname):
# type: (unicode) -> None
- """Import and setup a Sphinx extension module. No-op if called twice."""
+ """Import and setup a Sphinx extension module.
+
+ Load the extension given by the module *name*. Use this if your
+ extension needs the features provided by another extension. No-op if
+ called twice.
+ """
logger.debug('[app] setting up extension: %r', extname)
self.registry.load_extension(self, extname)
def require_sphinx(self, version):
# type: (unicode) -> None
- # check the Sphinx version if requested
+ """Check the Sphinx version if requested.
+
+ Compare *version* (which must be a ``major.minor`` version string, e.g.
+ ``'1.1'``) with the version of the running Sphinx, and abort the build
+ when it is too old.
+
+ .. versionadded:: 1.0
+ """
if version > sphinx.__display_version__[:3]:
raise VersionRequirementError(version)
def import_object(self, objname, source=None):
# type: (str, unicode) -> Any
- """Import an object from a 'module.name' string."""
+ """Import an object from a ``module.name`` string.
+
+ .. deprecated:: 1.8
+ Use ``sphinx.util.import_object()`` instead.
+ """
+ warnings.warn('app.import_object() is deprecated. '
+ 'Use sphinx.util.add_object_type() instead.',
+ RemovedInSphinx30Warning)
return import_object(objname, source=None)
# event interface
def connect(self, event, callback):
# type: (unicode, Callable) -> int
+ """Register *callback* to be called when *event* is emitted.
+
+ For details on available core events and the arguments of callback
+ functions, please see :ref:`events`.
+
+ The method returns a "listener ID" that can be used as an argument to
+ :meth:`disconnect`.
+ """
listener_id = self.events.connect(event, callback)
logger.debug('[app] connecting event %r: %r [id=%s]', event, callback, listener_id)
return listener_id
def disconnect(self, listener_id):
# type: (int) -> None
+ """Unregister callback by *listener_id*."""
logger.debug('[app] disconnecting event: [id=%s]', listener_id)
self.events.disconnect(listener_id)
def emit(self, event, *args):
# type: (unicode, Any) -> List
+ """Emit *event* and pass *arguments* to the callback functions.
+
+ Return the return values of all callbacks as a list. Do not emit core
+ Sphinx events in extensions!
+ """
try:
logger.debug('[app] emitting event: %r%s', event, repr(args)[:100])
except Exception:
@@ -445,110 +510,262 @@ class Sphinx(object):
def emit_firstresult(self, event, *args):
# type: (unicode, Any) -> Any
+ """Emit *event* and pass *arguments* to the callback functions.
+
+ Return the result of the first callback that doesn't return ``None``.
+
+ .. versionadded:: 0.5
+ """
return self.events.emit_firstresult(event, self, *args)
# registering addon parts
- def add_builder(self, builder):
- # type: (Type[Builder]) -> None
- self.registry.add_builder(builder)
+ def add_builder(self, builder, override=False):
+ # type: (Type[Builder], bool) -> None
+ """Register a new builder.
+
+ *builder* must be a class that inherits from
+ :class:`~sphinx.builders.Builder`.
+
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
+ self.registry.add_builder(builder, override=override)
+ # TODO(stephenfin): Describe 'types' parameter
def add_config_value(self, name, default, rebuild, types=()):
# type: (unicode, Any, Union[bool, unicode], Any) -> None
+ """Register a configuration value.
+
+ This is necessary for Sphinx to recognize new values and set default
+ values accordingly. The *name* should be prefixed with the extension
+ name, to avoid clashes. The *default* value can be any Python object.
+ The string value *rebuild* must be one of those values:
+
+ * ``'env'`` if a change in the setting only takes effect when a
+ document is parsed -- this means that the whole environment must be
+ rebuilt.
+ * ``'html'`` if a change in the setting needs a full rebuild of HTML
+ documents.
+ * ``''`` if a change in the setting will not need any special rebuild.
+
+ .. versionchanged:: 0.6
+ Changed *rebuild* from a simple boolean (equivalent to ``''`` or
+ ``'env'``) to a string. However, booleans are still accepted and
+ converted internally.
+
+ .. versionchanged:: 0.4
+ If the *default* value is a callable, it will be called with the
+ config object as its argument in order to get the default value.
+ This can be used to implement config values whose default depends on
+ other values.
+ """
logger.debug('[app] adding config value: %r',
(name, default, rebuild) + ((types,) if types else ())) # type: ignore
- if name in self.config:
- raise ExtensionError(__('Config value %r already present') % name)
if rebuild in (False, True):
rebuild = rebuild and 'env' or ''
self.config.add(name, default, rebuild, types)
def add_event(self, name):
# type: (unicode) -> None
+ """Register an event called *name*.
+
+ This is needed to be able to emit it.
+ """
logger.debug('[app] adding event: %r', name)
self.events.add(name)
- def set_translator(self, name, translator_class):
- # type: (unicode, Type[nodes.NodeVisitor]) -> None
- self.registry.add_translator(name, translator_class)
+ def set_translator(self, name, translator_class, override=False):
+ # type: (unicode, Type[nodes.NodeVisitor], bool) -> None
+ """Register or override a Docutils translator class.
+
+ This is used to register a custom output translator or to replace a
+ builtin translator. This allows extensions to use custom translator
+ and define custom nodes for the translator (see :meth:`add_node`).
- def add_node(self, node, **kwds):
- # type: (nodes.Node, Any) -> None
+ .. versionadded:: 1.3
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
+ self.registry.add_translator(name, translator_class, override=override)
+
+ def add_node(self, node, override=False, **kwds):
+ # type: (nodes.Node, bool, Any) -> None
+ """Register a Docutils node class.
+
+ This is necessary for Docutils internals. It may also be used in the
+ future to validate nodes in the parsed documents.
+
+ Node visitor functions for the Sphinx HTML, LaTeX, text and manpage
+ writers can be given as keyword arguments: the keyword should be one or
+ more of ``'html'``, ``'latex'``, ``'text'``, ``'man'``, ``'texinfo'``
+ or any other supported translators, the value a 2-tuple of ``(visit,
+ depart)`` methods. ``depart`` can be ``None`` if the ``visit``
+ function raises :exc:`docutils.nodes.SkipNode`. Example:
+
+ .. code-block:: python
+
+ class math(docutils.nodes.Element): pass
+
+ def visit_math_html(self, node):
+ self.body.append(self.starttag(node, 'math'))
+ def depart_math_html(self, node):
+ self.body.append('</math>')
+
+ app.add_node(math, html=(visit_math_html, depart_math_html))
+
+ Obviously, translators for which you don't specify visitor methods will
+ choke on the node when encountered in a document to translate.
+
+ .. versionchanged:: 0.5
+ Added the support for keyword arguments giving visit functions.
+ """
logger.debug('[app] adding node: %r', (node, kwds))
- if not kwds.pop('override', False) and \
- hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__):
+ if not override and docutils.is_node_registered(node):
logger.warning(__('while setting up extension %s: node class %r is '
'already registered, its visitors will be overridden'),
self._setting_up_extension, node.__name__,
type='app', subtype='add_node')
- nodes._add_node_class_names([node.__name__])
- for key, val in iteritems(kwds):
- try:
- visit, depart = val
- except ValueError:
- raise ExtensionError(__('Value for key %r must be a '
- '(visit, depart) function tuple') % key)
- translator = self.registry.translators.get(key)
- translators = []
- if translator is not None:
- translators.append(translator)
- elif key == 'html':
- from sphinx.writers.html import HTMLTranslator
- translators.append(HTMLTranslator)
- if is_html5_writer_available():
- from sphinx.writers.html5 import HTML5Translator
- translators.append(HTML5Translator)
- elif key == 'latex':
- from sphinx.writers.latex import LaTeXTranslator
- translators.append(LaTeXTranslator)
- elif key == 'text':
- from sphinx.writers.text import TextTranslator
- translators.append(TextTranslator)
- elif key == 'man':
- from sphinx.writers.manpage import ManualPageTranslator
- translators.append(ManualPageTranslator)
- elif key == 'texinfo':
- from sphinx.writers.texinfo import TexinfoTranslator
- translators.append(TexinfoTranslator)
-
- for translator in translators:
- setattr(translator, 'visit_' + node.__name__, visit)
- if depart:
- setattr(translator, 'depart_' + node.__name__, depart)
-
- def add_enumerable_node(self, node, figtype, title_getter=None, **kwds):
- # type: (nodes.Node, unicode, Callable, Any) -> None
- self.enumerable_nodes[node] = (figtype, title_getter)
- self.add_node(node, **kwds)
-
- def add_directive(self, name, obj, content=None, arguments=None, **options):
- # type: (unicode, Any, bool, Tuple[int, int, bool], Any) -> None
+ docutils.register_node(node)
+ self.registry.add_translation_handlers(node, **kwds)
+
+ def add_enumerable_node(self, node, figtype, title_getter=None, override=False, **kwds):
+ # type: (nodes.Node, unicode, TitleGetter, bool, Any) -> None
+ """Register a Docutils node class as a numfig target.
+
+ Sphinx numbers the node automatically. And then the users can refer it
+ using :rst:role:`numref`.
+
+ *figtype* is a type of enumerable nodes. Each figtypes have individual
+ numbering sequences. As a system figtypes, ``figure``, ``table`` and
+ ``code-block`` are defined. It is able to add custom nodes to these
+ default figtypes. It is also able to define new custom figtype if new
+ figtype is given.
+
+ *title_getter* is a getter function to obtain the title of node. It
+ takes an instance of the enumerable node, and it must return its title
+ as string. The title is used to the default title of references for
+ :rst:role:`ref`. By default, Sphinx searches
+ ``docutils.nodes.caption`` or ``docutils.nodes.title`` from the node as
+ a title.
+
+ Other keyword arguments are used for node visitor functions. See the
+ :meth:`Sphinx.add_node` for details.
+
+ .. versionadded:: 1.4
+ """
+ self.registry.add_enumerable_node(node, figtype, title_getter, override=override)
+ self.add_node(node, override=override, **kwds)
+
+ @property
+ def enumerable_nodes(self):
+ # type: () -> Dict[nodes.Node, Tuple[unicode, TitleGetter]]
+ warnings.warn('app.enumerable_nodes() is deprecated. '
+ 'Use app.get_domain("std").enumerable_nodes instead.',
+ RemovedInSphinx30Warning)
+ return self.registry.enumerable_nodes
+
+ def add_directive(self, name, obj, content=None, arguments=None, override=False, **options): # NOQA
+ # type: (unicode, Any, bool, Tuple[int, int, bool], bool, Any) -> None
+ """Register a Docutils directive.
+
+ *name* must be the prospective directive name. There are two possible
+ ways to write a directive:
+
+ - In the docutils 0.4 style, *obj* is the directive function.
+ *content*, *arguments* and *options* are set as attributes on the
+ function and determine whether the directive has content, arguments
+ and options, respectively. **This style is deprecated.**
+
+ - In the docutils 0.5 style, *obj* is the directive class.
+ It must already have attributes named *has_content*,
+ *required_arguments*, *optional_arguments*,
+ *final_argument_whitespace* and *option_spec* that correspond to the
+ options for the function way. See `the Docutils docs
+ <http://docutils.sourceforge.net/docs/howto/rst-directives.html>`_
+ for details.
+
+ The directive class must inherit from the class
+ ``docutils.parsers.rst.Directive``.
+
+ For example, the (already existing) :rst:dir:`literalinclude` directive
+ would be added like this:
+
+ .. code-block:: python
+
+ from docutils.parsers.rst import Directive, directives
+
+ class LiteralIncludeDirective(Directive):
+ has_content = True
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec = {
+ 'class': directives.class_option,
+ 'name': directives.unchanged,
+ }
+
+ def run(self):
+ ...
+
+ add_directive('literalinclude', LiteralIncludeDirective)
+
+ .. versionchanged:: 0.6
+ Docutils 0.5-style directive classes are now supported.
+ .. deprecated:: 1.8
+ Docutils 0.4-style (function based) directives support is deprecated.
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
logger.debug('[app] adding directive: %r',
(name, obj, content, arguments, options))
- if name in directives._directives:
+ if name in directives._directives and not override:
logger.warning(__('while setting up extension %s: directive %r is '
'already registered, it will be overridden'),
self._setting_up_extension[-1], name,
type='app', subtype='add_directive')
- directive = directive_helper(obj, content, arguments, **options)
- directives.register_directive(name, directive)
- def add_role(self, name, role):
- # type: (unicode, Any) -> None
+ if not isclass(obj) or not issubclass(obj, Directive):
+ directive = directive_helper(obj, content, arguments, **options)
+ directives.register_directive(name, directive)
+ else:
+ directives.register_directive(name, obj)
+
+ def add_role(self, name, role, override=False):
+ # type: (unicode, Any, bool) -> None
+ """Register a Docutils role.
+
+ *name* must be the role name that occurs in the source, *role* the role
+ function. Refer to the `Docutils documentation
+ <http://docutils.sourceforge.net/docs/howto/rst-roles.html>`_ for
+ more information.
+
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
logger.debug('[app] adding role: %r', (name, role))
- if name in roles._roles:
+ if name in roles._roles and not override:
logger.warning(__('while setting up extension %s: role %r is '
'already registered, it will be overridden'),
self._setting_up_extension[-1], name,
type='app', subtype='add_role')
roles.register_local_role(name, role)
- def add_generic_role(self, name, nodeclass):
- # type: (unicode, Any) -> None
- # don't use roles.register_generic_role because it uses
- # register_canonical_role
+ def add_generic_role(self, name, nodeclass, override=False):
+ # type: (unicode, Any, bool) -> None
+ """Register a generic Docutils role.
+
+ Register a Docutils role that does nothing but wrap its contents in the
+ node given by *nodeclass*.
+
+ .. versionadded:: 0.6
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
+ # Don't use ``roles.register_generic_role`` because it uses
+ # ``register_canonical_role``.
logger.debug('[app] adding generic role: %r', (name, nodeclass))
- if name in roles._roles:
+ if name in roles._roles and not override:
logger.warning(__('while setting up extension %s: role %r is '
'already registered, it will be overridden'),
self._setting_up_extension[-1], name,
@@ -556,39 +773,153 @@ class Sphinx(object):
role = roles.GenericRole(name, nodeclass)
roles.register_local_role(name, role)
- def add_domain(self, domain):
- # type: (Type[Domain]) -> None
- self.registry.add_domain(domain)
+ def add_domain(self, domain, override=False):
+ # type: (Type[Domain], bool) -> None
+ """Register a domain.
+
+ Make the given *domain* (which must be a class; more precisely, a
+ subclass of :class:`~sphinx.domains.Domain`) known to Sphinx.
+
+ .. versionadded:: 1.0
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
+ self.registry.add_domain(domain, override=override)
def override_domain(self, domain):
# type: (Type[Domain]) -> None
- self.registry.override_domain(domain)
+ """Override a registered domain.
+
+ Make the given *domain* class known to Sphinx, assuming that there is
+ already a domain with its ``.name``. The new domain must be a subclass
+ of the existing one.
- def add_directive_to_domain(self, domain, name, obj,
- has_content=None, argument_spec=None, **option_spec):
- # type: (unicode, unicode, Any, bool, Any, Any) -> None
+ .. versionadded:: 1.0
+ .. deprecated:: 1.8
+ Integrated to :meth:`add_domain`.
+ """
+ warnings.warn('app.override_domain() is deprecated. '
+ 'Use app.add_domain() with override option instead.',
+ RemovedInSphinx30Warning)
+ self.registry.add_domain(domain, override=True)
+
+ def add_directive_to_domain(self, domain, name, obj, has_content=None, argument_spec=None,
+ override=False, **option_spec):
+ # type: (unicode, unicode, Any, bool, Any, bool, Any) -> None
+ """Register a Docutils directive in a domain.
+
+ Like :meth:`add_directive`, but the directive is added to the domain
+ named *domain*.
+
+ .. versionadded:: 1.0
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
self.registry.add_directive_to_domain(domain, name, obj,
- has_content, argument_spec, **option_spec)
+ has_content, argument_spec, override=override,
+ **option_spec)
+
+ def add_role_to_domain(self, domain, name, role, override=False):
+ # type: (unicode, unicode, Union[RoleFunction, XRefRole], bool) -> None
+ """Register a Docutils role in a domain.
+
+ Like :meth:`add_role`, but the role is added to the domain named
+ *domain*.
+
+ .. versionadded:: 1.0
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
+ self.registry.add_role_to_domain(domain, name, role, override=override)
+
+ def add_index_to_domain(self, domain, index, override=False):
+ # type: (unicode, Type[Index], bool) -> None
+ """Register a custom index for a domain.
- def add_role_to_domain(self, domain, name, role):
- # type: (unicode, unicode, Union[RoleFunction, XRefRole]) -> None
- self.registry.add_role_to_domain(domain, name, role)
+ Add a custom *index* class to the domain named *domain*. *index* must
+ be a subclass of :class:`~sphinx.domains.Index`.
- def add_index_to_domain(self, domain, index):
- # type: (unicode, Type[Index]) -> None
+ .. versionadded:: 1.0
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
self.registry.add_index_to_domain(domain, index)
def add_object_type(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None, objname='',
- doc_field_types=[]):
- # type: (unicode, unicode, unicode, Callable, nodes.Node, unicode, List) -> None
+ doc_field_types=[], override=False):
+ # type: (unicode, unicode, unicode, Callable, nodes.Node, unicode, List, bool) -> None
+ """Register a new object type.
+
+ This method is a very convenient way to add a new :term:`object` type
+ that can be cross-referenced. It will do this:
+
+ - Create a new directive (called *directivename*) for documenting an
+ object. It will automatically add index entries if *indextemplate*
+ is nonempty; if given, it must contain exactly one instance of
+ ``%s``. See the example below for how the template will be
+ interpreted. * Create a new role (called *rolename*) to
+ cross-reference to these object descriptions.
+ - If you provide *parse_node*, it must be a function that takes a
+ string and a docutils node, and it must populate the node with
+ children parsed from the string. It must then return the name of the
+ item to be used in cross-referencing and index entries. See the
+ :file:`conf.py` file in the source for this documentation for an
+ example.
+ - The *objname* (if not given, will default to *directivename*) names
+ the type of object. It is used when listing objects, e.g. in search
+ results.
+
+ For example, if you have this call in a custom Sphinx extension::
+
+ app.add_object_type('directive', 'dir', 'pair: %s; directive')
+
+ you can use this markup in your documents::
+
+ .. rst:directive:: function
+
+ Document a function.
+
+ <...>
+
+ See also the :rst:dir:`function` directive.
+
+ For the directive, an index entry will be generated as if you had prepended ::
+
+ .. index:: pair: function; directive
+
+ The reference node will be of class ``literal`` (so it will be rendered
+ in a proportional font, as appropriate for code) unless you give the
+ *ref_nodeclass* argument, which must be a docutils node class. Most
+ useful are ``docutils.nodes.emphasis`` or ``docutils.nodes.strong`` --
+ you can also use ``docutils.nodes.generated`` if you want no further
+ text decoration. If the text should be treated as literal (e.g. no
+ smart quote replacement), but not have typewriter styling, use
+ ``sphinx.addnodes.literal_emphasis`` or
+ ``sphinx.addnodes.literal_strong``.
+
+ For the role content, you have the same syntactical possibilities as
+ for standard Sphinx roles (see :ref:`xref-syntax`).
+
+ This method is also available under the deprecated alias
+ :meth:`add_description_unit`.
+
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
self.registry.add_object_type(directivename, rolename, indextemplate, parse_node,
- ref_nodeclass, objname, doc_field_types)
+ ref_nodeclass, objname, doc_field_types,
+ override=override)
def add_description_unit(self, directivename, rolename, indextemplate='',
parse_node=None, ref_nodeclass=None, objname='',
doc_field_types=[]):
# type: (unicode, unicode, unicode, Callable, nodes.Node, unicode, List) -> None
+ """Deprecated alias for :meth:`add_object_type`.
+
+ .. deprecated:: 1.6
+ Use :meth:`add_object_type` instead.
+ """
warnings.warn('app.add_description_unit() is now deprecated. '
'Use app.add_object_type() instead.',
RemovedInSphinx20Warning)
@@ -596,21 +927,96 @@ class Sphinx(object):
ref_nodeclass, objname, doc_field_types)
def add_crossref_type(self, directivename, rolename, indextemplate='',
- ref_nodeclass=None, objname=''):
- # type: (unicode, unicode, unicode, nodes.Node, unicode) -> None
+ ref_nodeclass=None, objname='', override=False):
+ # type: (unicode, unicode, unicode, nodes.Node, unicode, bool) -> None
+ """Register a new crossref object type.
+
+ This method is very similar to :meth:`add_object_type` except that the
+ directive it generates must be empty, and will produce no output.
+
+ That means that you can add semantic targets to your sources, and refer
+ to them using custom roles instead of generic ones (like
+ :rst:role:`ref`). Example call::
+
+ app.add_crossref_type('topic', 'topic', 'single: %s',
+ docutils.nodes.emphasis)
+
+ Example usage::
+
+ .. topic:: application API
+
+ The application API
+ -------------------
+
+ Some random text here.
+
+ See also :topic:`this section <application API>`.
+
+ (Of course, the element following the ``topic`` directive needn't be a
+ section.)
+
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
self.registry.add_crossref_type(directivename, rolename,
- indextemplate, ref_nodeclass, objname)
+ indextemplate, ref_nodeclass, objname,
+ override=override)
def add_transform(self, transform):
# type: (Type[Transform]) -> None
+ """Register a Docutils transform to be applied after parsing.
+
+ Add the standard docutils :class:`Transform` subclass *transform* to
+ the list of transforms that are applied after Sphinx parses a reST
+ document.
+
+ .. list-table:: priority range categories for Sphinx transforms
+
+ * - Priority
+ - Main purpose in Sphinx
+ * - 0-99
+ - Fix invalid nodes by docutils. Translate a doctree.
+ * - 100-299
+ - Preparation
+ * - 300-399
+ - early
+ * - 400-699
+ - main
+ * - 700-799
+ - Post processing. Deadline to modify text and referencing.
+ * - 800-899
+ - Collect referencing and referenced nodes. Domain processing.
+ * - 900-999
+ - Finalize and clean up.
+
+ refs: `Transform Priority Range Categories`__
+
+ __ http://docutils.sourceforge.net/docs/ref/transforms.html#transform-priority-range-categories
+ """ # NOQA
self.registry.add_transform(transform)
def add_post_transform(self, transform):
# type: (Type[Transform]) -> None
+ """Register a Docutils transform to be applied before writing.
+
+ Add the standard docutils :class:`Transform` subclass *transform* to
+ the list of transforms that are applied before Sphinx writes a
+ document.
+ """
self.registry.add_post_transform(transform)
def add_javascript(self, filename):
# type: (unicode) -> None
+ """Register a JavaScript file to include in the HTML output.
+
+ Add *filename* to the list of JavaScript files that the default HTML
+ template will include. The filename must be relative to the HTML
+ static path, see :confval:`the docs for the config value
+ <html_static_path>`. A full URI with scheme, like
+ ``http://example.org/foo.js``, is also supported.
+
+ .. versionadded:: 0.5
+ """
logger.debug('[app] adding javascript: %r', filename)
from sphinx.builders.html import StandaloneHTMLBuilder
if '://' in filename:
@@ -619,27 +1025,90 @@ class Sphinx(object):
StandaloneHTMLBuilder.script_files.append(
posixpath.join('_static', filename))
+ def add_css_file(self, filename, **kwargs):
+ # type: (unicode, **unicode) -> None
+ """Register a stylesheet to include in the HTML output.
+
+ Add *filename* to the list of CSS files that the default HTML template
+ will include. The filename must be relative to the HTML static path,
+ or a full URI with scheme. The keyword arguments are also accepted for
+ attributes of ``<link>`` tag.
+
+ Example::
+
+ app.add_css_file('custom.css')
+ # => <link rel="stylesheet" href="_static/custom.css" type="text/css" />
+
+ app.add_css_file('print.css', media='print')
+ # => <link rel="stylesheet" href="_static/print.css"
+ # type="text/css" media="print" />
+
+ app.add_css_file('fancy.css', rel='alternate stylesheet', title='fancy')
+ # => <link rel="alternate stylesheet" href="_static/fancy.css"
+ # type="text/css" title="fancy" />
+
+ .. versionadded:: 1.0
+
+ .. versionchanged:: 1.6
+ Optional ``alternate`` and/or ``title`` attributes can be supplied
+ with the *alternate* (of boolean type) and *title* (a string)
+ arguments. The default is no title and *alternate* = ``False``. For
+ more information, refer to the `documentation
+ <https://mdn.io/Web/CSS/Alternative_style_sheets>`__.
+
+ .. versionchanged:: 1.8
+ Renamed from ``app.add_stylesheet()``.
+ And it allows keyword arguments as attributes of link tag.
+ """
+ logger.debug('[app] adding stylesheet: %r', filename)
+ self.registry.add_css_files(filename, **kwargs)
+
def add_stylesheet(self, filename, alternate=False, title=None):
# type: (unicode, bool, unicode) -> None
- logger.debug('[app] adding stylesheet: %r', filename)
- from sphinx.builders.html import StandaloneHTMLBuilder, Stylesheet
- if '://' not in filename:
- filename = posixpath.join('_static', filename)
+ """An alias of :meth:`add_css_file`."""
+ warnings.warn('The app.add_stylesheet() is deprecated. '
+ 'Please use app.add_css_file() instead.',
+ RemovedInSphinx40Warning)
+
+ attributes = {} # type: Dict[unicode, unicode]
if alternate:
- rel = u'alternate stylesheet'
+ attributes['rel'] = 'alternate stylesheet'
else:
- rel = u'stylesheet'
- css = Stylesheet(filename, title, rel) # type: ignore
- StandaloneHTMLBuilder.css_files.append(css)
+ attributes['rel'] = 'stylesheet'
+
+ if title:
+ attributes['title'] = title
+
+ self.add_css_file(filename, **attributes)
def add_latex_package(self, packagename, options=None):
# type: (unicode, unicode) -> None
- logger.debug('[app] adding latex package: %r', packagename)
- if hasattr(self.builder, 'usepackages'): # only for LaTeX builder
- self.builder.usepackages.append((packagename, options)) # type: ignore
+ r"""Register a package to include in the LaTeX source code.
+
+ Add *packagename* to the list of packages that LaTeX source code will
+ include. If you provide *options*, it will be taken to `\usepackage`
+ declaration.
+
+ .. code-block:: python
+
+ app.add_latex_package('mypackage')
+ # => \usepackage{mypackage}
+ app.add_latex_package('mypackage', 'foo,bar')
+ # => \usepackage[foo,bar]{mypackage}
+
+ .. versionadded:: 1.3
+ """
+ self.registry.add_latex_package(packagename, options)
def add_lexer(self, alias, lexer):
# type: (unicode, Any) -> None
+ """Register a new lexer for source code.
+
+ Use *lexer*, which must be an instance of a Pygments lexer class, to
+ highlight code blocks with the given language *alias*.
+
+ .. versionadded:: 0.6
+ """
logger.debug('[app] adding lexer: %r', (alias, lexer))
from sphinx.highlighting import lexers
if lexers is None:
@@ -648,6 +1117,18 @@ class Sphinx(object):
def add_autodocumenter(self, cls):
# type: (Any) -> None
+ """Register a new documenter class for the autodoc extension.
+
+ Add *cls* as a new documenter class for the :mod:`sphinx.ext.autodoc`
+ extension. It must be a subclass of
+ :class:`sphinx.ext.autodoc.Documenter`. This allows to auto-document
+ new types of objects. See the source of the autodoc module for
+ examples on how to subclass :class:`Documenter`.
+
+ .. todo:: Add real docs for Documenter and subclassing
+
+ .. versionadded:: 0.6
+ """
logger.debug('[app] adding autodocumenter: %r', cls)
from sphinx.ext.autodoc.directive import AutodocDirective
self.registry.add_documenter(cls.objtype, cls)
@@ -655,30 +1136,96 @@ class Sphinx(object):
def add_autodoc_attrgetter(self, typ, getter):
# type: (Type, Callable[[Any, unicode, Any], Any]) -> None
+ """Register a new ``getattr``-like function for the autodoc extension.
+
+ Add *getter*, which must be a function with an interface compatible to
+ the :func:`getattr` builtin, as the autodoc attribute getter for
+ objects that are instances of *typ*. All cases where autodoc needs to
+ get an attribute of a type are then handled by this function instead of
+ :func:`getattr`.
+
+ .. versionadded:: 0.6
+ """
logger.debug('[app] adding autodoc attrgetter: %r', (typ, getter))
self.registry.add_autodoc_attrgetter(typ, getter)
def add_search_language(self, cls):
# type: (Any) -> None
+ """Register a new language for the HTML search index.
+
+ Add *cls*, which must be a subclass of
+ :class:`sphinx.search.SearchLanguage`, as a support language for
+ building the HTML full-text search index. The class must have a *lang*
+ attribute that indicates the language it should be used for. See
+ :confval:`html_search_language`.
+
+ .. versionadded:: 1.1
+ """
logger.debug('[app] adding search language: %r', cls)
from sphinx.search import languages, SearchLanguage
assert issubclass(cls, SearchLanguage)
languages[cls.lang] = cls
- def add_source_parser(self, suffix, parser):
- # type: (unicode, Parser) -> None
- self.registry.add_source_parser(suffix, parser)
+ def add_source_suffix(self, suffix, filetype, override=False):
+ # type: (unicode, unicode, bool) -> None
+ """Register a suffix of source files.
+
+ Same as :confval:`source_suffix`. The users can override this
+ using the setting.
+
+ .. versionadded:: 1.8
+ """
+ self.registry.add_source_suffix(suffix, filetype, override=override)
+
+ def add_source_parser(self, *args, **kwargs):
+ # type: (Any, Any) -> None
+ """Register a parser class.
+
+ .. versionadded:: 1.4
+ .. versionchanged:: 1.8
+ *suffix* argument is deprecated. It only accepts *parser* argument.
+ Use :meth:`add_source_suffix` API to register suffix instead.
+ .. versionchanged:: 1.8
+ Add *override* keyword.
+ """
+ self.registry.add_source_parser(*args, **kwargs)
def add_env_collector(self, collector):
# type: (Type[EnvironmentCollector]) -> None
+ """Register an environment collector class.
+
+ Refer to :ref:`collector-api`.
+
+ .. versionadded:: 1.6
+ """
logger.debug('[app] adding environment collector: %r', collector)
collector().enable(self)
def add_html_theme(self, name, theme_path):
# type: (unicode, unicode) -> None
+ """Register a HTML Theme.
+
+ The *name* is a name of theme, and *path* is a full path to the theme
+ (refs: :ref:`distribute-your-theme`).
+
+ .. versionadded:: 1.6
+ """
logger.debug('[app] adding HTML theme: %r, %r', name, theme_path)
self.html_themes[name] = theme_path
+ def add_message_catalog(self, catalog, locale_dir):
+ # type: (unicode, unicode) -> None
+ """Register a message catalog.
+
+ The *catalog* is a name of catalog, and *locale_dir* is a base path
+ of message catalog. For more details, see
+ :func:`sphinx.locale.get_translation()`.
+
+ .. versionadded:: 1.8
+ """
+ locale.init([locale_dir], self.config.language, catalog)
+ locale.init_console(locale_dir, catalog)
+
# ---- other methods -------------------------------------------------
def is_parallel_allowed(self, typ):
# type: (unicode) -> bool
@@ -705,7 +1252,7 @@ class Sphinx(object):
allowed = getattr(ext, attrname, None)
if allowed is None:
logger.warning(message, ext.name)
- logger.warning('doing serial %s', typ)
+ logger.warning(__('doing serial %s'), typ)
return False
elif not allowed:
return False