"""Theming support for LaTeX builder."""
import configparser
from os import path
from typing import Dict
from sphinx.application import Sphinx
from sphinx.config import Config
from sphinx.errors import ThemeError
from sphinx.locale import __
from sphinx.util import logging
logger = logging.getLogger(__name__)
class Theme:
"""A set of LaTeX configurations."""
LATEX_ELEMENTS_KEYS = ['papersize', 'pointsize']
UPDATABLE_KEYS = ['papersize', 'pointsize']
def __init__(self, name: str) -> None:
self.name = name
self.docclass = name
self.wrapperclass = name
self.papersize = 'letterpaper'
self.pointsize = '10pt'
self.toplevel_sectioning = 'chapter'
def update(self, config: Config) -> None:
"""Override theme settings by user's configuration."""
for key in self.LATEX_ELEMENTS_KEYS:
if config.latex_elements.get(key):
value = config.latex_elements[key]
setattr(self, key, value)
for key in self.UPDATABLE_KEYS:
if key in config.latex_theme_options:
value = config.latex_theme_options[key]
setattr(self, key, value)
class BuiltInTheme(Theme):
"""A built-in LaTeX theme."""
def __init__(self, name: str, config: Config) -> None:
super().__init__(name)
if name == 'howto':
self.docclass = config.latex_docclass.get('howto', 'article')
else:
self.docclass = config.latex_docclass.get('manual', 'report')
if name in ('manual', 'howto'):
self.wrapperclass = 'sphinx' + name
else:
self.wrapperclass = name
# we assume LaTeX class provides \chapter command except in case
# of non-Japanese 'howto' case
if name == 'howto' and not self.docclass.startswith('j'):
self.toplevel_sectioning = 'section'
else:
self.toplevel_sectioning = 'chapter'
class UserTheme(Theme):
"""A user defined LaTeX theme."""
REQUIRED_CONFIG_KEYS = ['docclass', 'wrapperclass']
OPTIONAL_CONFIG_KEYS = ['papersize', 'pointsize', 'toplevel_sectioning']
def __init__(self, name: str, filename: str) -> None:
super().__init__(name)
self.config = configparser.RawConfigParser()
self.config.read(path.join(filename))
for key in self.REQUIRED_CONFIG_KEYS:
try:
value = self.config.get('theme', key)
setattr(self, key, value)
except configparser.NoSectionError as exc:
raise ThemeError(__('%r doesn\'t have "theme" setting') %
filename) from exc
except configparser.NoOptionError as exc:
raise ThemeError(__('%r doesn\'t have "%s" setting') %
(filename, exc.args[0])) from exc
for key in self.OPTIONAL_CONFIG_KEYS:
try:
value = self.config.get('theme', key)
setattr(self, key, value)
except configparser.NoOptionError:
pass
class ThemeFactory:
"""A factory class for LaTeX Themes."""
def __init__(self, app: Sphinx) -> None:
self.themes: Dict[str, Theme] = {}
self.theme_paths = [path.join(app.srcdir, p) for p in app.config.latex_theme_path]
self.config = app.config
self.load_builtin_themes(app.config)
def load_builtin_themes(self, config: Config) -> None:
"""Load built-in themes."""
self.themes['manual'] = BuiltInTheme('manual', config)
self.themes['howto'] = BuiltInTheme('howto', config)
def get(self, name: str) -> Theme:
"""Get a theme for given *name*."""
if name in self.themes:
theme = self.themes[name]
else:
theme = self.find_user_theme(name)
if not theme:
theme = Theme(name)
theme.update(self.config)
return theme
def find_user_theme(self, name: str) -> Theme:
"""Find a theme named as *name* from latex_theme_path."""
for theme_path in self.theme_paths:
config_path = path.join(theme_path, name, 'theme.conf')
if path.isfile(config_path):
try:
return UserTheme(name, config_path)
except ThemeError as exc:
logger.warning(exc)
return None