# resourceutil.py - utility for looking up resources # # Copyright 2005 K. Thananchayan # Copyright 2005-2007 Olivia Mackall # Copyright 2006 Vadim Gelfer # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import annotations import os import sys import typing from typing import Iterator from .. import pycompat if typing.TYPE_CHECKING: from typing import ( BinaryIO, Iterator, ) def mainfrozen(): """return True if we are a frozen executable. The code supports py2exe (most common, Windows only) and tools/freeze (portable, not much used). """ return ( hasattr(sys, "frozen") # new py2exe or hasattr(sys, "importers") # old py2exe or getattr( getattr(sys.modules.get('__main__'), '__spec__', None), 'origin', None, ) == 'frozen' # tools/freeze ) # the location of data files matching the source code if mainfrozen() and getattr(sys, "frozen", None) != "macosx_app": # executable version (py2exe) doesn't support __file__ datapath = os.path.dirname(pycompat.sysexecutable) _rootpath = datapath # The installers store the files outside of library.zip, like # C:\Program Files\Mercurial\defaultrc\*.rc. This strips the # leading "mercurial." off of the package name, so that these # pseudo resources are found in their directory next to the # executable. def _package_path(package: bytes) -> bytes: dirs = package.split(b".") assert dirs[0] == b"mercurial" return os.path.join(_rootpath, *dirs[1:]) else: datapath = os.path.dirname(os.path.dirname(pycompat.fsencode(__file__))) _rootpath = os.path.dirname(datapath) def _package_path(package: bytes) -> bytes: return os.path.join(_rootpath, *package.split(b".")) try: # importlib.resources exists from Python 3.7; see fallback in except clause # further down from importlib import resources # pytype: disable=import-error # Force loading of the resources module if hasattr(resources, 'files'): # Introduced in Python 3.9 resources.files # pytype: disable=module-attr else: resources.open_binary # pytype: disable=module-attr # py2exe raises an AssertionError if uses importlib.resources if getattr(sys, "frozen", None) in ("console_exe", "windows_exe"): raise ImportError except (ImportError, AttributeError): # importlib.resources was not found (almost definitely because we're on a # Python version before 3.7) def open_resource(package: bytes, name: bytes) -> "BinaryIO": path = os.path.join(_package_path(package), name) return open(path, "rb") def is_resource(package: bytes, name: bytes) -> bool: path = os.path.join(_package_path(package), name) try: return os.path.isfile(pycompat.fsdecode(path)) except (IOError, OSError): return False def contents(package: bytes) -> "Iterator[bytes]": path = pycompat.fsdecode(_package_path(package)) for p in os.listdir(path): yield pycompat.fsencode(p) else: from .. import encoding def open_resource(package: bytes, name: bytes) -> "BinaryIO": if hasattr(resources, 'files'): return ( resources.files( # pytype: disable=module-attr pycompat.sysstr(package) ) .joinpath(pycompat.sysstr(name)) .open('rb') ) else: return resources.open_binary( # pytype: disable=module-attr pycompat.sysstr(package), pycompat.sysstr(name) ) def is_resource(package: bytes, name: bytes) -> bool: if hasattr(resources, 'files'): # Introduced in Python 3.9 return ( resources.files(pycompat.sysstr(package)) .joinpath(encoding.strfromlocal(name)) .is_file() ) else: return resources.is_resource( # pytype: disable=module-attr pycompat.sysstr(package), encoding.strfromlocal(name) ) def contents(package: bytes) -> "Iterator[bytes]": if hasattr(resources, 'files'): # Introduced in Python 3.9 for path in resources.files(pycompat.sysstr(package)).iterdir(): if path.is_file(): yield encoding.strtolocal(path.name) else: # pytype: disable=module-attr for r in resources.contents(pycompat.sysstr(package)): # pytype: enable=module-attr yield encoding.strtolocal(r)