digitalrounin/py-lambda-packer

View on GitHub
plpacker/fileset.py

Summary

Maintainability
A
1 hr
Test Coverage
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import fnmatch
from glob import glob
import os
import logging

import plpacker.utils

LOGGER = logging.getLogger(__name__)


class FileSet(object):
    # pylint: disable=too-few-public-methods
    def __init__(self, directory, includes, excludes=(), followlinks=False):
        # Validate
        if directory is None:
            raise ValueError('"None" is not an acceptable "directory" value.')
        if not includes:
            raise ValueError('"includes" must be provided.')

        # Scalars to tuples
        if not isinstance(includes, (list, tuple)):
            includes = (includes,)
        if excludes and not isinstance(excludes, (list, tuple)):
            excludes = (excludes,)

        # Save
        self.directory = plpacker.utils.expand_path(directory, True)
        self.includes = (includes
                         if isinstance(includes, tuple) else tuple(includes))
        self.excludes = (excludes
                         if isinstance(excludes, tuple) else tuple(excludes))
        self.followlinks = followlinks
        self.fileset = self._expand_fileset()

    def __len__(self):
        return len(self.fileset)

    def __getitem__(self, index):
        return self.fileset[index]

    def __iter__(self):
        return iter(self.fileset)

    def pairs(self):
        return ((os.path.join(self.directory, item),
                 item) for item in self.fileset)

    def _expand_fileset(self):
        files = set()
        for include in self.includes:
            files.update(self._expand_glob(include, self.followlinks))

        for exclude in self.excludes:
            files = files.difference(
                self._expand_glob(exclude, self.followlinks))

        return tuple(sorted(item for item in files))

    def _expand_glob(self, expression, followlinks=False):
        if os.path.isabs(expression):
            raise ValueError('Absolute paths in globs are not supported: {}'
                             .format(expression))

        if expression:
            full_glob = os.path.join(self.directory, expression)
        else:
            full_glob = self.directory

        if full_glob != os.path.normpath(full_glob):
            raise ValueError('Dots (".." or ".") are not permitted: {}'
                             .format(expression))

        if '**' in expression:
            result = _find_files_recursively(full_glob, followlinks)
        else:
            result = _find_files(full_glob)
        directory_len = len(self.directory) + 1
        return tuple(sorted(item[directory_len:] for item in result))


def _find_files_recursively(expression, followlinks=False):
    if '**' not in expression:
        raise ValueError('Glob is missing "**": {}'.format(expression))
    (directories, glob_part) = expression.rsplit('**', 1)
    if not glob_part:
        glob_part = '*'
    elif glob_part.startswith(os.path.sep):
        glob_part = glob_part[len(os.path.sep):]

    result = []
    for directory in glob(directories):
        for (dirpath, _, filenames) in os.walk(
                directory, followlinks=followlinks):
            for filename in fnmatch.filter(filenames, glob_part):
                result.append(os.path.join(dirpath, filename))
    return result


def _find_files(expression):
    items = []
    for item in glob(expression):
        if os.path.isfile(item):
            items.append(item)
    return items