class AssetAttributes(object):
    """Provides access to asset path properties. The attributes object is
    created with environment object and relative (or logical) asset path.

    Some properties may be useful or not, depending on the type of passed path.
    If it is a relative asset path, you can use all properties except
    :attr:`search_paths`. In case of a logical asset path it makes sense to
    use only those properties that are not related to processors and compressor.

    :param environment: an instance of :class:`~gears.environment.Environment`
                        class.
    :param path: a relative or logical path of the asset.
    """

    def __init__(self, environment, path):
        #: Used to access the registries of compilers, processors, etc.
        #: It can be also used by asset. See
        #: :class:`~gears.environment.Environment` for more information.
        self.environment = environment

        #: The relative (or logical) path to asset.
        self.path = path

        #: The relative path to the directory the asset.
        self.dirname = os.path.dirname(path)

    @cached_property
    def search_paths(self):
        """The list of logical paths which are used to search for an asset.
        This property makes sense only if the attributes was created with
        logical path.

        It is assumed that the logical path can be a directory containing a
        file named ``index`` with the same suffix.

        Example::

            >>> attrs = AssetAttributes(environment, 'js/app.js')
            >>> attrs.search_paths
            ['js/app.js', 'js/app/index.js']

            >>> attrs = AssetAttributes(environment, 'js/app/index.js')
            >>> attrs.search_paths
            ['js/models/index.js']
        """
        paths = [self.path]
        if os.path.basename(self.path_without_suffix) != 'index':
            path = os.path.join(self.path_without_suffix, 'index')
            paths.append(path + ''.join(self.suffix))
        return paths

    @cached_property
    def path_without_suffix(self):
        """The relative path to asset without suffix.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/app.js')
            >>> attrs.path_without_suffix
            'js/app'
        """
        if self.suffix:
            return self.path[:-len(''.join(self.suffix))]
        return self.path

    @property
    def logical_path(self):
        """The logical path to asset.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/models.js.coffee')
            >>> attrs.logical_path
            'js/models.js'
        """
        format_extension = self.format_extension or self.compiler_format_extension
        if format_extension is None:
            return self.path
        return self.path_without_suffix + format_extension

    @cached_property
    def extensions(self):
        """The list of asset extensions.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/models.js.coffee')
            >>> attrs.extensions
            ['.js', '.coffee']

            >>> attrs = AssetAttributes(environment, 'js/lib/external.min.js.coffee')
            >>> attrs.format_extension
            ['.min', '.js', '.coffee']
        """
        return re.findall(r'\.[^.]+', os.path.basename(self.path))

    @cached_property
    def format_extension(self):
        """The format extension of asset.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/models.js.coffee')
            >>> attrs.format_extension
            '.js'

            >>> attrs = AssetAttributes(environment, 'js/lib/external.min.js.coffee')
            >>> attrs.format_extension
            '.js'
        """
        for extension in reversed(self.extensions):
            compiler = self.environment.compilers.get(extension)
            if not compiler and self.environment.mimetypes.get(extension):
                return extension

    @cached_property
    def suffix(self):
        """The list of asset extensions starting from the first known extension.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/lib/external.min.js.coffee')
            >>> attrs.suffix
            ['.js', '.coffee']
        """
        return self.extensions[len(self.unknown_extensions):]

    @cached_property
    def unknown_extensions(self):
        """The list of unknown extensions, which are actually parts of asset
        filename. Example::

            >>> attrs = AssetAttributes(environment, 'js/lib-2.0.min.js')
            >>> attrs.suffix
            ['.0', '.min']
        """
        unknown_extensions = []
        for extension in self.extensions:
            compiler = self.environment.compilers.get(extension)
            if compiler or self.environment.mimetypes.get(extension):
                return unknown_extensions
            unknown_extensions.append(extension)
        return unknown_extensions

    @cached_property
    def compiler_extensions(self):
        """The list of compiler extensions.
        Example::

            >>> attrs = AssetAttributes(environment, 'js/lib/external.min.js.coffee')
            >>> attrs.compiler_extensions
            ['.coffee']
        """
        try:
            index = self.extensions.index(self.format_extension)
        except ValueError:
            index = 0
        extensions = self.extensions[index:]
        return [e for e in extensions if self.environment.compilers.get(e)]

    @cached_property
    def compilers(self):
        """The list of compilers used to build asset."""
        return [self.environment.compilers.get(e) for e in self.compiler_extensions]

    @property
    def preprocessors(self):
        """The list of preprocessors used to build asset."""
        return self.environment.preprocessors.get(self.mimetype)

    @property
    def postprocessors(self):
        """The list of postprocessors used to build asset."""
        return self.environment.postprocessors.get(self.mimetype)

    @property
    def processors(self):
        """The list of all processors (preprocessors, compilers,
        postprocessors) used to build asset.
        """
        return self.preprocessors + list(reversed(self.compilers)) + self.postprocessors

    @property
    def compressor(self):
        """The compressors used to compress the asset."""
        return self.environment.compressors.get(self.mimetype)

    @cached_property
    def mimetype(self):
        """MIME type of the asset."""
        return (self.environment.mimetypes.get(self.format_extension) or
                self.compiler_mimetype or 'application/octet-stream')

    @cached_property
    def compiler_mimetype(self):
        """Implicit MIME type of the asset by its compilers."""
        for compiler in reversed(self.compilers):
            if compiler.result_mimetype:
                return compiler.result_mimetype
        return None

    @cached_property
    def compiler_format_extension(self):
        """Implicit format extension on the asset by its compilers."""
        for extension, mimetype in self.environment.mimetypes.items():
            if mimetype == self.compiler_mimetype:
                return extension
        return None def build_asset(environment, path, check=False):
    path = strip_fingerprint(path)
    asset_attributes = AssetAttributes(environment, path)
    asset_attributes, absolute_path = environment.find(asset_attributes, True)
    if not asset_attributes.processors:
        return StaticAsset(asset_attributes, absolute_path)
    if check:
        return CheckAsset(asset_attributes, absolute_path)
    return Asset(asset_attributes, absolute_path) class SimpleCache(dict):

    def set(self, key, value):
        self[key] = value

    def get(self, key):
        return super(SimpleCache, self).get(key) def bytes(obj):
    if isinstance(obj, collections.Iterable):
        return ''.join(obj)
    return str(obj) class FileNotFound(Exception):
    pass class Manifest(object):

    def __init__(self, path):
        self.path = path
        self.data = {}
        if self.path:
            self.load()

    @property
    def files(self):
        return self.data.setdefault('files', {})

    def load(self):
        try:
            with open(self.path) as f:
                self.data = json.load(f)
        except IOError as e:
            if e.errno != errno.ENOENT:
                raise

    def dump(self):
        if not self.path:
            return
        dirpath = os.path.dirname(self.path)
        if not os.path.exists(dirpath):
            os.makedirs(dirpath)
        with open(self.path, 'w') as f:
            json.dump(self.data, f, indent=2) class DirectivesProcessor(BaseProcessor):

    supports_check_mode = True

    def __init__(self):
        self.types = {
            'public': self.process_public_directive,
            'params': self.process_params_directive,
            'require': self.process_require_directive,
            'require_directory': self.process_require_directory_directive,
            'require_tree': self.process_require_tree_directive,
            'require_self': self.process_require_self_directive,
            'depend_on': self.process_depend_on_directive,
        }

    def __call__(self, asset, check=False):
        self.asset = asset
        self.check = check
        self.parse()
        self.process_directives()

    def parse(self):
        directives, source = DirectivesParser().parse(self.asset.processed_source)
        self.directives = directives
        self.asset.processed_source = source

    def process_directives(self):
        for directive in self.directives:
            # shlex didn't support Unicode prior to 2.7.3
            if sys.version_info < (2, 7, 3):
                directive = directive.encode('utf-8')
            args = shlex.split(directive)
            self.types[args[0]](*args[1:])

    def process_public_directive(self):
        self.asset.params['public'] = True

    def process_params_directive(self, *params):
        for param in params:
            if '=' in param:
                key, value = param.split('=', 1)
                self.asset.params[key] = value

    def process_require_directive(self, path):
        if self.check:
            return

        found = False
        path = self.get_relative_path(path)
        list = self.asset.attributes.environment.list(path, self.asset.attributes.mimetype)
        for asset_attributes, absolute_path in sorted(list, key=lambda x: x[0].path.split('/')):
            self.asset.requirements.add(self.get_asset(asset_attributes, absolute_path))
            self.asset.dependencies.add(os.path.dirname(absolute_path))
            found = True
        if not found:
            raise FileNotFound(path)

    def process_require_directory_directive(self, path):
        if self.check:
            return
        self.process_require_directive(os.path.join(path, '*'))

    def process_require_tree_directive(self, path):
        if self.check:
            return
        self.process_require_directive(os.path.join(path, '**'))

    def process_require_self_directive(self):
        if self.check:
            return
        self.asset.requirements.add(self.asset)

    def process_depend_on_directive(self, path):
        if self.check:
            return

        found = False
        path = self.get_relative_path(path)
        list = self.asset.attributes.environment.list(path, self.asset.attributes.mimetype)
        for asset_attributes, absolute_path in list:
            self.asset.dependencies.add(absolute_path)
            found = True
        if not found:
            raise FileNotFound(path)

    def get_relative_path(self, require_path, is_directory=False):
        require_path = os.path.join(self.asset.attributes.dirname, require_path)
        return os.path.normpath(require_path)

    def get_asset(self, asset_attributes, absolute_path):
        return Asset(asset_attributes, absolute_path, self.asset.calls) class HexdigestPathsProcessor(BaseProcessor):

    url_re = re.compile(r"""url\((['"]?)\s*(.*?)\s*\1\)""")

    def __call__(self, asset):
        self.asset = asset
        self.environment = self.asset.attributes.environment
        self.current_dir = self.asset.attributes.dirname
        self.process()

    def process(self):
        if self.environment.fingerprinting:
            self.asset.processed_source = rewrite_paths(
                self.asset.processed_source,
                self.rewrite_path,
            )

    def rewrite_path(self, path):
        logical_path = os.path.normpath(os.path.join(self.current_dir, path))
        try:
            asset = build_asset(self.environment, logical_path)
        except FileNotFound:
            return path
        self.asset.dependencies.add(asset.absolute_path)
        relpath = str(os.path.relpath(asset.hexdigest_path, self.current_dir))
        if is_py2:
            return relpath.encode('string-escape')
        elif is_py3:
            return relpath.encode('unicode-escape').decode() class SemicolonsProcessor(BaseProcessor):

    def __call__(self, asset):
        if needs_semicolon(asset.processed_source):
            asset.processed_source += ';\n' def get_condition_func(condition):
    if isinstance(condition, Callable):
        return condition
    if isinstance(condition, str):
        condition = re.compile(condition)
    return lambda path: condition.search(path) def unique(iterable, key=lambda x: x):
    yielded = set()
    for item in iterable:
        keyitem = key(item)
        if keyitem not in yielded:
            yielded.add(keyitem)
            yield item
