Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sphinx factory class #3656

Merged
merged 10 commits into from
Apr 23, 2017
3 changes: 3 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Incompatible changes
* ``Builder.env`` is not filled at instantiation
* #3594: LaTeX: single raw directive has been considered as block level element
* #3639: If ``html_experimental_html5_writer`` is available, epub builder use it by default.
* ``Sphinx.add_source_parser()`` raises an error if duplicated

Features removed
----------------
Expand Down Expand Up @@ -150,6 +151,8 @@ Deprecated
instead (as Sphinx does since 1.5.)
* ``Sphinx.status_iterator()`` and ``Sphinx.old_status_iterator()`` is now
deprecated. Please use ``sphinx.util:status_iterator()`` intead.
* ``Sphinx._directive_helper()`` is deprecated. Please use
``sphinx.util.docutils.directive_helper()`` instead.
* ``BuildEnvironment.set_warnfunc()`` is now deprecated
* Following methods of ``BuildEnvironment`` is now deprecated.

Expand Down
179 changes: 61 additions & 118 deletions sphinx/application.py

Large diffs are not rendered by default.

40 changes: 37 additions & 3 deletions sphinx/builders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import os
from os import path
import warnings

try:
import multiprocessing
Expand All @@ -20,6 +21,7 @@
from six import itervalues
from docutils import nodes

from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.util import i18n, path_stabilize, logging, status_iterator
from sphinx.util.osutil import SEP, relative_uri
from sphinx.util.i18n import find_catalog
Expand Down Expand Up @@ -53,6 +55,9 @@ class Builder(object):
name = '' # type: unicode
#: The builder's output format, or '' if no document output is produced.
format = '' # type: unicode
# default translator class for the builder. This will be overrided by
# ``app.set_translator()``.
default_translator_class = None # type: nodes.NodeVisitor
# doctree versioning method
versioning_method = 'none' # type: unicode
versioning_compare = False
Expand Down Expand Up @@ -101,16 +106,45 @@ def __init__(self, app):
self.parallel_ok = False
self.finish_tasks = None # type: Any

# load default translator class
self.translator_class = app._translators.get(self.name)

def set_environment(self, env):
# type: (BuildEnvironment) -> None
"""Store BuildEnvironment object."""
self.env = env
self.env.set_versioning_method(self.versioning_method,
self.versioning_compare)

def get_translator_class(self, *args):
# type: (Any) -> nodes.NodeVisitor
"""Return a class of translator."""
return self.app.registry.get_translator_class(self)

def create_translator(self, *args):
# type: (Any) -> nodes.NodeVisitor
"""Return an instance of translator.

This method returns an instance of ``default_translator_class`` by default.
Users can replace the translator class with ``app.set_translator()`` API.
"""
translator_class = self.app.registry.get_translator_class(self)
assert translator_class, "translator not found for %s" % self.__class__.__name__
return translator_class(*args)

@property
def translator_class(self):
# type: () -> Callable[[Any], nodes.NodeVisitor]
"""Return a class of translator.

.. deprecated:: 1.6
"""
translator_class = self.app.registry.get_translator_class(self)
if translator_class is None and self.default_translator_class is None:
warnings.warn('builder.translator_class() is now deprecated. '
'Please use builder.create_translator() and '
'builder.default_translator_class instead.',
RemovedInSphinx20Warning)
return None
return self.create_translator

# helper methods
def init(self):
# type: () -> None
Expand Down
29 changes: 11 additions & 18 deletions sphinx/builders/html.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ def init(self):

self.init_templates()
self.init_highlighter()
self.init_translator_class()
if self.config.html_file_suffix is not None:
self.out_suffix = self.config.html_file_suffix

Expand Down Expand Up @@ -218,23 +217,18 @@ def init_highlighter(self):
self.highlighter = PygmentsBridge('html', style,
self.config.trim_doctest_flags)

def init_translator_class(self):
# type: () -> None
if self.translator_class is None:
use_html5_writer = self.config.html_experimental_html5_writer
if use_html5_writer is None:
use_html5_writer = self.default_html5_translator and html5_ready

if use_html5_writer and html5_ready:
if self.config.html_use_smartypants:
self.translator_class = SmartyPantsHTML5Translator
else:
self.translator_class = HTML5Translator
@property
def default_translator_class(self):
if self.config.html_experimental_html5_writer and html5_ready:
if self.config.html_use_smartypants:
return SmartyPantsHTML5Translator
else:
if self.config.html_use_smartypants:
self.translator_class = SmartyPantsHTMLTranslator
else:
self.translator_class = HTMLTranslator
return HTML5Translator
else:
if self.config.html_use_smartypants:
return SmartyPantsHTMLTranslator
else:
return HTMLTranslator

