You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
5.5 KiB
146 lines
5.5 KiB
from __future__ import annotations
|
|
|
|
import os
|
|
import warnings
|
|
import zlib
|
|
from typing import TYPE_CHECKING, Any, NoReturn
|
|
|
|
from sphinx.deprecation import RemovedInSphinx90Warning
|
|
from sphinx.errors import ThemeError
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
class _CascadingStyleSheet:
|
|
filename: str | os.PathLike[str]
|
|
priority: int
|
|
attributes: dict[str, str]
|
|
|
|
def __init__(
|
|
self,
|
|
filename: str | os.PathLike[str], /, *,
|
|
priority: int = 500,
|
|
rel: str = 'stylesheet',
|
|
type: str = 'text/css',
|
|
**attributes: str,
|
|
) -> None:
|
|
object.__setattr__(self, 'filename', filename)
|
|
object.__setattr__(self, 'priority', priority)
|
|
object.__setattr__(self, 'attributes', {'rel': rel, 'type': type} | attributes)
|
|
|
|
def __str__(self) -> str:
|
|
attr = ', '.join(f'{k}={v!r}' for k, v in self.attributes.items())
|
|
return (f'{self.__class__.__name__}({self.filename!r}, '
|
|
f'priority={self.priority}, '
|
|
f'{attr})')
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if isinstance(other, str):
|
|
warnings.warn('The str interface for _CascadingStyleSheet objects is deprecated. '
|
|
'Use css.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
|
return self.filename == other
|
|
if not isinstance(other, _CascadingStyleSheet):
|
|
return NotImplemented
|
|
return (self.filename == other.filename
|
|
and self.priority == other.priority
|
|
and self.attributes == other.attributes)
|
|
|
|
def __hash__(self) -> int:
|
|
return hash((self.filename, self.priority, *sorted(self.attributes.items())))
|
|
|
|
def __setattr__(self, key: str, value: Any) -> NoReturn:
|
|
msg = f'{self.__class__.__name__} is immutable'
|
|
raise AttributeError(msg)
|
|
|
|
def __delattr__(self, key: str) -> NoReturn:
|
|
msg = f'{self.__class__.__name__} is immutable'
|
|
raise AttributeError(msg)
|
|
|
|
def __getattr__(self, key: str) -> str:
|
|
warnings.warn('The str interface for _CascadingStyleSheet objects is deprecated. '
|
|
'Use css.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
|
return getattr(os.fspath(self.filename), key)
|
|
|
|
def __getitem__(self, key: int | slice) -> str:
|
|
warnings.warn('The str interface for _CascadingStyleSheet objects is deprecated. '
|
|
'Use css.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
|
return os.fspath(self.filename)[key]
|
|
|
|
|
|
class _JavaScript:
|
|
filename: str | os.PathLike[str]
|
|
priority: int
|
|
attributes: dict[str, str]
|
|
|
|
def __init__(
|
|
self,
|
|
filename: str | os.PathLike[str], /, *,
|
|
priority: int = 500,
|
|
**attributes: str,
|
|
) -> None:
|
|
object.__setattr__(self, 'filename', filename)
|
|
object.__setattr__(self, 'priority', priority)
|
|
object.__setattr__(self, 'attributes', attributes)
|
|
|
|
def __str__(self) -> str:
|
|
attr = ''
|
|
if self.attributes:
|
|
attr = ', ' + ', '.join(f'{k}={v!r}' for k, v in self.attributes.items())
|
|
return (f'{self.__class__.__name__}({self.filename!r}, '
|
|
f'priority={self.priority}'
|
|
f'{attr})')
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if isinstance(other, str):
|
|
warnings.warn('The str interface for _JavaScript objects is deprecated. '
|
|
'Use js.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
|
return self.filename == other
|
|
if not isinstance(other, _JavaScript):
|
|
return NotImplemented
|
|
return (self.filename == other.filename
|
|
and self.priority == other.priority
|
|
and self.attributes == other.attributes)
|
|
|
|
def __hash__(self) -> int:
|
|
return hash((self.filename, self.priority, *sorted(self.attributes.items())))
|
|
|
|
def __setattr__(self, key: str, value: Any) -> NoReturn:
|
|
msg = f'{self.__class__.__name__} is immutable'
|
|
raise AttributeError(msg)
|
|
|
|
def __delattr__(self, key: str) -> NoReturn:
|
|
msg = f'{self.__class__.__name__} is immutable'
|
|
raise AttributeError(msg)
|
|
|
|
def __getattr__(self, key: str) -> str:
|
|
warnings.warn('The str interface for _JavaScript objects is deprecated. '
|
|
'Use js.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
|
return getattr(os.fspath(self.filename), key)
|
|
|
|
def __getitem__(self, key: int | slice) -> str:
|
|
warnings.warn('The str interface for _JavaScript objects is deprecated. '
|
|
'Use js.filename instead.', RemovedInSphinx90Warning, stacklevel=2)
|
|
return os.fspath(self.filename)[key]
|
|
|
|
|
|
def _file_checksum(outdir: Path, filename: str | os.PathLike[str]) -> str:
|
|
filename = os.fspath(filename)
|
|
# Don't generate checksums for HTTP URIs
|
|
if '://' in filename:
|
|
return ''
|
|
# Some themes and extensions have used query strings
|
|
# for a similar asset checksum feature.
|
|
# As we cannot safely strip the query string,
|
|
# raise an error to the user.
|
|
if '?' in filename:
|
|
msg = f'Local asset file paths must not contain query strings: {filename!r}'
|
|
raise ThemeError(msg)
|
|
try:
|
|
# Remove all carriage returns to avoid checksum differences
|
|
content = outdir.joinpath(filename).read_bytes().translate(None, b'\r')
|
|
except FileNotFoundError:
|
|
return ''
|
|
if not content:
|
|
return ''
|
|
return f'{zlib.crc32(content):08x}'
|
|
|