diff --git a/.gitignore b/.gitignore index cfb0ee0..3e39b2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +build/ +PySDL3.egg-info/ shaders/ sdl3/bin/ docs/temp/ diff --git a/pyproject.toml b/pyproject.toml index 45c203c..9eef53e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,4 +37,18 @@ include-package-data = true "sdl3" = ["*.py"] [tool.setuptools.dynamic] -version = {attr = "sdl3.__version__"} \ No newline at end of file +version = {attr = "sdl3.__version__"} + +[tool.pyright] +pythonVersion = "3.10" + +[tool.ruff] +target-version = "py310" + +[tool.ruff.lint.flake8-quotes] +inline-quotes = "double" +docstring-quotes = "double" + +[tool.ruff.format] +quote-style = "double" +indent-style = "tab" diff --git a/sdl3/__init__.py b/sdl3/__init__.py index 4717d0b..63d8805 100644 --- a/sdl3/__init__.py +++ b/sdl3/__init__.py @@ -2,26 +2,45 @@ __version__ = "0.9.8b6" -import sys, os, requests, ctypes, ctypes.util, platform, keyword, inspect, collections.abc as abc, \ - asyncio, aiohttp, zipfile, typing, types, array, importlib, atexit, packaging.version, json, re +import sys +import os +import requests +import ctypes +import ctypes.util +import platform +import keyword +import inspect # noqa: F401 - We pass the import to other files +import collections.abc as abc +import asyncio +import aiohttp +import zipfile +import typing +import types +import array # noqa: F401 - We pass the import to other files +import atexit # noqa: F401 - We pass the import to other files +import packaging.version +import json +import re + +from sdl3.SDL import SDL_VERSIONNUM_MAJOR, SDL_VERSIONNUM_MINOR, SDL_VERSIONNUM_MICRO, SDL_GetVersion, IMG_Version, Mix_Version, TTF_Version, RTF_Version, NET_GetVersion, SDL_VERSION, SDL_IMAGE_VERSION, SDL_MIXER_VERSION, SDL_TTF_VERSION, SDL_RTF_VERSION, SDL_NET_VERSION SDL_BINARY, SDL_IMAGE_BINARY, SDL_MIXER_BINARY, SDL_TTF_BINARY, SDL_RTF_BINARY, SDL_NET_BINARY, SDL_SHADERCROSS_BINARY, \ - *SDL_MODULES = ["SDL3", "SDL3_image", "SDL3_mixer", "SDL3_ttf", "SDL3_rtf", "SDL3_net", "SDL3_shadercross"] * 2 + *SDL_MODULES = ["SDL3", "SDL3_image", "SDL3_mixer", "SDL3_ttf", "SDL3_rtf", "SDL3_net", "SDL3_shadercross"] * 2 def SDL_FORMAT_ARCH(arch: str) -> str: - """Format the architecture string.""" - if arch.lower() in ["x64", "x86_64", "amd64"]: return "AMD64" - if arch.lower() in ["aarch64", "arm64"]: return "ARM64" - assert False, "Unknown architecture." + """Format the architecture string.""" + if arch.lower() in ["x64", "x86_64", "amd64"]: return "AMD64" + if arch.lower() in ["aarch64", "arm64"]: return "ARM64" + assert False, "Unknown architecture." SDL_SYSTEM, SDL_ARCH = platform.system(), SDL_FORMAT_ARCH(platform.machine()) SDL_BINARY_EXTENSIONS, SDL_BINARY_PATTERNS = {"Windows": ["dll"], "Darwin": ["dylib"], "Linux": ["so"]}, \ - {"Windows": ["{}.dll"], "Darwin": ["lib{}.dylib", "{0}.framework/{0}", "{0}.framework/Versions/Current/{0}"], "Linux": ["lib{}.so"]} + {"Windows": ["{}.dll"], "Darwin": ["lib{}.dylib", "{0}.framework/{0}", "{0}.framework/Versions/Current/{0}"], "Linux": ["lib{}.so"]} def SDL_PLATFORM_SPECIFIC(system: list[str] | None = None, arch: list[str] | None = None) -> bool: - """Check if the current platform is inside the given platforms.""" - if int(os.environ.get("SDL_PLATFORM_AGNOSTIC", "0")) > 0: return True - return (not system or SDL_SYSTEM in system) and (not arch or SDL_ARCH in arch) + """Check if the current platform is inside the given platforms.""" + if int(os.environ.get("SDL_PLATFORM_AGNOSTIC", "0")) > 0: return True + return (not system or SDL_SYSTEM in system) and (not arch or SDL_ARCH in arch) __release__: bool = not int(os.environ.get("SDL_DEBUG", "0")) > 0 __frozen__: bool = getattr(sys, "frozen", False) or getattr(sys, "_MEIPASS", None) is not None @@ -33,727 +52,720 @@ def SDL_PLATFORM_SPECIFIC(system: list[str] | None = None, arch: list[str] | Non __module__: types.ModuleType = sys.modules[__name__.split(".")[0] if "." in __name__ else __name__] class SDL_LOGGER: - Info, Warning, Error = range(3) - __level__: int = int(os.environ.get("SDL_LOG_LEVEL", "0")) - __colors__: dict[int, str] = {Info: "\33[32m", Warning: "\33[35m", Error: "\33[31m"} - __names__: dict[int, str] = {Info: "Info", Warning: "Warning", Error: "Error"} + Info, Warning, Error = range(3) + __level__: int = int(os.environ.get("SDL_LOG_LEVEL", "0")) + __colors__: dict[int, str] = {Info: "\33[32m", Warning: "\33[35m", Error: "\33[31m"} + __names__: dict[int, str] = {Info: "Info", Warning: "Warning", Error: "Error"} - @classmethod - def Log(cls, level: int, message: str | None = None, end: str | None = "\n") -> None: - """Log a message at a specific level.""" - assert level in [cls.Error, cls.Warning, cls.Info], "Unknown log level." - if cls.__level__ is None or level < cls.__level__: return + @classmethod + def Log(cls, level: int, message: str | None = None, end: str | None = "\n") -> None: + """Log a message at a specific level.""" + assert level in [cls.Error, cls.Warning, cls.Info], "Unknown log level." + if cls.__level__ is None or level < cls.__level__: return - if message is not None: - sys.stdout.write(f"{cls.__colors__[level]}{cls.__names__[level]}: {message}\33[0m") + if message is not None: + sys.stdout.write(f"{cls.__colors__[level]}{cls.__names__[level]}: {message}\33[0m") - if end is not None: - sys.stdout.write(end) + if end is not None: + sys.stdout.write(end) - sys.stdout.flush() + sys.stdout.flush() def SDL_FIND_BINARIES(libraries: list[str]) -> list[str]: - """Search for binaries in the system libraries.""" - libraries = libraries + [f"{library}d" for library in libraries] - binaries = [(f"./{file}" if SDL_SYSTEM in ["Windows"] and not ("/" in file or "\\" in file) else file) for library in libraries if (file := ctypes.util.find_library(library))] + """Search for binaries in the system libraries.""" + libraries = libraries + [f"{library}d" for library in libraries] + binaries = [(f"./{file}" if SDL_SYSTEM in ["Windows"] and not ("/" in file or "\\" in file) else file) for library in libraries if (file := ctypes.util.find_library(library))] - if SDL_SYSTEM in ["Darwin"] and SDL_ARCH in ["ARM64"] and os.path.exists(path := "/opt/Homebrew/lib"): - binaries += [file for library in libraries for pattern in SDL_BINARY_PATTERNS[SDL_SYSTEM] if os.path.exists(file := os.path.join(path, pattern.format(library)))] + if SDL_SYSTEM in ["Darwin"] and SDL_ARCH in ["ARM64"] and os.path.exists(path := "/opt/Homebrew/lib"): + binaries += [file for library in libraries for pattern in SDL_BINARY_PATTERNS[SDL_SYSTEM] if os.path.exists(file := os.path.join(path, pattern.format(library)))] - return binaries + return binaries -def SDL_DOWNLOAD_BINARIES(path: str, system: str = SDL_SYSTEM, arch: str = SDL_ARCH) -> bool: - """Download specific SDL3 binaries to the given path.""" - assert system.capitalize() in ["Darwin", "Linux", "Windows"], "Unknown system." - assert arch.upper() in ["AMD64", "ARM64"], "Unknown architecture." - if not os.path.exists(path): os.makedirs(path) +def SDL_DOWNLOAD_BINARIES(path: str, system: str = SDL_SYSTEM, arch: str = SDL_ARCH) -> bool | None: + """Download specific SDL3 binaries to the given path.""" + assert system.capitalize() in ["Darwin", "Linux", "Windows"], "Unknown system." + assert arch.upper() in ["AMD64", "ARM64"], "Unknown architecture." + if not os.path.exists(path): os.makedirs(path) - headers = {"Accept": "application/vnd.github+json"} + headers = {"Accept": "application/vnd.github+json"} - if len(os.environ.get("SDL_GITHUB_TOKEN", "")) != 0: - headers["Authorization"] = f"Bearer {os.environ['SDL_GITHUB_TOKEN']}" + if len(os.environ.get("SDL_GITHUB_TOKEN", "")) != 0: + headers["Authorization"] = f"Bearer {os.environ['SDL_GITHUB_TOKEN']}" - try: - for release in (_ := requests.get("https://api.github.com/repos/Aermoss/PySDL3-Build/releases", headers = headers).json()): - if isinstance(release, str): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to download binaries: {_[release]}") - return False + try: + for release in (_ := requests.get("https://api.github.com/repos/Aermoss/PySDL3-Build/releases", headers = headers).json()): + if isinstance(release, str): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to download binaries: {_[release]}") + return False - if release["draft"] or release["prerelease"]: - continue + if release["draft"] or release["prerelease"]: + continue - for asset in release["assets"]: - if asset["name"] != f"{system.capitalize()}-{arch.upper()}-{release['tag_name']}.zip": - continue + for asset in release["assets"]: + if asset["name"] != f"{system.capitalize()}-{arch.upper()}-{release['tag_name']}.zip": + continue - with requests.get(asset["browser_download_url"], headers = headers, stream = True) as response: - if response.status_code != 200: - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to download binaries, status: {response.status_code} ({response.reason}).") - return False + with requests.get(asset["browser_download_url"], headers = headers, stream = True) as response: + if response.status_code != 200: + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to download binaries, status: {response.status_code} ({response.reason}).") + return False - size, current = int(response.headers.get("content-length", 0)), 0 + size, current = int(response.headers.get("content-length", 0)), 0 - with open(os.path.join(path, asset["name"]), "wb") as file: - for chunk in response.iter_content(chunk_size = 8192): - if chunk: file.write(chunk); current += len(chunk) - SDL_LOGGER.Log(SDL_LOGGER.Info, f"Downloading '{asset['browser_download_url']}'... {current / size * 100:.1f}% ({current / (1024 ** 2):.2f}/{size / (1024 ** 2):.2f} MB).", end = "\r") + with open(os.path.join(path, asset["name"]), "wb") as file: + for chunk in response.iter_content(chunk_size = 8192): + if chunk: file.write(chunk); current += len(chunk) + SDL_LOGGER.Log(SDL_LOGGER.Info, f"Downloading '{asset['browser_download_url']}'... {current / size * 100:.1f}% ({current / (1024 ** 2):.2f}/{size / (1024 ** 2):.2f} MB).", end = "\r") - SDL_LOGGER.Log(SDL_LOGGER.Info, end = "\n") + SDL_LOGGER.Log(SDL_LOGGER.Info, end = "\n") - data = { - "arch": arch, "system": system, "target": f"v{__version__}", - "version": release["tag_name"], "url": asset["browser_download_url"], - "created": asset["created_at"], "updated": asset["updated_at"], - "uploader": asset["uploader"]["login"], "files": [], - "repair": True, "find": True - } + data = { + "arch": arch, "system": system, "target": f"v{__version__}", + "version": release["tag_name"], "url": asset["browser_download_url"], + "created": asset["created_at"], "updated": asset["updated_at"], + "uploader": asset["uploader"]["login"], "files": [], + "repair": True, "find": True + } - with zipfile.ZipFile(os.path.join(path, asset["name"]), "r") as ref: - for name in ref.namelist(): - data["files"].append(f"./{name}") + with zipfile.ZipFile(os.path.join(path, asset["name"]), "r") as ref: + for name in ref.namelist(): + data["files"].append(f"./{name}") - if os.path.exists(os.path.join(path, name)): - os.remove(os.path.join(path, name)) + if os.path.exists(os.path.join(path, name)): + os.remove(os.path.join(path, name)) - ref.extractall(path) + ref.extractall(path) - with open(os.path.join(path, "metadata.json"), "w") as file: - json.dump(data, file, indent = 4) + with open(os.path.join(path, "metadata.json"), "w") as file: + json.dump(data, file, indent = 4) - os.remove(os.path.join(path, asset["name"])) - return True + os.remove(os.path.join(path, asset["name"])) + return True - SDL_LOGGER.Log(SDL_LOGGER.Error, "Failed to download binaries, no binaries found.") - return False + SDL_LOGGER.Log(SDL_LOGGER.Error, "Failed to download binaries, no binaries found.") + return False - except requests.RequestException as exc: - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to download binaries: {exc}.") - return False + except requests.RequestException as exc: + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to download binaries: {exc}.") + return False def SDL_CHECK_BINARY_NAME(name: str) -> bool: - """Check if the given name is a valid binary name in the current system.""" + """Check if the given name is a valid binary name in the current system.""" - if len(_ := name.split(".")) > 1: - return _[1] in SDL_BINARY_EXTENSIONS[SDL_SYSTEM] + if len(_ := name.split(".")) > 1: + return _[1] in SDL_BINARY_EXTENSIONS[SDL_SYSTEM] - else: - return SDL_SYSTEM in ["Darwin"] + else: + return SDL_SYSTEM in ["Darwin"] if not __initialized__: - if int(os.environ.get("SDL_RELOAD_FIX", "1")) > 0: - reloaded = False + if int(os.environ.get("SDL_RELOAD_FIX", "1")) > 0: + reloaded = False - for module in sys.modules.copy(): - if module.startswith("sdl3."): - del sys.modules[module] - reloaded = True + for module in sys.modules.copy(): + if module.startswith("sdl3."): + del sys.modules[module] + reloaded = True - if "sdl3" in sys.modules and reloaded: - del sys.modules["sdl3"] + if "sdl3" in sys.modules and reloaded: + del sys.modules["sdl3"] - if int(os.environ.get("SDL_CHECK_VERSION", "0" if __frozen__ else "1")) > 0: - try: - version = requests.get(f"https://pypi.org/pypi/PySDL3/json", timeout = 0.5).json()["info"]["version"] + if int(os.environ.get("SDL_CHECK_VERSION", "0" if __frozen__ else "1")) > 0: + try: + version = requests.get("https://pypi.org/pypi/PySDL3/json", timeout = 0.5).json()["info"]["version"] - if packaging.version.parse(__version__) < packaging.version.parse(version): - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"You are using an older version of PySDL3 (current: {__version__}, latest: {version}).") + if packaging.version.parse(__version__) < packaging.version.parse(version): + SDL_LOGGER.Log(SDL_LOGGER.Warning, f"You are using an older version of PySDL3 (current: {__version__}, latest: {version}).") - except requests.RequestException: - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Failed to check for the latest version of PySDL3, please check manually.") + except requests.RequestException: + SDL_LOGGER.Log(SDL_LOGGER.Warning, "Failed to check for the latest version of PySDL3, please check manually.") - functions, binaryMap = {module: {} for module in SDL_MODULES}, {} - binaryData, missing = {"system": SDL_SYSTEM, "arch": SDL_ARCH, "files": []}, None - binaryPath = os.environ.get("SDL_BINARY_PATH", os.path.join(os.path.dirname(__file__), "bin")) - absPath = lambda path: path if os.path.isabs(path) else os.path.abspath(os.path.join(binaryPath, path)) + functions, binaryMap = {module: {} for module in SDL_MODULES}, {} + binaryData, missing = {"system": SDL_SYSTEM, "arch": SDL_ARCH, "files": []}, None + binaryPath = os.environ.get("SDL_BINARY_PATH", os.path.join(os.path.dirname(__file__), "bin")) + absPath = lambda path: path if os.path.isabs(path) else os.path.abspath(os.path.join(binaryPath, path)) - if not os.path.exists(binaryPath): - try: - os.makedirs(binaryPath) + if not os.path.exists(binaryPath): + try: + os.makedirs(binaryPath) - except OSError as exc: - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to create binary path: {exc}.") + except OSError as exc: + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to create binary path: {exc}.") - if int(os.environ.get("SDL_DISABLE_METADATA", "0")) > 0: - if os.path.exists(binaryPath) and os.path.isdir(binaryPath): - for root, _, files in os.walk(binaryPath): - binaryData["files"] += [os.path.join(root, file) for file in files if SDL_CHECK_BINARY_NAME(file)] + if int(os.environ.get("SDL_DISABLE_METADATA", "0")) > 0: + if os.path.exists(binaryPath) and os.path.isdir(binaryPath): + for root, _, files in os.walk(binaryPath): + binaryData["files"] += [os.path.join(root, file) for file in files if SDL_CHECK_BINARY_NAME(file)] - else: - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Binary path does not exist.") + else: + SDL_LOGGER.Log(SDL_LOGGER.Warning, "Binary path does not exist.") - elif "metadata.json" in (files := os.listdir(binaryPath)): - with open(os.path.join(binaryPath, "metadata.json"), "r") as file: - binaryData, missing = json.load(file), True - binaryData["files"] = [absPath(path) for path in binaryData["files"]] + elif "metadata.json" in (files := os.listdir(binaryPath)): + with open(os.path.join(binaryPath, "metadata.json"), "r") as file: + binaryData, missing = json.load(file), True + binaryData["files"] = [absPath(path) for path in binaryData["files"]] - if packaging.version.parse(__version__) > packaging.version.parse(binaryData.get("target", __version__)): - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Incompatible target version detected: '{binaryData['target']}', current: 'v{__version__}'.") + if packaging.version.parse(__version__) > packaging.version.parse(binaryData.get("target", __version__)): + SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Incompatible target version detected: '{binaryData['target']}', current: 'v{__version__}'.") - elif binaryData["system"].lower() != SDL_SYSTEM.lower() or binaryData["arch"].lower() != SDL_ARCH.lower(): - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Incompatible binary architecture and/or system detected: '{binaryData['system']} ({binaryData['arch']})'.") - binaryData["repair"] = True + elif binaryData["system"].lower() != SDL_SYSTEM.lower() or binaryData["arch"].lower() != SDL_ARCH.lower(): + SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Incompatible binary architecture and/or system detected: '{binaryData['system']} ({binaryData['arch']})'.") + binaryData["repair"] = True - for path in binaryData["files"]: - if os.path.exists(path): os.remove(path) + for path in binaryData["files"]: + if os.path.exists(path): os.remove(path) - else: - if missing := [path for path in binaryData["files"] if not os.path.exists(path)]: - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Missing binary file(s) detected: '{', '.join(missing)}'.") + else: + if missing := [path for path in binaryData["files"] if not os.path.exists(path)]: + SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Missing binary file(s) detected: '{', '.join(missing)}'.") - else: - SDL_LOGGER.Log(SDL_LOGGER.Warning, "No metadata detected.") - missing = True + else: + SDL_LOGGER.Log(SDL_LOGGER.Warning, "No metadata detected.") + missing = True - if int(os.environ.get("SDL_DOWNLOAD_BINARIES", "1" if binaryData.get("repair", True) else "0")) > 0 and missing: - if _ := SDL_DOWNLOAD_BINARIES(binaryPath, SDL_SYSTEM, SDL_ARCH): - with open(os.path.join(binaryPath, "metadata.json"), "r") as file: - binaryData, missing = json.load(file), None - binaryData["files"] = [absPath(path) for path in binaryData["files"]] + if int(os.environ.get("SDL_DOWNLOAD_BINARIES", "1" if binaryData.get("repair", True) else "0")) > 0 and missing: + if _ := SDL_DOWNLOAD_BINARIES(binaryPath, SDL_SYSTEM, SDL_ARCH): + with open(os.path.join(binaryPath, "metadata.json"), "r") as file: + binaryData, missing = json.load(file), None + binaryData["files"] = [absPath(path) for path in binaryData["files"]] - if int(os.environ.get("SDL_FIND_BINARIES", "1" if binaryData.get("find", missing is None) else "0")) > 0: - binaryData["files"] += SDL_FIND_BINARIES(SDL_MODULES) + if int(os.environ.get("SDL_FIND_BINARIES", "1" if binaryData.get("find", missing is None) else "0")) > 0: + binaryData["files"] += SDL_FIND_BINARIES(SDL_MODULES) - for module in SDL_MODULES: - for path in binaryData["files"]: - if module in binaryMap: - break + for module in SDL_MODULES: + for path in binaryData["files"]: + if module in binaryMap: + break - if os.path.exists(path) and SDL_CHECK_BINARY_NAME(name := os.path.split(path)[1]): - for _name in [module, f"{module}d"]: - if (name.split(".")[0] if "." in name else name).endswith(_name): - binaryMap[module] = ctypes.CDLL(os.path.abspath(path)) + if os.path.exists(path) and SDL_CHECK_BINARY_NAME(name := os.path.split(path)[1]): + for _name in [module, f"{module}d"]: + if (name.split(".")[0] if "." in name else name).endswith(_name): + binaryMap[module] = ctypes.CDLL(os.path.abspath(path)) BaseType = typing.TypeVar("BaseType") TargetType = typing.TypeVar("TargetType") def SDL_ARRAY(*args: BaseType, **kwargs: TargetType) -> tuple[ctypes.Array[BaseType | TargetType], int]: - """Create a ctypes array from the given arguments.""" - return ((kwargs.get("type", None) or args[0].__class__) * len(args))(*args), len(args) + """Create a ctypes array from the given arguments.""" + return ((kwargs.get("type", None) or args[0].__class__) * len(args))(*args), len(args) def SDL_DEREFERENCE(value: typing.Any) -> typing.Any: - """Dereference a ctypes pointer or object.""" - if isinstance(value, ctypes._Pointer): return value.contents - elif hasattr(value, "_obj"): return value._obj - else: return value + """Dereference a ctypes pointer or object.""" + if isinstance(value, ctypes._Pointer): return value.contents + elif hasattr(value, "_obj"): return value._obj + else: return value def SDL_CACHE_FUNC(func: abc.Callable[..., typing.Any]) -> abc.Callable[..., typing.Any]: - """Simple function cache decorator.""" - cache = {} + """Simple function cache decorator.""" + cache = {} - def __inner__(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: - _hash = hash((args, tuple(frozenset(sorted(kwargs.items()))))) - if _hash not in cache: cache.update({_hash: func(*args, **kwargs)}) - return cache.get(_hash, None) + def __inner__(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: + _hash = hash((args, tuple(frozenset(sorted(kwargs.items()))))) + if _hash not in cache: cache.update({_hash: func(*args, **kwargs)}) + return cache.get(_hash, None) - return __inner__ + return __inner__ @SDL_CACHE_FUNC def SDL_GET_BINARY_NAME(binary: ctypes.CDLL) -> str | None: - """Get the name of an SDL3 binary.""" - return {v: k for k, v in __module__.binaryMap.items()}.get(binary, None) + """Get the name of an SDL3 binary.""" + return {v: k for k, v in __module__.binaryMap.items()}.get(binary, None) def SDL_GET_BINARY(name: str) -> ctypes.CDLL | None: - """Get an SDL3 binary by its name.""" - return __module__.binaryMap.get(name, None) + """Get an SDL3 binary by its name.""" + return __module__.binaryMap.get(name, None) def SDL_NOT_IMPLEMENTED(name: str) -> abc.Callable[..., None]: - return lambda *args, **kwargs: \ - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Invoked an unimplemented function: '{name}'.") + return lambda *args, **kwargs: \ + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Invoked an unimplemented function: '{name}'.") class SDL_FUNC: - @classmethod - def __class_getitem__(cls, key: tuple[str, type, list[type], str]) -> typing.Any: - """Create a new ctypes function definition.""" - - if not (__frozen__ or __release__): - assert isinstance(key, tuple), "Expected a tuple, got a single argument." - assert len(key) == 4, "Expected a tuple with length 4." - assert isinstance(key[0], str), "Expected a string as the first argument." - assert isinstance(key[1], type) or key[1] is None, "Expected a type as the second argument." - assert isinstance(key[2], list), "Expected a list as the third argument." - assert ... not in key[2] or key[2].count(...) == 1, "Expected at most 1 '...' in the argument list." - assert ... not in key[2] or key[2][-1] == ..., "Expected '...' at the end of the argument list." - assert isinstance(key[3], str), "Expected a string as the fourth argument." - assert key[3] in SDL_MODULES, "Unknown binary." - - if binary := SDL_GET_BINARY(key[3]): - func = getattr(binary, key[0], None) - - if not func and not int(os.environ.get("SDL_IGNORE_MISSING_FUNCTIONS", "1" if __frozen__ or __release__ else "0")) > 0: - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Function '{key[0]}' not found in binary: '{SDL_BINARY_PATTERNS[SDL_SYSTEM][0].format(key[3])}'.") - - if not binary or not func: func = SDL_NOT_IMPLEMENTED(key[0]) - func.restype, func.binary = key[1], binary - - if ... in key[2]: - def __inner__(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: - for arg in args[len(__inner__.func.argtypes):]: - if isinstance(arg, int): __inner__.func.argtypes += [ctypes.c_int] - elif isinstance(arg, float): __inner__.func.argtypes += [ctypes.c_float] - elif isinstance(arg, bytes): __inner__.func.argtypes += [ctypes.c_char_p] - elif isinstance(arg, bool): __inner__.func.argtypes += [ctypes.c_bool] - else: __inner__.func.argtypes += [arg.__class__] - - value = __inner__.func(*args, **kwargs) - __inner__.func.argtypes = key[2][:-1] - return value - - func.argtypes, func.vararg = key[2][:-1], True - func, __inner__.func = __inner__, func - - else: - func.argtypes, func.vararg = key[2], False - - __module__.functions[key[3]][key[0]] = func - return func + def __class_getitem__(cls, key: tuple[str, type, list[type], str]) -> typing.Any: + """Create a new ctypes function definition.""" + + if not (__frozen__ or __release__): + assert isinstance(key, tuple), "Expected a tuple, got a single argument." + assert len(key) == 4, "Expected a tuple with length 4." + assert isinstance(key[0], str), "Expected a string as the first argument." + assert isinstance(key[1], type) or key[1] is None, "Expected a type as the second argument." + assert isinstance(key[2], list), "Expected a list as the third argument." + assert ... not in key[2] or key[2].count(...) == 1, "Expected at most 1 '...' in the argument list." + assert ... not in key[2] or key[2][-1] == ..., "Expected '...' at the end of the argument list." + assert isinstance(key[3], str), "Expected a string as the fourth argument." + assert key[3] in SDL_MODULES, "Unknown binary." + + if binary := SDL_GET_BINARY(key[3]): + func = getattr(binary, key[0], None) + + if not func and not int(os.environ.get("SDL_IGNORE_MISSING_FUNCTIONS", "1" if __frozen__ or __release__ else "0")) > 0: + SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Function '{key[0]}' not found in binary: '{SDL_BINARY_PATTERNS[SDL_SYSTEM][0].format(key[3])}'.") + + if not binary or not func: func = SDL_NOT_IMPLEMENTED(key[0]) + func.restype, func.binary = key[1], binary + + if ... in key[2]: + def __inner__(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: + for arg in args[len(__inner__.func.argtypes):]: + if isinstance(arg, int): __inner__.func.argtypes += [ctypes.c_int] + elif isinstance(arg, float): __inner__.func.argtypes += [ctypes.c_float] + elif isinstance(arg, bytes): __inner__.func.argtypes += [ctypes.c_char_p] + elif isinstance(arg, bool): __inner__.func.argtypes += [ctypes.c_bool] + else: __inner__.func.argtypes += [arg.__class__] + + value = __inner__.func(*args, **kwargs) + __inner__.func.argtypes = key[2][:-1] + return value + + func.argtypes, func.vararg = key[2][:-1], True + func, __inner__.func = __inner__, func + + else: + func.argtypes, func.vararg = key[2], False + + __module__.functions[key[3]][key[0]] = func + return func class SDL_POINTER: - @classmethod - def __class_getitem__(cls, key: type) -> type: - """Create a ctypes pointer type from a ctypes type.""" + def __class_getitem__(cls, key: type) -> type: + """Create a ctypes pointer type from a ctypes type.""" - if not (__frozen__ or __release__): - assert not isinstance(key, tuple), "Expected a single argument, got a tuple." - assert isinstance(key, type), "Expected a type as the first argument." + if not (__frozen__ or __release__): + assert not isinstance(key, tuple), "Expected a single argument, got a tuple." + assert isinstance(key, type), "Expected a type as the first argument." - return ctypes.POINTER(key) + return ctypes.POINTER(key) class SDL_CAST: - @classmethod - def __class_getitem__(cls, key: tuple[typing.Any, type]) -> typing.Any: - """Cast a ctypes pointer to an another type.""" + def __class_getitem__(cls, key: tuple[typing.Any, type]) -> typing.Any: + """Cast a ctypes pointer to an another type.""" - if not (__frozen__ or __release__): - assert isinstance(key, tuple), "Expected a tuple, got a single argument." - assert len(key) == 2, "Expected a tuple with length 2." - assert isinstance(key[0], ctypes._Pointer), "Expected a pointer as the first argument." - assert isinstance(key[1], type), "Expected a type as the second argument." + if not (__frozen__ or __release__): + assert isinstance(key, tuple), "Expected a tuple, got a single argument." + assert len(key) == 2, "Expected a tuple with length 2." + assert isinstance(key[0], ctypes._Pointer), "Expected a pointer as the first argument." + assert isinstance(key[1], type), "Expected a type as the second argument." - return ctypes.cast(key[0], key[1]) + return ctypes.cast(key[0], key[1]) class SDL_TYPE: - @classmethod - def __class_getitem__(cls, key: tuple[str, type]) -> type: - """Create a new type from a ctypes type.""" + def __class_getitem__(cls, key: tuple[str, type]) -> type: + """Create a new type from a ctypes type.""" - if not (__frozen__ or __release__): - assert isinstance(key, tuple), "Expected a tuple, got a single argument." - assert len(key) == 2, "Expected a tuple with length 2." - assert isinstance(key[0], str), "Expected a string as the first argument." - assert isinstance(key[1], type), "Expected a type as the second argument." + if not (__frozen__ or __release__): + assert isinstance(key, tuple), "Expected a tuple, got a single argument." + assert len(key) == 2, "Expected a tuple with length 2." + assert isinstance(key[0], str), "Expected a string as the first argument." + assert isinstance(key[1], type), "Expected a type as the second argument." - if hasattr(key[1], "contents"): - return type(key[0], (ctypes._Pointer, ), {"_type_": key[1]._type_, "contents": key[1].contents}) - - elif hasattr(key[1], "_fields_"): - return type(key[0], (ctypes.Structure, ), {"_fields_": key[1]._fields_}) + if hasattr(key[1], "contents"): + return type(key[0], (ctypes._Pointer, ), {"_type_": key[1]._type_, "contents": key[1].contents}) - else: - return type(key[0], (ctypes._SimpleCData, ), {"_type_": key[1]._type_}) + elif hasattr(key[1], "_fields_"): + return type(key[0], (ctypes.Structure, ), {"_fields_": key[1]._fields_}) + + else: + return type(key[0], (ctypes._SimpleCData, ), {"_type_": key[1]._type_}) class SDL_FUNC_TYPE: - @classmethod - def __class_getitem__(cls, key: tuple[str, type, list[type]]) -> type: - """Create a new ctypes function type.""" - - if not (__frozen__ or __release__): - assert isinstance(key, tuple), "Expected a tuple, got a single argument." - assert len(key) == 3, "Expected a tuple with length 3." - assert isinstance(key[0], str), "Expected a string as the first argument." - assert isinstance(key[1], type) or key[1] is None, "Expected a type as the second argument." - assert isinstance(key[2], list), "Expected a list as the third argument." - - _, ctypes._c_functype_cache = ctypes._c_functype_cache, {} - value = ctypes.CFUNCTYPE(key[1], *key[2]) - value.__name__, ctypes._c_functype_cache = key[0], _ - return value + def __class_getitem__(cls, key: tuple[str, type, list[type]]) -> type: + """Create a new ctypes function type.""" + + if not (__frozen__ or __release__): + assert isinstance(key, tuple), "Expected a tuple, got a single argument." + assert len(key) == 3, "Expected a tuple with length 3." + assert isinstance(key[0], str), "Expected a string as the first argument." + assert isinstance(key[1], type) or key[1] is None, "Expected a type as the second argument." + assert isinstance(key[2], list), "Expected a list as the third argument." + + _, ctypes._c_functype_cache = ctypes._c_functype_cache, {} + value = ctypes.CFUNCTYPE(key[1], *key[2]) + value.__name__, ctypes._c_functype_cache = key[0], _ + return value SDL_ENUM: typing.TypeAlias = SDL_TYPE["SDL_ENUM", ctypes.c_int] SDL_VA_LIST: typing.TypeAlias = SDL_TYPE["SDL_VA_LIST", ctypes.c_char_p] def SDL_PARSE_ARGUMENTS(argc: ctypes.c_int, argv: SDL_POINTER[ctypes.c_char_p]) -> list[str]: - return [argv[i].decode("utf-8") for i in range(argc)] + return [argv[i].decode("utf-8") for i in range(argc)] def SDL_PROCESS_DESCRIPTION(description: str, url: str | None = None, rst: bool = False) -> str: - """Process HTML description.""" + """Process HTML description.""" - return re.sub(r"([a-zA-Z0-9_]+)", r"\1" if rst or not url else rf"[\2]({'/'.join(url.split('/')[:-1])}/\1)", description) \ - .replace("", "" if rst else "*").replace("", "`" if rst else "``").replace("", "" if rst else "**") + return re.sub(r"([a-zA-Z0-9_]+)", r"\1" if rst or not url else rf"[\2]({'/'.join(url.split('/')[:-1])}/\1)", description) \ + .replace("", "" if rst else "*").replace("", "`" if rst else "``").replace("", "" if rst else "**") async def SDL_GET_LATEST_RELEASES() -> dict[str, str]: - """Get latest releases of SDL3 modules from their official github repositories.""" - session, releases, tasks = aiohttp.ClientSession(), {}, [] - headers = {"Accept": "application/vnd.github+json"} + """Get latest releases of SDL3 modules from their official github repositories.""" + session, releases, tasks = aiohttp.ClientSession(), {}, [] + headers = {"Accept": "application/vnd.github+json"} - if "SDL_GITHUB_TOKEN" in os.environ: - headers["Authorization"] = f"Bearer {os.environ['SDL_GITHUB_TOKEN']}" + if "SDL_GITHUB_TOKEN" in os.environ: + headers["Authorization"] = f"Bearer {os.environ['SDL_GITHUB_TOKEN']}" - for module in SDL_MODULES: - url = f"https://api.github.com/repos/libsdl-org/{module}/releases" - tasks.append(asyncio.create_task(session.get(url, headers = headers, ssl = False))) - SDL_LOGGER.Log(SDL_LOGGER.Info, f"Sending a request to '{url}'.") + for module in SDL_MODULES: + url = f"https://api.github.com/repos/libsdl-org/{module}/releases" + tasks.append(asyncio.create_task(session.get(url, headers = headers, ssl = False))) + SDL_LOGGER.Log(SDL_LOGGER.Info, f"Sending a request to '{url}'.") - responses = await asyncio.gather(*tasks) - SDL_LOGGER.Log(SDL_LOGGER.Info, f"Response gathering completed ({len(responses)} responses).") + responses = await asyncio.gather(*tasks) + SDL_LOGGER.Log(SDL_LOGGER.Info, f"Response gathering completed ({len(responses)} responses).") - for response, module in zip(responses, SDL_MODULES): - if response.status != 200: - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Failed to get latest release of '{response.url}', skipping (status: {response.status}).") - releases[module] = None + for response, module in zip(responses, SDL_MODULES): + if response.status != 200: + SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Failed to get latest release of '{response.url}', skipping (status: {response.status}).") + releases[module] = None - else: - latestRelease = (None, None) + else: + latestRelease = (None, None) - for release in await response.json(): - score = packaging.version.parse(release["tag_name"].split("-")[1]) + for release in await response.json(): + score = packaging.version.parse(release["tag_name"].split("-")[1]) - if not latestRelease[0] or score > latestRelease[0]: - latestRelease = (score, release["tag_name"]) + if not latestRelease[0] or score > latestRelease[0]: + latestRelease = (score, release["tag_name"]) - releases[module] = latestRelease[1] + releases[module] = latestRelease[1] - await session.close() - return releases + await session.close() + return releases async def SDL_GET_FUNC_DESCRIPTIONS(funcs: list[tuple[str, str]], rst: bool = False) -> tuple[list[str], list[list[str]]]: - """Get descriptions, arguments and return types of SDL3 functions from the official SDL3 wiki.""" - session, tasks = aiohttp.ClientSession(), [] + """Get descriptions, arguments and return types of SDL3 functions from the official SDL3 wiki.""" + session, tasks = aiohttp.ClientSession(), [] - for module, func in funcs: - url = f"https://wiki.libsdl.org/{module}/{func}" - SDL_LOGGER.Log(SDL_LOGGER.Info, f"Sending a request to '{url}'.") - tasks.append(asyncio.create_task(session.get(url, ssl = False))) + for module, func in funcs: + url = f"https://wiki.libsdl.org/{module}/{func}" + SDL_LOGGER.Log(SDL_LOGGER.Info, f"Sending a request to '{url}'.") + tasks.append(asyncio.create_task(session.get(url, ssl = False))) - responses = await asyncio.gather(*tasks) - SDL_LOGGER.Log(SDL_LOGGER.Info, f"Response gathering completed ({len(responses)} responses).") - descriptions, arguments, returns = [], [], [] + responses = await asyncio.gather(*tasks) + SDL_LOGGER.Log(SDL_LOGGER.Info, f"Response gathering completed ({len(responses)} responses).") + descriptions, arguments, returns = [], [], [] - for response in responses: - if response.status != 200: - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"No such page: '{response.url}', skipping (status: {response.status}).") - descriptions.append(None) - arguments.append(None) - returns.append(None) + for response in responses: + if response.status != 200: + SDL_LOGGER.Log(SDL_LOGGER.Warning, f"No such page: '{response.url}', skipping (status: {response.status}).") + descriptions.append(None) + arguments.append(None) + returns.append(None) - else: - content = (await response.content.read()).decode() + else: + content = (await response.content.read()).decode() - for a, b in zip(list(re.finditer(r"

