Merge pull request #6956 from tk0miya/refactor_type_annotation3

Migrate to py3 style type annotation: highlighting, jinja2glue, roles and theming
This commit is contained in:
Takeshi KOMIYA
2019-12-25 09:55:50 +09:00
committed by GitHub
4 changed files with 109 additions and 159 deletions

View File

@@ -12,14 +12,17 @@ import html
import warnings
from functools import partial
from importlib import import_module
from typing import Any, Dict
from pygments import highlight
from pygments.filters import ErrorToken
from pygments.formatter import Formatter
from pygments.formatters import HtmlFormatter, LatexFormatter
from pygments.lexer import Lexer
from pygments.lexers import get_lexer_by_name, guess_lexer
from pygments.lexers import PythonLexer, Python3Lexer, PythonConsoleLexer, \
CLexer, TextLexer, RstLexer
from pygments.style import Style
from pygments.styles import get_style_by_name
from pygments.util import ClassNotFound
@@ -29,12 +32,6 @@ from sphinx.locale import __
from sphinx.pygments_styles import SphinxStyle, NoneStyle
from sphinx.util import logging, texescape
if False:
# For type annotation
from typing import Any, Dict # NOQA
from pygments.formatter import Formatter # NOQA
from pygments.style import Style # NOQA
logger = logging.getLogger(__name__)
@@ -67,9 +64,8 @@ class PygmentsBridge:
html_formatter = HtmlFormatter
latex_formatter = LatexFormatter
def __init__(self, dest='html', stylename='sphinx', trim_doctest_flags=None,
latex_engine=None):
# type: (str, str, bool, str) -> None
def __init__(self, dest: str = 'html', stylename: str = 'sphinx',
trim_doctest_flags: bool = None, latex_engine: str = None) -> None:
self.dest = dest
self.latex_engine = latex_engine
@@ -86,8 +82,7 @@ class PygmentsBridge:
warnings.warn('trim_doctest_flags option for PygmentsBridge is now deprecated.',
RemovedInSphinx30Warning, stacklevel=2)
def get_style(self, stylename):
# type: (str) -> Style
def get_style(self, stylename: str) -> Style:
if stylename is None or stylename == 'sphinx':
return SphinxStyle
elif stylename == 'none':
@@ -98,13 +93,11 @@ class PygmentsBridge:
else:
return get_style_by_name(stylename)
def get_formatter(self, **kwargs):
# type: (Any) -> Formatter
def get_formatter(self, **kwargs) -> Formatter:
kwargs.update(self.formatter_args)
return self.formatter(**kwargs)
def unhighlighted(self, source):
# type: (str) -> str
def unhighlighted(self, source: str) -> str:
warnings.warn('PygmentsBridge.unhighlighted() is now deprecated.',
RemovedInSphinx30Warning, stacklevel=2)
if self.dest == 'html':
@@ -117,8 +110,8 @@ class PygmentsBridge:
return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \
source + '\\end{Verbatim}\n'
def get_lexer(self, source, lang, opts=None, force=False, location=None):
# type: (str, str, Dict, bool, Any) -> Lexer
def get_lexer(self, source: str, lang: str, opts: Dict = None,
force: bool = False, location: Any = None) -> Lexer:
if not opts:
opts = {}
@@ -161,8 +154,8 @@ class PygmentsBridge:
return lexer
def highlight_block(self, source, lang, opts=None, force=False, location=None, **kwargs):
# type: (str, str, Dict, bool, Any, Any) -> str
def highlight_block(self, source: str, lang: str, opts: Dict = None,
force: bool = False, location: Any = None, **kwargs) -> str:
if not isinstance(source, str):
source = source.decode()
@@ -196,8 +189,7 @@ class PygmentsBridge:
# MEMO: this is done to escape Unicode chars with non-Unicode engines
return texescape.hlescape(hlsource, self.latex_engine)
def get_stylesheet(self):
# type: () -> str
def get_stylesheet(self) -> str:
formatter = self.get_formatter()
if self.dest == 'html':
return formatter.get_style_defs('.highlight')

View File

