text: # -*- coding: utf-8 -*-
  
  #
  
  # Copyright (C) 2013-2017 Vinay Sajip.
  
  # Licensed to the Python Software Foundation under a contributor agreement.
  
  # See LICENSE.txt and CONTRIBUTORS.txt.
  
  #
  
  from __future__ import unicode_literals
  
  
  
  import bisect
  
  import io
  
  import logging
  
  import os
  
  import pkgutil
  
  import shutil
  
  import sys
  
  import types
  
  import zipimport
  
  
  
  from . import DistlibException
  
  from .util import cached_property, get_cache_base, path_to_cache_dir, Cache
  
  
  
  logger = logging.getLogger(__name__)
  
  
  
  
  
  cache = None    # created when needed
  
  
  
  
  
  class ResourceCache(Cache):
  
      def __init__(self, base=None):
  
          if base is None:
  
              # Use native string to avoid issues on 2.x: see Python #20140.
  
              base = os.path.join(get_cache_base(), str('resource-cache'))
  
          super(ResourceCache, self).__init__(base)
  
  
  
      def is_stale(self, resource, path):
  
          """
  
          Is the cache stale for the given resource?
  
  
  
          :param resource: The :class:`Resource` being cached.
  
          :param path: The path of the resource in the cache.
  
          :return: True if the cache is stale.
  
          """
  
          # Cache invalidation is a hard problem :-)
  
          return True
  
  
  
      def get(self, resource):
  
          """
  
          Get a resource into the cache,
  
  
  
          :param resource: A :class:`Resource` instance.
  
          :return: The pathname of the resource in the cache.
  
          """
  
          prefix, path = resource.finder.get_cache_info(resource)
  
          if prefix is None:
  
              result = path
  
          else:
  
              result = os.path.join(self.base, self.prefix_to_dir(prefix), path)
  
              dirname = os.path.dirname(result)
  
              if not os.path.isdir(dirname):
  
                  os.makedirs(dirname)
  
              if not os.path.exists(result):
  
                  stale = True
  
              else:
  
                  stale = self.is_stale(resource, path)
  
              if stale:
  
                  # write the bytes of the resource to the cache location
  
                  with open(result, 'wb') as f:
  
                      f.write(resource.bytes)
  
          return result
  
  
  
  
  
  class ResourceBase(object):
  
      def __init__(self, finder, name):
  
          self.finder = finder
  
          self.name = name
  
  
  
  
  
  class Resource(ResourceBase):
  
      """
  
      A class representing an in-package resource, such as a data file. This is
  
      not normally instantiated by user code, but rather by a
  
      :class:`ResourceFinder` which manages the resource.
  
      """
  
      is_container = False        # Backwards compatibility
  
  
  
      def as_stream(self):
  
          """
  
          Get the resource as a stream.
  
  
  
          This is not a property to make it obvious that it returns a new stream
  
          each time.
  
          """
  
          return self.finder.get_stream(self)
  
  
  
      @cached_property
  
      def file_path(self):
  
          global cache
  
          if cache is None:
  
              cache = ResourceCache()
  
          return cache.get(self)
  
  
  
      @cached_property
  
      def bytes(self):
  
          return self.finder.get_bytes(self)
  
  
  
      @cached_property
  
      def size(self):
  
          return self.finder.get_size(self)
  
  
  
  
  
  class ResourceContainer(ResourceBase):
  
      is_container = True     # Backwards compatibility
  
  
  
      @cached_property
  
      def resources(self):
  
          return self.finder.get_resources(self)
  
  
  
  
  
  class ResourceFinder(object):
  
      """
  
      Resource finder for file system resources.
  
      """
  
  
  
      if sys.platform.startswith('java'):
  
          skipped_extensions = ('.pyc', '.pyo', '.class')
  
      else:
  
          skipped_extensions = ('.pyc', '.pyo')
  
  
  
      def __init__(self, module):
  
          self.module = module
  
          self.loader = getattr(module, '__loader__', None)
  
          self.base = os.path.dirname(getattr(module, '__file__', ''))
  
  
  
      def _adjust_path(self, path):
  
          return os.path.realpath(path)
  
  
  
      def _make_path(self, resource_name):
  
          # Issue #50: need to preserve type of path on Python 2.x
  
          # like os.path._get_sep
  
          if isinstance(resource_name, bytes):    # should only happen on 2.x
  
              sep = b'/'
  
          else:
  
              sep = '/'
  
          parts = resource_name.split(sep)
  
          parts.insert(0, self.base)
  
          result = os.path.join(*parts)
  
          return self._adjust_path(result)
  
  
  
      def _find(self, path):
  
          return os.path.exists(path)
  
  
  
      def get_cache_info(self, resource):
  
          return None, resource.path
  
  
  
      def find(self, resource_name):
  
          path = self._make_path(resource_name)
  
          if not self._find(path):
  
              result = None
  
          else:
  
              if self._is_directory(path):
  
                  result = ResourceContainer(self, resource_name)
  
              else:
  
                  result = Resource(self, resource_name)
  
              result.path = path
  
          return result
  
  
  
      def get_stream(self, resource):
  
          return open(resource.path, 'rb')
  
  
  
      def get_bytes(self, resource):
  
          with open(resource.path, 'rb') as f:
  
              return f.read()
  
  
  
      def get_size(self, resource):
  
          return os.path.getsize(resource.path)
  
  
  
      def get_resources(self, resource):
  
          def allowed(f):
  
              return (f != '__pycache__' and not
  
                      f.endswith(self.skipped_extensions))
  
          return set([f for f in os.listdir(resource.path) if allowed(f)])
  
  
  
      def is_container(self, resource):
  
          return self._is_directory(resource.path)
  
  
  
      _is_directory = staticmethod(os.path.isdir)
  
  
  
      def iterator(self, resource_name):
  
          resource = self.find(resource_name)
  
          if resource is not None:
  
              todo = [resource]
  
              while todo:
  
                  resource = todo.pop(0)
  
                  yield resource
  
                  if resource.is_container:
  
                      rname = resource.name
  
                      for name in resource.resources:
  
                          if not rname:
  
                              new_name = name
  
                          else:
  
                              new_name = '/'.join([rname, name])
  
                          child = self.find(new_name)
  
                          if child.is_container:
  
                              todo.append(child)
  
                          else:
  
                              yield child
  
  
  
  
  
  class ZipResourceFinder(ResourceFinder):
  
      """
  
      Resource finder for resources in .zip files.
  
      """
  
      def __init__(self, module):
  
          super(ZipResourceFinder, self).__init__(module)
  
          archive = self.loader.archive
  
          self.prefix_len = 1 + len(archive)
  
          # PyPy doesn't have a _files attr on zipimporter, and you can't set one
  
          if hasattr(self.loader, '_files'):
  
              self._files = self.loader._files
  
          else:
  
              self._files = zipimport._zip_directory_cache[archive]
  
          self.index = sorted(self._files)
  
  
  
      def _adjust_path(self, path):
  
          return path
  
  
  
      def _find(self, path):
  
          path = path[self.prefix_len:]
  
          if path in self._files:
  
              result = True
  
          else:
  
              if path and path[-1] != os.sep:
  
                  path = path + os.sep
  
              i = bisect.bisect(self.index, path)
  
              try:
  
                  result = self.index[i].startswith(path)
  
              except IndexError:
  
                  result = False
  
          if not result:
  
              logger.debug('_find failed: %r %r', path, self.loader.prefix)
  
          else:
  
              logger.debug('_find worked: %r %r', path, self.loader.prefix)
  
          return result
  
  
  
      def get_cache_info(self, resource):
  
          prefix = self.loader.archive
  
          path = resource.path[1 + len(prefix):]
  
          return prefix, path
  
  
  
      def get_bytes(self, resource):
  
          return self.loader.get_data(resource.path)
  
  
  
      def get_stream(self, resource):
  
          return io.BytesIO(self.get_bytes(resource))
  
  
  
      def get_size(self, resource):
  
          path = resource.path[self.prefix_len:]
  
          return self._files[path][3]
  
  
  
      def get_resources(self, resource):
  
          path = resource.path[self.prefix_len:]
  
          if path and path[-1] != os.sep:
  
              path += os.sep
  
          plen = len(path)
  
          result = set()
  
          i = bisect.bisect(self.index, path)
  
          while i < len(self.index):
  
              if not self.index[i].startswith(path):
  
                  break
  
              s = self.index[i][plen:]
  
              result.add(s.split(os.sep, 1)[0])   # only immediate children
  
              i += 1
  
          return result
  
  
  
      def _is_directory(self, path):
  
          path = path[self.prefix_len:]
  
          if path and path[-1] != os.sep:
  
              path += os.sep
  
          i = bisect.bisect(self.index, path)
  
          try:
  
              result = self.index[i].startswith(path)
  
          except IndexError:
  
              result = False
  
          return result
  
  
  
  _finder_registry = {
  
      type(None): ResourceFinder,
  
      zipimport.zipimporter: ZipResourceFinder
  
  }
  
  
  
  try:
  
      # In Python 3.6, _frozen_importlib -> _frozen_importlib_external
  
      try:
  
          import _frozen_importlib_external as _fi
  
      except ImportError:
  
          import _frozen_importlib as _fi
  
      _finder_registry[_fi.SourceFileLoader] = ResourceFinder
  
      _finder_registry[_fi.FileFinder] = ResourceFinder
  
      del _fi
  
  except (ImportError, AttributeError):
  
      pass
  
  
  
  
  
  def register_finder(loader, finder_maker):
  
      _finder_registry[type(loader)] = finder_maker
  
  
  
  _finder_cache = {}
  
  
  
  
  
  def finder(package):
  
      """
  
      Return a resource finder for a package.
  
      :param package: The name of the package.
  
      :return: A :class:`ResourceFinder` instance for the package.
  
      """
  
      if package in _finder_cache:
  
          result = _finder_cache[package]
  
      else:
  
          if package not in sys.modules:
  
              __import__(package)
  
          module = sys.modules[package]
  
          path = getattr(module, '__path__', None)
  
          if path is None:
  
              raise DistlibException('You cannot get a finder for a module, '
  
                                     'only for a package')
  
          loader = getattr(module, '__loader__', None)
  
          finder_maker = _finder_registry.get(type(loader))
  
          if finder_maker is None:
  
              raise DistlibException('Unable to locate finder for %r' % package)
  
          result = finder_maker(module)
  
          _finder_cache[package] = result
  
      return result
  
  
  
  
  
  _dummy_module = types.ModuleType(str('__dummy__'))
  
  
  
  
  
  def finder_for_path(path):
  
      """
  
      Return a resource finder for a path, which should represent a container.
  
  
  
      :param path: The path.
  
      :return: A :class:`ResourceFinder` instance for the path.
  
      """
  
      result = None
  
      # calls any path hooks, gets importer into cache
  
      pkgutil.get_importer(path)
  
      loader = sys.path_importer_cache.get(path)
  
      finder = _finder_registry.get(type(loader))
  
      if finder:
  
          module = _dummy_module
  
          module.__file__ = os.path.join(path, '')
  
          module.__loader__ = loader
  
          result = finder(module)
  
      return result
  
  