"""Theming support for LaTeX builder.""" from __future__ import annotations import configparser from os import path from typing import TYPE_CHECKING from sphinx.errors import ThemeError from sphinx.locale import __ from sphinx.util import logging if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.config import Config 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), encoding='utf-8') 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) or Theme(name) theme.update(self.config) return theme def find_user_theme(self, name: str) -> Theme | None: """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