@@ -10,42 +10,37 @@
from os import path
from pprint import pformat
from typing import Any, Callable, Iterator, Tuple # NOQA
from typing import Any, Callable, Dict, Iterator, List, Tuple, Union
from jinja2 import FileSystemLoader, BaseLoader, TemplateNotFound, \
contextfunction
from jinja2 import FileSystemLoader, BaseLoader, TemplateNotFound, contextfunction
from jinja2.environment import Environment
from jinja2.sandbox import SandboxedEnvironment
from jinja2.utils import open_if_exists
from sphinx.application import TemplateBridge
from sphinx.theming import Theme
from sphinx.util import logging
from sphinx.util.osutil import mtimes_of_files
if False:
# For type annotation
from typing import Dict, List, Union # NOQA
from jinja2.environment import Environment # NOQA
from sphinx.builders import Builder # NOQA
from sphinx.theming import Theme # NOQA
from sphinx.builders import Builder
def _tobool(val):
# type: (str) -> bool
def _tobool(val: str) -> bool:
if isinstance(val, str):
return val.lower() in ('true', '1', 'yes', 'on')
return bool(val)
def _toint(val):
# type: (str) -> int
def _toint(val: str) -> int:
try:
return int(val)
except ValueError:
return 0
def _todim(val):
# type: (Union[int, str]) -> str
def _todim(val: Union[int, str]) -> str:
"""
Make val a css dimension. In particular the following transformations
are performed:
@@ -63,8 +58,7 @@ def _todim(val):
return val # type: ignore
def _slice_index(values, slices):
# type: (List, int) -> Iterator[List]
def _slice_index(values: List, slices: int) -> Iterator[List]:
seq = list(values)
length = 0
for value in values:
@@ -85,8 +79,7 @@ def _slice_index(values, slices):
yield seq[start:offset]
def accesskey(context, key):
# type: (Any, str) -> str
def accesskey(context: Any, key: str) -> str:
"""Helper to output each access key only once."""
if '_accesskeys' not in context:
context.vars['_accesskeys'] = {}
@@ -97,24 +90,20 @@ def accesskey(context, key):
class idgen:
def __init__(self):
# type: () -> None
def __init__(self) -> None:
self.id = 0
def current(self):
# type: () -> int
def current(self) -> int:
return self.id
def __next__(self):
# type: () -> int
def __next__(self) -> int:
self.id += 1
return self.id
next = __next__ # Python 2/Jinja compatibility
@contextfunction
def warning(context, message, *args, **kwargs):
# type: (Dict, str, Any, Any) -> str
def warning(context: Dict, message: str, *args, **kwargs) -> str:
if 'pagename' in context:
filename = context.get('pagename') + context.get('file_suffix', '')
message = 'in rendering %s: %s' % (filename, message)
@@ -129,8 +118,7 @@ class SphinxFileSystemLoader(FileSystemLoader):
template names.
"""
def get_source(self, environment, template):
# type: (Environment, str) -> Tuple[str, str, Callable]
def get_source(self, environment: Environment, template: str) -> Tuple[str, str, Callable]:
for searchpath in self.searchpath:
filename = path.join(searchpath, template)
f = open_if_exists(filename)
@@ -141,8 +129,7 @@ class SphinxFileSystemLoader(FileSystemLoader):
mtime = path.getmtime(filename)
def uptodate():
# type: () -> bool
def uptodate() -> bool:
try:
return path.getmtime(filename) == mtime
except OSError:
@@ -158,8 +145,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
# TemplateBridge interface
def init(self, builder, theme=None, dirs=None):
# type: (Builder, Theme, List[str]) -> None
def init(self, builder: "Builder", theme: Theme = None, dirs: List[str] = None) -> None:
# create a chain of paths to search
if theme:
# the theme's own dir and its bases' dirs
@@ -202,22 +188,18 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
if use_i18n:
self.environment.install_gettext_translations(builder.app.translator) # type: ignore # NOQA
def render(self, template, context): # type: ignore
# type: (str, Dict) -> str
def render(self, template: str, context: Dict) -> str: # type: ignore
return self.environment.get_template(template).render(context)
def render_string(self, source, context):
# type: (str, Dict) -> str
def render_string(self, source: str, context: Dict) -> str:
return self.environment.from_string(source).render(context)
def newest_template_mtime(self):
# type: () -> float
def newest_template_mtime(self) -> float:
return max(mtimes_of_files(self.pathchain, '.html'))
# Loader interface
def get_source(self, environment, template):
# type: (Environment, str) -> Tuple[str, str, Callable]
def get_source(self, environment: Environment, template: str) -> Tuple[str, str, Callable]:
loaders = self.loaders
# exclamation mark starts search from theme
if template.startswith('!'):