def get_outdated_docs(self):
# type: () -> Iterator[unicode]
Expand Down Expand Up @@ -1200,7 +1194,6 @@ def init(self):
self.current_docname = None
self.theme = None # no theme necessary
self.templates = None # no template bridge necessary
self.init_translator_class()
self.init_templates()
self.init_highlighter()
self.use_index = self.get_builder_config('use_index', 'html')
Expand Down
3 changes: 2 additions & 1 deletion sphinx/builders/latex.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from sphinx.util.fileutil import copy_asset_file
from sphinx.util.osutil import SEP, make_filename
from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.writers.latex import LaTeXWriter
from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator

if False:
# For type annotation
Expand All @@ -51,6 +51,7 @@ class LaTeXBuilder(Builder):
format = 'latex'
supported_image_types = ['application/pdf', 'image/png', 'image/jpeg']
supported_remote_images = False
default_translator_class = LaTeXTranslator

def init(self):
# type: () -> None
Expand Down
3 changes: 2 additions & 1 deletion sphinx/builders/manpage.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.osutil import make_filename
from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.writers.manpage import ManualPageWriter
from sphinx.writers.manpage import ManualPageWriter, ManualPageTranslator

if False:
# For type annotation
Expand All @@ -40,6 +40,7 @@ class ManualPageBuilder(Builder):
"""
name = 'man'
format = 'man'
default_translator_class = ManualPageTranslator
supported_image_types = [] # type: List[unicode]

def init(self):
Expand Down
3 changes: 2 additions & 1 deletion sphinx/builders/texinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from sphinx.util.nodes import inline_all_toctrees
from sphinx.util.osutil import SEP, make_filename
from sphinx.util.console import bold, darkgreen # type: ignore
from sphinx.writers.texinfo import TexinfoWriter
from sphinx.writers.texinfo import TexinfoWriter, TexinfoTranslator

if False:
# For type annotation
Expand Down Expand Up @@ -99,6 +99,7 @@ class TexinfoBuilder(Builder):
format = 'texinfo'
supported_image_types = ['image/png', 'image/jpeg',
'image/gif']
default_translator_class = TexinfoTranslator

def init(self):
# type: () -> None
Expand Down
3 changes: 2 additions & 1 deletion sphinx/builders/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from sphinx.builders import Builder
from sphinx.util import logging
from sphinx.util.osutil import ensuredir, os_path
from sphinx.writers.text import TextWriter
from sphinx.writers.text import TextWriter, TextTranslator

if False:
# For type annotation
Expand All @@ -33,6 +33,7 @@ class TextBuilder(Builder):
format = 'text'
out_suffix = '.txt'
allow_parallel = True
default_translator_class = TextTranslator

current_docname = None # type: unicode

Expand Down
6 changes: 1 addition & 5 deletions sphinx/builders/websupport.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class WebSupportBuilder(PickleHTMLBuilder):
name = 'websupport'
versioning_method = 'commentable'
versioning_compare = True # for commentable node's uuid stability.
default_translator_class = WebSupportTranslator

def init(self):
# type: () -> None
Expand All @@ -54,11 +55,6 @@ def set_webinfo(self, staticdir, virtual_staticdir, search, storage):
self.search = search
self.storage = storage

def init_translator_class(self):
# type: () -> None
if self.translator_class is None:
self.translator_class = WebSupportTranslator

def prepare_writing(self, docnames):
# type: (Iterable[unicode]) -> None
PickleHTMLBuilder.prepare_writing(self, docnames)
Expand Down
2 changes: 2 additions & 0 deletions sphinx/builders/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from docutils import nodes
from docutils.io import StringOutput
from docutils.writers.docutils_xml import XMLTranslator

from sphinx.builders import Builder
from sphinx.util import logging
Expand All @@ -38,6 +39,7 @@ class XMLBuilder(Builder):
allow_parallel = True

_writer_class = XMLWriter
default_translator_class = XMLTranslator

def init(self):
# type: () -> None
Expand Down
3 changes: 2 additions & 1 deletion sphinx/environment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,8 @@ def read_doc(self, docname, app=None):
codecs.register_error('sphinx', self.warn_and_replace) # type: ignore

# publish manually
reader = SphinxStandaloneReader(self.app, parsers=self.config.source_parsers)
reader = SphinxStandaloneReader(self.app,
parsers=self.app.registry.get_source_parsers())
pub = Publisher(reader=reader,
writer=SphinxDummyWriter(),
destination_class=NullOutput)
Expand Down
2 changes: 1 addition & 1 deletion sphinx/ext/autosummary/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ def get_rst_suffix(app):
# type: (Sphinx) -> unicode
def get_supported_format(suffix):
# type: (unicode) -> Tuple[unicode]
parser_class = app.config.source_parsers.get(suffix)
parser_class = app.registry.get_source_parsers().get(suffix)
if parser_class is None:
return ('restructuredtext',)
if isinstance(parser_class, string_types):
Expand Down
62 changes: 2 additions & 60 deletions sphinx/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,20 @@
:license: BSD, see LICENSE for details.
"""

