33from __future__ import annotations
44
55import configparser
6+ import contextlib
67import os
78import shutil
89import sys
1112from typing import TYPE_CHECKING , Any
1213from zipfile import ZipFile
1314
14- if sys .version_info >= (3 , 10 ):
15- from importlib .metadata import entry_points
16- else :
17- from importlib_metadata import entry_points
18-
19- import contextlib
20-
2115from sphinx import package_dir
2216from sphinx .config import check_confval_types as _config_post_init
2317from sphinx .errors import ThemeError
2418from sphinx .locale import __
2519from sphinx .util import logging
2620from sphinx .util .osutil import ensuredir
2721
22+ if sys .version_info >= (3 , 10 ):
23+ from importlib .metadata import entry_points
24+ else :
25+ from importlib_metadata import entry_points
26+
2827if TYPE_CHECKING :
2928 from sphinx .application import Sphinx
3029
30+ __all__ = 'Theme' , 'HTMLThemeFactory'
3131
3232logger = logging .getLogger (__name__ )
3333
34- NODEFAULT = object ()
35- THEMECONF = 'theme.conf'
34+ _NO_DEFAULT = object ()
35+ _THEME_CONF = 'theme.conf'
3636
3737
38- def extract_zip (filename : str , targetdir : str ) -> None :
38+ def _extract_zip (filename : str , target_dir : str , / ) -> None :
3939 """Extract zip file to target directory."""
40- ensuredir (targetdir )
40+ ensuredir (target_dir )
4141
4242 with ZipFile (filename ) as archive :
4343 for name in archive .namelist ():
4444 if name .endswith ('/' ):
4545 continue
46- entry = path .join (targetdir , name )
46+ entry = path .join (target_dir , name )
4747 ensuredir (path .dirname (entry ))
4848 with open (path .join (entry ), 'wb' ) as fp :
4949 fp .write (archive .read (name ))
@@ -55,23 +55,22 @@ class Theme:
5555 This class supports both theme directory and theme archive (zipped theme).
5656 """
5757
58- def __init__ (self , name : str , theme_path : str , factory : HTMLThemeFactory ) -> None :
58+ def __init__ (self , name : str , theme_path : str , theme_factory : HTMLThemeFactory ) -> None :
5959 self .name = name
60- self .base = None
61- self .rootdir = None
60+ self ._base : Theme | None = None
6261
6362 if path .isdir (theme_path ):
6463 # already a directory, do nothing
65- self .rootdir = None
66- self .themedir = theme_path
64+ self ._root_dir = None
65+ self ._theme_dir = theme_path
6766 else :
6867 # extract the theme to a temp directory
69- self .rootdir = tempfile .mkdtemp ('sxt' )
70- self .themedir = path .join (self .rootdir , name )
71- extract_zip (theme_path , self .themedir )
68+ self ._root_dir = tempfile .mkdtemp ('sxt' )
69+ self ._theme_dir = path .join (self ._root_dir , name )
70+ _extract_zip (theme_path , self ._theme_dir )
7271
7372 self .config = configparser .RawConfigParser ()
74- config_file_path = path .join (self .themedir , THEMECONF )
73+ config_file_path = path .join (self ._theme_dir , _THEME_CONF )
7574 if not os .path .isfile (config_file_path ):
7675 raise ThemeError (__ ('theme configuration file %r not found' ) % config_file_path )
7776 self .config .read (config_file_path , encoding = 'utf-8' )
@@ -85,7 +84,7 @@ def __init__(self, name: str, theme_path: str, factory: HTMLThemeFactory) -> Non
8584
8685 if inherit != 'none' :
8786 try :
88- self .base = factory .create (inherit )
87+ self ._base = theme_factory .create (inherit )
8988 except ThemeError as exc :
9089 raise ThemeError (__ ('no theme named %r found, inherited by %r' ) %
9190 (inherit , name )) from exc
@@ -94,33 +93,33 @@ def get_theme_dirs(self) -> list[str]:
9493 """Return a list of theme directories, beginning with this theme's,
9594 then the base theme's, then that one's base theme's, etc.
9695 """
97- if self .base is None :
98- return [self .themedir ]
96+ if self ._base is None :
97+ return [self ._theme_dir ]
9998 else :
100- return [self .themedir ] + self .base .get_theme_dirs ()
99+ return [self ._theme_dir ] + self ._base .get_theme_dirs ()
101100
102- def get_config (self , section : str , name : str , default : Any = NODEFAULT ) -> Any :
101+ def get_config (self , section : str , name : str , default : Any = _NO_DEFAULT ) -> Any :
103102 """Return the value for a theme configuration setting, searching the
104103 base theme chain.
105104 """
106105 try :
107106 return self .config .get (section , name )
108- except (configparser .NoOptionError , configparser .NoSectionError ) as exc :
109- if self .base :
110- return self .base .get_config (section , name , default )
107+ except (configparser .NoOptionError , configparser .NoSectionError ):
108+ if self ._base :
109+ return self ._base .get_config (section , name , default )
111110
112- if default is NODEFAULT :
111+ if default is _NO_DEFAULT :
113112 raise ThemeError (__ ('setting %s.%s occurs in none of the '
114- 'searched theme configs' ) % (section , name )) from exc
113+ 'searched theme configs' ) % (section , name )) from None
115114 return default
116115
117116 def get_options (self , overrides : dict [str , Any ] | None = None ) -> dict [str , Any ]:
118117 """Return a dictionary of theme options and their values."""
119118 if overrides is None :
120119 overrides = {}
121120
122- if self .base :
123- options = self .base .get_options ()
121+ if self ._base :
122+ options = self ._base .get_options ()
124123 else :
125124 options = {}
126125
@@ -135,21 +134,21 @@ def get_options(self, overrides: dict[str, Any] | None = None) -> dict[str, Any]
135134
136135 return options
137136
138- def cleanup (self ) -> None :
137+ def _cleanup (self ) -> None :
139138 """Remove temporary directories."""
140- if self .rootdir :
139+ if self ._root_dir :
141140 with contextlib .suppress (Exception ):
142- shutil .rmtree (self .rootdir )
141+ shutil .rmtree (self ._root_dir )
143142
144- if self .base :
145- self .base . cleanup ()
143+ if self ._base is not None :
144+ self ._base . _cleanup ()
146145
147146
148- def is_archived_theme (filename : str ) -> bool :
147+ def _is_archived_theme (filename : str , / ) -> bool :
149148 """Check whether the specified file is an archived theme file or not."""
150149 try :
151150 with ZipFile (filename ) as f :
152- return THEMECONF in f .namelist ()
151+ return _THEME_CONF in f .namelist ()
153152 except Exception :
154153 return False
155154
@@ -158,27 +157,27 @@ class HTMLThemeFactory:
158157 """A factory class for HTML Themes."""
159158
160159 def __init__ (self , app : Sphinx ) -> None :
161- self .app = app
162- self .themes = app .registry .html_themes
163- self .load_builtin_themes ()
160+ self ._app = app
161+ self ._themes = app .registry .html_themes
162+ self ._load_builtin_themes ()
164163 if getattr (app .config , 'html_theme_path' , None ):
165- self .load_additional_themes (app .config .html_theme_path )
164+ self ._load_additional_themes (app .config .html_theme_path )
166165
167- def load_builtin_themes (self ) -> None :
166+ def _load_builtin_themes (self ) -> None :
168167 """Load built-in themes."""
169- themes = self .find_themes (path .join (package_dir , 'themes' ))
168+ themes = self ._find_themes (path .join (package_dir , 'themes' ))
170169 for name , theme in themes .items ():
171- self .themes [name ] = theme
170+ self ._themes [name ] = theme
172171
173- def load_additional_themes (self , theme_paths : str ) -> None :
172+ def _load_additional_themes (self , theme_paths : str ) -> None :
174173 """Load additional themes placed at specified directories."""
175174 for theme_path in theme_paths :
176- abs_theme_path = path .abspath (path .join (self .app .confdir , theme_path ))
177- themes = self .find_themes (abs_theme_path )
175+ abs_theme_path = path .abspath (path .join (self ._app .confdir , theme_path ))
176+ themes = self ._find_themes (abs_theme_path )
178177 for name , theme in themes .items ():
179- self .themes [name ] = theme
178+ self ._themes [name ] = theme
180179
181- def load_extra_theme (self , name : str ) -> None :
180+ def _load_extra_theme (self , name : str ) -> None :
182181 """Try to load a theme with the specified name.
183182
184183 This uses the ``sphinx.html_themes`` entry point from package metadata.
@@ -189,10 +188,10 @@ def load_extra_theme(self, name: str) -> None:
189188 except KeyError :
190189 pass
191190 else :
192- self .app .registry .load_extension (self .app , entry_point .module )
193- _config_post_init (None , self .app .config )
191+ self ._app .registry .load_extension (self ._app , entry_point .module )
192+ _config_post_init (None , self ._app .config )
194193
195- def find_themes (self , theme_path : str ) -> dict [str , str ]:
194+ def _find_themes (self , theme_path : str ) -> dict [str , str ]:
196195 """Search themes from specified directory."""
197196 themes : dict [str , str ] = {}
198197 if not path .isdir (theme_path ):
@@ -201,24 +200,24 @@ def find_themes(self, theme_path: str) -> dict[str, str]:
201200 for entry in os .listdir (theme_path ):
202201 pathname = path .join (theme_path , entry )
203202 if path .isfile (pathname ) and entry .lower ().endswith ('.zip' ):
204- if is_archived_theme (pathname ):
203+ if _is_archived_theme (pathname ):
205204 name = entry [:- 4 ]
206205 themes [name ] = pathname
207206 else :
208207 logger .warning (__ ('file %r on theme path is not a valid '
209208 'zipfile or contains no theme' ), entry )
210209 else :
211- if path .isfile (path .join (pathname , THEMECONF )):
210+ if path .isfile (path .join (pathname , _THEME_CONF )):
212211 themes [entry ] = pathname
213212
214213 return themes
215214
216215 def create (self , name : str ) -> Theme :
217216 """Create an instance of theme."""
218- if name not in self .themes :
219- self .load_extra_theme (name )
217+ if name not in self ._themes :
218+ self ._load_extra_theme (name )
220219
221- if name not in self .themes :
220+ if name not in self ._themes :
222221 raise ThemeError (__ ('no theme named %r found (missing theme.conf?)' ) % name )
223222
224- return Theme (name , self .themes [name ], factory = self )
223+ return Theme (name , self ._themes [name ], self )
0 commit comments