", content)), list(re.finditer(r"

", content))): - if content[a.end()] == "<": continue - descriptions.append(SDL_PROCESS_DESCRIPTION(content[a.end():b.start()], str(response.url), rst)) - break + for a, b in zip(list(re.finditer(r"

", content)), list(re.finditer(r"

", content))): + if content[a.end()] == "<": continue + descriptions.append(SDL_PROCESS_DESCRIPTION(content[a.end():b.start()], str(response.url), rst)) + break - for a, b in zip(list(re.finditer(r"", content)), list(re.finditer(r"
", content))): - if "sourceCode c" not in content[a.start():a.end()]: continue - text = re.sub(r"<(/)?([a-zA-Z0-9_=#\s\"\-]+)>", r"", content[a.end():b.start()]).split(";")[0] - if "\n" in text: text = " ".join(text.split("\n")) - returns.append(" ".join([i for i in text.split("(")[0].split(" ")[:-1] if i and i not in ["extern", "SDL_DECLSPEC"]])) - text = text[text.index("(") + 1:text.index(")") + 1].replace(")", ",")[:-1] - arguments.append({}) + for a, b in zip(list(re.finditer(r"", content)), list(re.finditer(r"
", content))): + if "sourceCode c" not in content[a.start():a.end()]: continue + text = re.sub(r"<(/)?([a-zA-Z0-9_=#\s\"\-]+)>", r"", content[a.end():b.start()]).split(";")[0] + if "\n" in text: text = " ".join(text.split("\n")) + returns.append(" ".join([i for i in text.split("(")[0].split(" ")[:-1] if i and i not in ["extern", "SDL_DECLSPEC"]])) + text = text[text.index("(") + 1:text.index(")") + 1].replace(")", ",")[:-1] + arguments.append({}) - for i in (text.split(", ") if "," in text else [text]) if text != "void" else []: - if i != "...": i = i.split(" ") - else: continue + for i in (text.split(", ") if "," in text else [text]) if text != "void" else []: + if i != "...": i = i.split(" ") + else: continue - arguments[-1][i[-1].replace("*", "")] = \ - " ".join(i[:-1]) + "*" * i[-1].count("*") + arguments[-1][i[-1].replace("*", "")] = \ + " ".join(i[:-1]) + "*" * i[-1].count("*") - break + break - await session.close() - return descriptions, arguments, returns + await session.close() + return descriptions, arguments, returns async def SDL_GET_STRUCT_DESCRIPTIONS(structs: list[tuple[str, str]], rst: bool = False) -> tuple[list[str], list[list[str]]]: - """Get descriptions and members of SDL3 structures from the official SDL3 wiki.""" - session, tasks = aiohttp.ClientSession(), [] + """Get descriptions and members of SDL3 structures from the official SDL3 wiki.""" + session, tasks = aiohttp.ClientSession(), [] - for module, struct in structs: - url = f"https://wiki.libsdl.org/{module}/{struct}" - SDL_LOGGER.Log(SDL_LOGGER.Info, f"Sending a request to '{url}'.") - tasks.append(asyncio.create_task(session.get(url, ssl = False))) + for module, struct in structs: + url = f"https://wiki.libsdl.org/{module}/{struct}" + SDL_LOGGER.Log(SDL_LOGGER.Info, f"Sending a request to '{url}'.") + tasks.append(asyncio.create_task(session.get(url, ssl = False))) - responses = await asyncio.gather(*tasks) - SDL_LOGGER.Log(SDL_LOGGER.Info, f"Response gathering completed ({len(responses)} responses).") - descriptions, members = [], [] + responses = await asyncio.gather(*tasks) + SDL_LOGGER.Log(SDL_LOGGER.Info, f"Response gathering completed ({len(responses)} responses).") + descriptions, members = [], [] - for response in responses: - if response.status != 200: - SDL_LOGGER.Log(SDL_LOGGER.Warning, f"No such page: '{response.url}', skipping (status: {response.status}).") - descriptions.append(None) - members.append(None) + for response in responses: + if response.status != 200: + SDL_LOGGER.Log(SDL_LOGGER.Warning, f"No such page: '{response.url}', skipping (status: {response.status}).") + descriptions.append(None) + members.append(None) - else: - content = (await response.content.read()).decode() + else: + content = (await response.content.read()).decode() - for a, b in zip(list(re.finditer(r"

", content)), list(re.finditer(r"

", content))): - if content[a.end()] == "<": continue - descriptions.append(SDL_PROCESS_DESCRIPTION(content[a.end():b.start()], str(response.url), rst)) - break + for a, b in zip(list(re.finditer(r"

", content)), list(re.finditer(r"

", content))): + if content[a.end()] == "<": continue + descriptions.append(SDL_PROCESS_DESCRIPTION(content[a.end():b.start()], str(response.url), rst)) + break - for a, b in zip(list(re.finditer(r"", content)), list(re.finditer(r"", content))): - if "sourceCode c" not in content[a.start():a.end()]: continue - text = re.sub(r"<(/)?([a-zA-Z0-9_=#\s\"\-]+)>", r"", content[a.end():b.start()]) - text, _ = re.sub(r"//.*", r"", re.sub(r"/\*[\s\S]*?\*/", r"", text)), text.split(" ") - members.append({}) + for a, b in zip(list(re.finditer(r"", content)), list(re.finditer(r"", content))): + if "sourceCode c" not in content[a.start():a.end()]: continue + text = re.sub(r"<(/)?([a-zA-Z0-9_=#\s\"\-]+)>", r"", content[a.end():b.start()]) + text, _ = re.sub(r"//.*", r"", re.sub(r"/\*[\s\S]*?\*/", r"", text)), text.split(" ") + members.append({}) - for line in text.split("\n"): - if ";" not in line or "{" in line or "}" in line: - continue + for line in text.split("\n"): + if ";" not in line or "{" in line or "}" in line: + continue - line = line.split(";")[0].strip().replace(", ", ",") + line = line.split(";")[0].strip().replace(", ", ",") - if "SDLCALL" not in line: - count, line = line.count("*"), line.replace("*", "").split(" ") + if "SDLCALL" not in line: + count, line = line.count("*"), line.replace("*", "").split(" ") - for i in line[-1].split(",") if "," in line[-1] else [line[-1]]: - members[-1][i] = " ".join(line[:-1]) + "*" * count + for i in line[-1].split(",") if "," in line[-1] else [line[-1]]: + members[-1][i] = " ".join(line[:-1]) + "*" * count - else: - _ = line.replace("SDLCALL", "").split("(") - arguments, name = _[2].split(")")[0].strip(), _[1].split(")")[0].replace("*", "").strip() - members[-1][name] = {"return": _[0].strip(), "arguments": {}} + else: + _ = line.replace("SDLCALL", "").split("(") + arguments, name = _[2].split(")")[0].strip(), _[1].split(")")[0].replace("*", "").strip() + members[-1][name] = {"return": _[0].strip(), "arguments": {}} - for i in arguments.split(",") if "," in arguments else [arguments]: - if i in ["...", "void"]: continue - count, i = i.count("*"), i.replace("*", "").split(" ") - members[-1][name]["arguments"][i[-1]] = " ".join(i[:-1]) + "*" * count + for i in arguments.split(",") if "," in arguments else [arguments]: + if i in ["...", "void"]: continue + count, i = i.count("*"), i.replace("*", "").split(" ") + members[-1][name]["arguments"][i[-1]] = " ".join(i[:-1]) + "*" * count - break + break - await session.close() - return descriptions, members + await session.close() + return descriptions, members def SDL_GET_MODULE_BY_NAME(name: str) -> str: - """Get the module of an SDL3 function/structure by its name.""" + """Get the module of an SDL3 function/structure by its name.""" - for prefix, module in sorted({"SDL": "SDL3", "IMG": "SDL3_image", "Mix": "SDL3_mixer", "TTF": "SDL3_ttf", "RTF": "SDL3_rtf", "NET": "SDL3_net", "SDL_ShaderCross": "SDL3_shadercross"}.items(), key = lambda x: -len(x[0])): - if name.startswith(prefix): return module + for prefix, module in sorted({"SDL": "SDL3", "IMG": "SDL3_image", "Mix": "SDL3_mixer", "TTF": "SDL3_ttf", "RTF": "SDL3_rtf", "NET": "SDL3_net", "SDL_ShaderCross": "SDL3_shadercross"}.items(), key = lambda x: -len(x[0])): + if name.startswith(prefix): return module def SDL_PYTHONIZE_TYPE(type: str, name: str | None = None, globals: dict[str, typing.Any] = {}) -> str: - """Pythonize C/C++ types.""" - array = (_ if str.isnumeric(_ := name.split("[")[1].split("]")[0]) else eval(_, globals)) if name is not None and name.count("[") == 1 and name.index("]") - name.index("[") != 1 else None - type, count = type.replace("*", ""), type.count("*") + (0 if name is None or array is not None else name.count("[")) - - if " " in type: - type = " ".join([i for i in type.split(" ") if i and i not in ["const", "struct", "union", "enum", "SDLCALL"]]) - - if type in (sdlTypes := { - "Sint8": "ctypes.c_int8", "Uint8": "ctypes.c_uint8", "Sint16": "ctypes.c_int16", "Uint16": "ctypes.c_uint16", - "Sint32": "ctypes.c_int32", "Uint32": "ctypes.c_uint32", "Sint64": "ctypes.c_int64", "Uint64": "ctypes.c_uint64" - }): type = sdlTypes[type] - - if count and type in ["void", "char", "wchar_t"]: - type, count = f"ctypes.c_{type.replace('_t', '')}_p", count - 1 - - if type in (cTypes := { - "int": "ctypes.c_int", "bool": "ctypes.c_bool", "float": "ctypes.c_float", "double": "ctypes.c_double", - "char": "ctypes.c_char", "short": "ctypes.c_short", "long long": "ctypes.c_longlong", "long": "ctypes.c_long", - "size_t": "ctypes.c_size_t", "ssize_t": "ctypes.c_ssize_t", "intptr_t": "ctypes.c_intptr_t", "uintptr_t": "ctypes.c_uintptr_t", - "int8_t": "ctypes.c_int8", "uint8_t": "ctypes.c_uint8", "int16_t": "ctypes.c_int16", "uint16_t": "ctypes.c_uint16", - "int32_t": "ctypes.c_int32", "uint32_t": "ctypes.c_uint32", "int64_t": "ctypes.c_int64", "uint64_t": "ctypes.c_uint64", - "unsigned short": "ctypes.c_ushort", "unsigned long long": "ctypes.c_ulonglong", "unsigned long": "ctypes.c_ulong", - "unsigned char": "ctypes.c_ubyte", "unsigned int": "ctypes.c_uint", "wchar_t": "ctypes.c_wchar", "va_list": "SDL_VA_LIST" - }): type = cTypes[type] - - if not count and type in ["void"]: type = "None" - if count and type.startswith("ctypes."): type = type.split(".")[1] - return f"{'LP_' * count}{type}{'_Array_%s' % array if array else ''}" + """Pythonize C/C++ types.""" + array = (_ if str.isnumeric(_ := name.split("[")[1].split("]")[0]) else eval(_, globals)) if name is not None and name.count("[") == 1 and name.index("]") - name.index("[") != 1 else None + type, count = type.replace("*", ""), type.count("*") + (0 if name is None or array is not None else name.count("[")) + + if " " in type: + type = " ".join([i for i in type.split(" ") if i and i not in ["const", "struct", "union", "enum", "SDLCALL"]]) + + if type in (sdlTypes := { + "Sint8": "ctypes.c_int8", "Uint8": "ctypes.c_uint8", "Sint16": "ctypes.c_int16", "Uint16": "ctypes.c_uint16", + "Sint32": "ctypes.c_int32", "Uint32": "ctypes.c_uint32", "Sint64": "ctypes.c_int64", "Uint64": "ctypes.c_uint64" + }): type = sdlTypes[type] + + if count and type in ["void", "char", "wchar_t"]: + type, count = f"ctypes.c_{type.replace('_t', '')}_p", count - 1 + + if type in (cTypes := { + "int": "ctypes.c_int", "bool": "ctypes.c_bool", "float": "ctypes.c_float", "double": "ctypes.c_double", + "char": "ctypes.c_char", "short": "ctypes.c_short", "long long": "ctypes.c_longlong", "long": "ctypes.c_long", + "size_t": "ctypes.c_size_t", "ssize_t": "ctypes.c_ssize_t", "intptr_t": "ctypes.c_intptr_t", "uintptr_t": "ctypes.c_uintptr_t", + "int8_t": "ctypes.c_int8", "uint8_t": "ctypes.c_uint8", "int16_t": "ctypes.c_int16", "uint16_t": "ctypes.c_uint16", + "int32_t": "ctypes.c_int32", "uint32_t": "ctypes.c_uint32", "int64_t": "ctypes.c_int64", "uint64_t": "ctypes.c_uint64", + "unsigned short": "ctypes.c_ushort", "unsigned long long": "ctypes.c_ulonglong", "unsigned long": "ctypes.c_ulong", + "unsigned char": "ctypes.c_ubyte", "unsigned int": "ctypes.c_uint", "wchar_t": "ctypes.c_wchar", "va_list": "SDL_VA_LIST" + }): type = cTypes[type] + + if not count and type in ["void"]: type = "None" + if count and type.startswith("ctypes."): type = type.split(".")[1] + return f"{'LP_' * count}{type}{'_Array_%s' % array if array else ''}" def SDL_GENERATE_DOCS(modules: list[str] = SDL_MODULES, raw: types.ModuleType | None = None, rst: bool = False) -> str: - """Generate type hints and docstring for SDL3 functions/structures using SDL3 wiki.""" + """Generate type hints and docstring for SDL3 functions/structures using SDL3 wiki.""" - __index, (descriptions, arguments, returns) = -1, \ - asyncio.run(SDL_GET_FUNC_DESCRIPTIONS([(module, func) for module in modules for func in __module__.functions[module]], rst)) + __index, (descriptions, arguments, returns) = -1, \ + asyncio.run(SDL_GET_FUNC_DESCRIPTIONS([(module, func) for module in modules for func in __module__.functions[module]], rst)) - for module in modules: - for func in __module__.functions[module]: - if (_ := __module__.functions[module][func]).__name__ == "__inner__": _ = _.func - _.__doc__ = (descriptions[__index := __index + 1], arguments[__index], returns[__index]) + for module in modules: + for func in __module__.functions[module]: + if (_ := __module__.functions[module][func]).__name__ == "__inner__": _ = _.func + _.__doc__ = (descriptions[__index := __index + 1], arguments[__index], returns[__index]) - result = "" if rst else f"\"\"\"\n# This file is auto-generated, do not modify it.\n__meta__ = " - if not rst: result += f"{{\"target\": \"v{__version__}\", \"system\": \"{SDL_SYSTEM}\"}}\n\"\"\"\n\n" - result += f"from {'sdl3' if rst else ''}.SDL import * # type: ignore\n\n" - result += f"from {'sdl3' if rst else ''}.__init__ import {'' if rst else 'raw, '}ctypes, typing, {'SDL_POINTER' if rst else ''}" - result += "\n" if rst else f"\\\n{' ' * 4}SDL_POINTER, SDL_CLONE_METACLASS as SDL_CloneMeta\n\n" - types, definitions = set(), "" + result = "" if rst else "\"\"\"\n# This file is auto-generated, do not modify it.\n__meta__ = " + if not rst: result += f"{{\"target\": \"v{__version__}\", \"system\": \"{SDL_SYSTEM}\"}}\n\"\"\"\n\n" + result += f"from {'sdl3' if rst else ''}.SDL import * # type: ignore\n\n" + result += f"from {'sdl3' if rst else ''}.__init__ import {'' if rst else 'raw, '}ctypes, typing, {'SDL_POINTER' if rst else ''}" + result += "\n" if rst else f"\\\n{' ' * 4}SDL_POINTER, SDL_CLONE_METACLASS as SDL_CloneMeta\n\n" + types, definitions = set(), "" - def SDL_GET_FULL_NAME(type: type | None) -> str: - if type is None: return "None" - if type.__name__.startswith("LP_"): types.add(type.__name__) - if type.__name__.startswith("c_"): return f"ctypes.{type.__name__}" - else: return type.__name__ + def SDL_GET_FULL_NAME(type: type | None) -> str: + if type is None: return "None" + if type.__name__.startswith("LP_"): types.add(type.__name__) + if type.__name__.startswith("c_"): return f"ctypes.{type.__name__}" + else: return type.__name__ - if not rst and raw is not None: - structs = [(SDL_GET_MODULE_BY_NAME(name), name) for name in dir(raw) if hasattr(getattr(raw, name), "_fields_") \ - and not name.startswith("_") and "_" in name and name not in ["SDL_GamepadBinding", "SDL_TLSID"]] + if not rst and raw is not None: + structs = [(SDL_GET_MODULE_BY_NAME(name), name) for name in dir(raw) if hasattr(getattr(raw, name), "_fields_") \ + and not name.startswith("_") and "_" in name and name not in ["SDL_GamepadBinding", "SDL_TLSID"]] - for (module, name), description, members in zip(structs, *asyncio.run(SDL_GET_STRUCT_DESCRIPTIONS(structs))): - if not description or not members: - continue + for (module, name), description, members in zip(structs, *asyncio.run(SDL_GET_STRUCT_DESCRIPTIONS(structs))): + if not description or not members: + continue - if len(members) != len(_fields_ := getattr(raw, name)._fields_): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member count mismatch for https://wiki.libsdl.org/{module}/{name} (expected: {len(members)}, got: {len(_fields_)}).") + if len(members) != len(_fields_ := getattr(raw, name)._fields_): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member count mismatch for https://wiki.libsdl.org/{module}/{name} (expected: {len(members)}, got: {len(_fields_)}).") - for field, member in zip(_fields_, members): - if field[0] != (_ := member.split("[")[0] if "[" in member else member): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member name mismatch for https://wiki.libsdl.org/{module}/{name} (expected: {_}, got: {field[0]}).") + for field, member in zip(_fields_, members): + if field[0] != (_ := member.split("[")[0] if "[" in member else member): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member name mismatch for https://wiki.libsdl.org/{module}/{name} (expected: {_}, got: {field[0]}).") - if isinstance(members[member], dict): - if (left := SDL_GET_FULL_NAME(field[1]._restype_)) != (right := SDL_PYTHONIZE_TYPE(members[member]["return"], globals = raw.__dict__)): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member return type mismatch for https://wiki.libsdl.org/{module}/{name} (member: {_}, expected: {right}, got: {left}).") + if isinstance(members[member], dict): + if (left := SDL_GET_FULL_NAME(field[1]._restype_)) != (right := SDL_PYTHONIZE_TYPE(members[member]["return"], globals = raw.__dict__)): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member return type mismatch for https://wiki.libsdl.org/{module}/{name} (member: {_}, expected: {right}, got: {left}).") - for left, (_name, right) in zip(field[1]._argtypes_, members[member]["arguments"].items()): - if (_left := SDL_GET_FULL_NAME(left)) != (_right := SDL_PYTHONIZE_TYPE(right, _name, globals = raw.__dict__)): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member argument type mismatch for https://wiki.libsdl.org/{module}/{name} (member: {_}, argument: {_name}, expected: {_right}, got: {_left}).") + for left, (_name, right) in zip(field[1]._argtypes_, members[member]["arguments"].items()): + if (_left := SDL_GET_FULL_NAME(left)) != (_right := SDL_PYTHONIZE_TYPE(right, _name, globals = raw.__dict__)): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member argument type mismatch for https://wiki.libsdl.org/{module}/{name} (member: {_}, argument: {_name}, expected: {_right}, got: {_left}).") - else: - if (left := SDL_GET_FULL_NAME(field[1])) != (right := SDL_PYTHONIZE_TYPE(members[member], member, globals = raw.__dict__)): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member type mismatch for https://wiki.libsdl.org/{module}/{name} (member: {member}, expected: {right}, got: {left}).") + else: + if (left := SDL_GET_FULL_NAME(field[1])) != (right := SDL_PYTHONIZE_TYPE(members[member], member, globals = raw.__dict__)): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Member type mismatch for https://wiki.libsdl.org/{module}/{name} (member: {member}, expected: {right}, got: {left}).") - result += f"class {name}(metaclass = SDL_CloneMeta):\n{' ' * 4}\"\"\"{description}{'' if description.endswith('.') else '.'}\"\"\"\n\n" - # result += f"{' ' * 4}def __init__({', '.join(["self"] + [f'{_[0]}: {SDL_GET_FULL_NAME(_[1])}' for _ in _fields_])}) -> None:\n" - # result += f"{' ' * 8}super({name}, self).__init__()\n\n{' ' * 8}{', '.join([f'self.{_[0]}' for _ in _fields_])} = {', '.join([_[0] for _ in _fields_])}\n\n" + result += f"class {name}(metaclass = SDL_CloneMeta):\n{' ' * 4}\"\"\"{description}{'' if description.endswith('.') else '.'}\"\"\"\n\n" + # result += f"{' ' * 4}def __init__({', '.join(["self"] + [f'{_[0]}: {SDL_GET_FULL_NAME(_[1])}' for _ in _fields_])}) -> None:\n" + # result += f"{' ' * 8}super({name}, self).__init__()\n\n{' ' * 8}{', '.join([f'self.{_[0]}' for _ in _fields_])} = {', '.join([_[0] for _ in _fields_])}\n\n" - for index, module in enumerate(modules): - if len(__module__.functions[module]) == 0: continue - if not rst: definitions += f"# {SDL_BINARY_PATTERNS[SDL_SYSTEM][0].format(module)} implementation.\n\n" + for index, module in enumerate(modules): + if len(__module__.functions[module]) == 0: continue + if not rst: definitions += f"# {SDL_BINARY_PATTERNS[SDL_SYSTEM][0].format(module)} implementation.\n\n" - for _index, func in enumerate(__module__.functions[module]): - if (_ := __module__.functions[module][func]).__name__ == "__inner__": _ = _.func - vararg, restype, argtypes, (description, arguments, _return) = _.vararg, _.restype, _.argtypes, _.__doc__ + for _index, func in enumerate(__module__.functions[module]): + if (_ := __module__.functions[module][func]).__name__ == "__inner__": _ = _.func + vararg, restype, argtypes, (description, arguments, _return) = _.vararg, _.restype, _.argtypes, _.__doc__ - if arguments is not None and len(arguments) != len(argtypes): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Argument count mismatch for 'https://wiki.libsdl.org/{module}/{func}' (expected: {len(arguments)}, got: {len(argtypes)}).") + if arguments is not None and len(arguments) != len(argtypes): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Argument count mismatch for 'https://wiki.libsdl.org/{module}/{func}' (expected: {len(arguments)}, got: {len(argtypes)}).") - if arguments is None: - arguments = [f"_{i}" for i in range(len(argtypes))] + if arguments is None: + arguments = [f"_{i}" for i in range(len(argtypes))] - else: - for __index, i in enumerate(arguments): - if (left := SDL_PYTHONIZE_TYPE(arguments[i], i)) != (right := SDL_GET_FULL_NAME(argtypes[__index])): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Argument type mismatch for 'https://wiki.libsdl.org/{module}/{func}' (argument: {i}, expected: {left}, got: {right}).") + else: + for __index, i in enumerate(arguments): + if (left := SDL_PYTHONIZE_TYPE(arguments[i], i)) != (right := SDL_GET_FULL_NAME(argtypes[__index])): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Argument type mismatch for 'https://wiki.libsdl.org/{module}/{func}' (argument: {i}, expected: {left}, got: {right}).") - arguments = [f"{'_' if i in keyword.kwlist else ''}{i.replace('[', '').replace(']', '')}" for i in arguments] + arguments = [f"{'_' if i in keyword.kwlist else ''}{i.replace('[', '').replace(']', '')}" for i in arguments] - if _return is not None and (left := SDL_PYTHONIZE_TYPE(_return)) != (right := SDL_GET_FULL_NAME(restype)): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Return type mismatch for 'https://wiki.libsdl.org/{module}/{func}' (expected: {left}, got: {right}).") + if _return is not None and (left := SDL_PYTHONIZE_TYPE(_return)) != (right := SDL_GET_FULL_NAME(restype)): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Return type mismatch for 'https://wiki.libsdl.org/{module}/{func}' (expected: {left}, got: {right}).") - definitions += f"def {func}({', '.join([f'{arg}: {SDL_GET_FULL_NAME(type)}' for arg, type in zip(arguments, argtypes)] + (['*args: typing.Any'] if vararg else []))}) -> {SDL_GET_FULL_NAME(restype)}:\n" + definitions += f"def {func}({', '.join([f'{arg}: {SDL_GET_FULL_NAME(type)}' for arg, type in zip(arguments, argtypes)] + (['*args: typing.Any'] if vararg else []))}) -> {SDL_GET_FULL_NAME(restype)}:\n" - if not rst or description is not None: definitions += f"{' ' * 4}\"\"\"\n" - if description is not None: definitions += f" {description}\n" - if not rst: definitions += f"\n{' ' * 4}https://wiki.libsdl.org/{module}/{func}\n" - if not rst or description is not None: definitions += f"{' ' * 4}\"\"\"" - if not rst: definitions += f"\n{' ' * 4}return raw.{func}({', '.join(arguments + (['*args'] if vararg else []))})" - if rst and description is None: definitions += f"\n{' ' * 4}..." + if not rst or description is not None: definitions += f"{' ' * 4}\"\"\"\n" + if description is not None: definitions += f" {description}\n" + if not rst: definitions += f"\n{' ' * 4}https://wiki.libsdl.org/{module}/{func}\n" + if not rst or description is not None: definitions += f"{' ' * 4}\"\"\"" + if not rst: definitions += f"\n{' ' * 4}return raw.{func}({', '.join(arguments + (['*args'] if vararg else []))})" + if rst and description is None: definitions += f"\n{' ' * 4}..." - if _index != len(__module__.functions[module]) - 1: - definitions += "\n\n" + if _index != len(__module__.functions[module]) - 1: + definitions += "\n\n" - if index != len(modules) - 1 and len(__module__.functions[modules[index + 1]]) != 0: - definitions += "\n\n" + if index != len(modules) - 1 and len(__module__.functions[modules[index + 1]]) != 0: + definitions += "\n\n" - for i in types: - count, func = i.count("LP_"), i.replace("LP_", "") - result += f"{i}: typing.TypeAlias = {'SDL_POINTER[' * count}{'ctypes.' if func.startswith('c_') else ''}{func}{']' * count}\n" + for i in types: + count, func = i.count("LP_"), i.replace("LP_", "") + result += f"{i}: typing.TypeAlias = {'SDL_POINTER[' * count}{'ctypes.' if func.startswith('c_') else ''}{func}{']' * count}\n" - return f"{result}\n{definitions}" + return f"{result}\n{definitions}" def SDL_GET_OR_GENERATE_DOCS(*args: typing.Any, **kwargs: typing.Any) -> bytes: - """Get type hints and docstrings for SDL3 functions/structures from github or by generating them.""" + """Get type hints and docstrings for SDL3 functions/structures from github or by generating them.""" - try: - headers = {"Accept": "application/vnd.github+json"} + try: + headers = {"Accept": "application/vnd.github+json"} - if "SDL_GITHUB_TOKEN" in os.environ: - headers["Authorization"] = f"Bearer {os.environ['SDL_GITHUB_TOKEN']}" + if "SDL_GITHUB_TOKEN" in os.environ: + headers["Authorization"] = f"Bearer {os.environ['SDL_GITHUB_TOKEN']}" - for release in (_ := requests.get(f"https://api.github.com/repos/Aermoss/PySDL3/releases", headers = headers).json()): - if isinstance(release, str): - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to get docs from github: {_[release]}") - break + for release in (_ := requests.get("https://api.github.com/repos/Aermoss/PySDL3/releases", headers = headers).json()): + if isinstance(release, str): + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to get docs from github: {_[release]}") + break - if release["tag_name"] != f"v{__version__}": - continue + if release["tag_name"] != f"v{__version__}": + continue - for asset in release["assets"]: - if asset["name"] != f"{SDL_SYSTEM}-Docs.py": - continue + for asset in release["assets"]: + if asset["name"] != f"{SDL_SYSTEM}-Docs.py": + continue - with requests.get(asset["browser_download_url"], headers = headers, stream = True) as response: - if response.status_code != 200: - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to get docs from github, status: {response.status_code} ({response.reason}).") - break + with requests.get(asset["browser_download_url"], headers = headers, stream = True) as response: + if response.status_code != 200: + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to get docs from github, status: {response.status_code} ({response.reason}).") + break - return bytearray().join([chunk for chunk in response.iter_content(chunk_size = 8192) if chunk]) + return bytearray().join([chunk for chunk in response.iter_content(chunk_size = 8192) if chunk]) - except requests.RequestException as exc: - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to get docs from github: {exc}.") + except requests.RequestException as exc: + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to get docs from github: {exc}.") - SDL_LOGGER.Log(SDL_LOGGER.Warning, "Generating docs... this may take a while.") - return SDL_GENERATE_DOCS(*args, **kwargs).encode("utf-8") + SDL_LOGGER.Log(SDL_LOGGER.Warning, "Generating docs... this may take a while.") + return SDL_GENERATE_DOCS(*args, **kwargs).encode("utf-8") if not __initialized__ and int(os.environ.get("SDL_CTYPES_ALIAS_FIX", "0" if __frozen__ or __release__ else "1")) > 0: - for i in dir(ctypes): - if i.startswith("c_") and getattr(ctypes, i).__name__ != i and hasattr(getattr(ctypes, i), "_type_"): - setattr(ctypes, i, SDL_TYPE[i, getattr(ctypes, i)]) - -from sdl3.SDL import * # type: ignore + for i in dir(ctypes): + if i.startswith("c_") and getattr(ctypes, i).__name__ != i and hasattr(getattr(ctypes, i), "_type_"): + setattr(ctypes, i, SDL_TYPE[i, getattr(ctypes, i)]) if __doc_generator__: - import sdl3.SDL as raw # type: ignore + import sdl3.SDL as raw # type: ignore + + class SDL_CLONE_METACLASS(type): + """Simple clone metaclass.""" - class SDL_CLONE_METACLASS(type): - """Simple clone metaclass.""" + def __new__(cls, name: str, _: typing.Any, attributes: dict[str, typing.Any]) -> typing.Any: + _class = getattr(raw, name) - def __new__(cls, name: str, _: typing.Any, attributes: dict[str, typing.Any]) -> typing.Any: - _class = getattr(raw, name) - - for key, value in attributes.items(): - if key in ["__init__"]: - setattr(_class, key, value) + for key, value in attributes.items(): + if key in ["__init__"]: + setattr(_class, key, value) - return _class + return _class SDL_VERSIONNUM_STRING: abc.Callable[[int], str] = lambda num: str(None).lower() if not num else \ - f"{SDL_VERSIONNUM_MAJOR(num)}.{SDL_VERSIONNUM_MINOR(num)}.{SDL_VERSIONNUM_MICRO(num)}" + f"{SDL_VERSIONNUM_MAJOR(num)}.{SDL_VERSIONNUM_MINOR(num)}.{SDL_VERSIONNUM_MICRO(num)}" if not __initialized__: - if int(os.environ.get("SDL_CHECK_BINARY_VERSION", "1")) > 0: - for module, left, right in zip(SDL_MODULES, [SDL_GetVersion, IMG_Version, Mix_Version, TTF_Version, RTF_Version, NET_GetVersion], [SDL_VERSION, SDL_IMAGE_VERSION, SDL_MIXER_VERSION, SDL_TTF_VERSION, SDL_RTF_VERSION, SDL_NET_VERSION]): - if module in binaryMap and (_ := left()) != right: SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Version mismatch with binary: '{SDL_BINARY_PATTERNS[SDL_SYSTEM][0].format(module)}' (expected: {SDL_VERSIONNUM_STRING(right)}, got: {SDL_VERSIONNUM_STRING(_)}).") - - def SDL_TRY_WRITE_DOCS() -> bool | None: - try: - with open(__doc_file__, "wb") as file: - file.write(SDL_GET_OR_GENERATE_DOCS(raw = raw)) - - return True - - except OSError as exc: - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to write docs: {exc}.") - - if __doc_generator__ and (os.path.exists(__doc_file__) or SDL_TRY_WRITE_DOCS()): - try: - if "sdl3.__doc__" in sys.modules: del sys.modules["sdl3.__doc__"] - from .__doc__ import * # type: ignore - exec(getattr(sys.modules["sdl3.__doc__"], "__doc__"), data := {}) - - except (SyntaxError, NameError, TypeError) as exc: - import traceback - traceback.print_exc() - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to read docs: {exc}.") - data = None - - if os.path.exists(__doc_file__) and (not data or "__meta__" not in data or data["__meta__"]["target"] != f"v{__version__}" or data["__meta__"]["system"] != SDL_SYSTEM) and SDL_TRY_WRITE_DOCS(): - if "sdl3.__doc__" in sys.modules: del sys.modules["sdl3.__doc__"] - SDL_LOGGER.Log(SDL_LOGGER.Warning, "Reloading module: 'sdl3.__doc__'...") - from .__doc__ import * # type: ignore - - else: - try: - if not (__frozen__ or __release__): - if os.path.exists(__doc_file__): os.remove(__doc_file__) - - except PermissionError as exc: - SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to remove docs: {exc}.") \ No newline at end of file + if int(os.environ.get("SDL_CHECK_BINARY_VERSION", "1")) > 0: + for module, left, right in zip(SDL_MODULES, [SDL_GetVersion, IMG_Version, Mix_Version, TTF_Version, RTF_Version, NET_GetVersion], [SDL_VERSION, SDL_IMAGE_VERSION, SDL_MIXER_VERSION, SDL_TTF_VERSION, SDL_RTF_VERSION, SDL_NET_VERSION]): + if module in binaryMap and (_ := left()) != right: SDL_LOGGER.Log(SDL_LOGGER.Warning, f"Version mismatch with binary: '{SDL_BINARY_PATTERNS[SDL_SYSTEM][0].format(module)}' (expected: {SDL_VERSIONNUM_STRING(right)}, got: {SDL_VERSIONNUM_STRING(_)}).") + + def SDL_TRY_WRITE_DOCS() -> bool | None: + try: + with open(__doc_file__, "wb") as file: + file.write(SDL_GET_OR_GENERATE_DOCS(raw = raw)) + + return True + + except OSError as exc: + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to write docs: {exc}.") + + if __doc_generator__ and (os.path.exists(__doc_file__) or SDL_TRY_WRITE_DOCS()): + try: + if "sdl3.__doc__" in sys.modules: del sys.modules["sdl3.__doc__"] + from .__doc__ import * # type: ignore + exec(getattr(sys.modules["sdl3.__doc__"], "__doc__"), data := {}) + + except (SyntaxError, NameError, TypeError) as exc: + import traceback + traceback.print_exc() + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to read docs: {exc}.") + data = None + + if os.path.exists(__doc_file__) and (not data or "__meta__" not in data or data["__meta__"]["target"] != f"v{__version__}" or data["__meta__"]["system"] != SDL_SYSTEM) and SDL_TRY_WRITE_DOCS(): + if "sdl3.__doc__" in sys.modules: del sys.modules["sdl3.__doc__"] + SDL_LOGGER.Log(SDL_LOGGER.Warning, "Reloading module: 'sdl3.__doc__'...") + from .__doc__ import * # type: ignore + + else: + try: + if not (__frozen__ or __release__): + if os.path.exists(__doc_file__): os.remove(__doc_file__) + + except PermissionError as exc: + SDL_LOGGER.Log(SDL_LOGGER.Error, f"Failed to remove docs: {exc}.")