import traceback

from six import iteritems

from sphinx.errors import ExtensionError, VersionRequirementError
from sphinx.errors import VersionRequirementError
from sphinx.locale import _
from sphinx.util import logging

if False:
# For type annotation
from typing import Any, Dict # NOQA
from typing import Dict # NOQA
from sphinx.application import Sphinx # NOQA


logger = logging.getLogger(__name__)


# list of deprecated extensions. Keys are extension name.
# Values are Sphinx version that merge the extension.
EXTENSION_BLACKLIST = {
"sphinxjp.themecore": "1.2"
} # type: Dict[unicode, unicode]


class Extension(object):
def __init__(self, name, module, **kwargs):
self.name = name
Expand All @@ -51,54 +41,6 @@ def __init__(self, name, module, **kwargs):
self.parallel_write_safe = kwargs.pop('parallel_read_safe', True)


def load_extension(app, extname):
# type: (Sphinx, unicode) -> None
"""Load a Sphinx extension."""
if extname in app.extensions: # alread loaded
return
if extname in EXTENSION_BLACKLIST:
logger.warning(_('the extension %r was already merged with Sphinx since '
'version %s; this extension is ignored.'),
extname, EXTENSION_BLACKLIST[extname])
return

# update loading context
app._setting_up_extension.append(extname)

try:
mod = __import__(extname, None, None, ['setup'])
except ImportError as err:
logger.verbose(_('Original exception:\n') + traceback.format_exc())
raise ExtensionError(_('Could not import extension %s') % extname, err)

if not hasattr(mod, 'setup'):
logger.warning(_('extension %r has no setup() function; is it really '
'a Sphinx extension module?'), extname)
metadata = {} # type: Dict[unicode, Any]
else:
try:
metadata = mod.setup(app)
except VersionRequirementError as err:
# add the extension name to the version required
raise VersionRequirementError(
_('The %s extension used by this project needs at least '
'Sphinx v%s; it therefore cannot be built with this '
'version.') % (extname, err)
)

if metadata is None:
metadata = {}
if extname == 'rst2pdf.pdfbuilder':
metadata['parallel_read_safe'] = True
elif not isinstance(metadata, dict):
logger.warning(_('extension %r returned an unsupported object from '
'its setup() function; it should return None or a '
'metadata dictionary'), extname)

app.extensions[extname] = Extension(extname, mod, **metadata)
app._setting_up_extension.pop()


def verify_required_extensions(app, requirements):
# type: (Sphinx, Dict[unicode, unicode]) -> None
"""Verify the required Sphinx extensions are loaded."""
Expand Down
5 changes: 2 additions & 3 deletions sphinx/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from docutils.io import FileInput
from docutils.readers import standalone
from docutils.writers import UnfilteredWriter
from six import string_types, text_type
from six import string_types, text_type, iteritems
from typing import Any, Union # NOQA

from sphinx.transforms import (
Expand Down Expand Up @@ -158,9 +158,8 @@ def read(self):
# type: () -> unicode
def get_parser_type(source_path):
# type: (unicode) -> Tuple[unicode]
for suffix in self.env.config.source_parsers:
for suffix, parser_class in iteritems(self.app.registry.get_source_parsers()):
if source_path.endswith(suffix):
parser_class = self.env.config.source_parsers[suffix]
if isinstance(parser_class, string_types):
parser_class = import_object(parser_class, 'source parser') # type: ignore # NOQA
return parser_class.supported
Expand Down
Loading