-
-
Notifications
You must be signed in to change notification settings - Fork 41
Expand file tree
/
Copy path_internal.py
More file actions
225 lines (169 loc) · 6.93 KB
/
_internal.py
File metadata and controls
225 lines (169 loc) · 6.93 KB
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
"""Internal helper functions used by the rest of the library."""
from __future__ import annotations
import locale
import sys
import warnings
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, AnyStr, Literal, NoReturn, SupportsInt, TypeVar
from typing_extensions import deprecated
from tcod.cffi import ffi, lib
if TYPE_CHECKING:
from pathlib import Path
from types import TracebackType
FuncType = Callable[..., Any]
F = TypeVar("F", bound=FuncType)
T = TypeVar("T")
def _deprecate_passthrough(
message: str, # noqa: ARG001
/,
*,
category: type[Warning] = DeprecationWarning, # noqa: ARG001
stacklevel: int = 0, # noqa: ARG001
) -> Callable[[F], F]:
"""Return a decorator which skips wrapping a warning onto functions. This is used for non-debug runs."""
def decorator(func: F) -> F:
return func
return decorator
deprecate = deprecated if __debug__ or TYPE_CHECKING else _deprecate_passthrough
def verify_order(order: Literal["C", "F"]) -> Literal["C", "F"]:
"""Verify and return a Numpy order string."""
order = order.upper() # type: ignore[assignment]
if order not in ("C", "F"):
msg = f"order must be 'C' or 'F', not {order!r}"
raise TypeError(msg)
return order
def _raise_tcod_error() -> NoReturn:
"""Raise an error from libtcod, this function assumes an error exists."""
raise RuntimeError(ffi.string(lib.TCOD_get_error()).decode("utf-8"))
def _check(error: int) -> int:
"""Detect and convert a libtcod error code into an exception."""
if error < 0:
_raise_tcod_error()
return error
def _check_p(pointer: T) -> T:
"""Treats NULL pointers as errors and raises a libtcod exception."""
if not pointer:
_raise_tcod_error()
return pointer
def _check_warn(error: int, stacklevel: int = 2) -> int:
"""Like _check, but raises a warning on positive error codes."""
if _check(error) > 0:
warnings.warn(
ffi.string(lib.TCOD_get_error()).decode(),
RuntimeWarning,
stacklevel=stacklevel + 1,
)
return error
def _unpack_char_p(char_p: Any) -> str: # noqa: ANN401
if char_p == ffi.NULL:
return ""
return str(ffi.string(char_p), encoding="utf-8")
def _int(int_or_str: SupportsInt | str | bytes) -> int:
"""Return an integer where a single character string may be expected."""
if isinstance(int_or_str, str):
return ord(int_or_str)
if isinstance(int_or_str, bytes):
return int_or_str[0]
return int(int_or_str)
def _bytes(string: AnyStr) -> bytes:
if isinstance(string, str):
return string.encode("utf-8")
return string
def _unicode(string: AnyStr, stacklevel: int = 2) -> str:
if isinstance(string, bytes):
warnings.warn(
"Passing byte strings as parameters to Unicode functions is deprecated.",
FutureWarning,
stacklevel=stacklevel + 1,
)
return string.decode("latin-1")
return str(string)
def _fmt(string: str, stacklevel: int = 2) -> bytes:
if isinstance(string, bytes):
warnings.warn(
"Passing byte strings as parameters to Unicode functions is deprecated.",
FutureWarning,
stacklevel=stacklevel + 1,
)
string = string.decode("latin-1")
return string.encode("utf-8").replace(b"%", b"%%")
def _path_encode(path: Path) -> bytes:
"""Return a bytes file path for the current locale when on Windows, uses fsdecode for other platforms."""
if sys.platform != "win32":
return bytes(path) # Sane and expected behavior for converting Path into bytes
try:
return str(path).encode(locale.getlocale()[1] or "utf-8") # Stay classy, Windows
except UnicodeEncodeError as exc:
if sys.version_info >= (3, 11):
exc.add_note("""Consider calling 'locale.setlocale(locale.LC_CTYPES, ".UTF8")' to support Unicode paths.""")
raise
class _PropagateException:
"""Context manager designed to propagate exceptions outside of a cffi callback context.
Normally cffi suppresses the exception.
When propagate is called this class will hold onto the error until the
control flow leaves the context, then the error will be raised.
with _PropagateException as propagate:
# give propagate as onerror parameter for ffi.def_extern
"""
def __init__(self) -> None:
self.caught: BaseException | None = None
def propagate(self, *exc_info: Any) -> None: # noqa: ANN401
"""Set an exception to be raised once this context exits.
If multiple errors are caught, only keep the first exception raised.
"""
if self.caught is None:
self.caught = exc_info[1]
def __enter__(self) -> Callable[[Any], None]:
"""Once in context, only the propagate call is needed to use this class effectively."""
return self.propagate
def __exit__(
self, _type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
) -> None:
"""If we're holding on to an exception, raise it now.
self.caught is reset now in case of nested manager shenanigans.
"""
to_raise, self.caught = self.caught, None
if to_raise is not None:
raise to_raise from value
class _CDataWrapper:
"""A generally deprecated CData wrapper class used by libtcodpy."""
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401
self.cdata = self._get_cdata_from_args(*args, **kwargs)
if self.cdata is None:
self.cdata = ffi.NULL
super().__init__()
@staticmethod
def _get_cdata_from_args(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401
if len(args) == 1 and isinstance(args[0], ffi.CData) and not kwargs:
return args[0]
return None
def __hash__(self) -> int:
return hash(self.cdata)
def __eq__(self, other: object) -> bool:
if not isinstance(other, _CDataWrapper):
return NotImplemented
return bool(self.cdata == other.cdata)
def __getattr__(self, attr: str) -> Any: # noqa: ANN401
if "cdata" in self.__dict__:
return getattr(self.__dict__["cdata"], attr)
raise AttributeError(attr)
def __setattr__(self, attr: str, value: Any) -> None: # noqa: ANN401
if hasattr(self, "cdata") and hasattr(self.cdata, attr):
setattr(self.cdata, attr, value)
else:
super().__setattr__(attr, value)
def _console(console: Any) -> Any: # noqa: ANN401
"""Return a cffi console pointer."""
try:
return console.console_c
except AttributeError:
warnings.warn(
(
"Falsy console parameters are deprecated, "
"always use the root console instance returned by "
"console_init_root."
),
DeprecationWarning,
stacklevel=3,
)
return ffi.NULL