summaryrefslogtreecommitdiffstats
path: root/sphinx/util/_pathlib.py
blob: 8bb1f311571639d0b6292cacb5e4351afe6f54ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""What follows is awful and will be gone in Sphinx 8"""

from __future__ import annotations

import sys
import warnings
from pathlib import Path, PosixPath, PurePath, WindowsPath
from typing import Any

from sphinx.deprecation import RemovedInSphinx80Warning

_STR_METHODS = frozenset(str.__dict__)
_PATH_NAME = Path().__class__.__name__

_MSG = (
    'Sphinx 8 will drop support for representing paths as strings. '
    'Use "pathlib.Path" or "os.fspath" instead.'
)

# https://docs.python.org/3/library/stdtypes.html#typesseq-common
# https://docs.python.org/3/library/stdtypes.html#string-methods

if sys.platform == 'win32':
    class _StrPath(WindowsPath):
        def replace(  # type: ignore[override]
            self, old: str, new: str, count: int = -1, /,
        ) -> str:
            # replace exists in both Path and str;
            # in Path it makes filesystem changes, so we use the safer str version
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return self.__str__().replace(old, new, count)

        def __getattr__(self, item: str) -> Any:
            if item in _STR_METHODS:
                warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
                return getattr(self.__str__(), item)
            msg = f'{_PATH_NAME!r} has no attribute {item!r}'
            raise AttributeError(msg)

        def __add__(self, other: str) -> str:
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return self.__str__() + other

        def __bool__(self) -> bool:
            if not self.__str__():
                warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
                return False
            return True

        def __contains__(self, item: str) -> bool:
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return item in self.__str__()

        def __eq__(self, other: object) -> bool:
            if isinstance(other, PurePath):
                return super().__eq__(other)
            if isinstance(other, str):
                warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
                return self.__str__() == other
            return NotImplemented

        def __hash__(self) -> int:
            return super().__hash__()

        def __getitem__(self, item: int | slice) -> str:
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return self.__str__()[item]

        def __len__(self) -> int:
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return len(self.__str__())
else:
    class _StrPath(PosixPath):
        def replace(  # type: ignore[override]
            self, old: str, new: str, count: int = -1, /,
        ) -> str:
            # replace exists in both Path and str;
            # in Path it makes filesystem changes, so we use the safer str version
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return self.__str__().replace(old, new, count)

        def __getattr__(self, item: str) -> Any:
            if item in _STR_METHODS:
                warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
                return getattr(self.__str__(), item)
            msg = f'{_PATH_NAME!r} has no attribute {item!r}'
            raise AttributeError(msg)

        def __add__(self, other: str) -> str:
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return self.__str__() + other

        def __bool__(self) -> bool:
            if not self.__str__():
                warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
                return False
            return True

        def __contains__(self, item: str) -> bool:
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return item in self.__str__()

        def __eq__(self, other: object) -> bool:
            if isinstance(other, PurePath):
                return super().__eq__(other)
            if isinstance(other, str):
                warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
                return self.__str__() == other
            return NotImplemented

        def __hash__(self) -> int:
            return super().__hash__()

        def __getitem__(self, item: int | slice) -> str:
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return self.__str__()[item]

        def __len__(self) -> int:
            warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2)
            return len(self.__str__())