import gzip
import os
from pkg_resources import iter_entry_points
from glob2.fnmatch import fnmatch
from .asset_attributes import AssetAttributes
from .assets import build_asset
from .cache import SimpleCache
from .compat import bytes
from .exceptions import FileNotFound
from .manifest import Manifest
from .processors import (
    DirectivesProcessor,
    HexdigestPathsProcessor,
    SemicolonsProcessor
)
from .utils import get_condition_func, unique
        self.preprocessors.register_defaults()
        self.postprocessors.register_defaults()

    def register_entry_points(self, exclude=()):
        """Allow Gears plugins to inject themselves to the environment. For
        example, if your plugin's package contains such ``entry_points``
        definition in ``setup.py``, ``gears_plugin.register`` function will be
        called with current environment during ``register_entry_points`` call::

            entry_points = {
                'gears': [
                    'register = gears_plugin:register',
                ],
            }

        Here is an example of such function::

            def register(environment):
                assets_dir = os.path.join(os.path.dirname(__file__), 'assets')
                assets_dir = os.path.absolute_path(assets_dir)
                environment.register(FileSystemFinder([assets_dir]))

        If you want to disable this behavior for some plugins, list their
        packages using ``exclude`` argument::

            environment.register_entry_points(exclude=['plugin'])
        """
        for entry_point in iter_entry_points('gears', 'register'):
            if entry_point.module_name not in exclude:
                register = entry_point.load()
                register(self)

    def find(self, item, logical=False):
        """Find files using :attr:`finders` registry. The ``item`` parameter
        can be an instance of :class:`~gears.asset_attributes.AssetAttributes`
        class, a path to the asset or a logical path to the asset. If ``item``
        is a logical path, `logical` parameter must be set to ``True``.

        Returns a tuple with :class:`~gears.asset_attributes.AssetAttributes`
        instance for found file path as first item, and absolute path to this
        file as second item.

        If nothing is found, :class:`gears.exceptions.FileNotFound` exception
        is rased.
        """
        if isinstance(item, AssetAttributes):
            for path in item.search_paths:
                try:
                    return self.find(path, logical)
                except FileNotFound:
                    continue
            raise FileNotFound(item.path)
        if logical:
            asset_attributes = AssetAttributes(self, item)
            suffixes = self.suffixes.find(asset_attributes.mimetype)
            if not suffixes:
                return self.find(item)
            path = asset_attributes.path_without_suffix
            for suffix in suffixes:
                try:
                    return self.find(path + suffix)
                except FileNotFound:
                    continue
        else:
            for finder in self.finders:
                try:
                    absolute_path = finder.find(item)
                except FileNotFound:
                    continue
                return AssetAttributes(self, item), absolute_path
        raise FileNotFound(item)

    def list(self, path, mimetype=None):
        """Yield two-tuples for all files found in the directory given by
        ``path`` parameter. Result can be filtered by the second parameter,
        ``mimetype``, that must be a MIME type of assets compiled source code.
        Each tuple has :class:`~gears.asset_attributes.AssetAttributes`
        instance for found file path as first item, and absolute path to this
        file as second item.

        Usage example::

            # Yield all files from 'js/templates' directory.
            environment.list('js/templates/*')

            # Yield only files that are in 'js/templates' directory and have
            # 'application/javascript' MIME type of compiled source code.
            environment.list('js/templates/*', mimetype='application/javascript')
        """
        basename_pattern = os.path.basename(path)

        if path.endswith('**'):
            paths = [path]
        else:
            paths = AssetAttributes(self, path).search_paths
        paths = map(lambda p: p if p.endswith('*') else p + '*', paths)

        results = unique(self._list_paths(paths), lambda x: x[0])
        for logical_path, absolute_path in results:
            asset_attributes = AssetAttributes(self, logical_path)
            if mimetype is not None and asset_attributes.mimetype != mimetype:
                continue

            basename = os.path.basename(asset_attributes.path_without_suffix)
            if not fnmatch(basename, basename_pattern) and basename != 'index':
                continue

            yield asset_attributes, absolute_path

    def _list_paths(self, paths):
        for path in paths:
            for result in self.finders.list(path):
                yield result

    def save(self):
        """Save handled public assets to :attr:`root` directory."""
        for asset_attributes, absolute_path in self.list('**'):
            logical_path = os.path.normpath(asset_attributes.logical_path)
            check_asset = build_asset(self, logical_path, check=True)
            if check_asset.is_public:

                asset = build_asset(self, logical_path)