View File

@@ -10,25 +10,27 @@
import re
import warnings
from typing import Any, Dict, List, Tuple
from typing import Type # for python3.5.1
from docutils import nodes, utils
from docutils.nodes import Element, Node, TextElement, system_message
from docutils.parsers.rst.states import Inliner
from sphinx import addnodes
from sphinx.deprecation import RemovedInSphinx40Warning
from sphinx.locale import _
from sphinx.util import ws_re
from sphinx.util.docutils import ReferenceRole, SphinxRole
from sphinx.util.nodes import split_explicit_title, process_index_entry, \
set_role_source_info
from sphinx.util.nodes import (
split_explicit_title, process_index_entry, set_role_source_info
)
from sphinx.util.typing import RoleFunction
if False:
# For type annotation
from typing import Any, Dict, List, Tuple # NOQA
from typing import Type # for python3.5.1
from docutils.parsers.rst.states import Inliner # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.environment import BuildEnvironment # NOQA
from sphinx.util.typing import RoleFunction # NOQA
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
generic_docroles = {
@@ -71,12 +73,12 @@ class XRefRole(ReferenceRole):
* Subclassing and overwriting `process_link()` and/or `result_nodes()`.
"""
nodeclass = addnodes.pending_xref # type: Type[nodes.Element]
innernodeclass = nodes.literal # type: Type[nodes.TextElement]
nodeclass = addnodes.pending_xref # type: Type[Element]
innernodeclass = nodes.literal # type: Type[TextElement]
def __init__(self, fix_parens=False, lowercase=False,
nodeclass=None, innernodeclass=None, warn_dangling=False):
# type: (bool, bool, Type[nodes.Element], Type[nodes.TextElement], bool) -> None
def __init__(self, fix_parens: bool = False, lowercase: bool = False,
nodeclass: Type[Element] = None, innernodeclass: Type[TextElement] = None,
warn_dangling: bool = False) -> None:
self.fix_parens = fix_parens
self.lowercase = lowercase
self.warn_dangling = warn_dangling
@@ -87,8 +89,8 @@ class XRefRole(ReferenceRole):
super().__init__()
def _fix_parens(self, env, has_explicit_title, title, target):
# type: (BuildEnvironment, bool, str, str) -> Tuple[str, str]
def _fix_parens(self, env: "BuildEnvironment", has_explicit_title: bool, title: str,
target: str) -> Tuple[str, str]:
warnings.warn('XRefRole._fix_parens() is deprecated.',
RemovedInSphinx40Warning, stacklevel=2)
if not has_explicit_title:
@@ -103,8 +105,7 @@ class XRefRole(ReferenceRole):
target = target[:-2]
return title, target
def update_title_and_target(self, title, target):
# type: (str, str) -> Tuple[str, str]
def update_title_and_target(self, title: str, target: str) -> Tuple[str, str]:
if not self.has_explicit_title:
if title.endswith('()'):
# remove parentheses
@@ -117,8 +118,7 @@ class XRefRole(ReferenceRole):
target = target[:-2]
return title, target
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
if ':' not in self.name:
self.refdomain, self.reftype = '', self.name
self.classes = ['xref', self.reftype]
@@ -132,8 +132,7 @@ class XRefRole(ReferenceRole):
else:
return self.create_xref_node()
def create_non_xref_node(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def create_non_xref_node(self) -> Tuple[List[Node], List[system_message]]:
text = utils.unescape(self.text[1:])
if self.fix_parens:
self.has_explicit_title = False # treat as implicit
@@ -142,8 +141,7 @@ class XRefRole(ReferenceRole):
node = self.innernodeclass(self.rawtext, text, classes=self.classes)
return self.result_nodes(self.inliner.document, self.env, node, is_ref=False)
def create_xref_node(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def create_xref_node(self) -> Tuple[List[Node], List[system_message]]:
target = self.target
title = self.title
if self.lowercase:
@@ -170,8 +168,8 @@ class XRefRole(ReferenceRole):
# methods that can be overwritten
def process_link(self, env, refnode, has_explicit_title, title, target):
# type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str]
def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,
title: str, target: str) -> Tuple[str, str]:
"""Called after parsing title and target text, and creating the
reference node (given in *refnode*). This method can alter the
reference node and must return a new (or the same) ``(title, target)``
@@ -179,8 +177,8 @@ class XRefRole(ReferenceRole):
"""
return title, ws_re.sub(' ', target)
def result_nodes(self, document, env, node, is_ref):
# type: (nodes.document, BuildEnvironment, nodes.Element, bool) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def result_nodes(self, document: nodes.document, env: "BuildEnvironment", node: Element,
is_ref: bool) -> Tuple[List[Node], List[system_message]]:
"""Called before returning the finished nodes. *node* is the reference
node if one was created (*is_ref* is then true), else the content node.
This method can add other nodes and must return a ``(nodes, messages)``
@@ -190,16 +188,17 @@ class XRefRole(ReferenceRole):
class AnyXRefRole(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title, target):
# type: (BuildEnvironment, nodes.Element, bool, str, str) -> Tuple[str, str]
def process_link(self, env: "BuildEnvironment", refnode: Element, has_explicit_title: bool,
title: str, target: str) -> Tuple[str, str]:
result = super().process_link(env, refnode, has_explicit_title, title, target)
# add all possible context info (i.e. std:program, py:module etc.)
refnode.attributes.update(env.ref_context)
return result
def indexmarkup_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def indexmarkup_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
"""Role for PEP/RFC references that generate an index entry."""
warnings.warn('indexmarkup_role() is deprecated. Please use PEP or RFC class instead.',
RemovedInSphinx40Warning, stacklevel=2)
@@ -267,8 +266,7 @@ def indexmarkup_role(typ, rawtext, text, lineno, inliner, options={}, content=[]
class PEP(ReferenceRole):
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
target_id = 'index-%s' % self.env.new_serialno('index')
entries = [('single', _('Python Enhancement Proposals; PEP %s') % self.target,
target_id, '', None)]
@@ -293,8 +291,7 @@ class PEP(ReferenceRole):
return [index, target, reference], []
def build_uri(self):
# type: () -> str
def build_uri(self) -> str:
base_url = self.inliner.document.settings.pep_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
@@ -304,8 +301,7 @@ class PEP(ReferenceRole):
class RFC(ReferenceRole):
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def run(self) -> Tuple[List[Node], List[system_message]]:
target_id = 'index-%s' % self.env.new_serialno('index')
entries = [('single', 'RFC; RFC %s' % self.target, target_id, '', None)]
@@ -329,8 +325,7 @@ class RFC(ReferenceRole):
return [index, target, reference], []
def build_uri(self):
# type: () -> str
def build_uri(self) -> str:
base_url = self.inliner.document.settings.rfc_base_url
ret = self.target.split('#', 1)
if len(ret) == 2:
@@ -342,8 +337,9 @@ class RFC(ReferenceRole):
_amp_re = re.compile(r'(?<!&)&(?![&\s])')
def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def menusel_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
warnings.warn('menusel_role() is deprecated. '
'Please use MenuSelection or GUILabel class instead.',
RemovedInSphinx40Warning, stacklevel=2)
@@ -382,8 +378,7 @@ def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
class GUILabel(SphinxRole):
amp_re = re.compile(r'(?<!&)&(?![&\s])')
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
node = nodes.inline(rawtext=self.rawtext, classes=[self.name])
spans = self.amp_re.split(self.text)
node += nodes.Text(spans.pop(0))
@@ -399,8 +394,7 @@ class GUILabel(SphinxRole):
class MenuSelection(GUILabel):
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
self.text = self.text.replace('-->', '\N{TRIANGULAR BULLET}')
return super().run()
@@ -409,9 +403,9 @@ _litvar_re = re.compile('{([^}]+)}')
parens_re = re.compile(r'(\\*{|\\*})')
def emph_literal_role(typ, rawtext, text, lineno, inliner,
options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def emph_literal_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
warnings.warn('emph_literal_role() is deprecated. '
'Please use EmphasizedLiteral class instead.',
RemovedInSphinx40Warning, stacklevel=2)
@@ -465,17 +459,15 @@ def emph_literal_role(typ, rawtext, text, lineno, inliner,
class EmphasizedLiteral(SphinxRole):
parens_re = re.compile(r'(\\\\|\\{|\\}|{|})')
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
children = self.parse(self.text)
node = nodes.literal(self.rawtext, '', *children,
role=self.name.lower(), classes=[self.name])
return [node], []
def parse(self, text):
# type: (str) -> List[nodes.Node]
result = [] # type: List[nodes.Node]
def parse(self, text: str) -> List[Node]:
result = [] # type: List[Node]
stack = ['']
for part in self.parens_re.split(text):
@@ -517,8 +509,9 @@ class EmphasizedLiteral(SphinxRole):
_abbr_re = re.compile(r'\((.*)\)$', re.S)
def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def abbr_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
warnings.warn('abbr_role() is deprecated. Please use Abbrevation class instead.',
RemovedInSphinx40Warning, stacklevel=2)
text = utils.unescape(text)
@@ -535,8 +528,7 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
class Abbreviation(SphinxRole):
abbr_re = re.compile(r'\((.*)\)$', re.S)
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
matched = self.abbr_re.search(self.text)
if matched:
text = self.text[:matched.start()].strip()
@@ -547,8 +539,9 @@ class Abbreviation(SphinxRole):
return [nodes.abbreviation(self.rawtext, text, **self.options)], []
def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
# type: (str, str, str, int, Inliner, Dict, List[str]) -> Tuple[List[nodes.Node], List[nodes.system_message]] # NOQA
def index_role(typ: str, rawtext: str, text: str, lineno: int, inliner: Inliner,
options: Dict = {}, content: List[str] = []
) -> Tuple[List[Node], List[system_message]]:
warnings.warn('index_role() is deprecated. Please use Index class instead.',
RemovedInSphinx40Warning, stacklevel=2)
# create new reference target
@@ -579,8 +572,7 @@ def index_role(typ, rawtext, text, lineno, inliner, options={}, content=[]):
class Index(ReferenceRole):
def run(self):
# type: () -> Tuple[List[nodes.Node], List[nodes.system_message]]
def run(self) -> Tuple[List[Node], List[system_message]]:
target_id = 'index-%s' % self.env.new_serialno('index')
if self.has_explicit_title:
# if an explicit target is given, process it as a full entry
@@ -619,8 +611,7 @@ specific_docroles = {
} # type: Dict[str, RoleFunction]
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
def setup(app: "Sphinx") -> Dict[str, Any]:
from docutils.parsers.rst import roles
for rolename, nodeclass in generic_docroles.items():

View File

@@ -13,6 +13,7 @@ import os
import shutil
import tempfile
from os import path
from typing import Any, Dict, List
from zipfile import ZipFile
import pkg_resources
@@ -23,19 +24,18 @@ from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.osutil import ensuredir
logger = logging.getLogger(__name__)
if False:
# For type annotation
from typing import Any, Dict, List # NOQA
from sphinx.application import Sphinx # NOQA
from sphinx.application import Sphinx
logger = logging.getLogger(__name__)
NODEFAULT = object()
THEMECONF = 'theme.conf'
def extract_zip(filename, targetdir):
# type: (str, str) -> None
def extract_zip(filename: str, targetdir: str) -> None:
"""Extract zip file to target directory."""
ensuredir(targetdir)
@@ -54,8 +54,7 @@ class Theme:
This class supports both theme directory and theme archive (zipped theme)."""
def __init__(self, name, theme_path, factory):
# type: (str, str, HTMLThemeFactory) -> None
def __init__(self, name: str, theme_path: str, factory: "HTMLThemeFactory") -> None:
self.name = name
self.base = None
self.rootdir = None
@@ -87,8 +86,7 @@ class Theme:
raise ThemeError(__('no theme named %r found, inherited by %r') %
(inherit, name))
def get_theme_dirs(self):
# type: () -> List[str]
def get_theme_dirs(self) -> List[str]:
"""Return a list of theme directories, beginning with this theme's,
then the base theme's, then that one's base theme's, etc.
"""
@@ -97,8 +95,7 @@ class Theme:
else:
return [self.themedir] + self.base.get_theme_dirs()
def get_config(self, section, name, default=NODEFAULT):
# type: (str, str, Any) -> Any
def get_config(self, section: str, name: str, default: Any = NODEFAULT) -> Any:
"""Return the value for a theme configuration setting, searching the
base theme chain.
"""
@@ -114,8 +111,7 @@ class Theme:
else:
return default
def get_options(self, overrides={}):
# type: (Dict[str, Any]) -> Dict[str, Any]
def get_options(self, overrides: Dict[str, Any] = {}) -> Dict[str, Any]:
"""Return a dictionary of theme options and their values."""
if self.base:
options = self.base.get_options()
@@ -135,8 +131,7 @@ class Theme:
return options
def cleanup(self):
# type: () -> None
def cleanup(self) -> None:
"""Remove temporary directories."""
if self.rootdir:
try:
@@ -147,8 +142,7 @@ class Theme:
self.base.cleanup()
def is_archived_theme(filename):
# type: (str) -> bool
def is_archived_theme(filename: str) -> bool:
"""Check the specified file is an archived theme file or not."""
try:
with ZipFile(filename) as f:
@@ -160,23 +154,20 @@ def is_archived_theme(filename):
class HTMLThemeFactory:
"""A factory class for HTML Themes."""
def __init__(self, app):
# type: (Sphinx) -> None
def __init__(self, app: "Sphinx") -> None:
self.app = app
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)
def load_builtin_themes(self):
# type: () -> None
def load_builtin_themes(self) -> None:
"""Load built-in themes."""
themes = self.find_themes(path.join(package_dir, 'themes'))
for name, theme in themes.items():
self.themes[name] = theme
def load_additional_themes(self, theme_paths):
# type: (str) -> None
def load_additional_themes(self, theme_paths: str) -> None:
"""Load additional themes placed at specified directories."""
for theme_path in theme_paths:
abs_theme_path = path.abspath(path.join(self.app.confdir, theme_path))
@@ -184,8 +175,7 @@ class HTMLThemeFactory:
for name, theme in themes.items():
self.themes[name] = theme
def load_extra_theme(self, name):
# type: (str) -> None
def load_extra_theme(self, name: str) -> None:
"""Try to load a theme having specifed name."""
if name == 'alabaster':
self.load_alabaster_theme()
@@ -194,14 +184,12 @@ class HTMLThemeFactory:
else:
self.load_external_theme(name)
def load_alabaster_theme(self):
# type: () -> None
def load_alabaster_theme(self) -> None:
"""Load alabaster theme."""
import alabaster
self.themes['alabaster'] = path.join(alabaster.get_path(), 'alabaster')
def load_sphinx_rtd_theme(self):
# type: () -> None
def load_sphinx_rtd_theme(self) -> None:
"""Load sphinx_rtd_theme theme (if exists)."""
try:
import sphinx_rtd_theme
@@ -210,8 +198,7 @@ class HTMLThemeFactory:
except ImportError:
pass
def load_external_theme(self, name):
# type: (str) -> None
def load_external_theme(self, name: str) -> None:
"""Try to load a theme using entry_points.
Sphinx refers to ``sphinx_themes`` entry_points.
@@ -225,8 +212,7 @@ class HTMLThemeFactory:
except StopIteration:
pass
def find_themes(self, theme_path):
# type: (str) -> Dict[str, str]
def find_themes(self, theme_path: str) -> Dict[str, str]:
"""Search themes from specified directory."""
themes = {} # type: Dict[str, str]
if not path.isdir(theme_path):
@@ -247,8 +233,7 @@ class HTMLThemeFactory:
return themes
def create(self, name):
# type: (str) -> Theme
def create(self, name: str) -> Theme:
"""Create an instance of theme."""
if name not in self.themes:
self.load_extra_theme(name)