mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Add HTMLThemeFactory class
This commit is contained in:
parent
b405c0aaf5
commit
faefe2b5a5
@ -127,6 +127,7 @@ class Sphinx(object):
|
||||
self.env = None # type: BuildEnvironment
|
||||
self.enumerable_nodes = {} # type: Dict[nodes.Node, Tuple[unicode, Callable]] # NOQA
|
||||
self.post_transforms = [] # type: List[Transform]
|
||||
self.html_themes = {} # type: Dict[unicode, unicode]
|
||||
|
||||
self.srcdir = srcdir
|
||||
self.confdir = confdir
|
||||
|
@ -16,7 +16,7 @@ from six import iteritems
|
||||
|
||||
from sphinx import package_dir
|
||||
from sphinx.locale import _
|
||||
from sphinx.theming import Theme
|
||||
from sphinx.theming import HTMLThemeFactory
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.osutil import ensuredir, os_path
|
||||
@ -42,8 +42,8 @@ class ChangesBuilder(Builder):
|
||||
def init(self):
|
||||
# type: () -> None
|
||||
self.create_template_bridge()
|
||||
Theme.init_themes(self.confdir, self.config.html_theme_path)
|
||||
self.theme = Theme.create('default')
|
||||
theme_factory = HTMLThemeFactory(self.app)
|
||||
self.theme = theme_factory.create('default')
|
||||
self.templates.init(self, self.theme)
|
||||
|
||||
def get_outdated_docs(self):
|
||||
|
@ -40,7 +40,7 @@ from sphinx.util.matching import patmatch, Matcher, DOTFILES
|
||||
from sphinx.config import string_classes
|
||||
from sphinx.locale import _, l_
|
||||
from sphinx.search import js_index
|
||||
from sphinx.theming import Theme
|
||||
from sphinx.theming import HTMLThemeFactory
|
||||
from sphinx.builders import Builder
|
||||
from sphinx.application import ENV_PICKLE_FILENAME
|
||||
from sphinx.highlighting import PygmentsBridge
|
||||
@ -196,9 +196,9 @@ class StandaloneHTMLBuilder(Builder):
|
||||
|
||||
def init_templates(self):
|
||||
# type: () -> None
|
||||
Theme.init_themes(self.confdir, self.config.html_theme_path)
|
||||
theme_factory = HTMLThemeFactory(self.app)
|
||||
themename, themeoptions = self.get_theme_config()
|
||||
self.theme = Theme.create(themename)
|
||||
self.theme = theme_factory.create(themename)
|
||||
self.theme_options = themeoptions.copy()
|
||||
self.create_template_bridge()
|
||||
self.templates.init(self, self.theme)
|
||||
|
@ -30,12 +30,13 @@ logger = logging.getLogger(__name__)
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Any, Dict, Iterator, List, Tuple # NOQA
|
||||
from sphinx.application import Sphinx # NOQA
|
||||
|
||||
NODEFAULT = object()
|
||||
THEMECONF = 'theme.conf'
|
||||
|
||||
|
||||
class _Theme(object):
|
||||
class Theme(object):
|
||||
def __init__(self):
|
||||
# type: () -> None
|
||||
self.name = None
|
||||
@ -126,69 +127,56 @@ def is_archived_theme(filename):
|
||||
return False
|
||||
|
||||
|
||||
def find_theme_entries(themedir):
|
||||
# type: (unicode) -> Iterator[Tuple[unicode, unicode]]
|
||||
for entry in os.listdir(themedir):
|
||||
pathname = path.join(themedir, entry)
|
||||
if path.isfile(pathname) and entry.lower().endswith('.zip'):
|
||||
if is_archived_theme(pathname):
|
||||
name = entry[:-4]
|
||||
yield name, pathname
|
||||
else:
|
||||
logger.warning(_('file %r on theme path is not a valid '
|
||||
'zipfile or contains no theme'), entry)
|
||||
else:
|
||||
if path.isfile(path.join(pathname, THEMECONF)):
|
||||
yield entry, pathname
|
||||
class HTMLThemeFactory(object):
|
||||
"""A factory class for HTML Themes."""
|
||||
|
||||
def __init__(self, app):
|
||||
# type: (Sphinx) -> None
|
||||
self.confdir = app.confdir
|
||||
self.themes = app.html_themes
|
||||
self.load_builtin_themes()
|
||||
if getattr(app.config, 'html_theme_path', None):
|
||||
self.load_additional_themes(app.config.html_theme_path)
|
||||
|
||||
class Theme(object):
|
||||
"""
|
||||
Represents the theme chosen in the configuration.
|
||||
"""
|
||||
themes = {} # type: Dict[unicode, unicode]
|
||||
def load_builtin_themes(self):
|
||||
# type: () -> None
|
||||
themes = self.find_themes(path.join(package_dir, 'themes'))
|
||||
for name, theme in iteritems(themes):
|
||||
self.themes[name] = theme
|
||||
|
||||
@classmethod
|
||||
def init_themes(cls, confdir, theme_path):
|
||||
# type: (unicode, unicode) -> None
|
||||
"""Search all theme paths for available themes."""
|
||||
themepath = list(theme_path)
|
||||
themepath.append(path.join(package_dir, 'themes'))
|
||||
def load_additional_themes(self, theme_paths):
|
||||
# type: (unicode) -> None
|
||||
for theme_path in theme_paths:
|
||||
abs_theme_path = path.abspath(path.join(self.confdir, theme_path))
|
||||
themes = self.find_themes(abs_theme_path)
|
||||
for name, theme in iteritems(themes):
|
||||
self.themes[name] = theme
|
||||
|
||||
# search themes from theme_paths
|
||||
for themedir in themepath:
|
||||
themedir = path.abspath(path.join(confdir, themedir))
|
||||
if not path.isdir(themedir):
|
||||
continue
|
||||
else:
|
||||
for name, theme in find_theme_entries(themedir):
|
||||
cls.themes[name] = theme
|
||||
|
||||
@classmethod
|
||||
def load_extra_theme(cls, name):
|
||||
def load_extra_theme(self, name):
|
||||
# type: (unicode) -> None
|
||||
if name == 'alabaster':
|
||||
cls.load_alabaster()
|
||||
self.load_alabaster_theme()
|
||||
elif name == 'sphinx_rtd_theme':
|
||||
cls.load_sphinx_rtd_theme()
|
||||
self.load_sphinx_rtd_theme()
|
||||
else:
|
||||
pass
|
||||
self.load_external_theme(name)
|
||||
|
||||
@classmethod
|
||||
def load_alabaster(cls):
|
||||
def load_alabaster_theme(self):
|
||||
# type: () -> None
|
||||
import alabaster
|
||||
cls.themes['alabaster'] = path.join(alabaster.get_path(), 'alabaster')
|
||||
self.themes['alabaster'] = path.join(alabaster.get_path(), 'alabaster')
|
||||
|
||||
@classmethod
|
||||
def load_sphinx_rtd_theme(cls):
|
||||
def load_sphinx_rtd_theme(self):
|
||||
# type: () -> None
|
||||
try:
|
||||
import sphinx_rtd_theme
|
||||
cls.themes['sphinx_rtd_theme'] = path.join(sphinx_rtd_theme.get_html_theme_path(),
|
||||
'sphinx_rtd_theme')
|
||||
theme_path = sphinx_rtd_theme.get_html_theme_path()
|
||||
self.themes['sphinx_rtd_theme'] = path.join(theme_path, 'sphinx_rtd_theme')
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def load_external_themes(cls, name):
|
||||
def load_external_themes(self, name):
|
||||
# type: (unicode) -> None
|
||||
for entry_point in pkg_resources.iter_entry_points('sphinx_themes'):
|
||||
target = entry_point.load()
|
||||
if callable(target):
|
||||
@ -199,17 +187,38 @@ class Theme(object):
|
||||
else:
|
||||
themedir = target
|
||||
|
||||
for entry, theme in find_theme_entries(themedir):
|
||||
themes = self.find_themes(themedir)
|
||||
for entry, theme in iteritems(themes):
|
||||
if name == entry:
|
||||
cls.themes[entry] = theme
|
||||
self.themes[name] = theme
|
||||
|
||||
@classmethod
|
||||
def create(cls, name):
|
||||
# type: (unicode) -> None
|
||||
if name not in cls.themes:
|
||||
cls.load_extra_theme(name)
|
||||
def find_themes(self, theme_path):
|
||||
# type: (unicode) -> Dict[unicode, unicode]
|
||||
themes = {}
|
||||
if not path.isdir(theme_path):
|
||||
return themes
|
||||
|
||||
if name not in cls.themes:
|
||||
for entry in os.listdir(theme_path):
|
||||
pathname = path.join(theme_path, entry)
|
||||
if path.isfile(pathname) and entry.lower().endswith('.zip'):
|
||||
if is_archived_theme(pathname):
|
||||
name = entry[:-4]
|
||||
themes[name] = pathname
|
||||
else:
|
||||
logger.warning(_('file %r on theme path is not a valid '
|
||||
'zipfile or contains no theme'), entry)
|
||||
else:
|
||||
if path.isfile(path.join(pathname, THEMECONF)):
|
||||
themes[entry] = pathname
|
||||
|
||||
return themes
|
||||
|
||||
def create(self, name):
|
||||
# type: (unicode) -> Theme
|
||||
if name not in self.themes:
|
||||
self.load_extra_theme(name)
|
||||
|
||||
if name not in self.themes:
|
||||
if name == 'sphinx_rtd_theme':
|
||||
raise ThemeError(_('sphinx_rtd_theme is no longer a hard dependency '
|
||||
'since version 1.4.0. Please install it manually.'
|
||||
@ -218,10 +227,10 @@ class Theme(object):
|
||||
raise ThemeError(_('no theme named %r found '
|
||||
'(missing theme.conf?)') % name)
|
||||
|
||||
theme = _Theme()
|
||||
theme = Theme()
|
||||
theme.name = name
|
||||
|
||||
themedir = cls.themes[name]
|
||||
themedir = self.themes[name]
|
||||
if path.isdir(themedir):
|
||||
# already a directory, do nothing
|
||||
theme.rootdir = None
|
||||
@ -253,10 +262,10 @@ class Theme(object):
|
||||
|
||||
if inherit == 'none':
|
||||
theme.base = None
|
||||
elif inherit not in cls.themes:
|
||||
elif inherit not in self.themes:
|
||||
raise ThemeError('no theme named %r found, inherited by %r' %
|
||||
(inherit, name))
|
||||
else:
|
||||
theme.base = cls.create(inherit)
|
||||
theme.base = self.create(inherit)
|
||||
|
||||
return theme
|
||||
|
@ -10,14 +10,10 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
import mock
|
||||
import pytest
|
||||
|
||||
from sphinx.theming import Theme, ThemeError
|
||||
|
||||
from util import path
|
||||
from sphinx.theming import ThemeError
|
||||
|
||||
|
||||
@pytest.mark.sphinx(
|
||||
@ -27,12 +23,12 @@ def test_theme_api(app, status, warning):
|
||||
cfg = app.config
|
||||
|
||||
# test Theme class API
|
||||
assert set(Theme.themes.keys()) == \
|
||||
assert set(app.html_themes.keys()) == \
|
||||
set(['basic', 'default', 'scrolls', 'agogo', 'sphinxdoc', 'haiku',
|
||||
'traditional', 'testtheme', 'ziptheme', 'epub', 'nature',
|
||||
'pyramid', 'bizstyle', 'classic', 'nonav'])
|
||||
assert Theme.themes['testtheme'] == app.srcdir / 'testtheme'
|
||||
assert Theme.themes['ziptheme'] == app.srcdir / 'ziptheme.zip'
|
||||
assert app.html_themes['testtheme'] == app.srcdir / 'testtheme'
|
||||
assert app.html_themes['ziptheme'] == app.srcdir / 'ziptheme.zip'
|
||||
|
||||
# test Theme instance API
|
||||
theme = app.builder.theme
|
||||
|
@ -24,7 +24,6 @@ from docutils.parsers.rst import directives, roles
|
||||
|
||||
from sphinx import application
|
||||
from sphinx.builders.latex import LaTeXBuilder
|
||||
from sphinx.theming import Theme
|
||||
from sphinx.ext.autodoc import AutoDirective
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.deprecation import RemovedInSphinx17Warning
|
||||
@ -168,7 +167,6 @@ class SphinxTestApp(application.Sphinx):
|
||||
raise
|
||||
|
||||
def cleanup(self, doctrees=False):
|
||||
Theme.themes.clear()
|
||||
AutoDirective._registry.clear()
|
||||
ModuleAnalyzer.cache.clear()
|
||||
LaTeXBuilder.usepackages = []
|
||||
|
Loading…
Reference in New Issue
Block a user