[Python-modules-commits] [python-glob2] 01/02: import pytest-bdd-0.5.tar.gz

Brian May bam at moszumanska.debian.org
Tue May 16 07:58:31 UTC 2017


This is an automated email from the git hooks/post-receive script.

bam pushed a commit to branch debian/master
in repository python-glob2.

commit 9c89b9c199a17254b2698fb86b839abc06612fcd
Author: Brian May <brian at linuxpenguins.xyz>
Date:   Tue May 16 17:36:44 2017 +1000

    import pytest-bdd-0.5.tar.gz
---
 .gitignore        |  13 ++++
 CHANGES           |  14 ++++
 LICENSE           |  27 ++++++++
 MANIFEST.in       |   2 +
 README.rst        |  88 ++++++++++++++++++++++++
 TODO              |   4 ++
 glob2/__init__.py |   5 ++
 glob2/compat.py   | 167 +++++++++++++++++++++++++++++++++++++++++++++
 glob2/fnmatch.py  | 114 +++++++++++++++++++++++++++++++
 glob2/impl.py     | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 setup.cfg         |   2 +
 setup.py          |  42 ++++++++++++
 test.py           | 174 +++++++++++++++++++++++++++++++++++++++++++++++
 13 files changed, 849 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c476452
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.pyc
+/LOCAL_TODO
+
+# distutils/setuptools
+/dist/
+*egg-info
+
+# IDEs
+*.wpr
+/.idea/
+
+# Folder config file
+[Dd]esktop.ini
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..d8f4c52
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,14 @@
+0.5 (2016-11-04)
+    - include_hidden option.
+    - Python 3 fixes.
+    - Publish a wheel.
+
+0.4 (2013-05-08)
+    - Support Python 3.
+
+0.3 (2012-01-19)
+    - Fix non-glob patterns (patch by Zalan).
+    - Don't shadow internal "glob" module.
+
+0.2 (2011-06-14)
+    - Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..953b038
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+Copyright (c) 2008, Michael Elsdörfer <http://elsdoerfer.name>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+
+    2. Redistributions in binary form must reproduce the above
+       copyright notice, this list of conditions and the following
+       disclaimer in the documentation and/or other materials
+       provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..84339d7
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+include README.rst CHANGES LICENSE
+include test.py
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..4988180
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,88 @@
+python-glob2
+============
+
+This is an extended version of Python's builtin glob module
+(http://docs.python.org/library/glob.html) which adds:
+
+- The ability to capture the text matched by glob patterns, and
+  return those matches alongside the filenames.
+
+- A recursive '**' globbing syntax, akin for example to the ``globstar``
+  option of the bash shell.
+
+- The ability to replace the filesystem functions used, in order to glob
+  on virtual filesystems.
+
+- Compatible with Python 2 and Python 3 (tested with 3.3).
+
+It's currently based on the glob code from Python 3.3.1.
+
+
+Examples
+--------
+
+Matches being returned:
+~~~~~~~~~~~~~~~~~~~~~~~
+
+::
+
+    import glob2
+
+    for filename, (version,) in glob2.iglob('./binaries/project-*.zip', with_matches=True):
+        print version
+
+
+Recursive glob:
+~~~~~~~~~~~~~~~
+
+::
+
+    >>> import glob2
+    >>> all_header_files = glob2.glob('src/**/*.h')
+    ['src/fs.h', 'src/media/mp3.h', 'src/media/mp3/frame.h', ...]
+
+
+Note that ``**`` must appear on it's own as a directory
+element to have its special meaning. ``**h`` will not have the
+desired effect.
+
+``**`` will match ".", so ``**/*.py`` returns Python files in the
+current directory. If this is not wanted, ``*/**/*.py`` should be used
+instead.
+
+
+Custom Globber:
+~~~~~~~~~~~~~~~
+
+::
+
+    from glob2 import Globber
+
+    class VirtualStorageGlobber(Globber):
+        def __init__(self, storage):
+            self.storage = storage
+        def listdir(self, path):
+            # Must raise os.error if path is not a directory
+            return self.storage.listdir(path)
+        def exists(self, path):
+            return self.storage.exists(path)
+        def isdir(self, path):
+            # Used only for trailing slash syntax (``foo/``).
+            return self.storage.isdir(path)
+        def islink(self, path):
+            # Used only for recursive glob (``**``).
+            return self.storage.islink(path)
+
+    globber = VirtualStorageGlobber(sftp_storage)
+    globber.glob('/var/www/**/*.js')
+
+
+If ``isdir`` and/or ``islink`` cannot be implemented for a storage, you can
+make them return a fixed value, with the following consequences:
+
+- If ``isdir`` returns ``True``, a glob expression ending with a slash
+  will return all items, even non-directories, if it returns ``False``,
+  the same glob expression will return nothing.
+
+- Return ``islink`` ``True``, the recursive globbing syntax ** will
+  follow all links. If you return ``False``, it will not work at all.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..53f96f3
--- /dev/null
+++ b/TODO
@@ -0,0 +1,4 @@
+Because our implementation of recursive directory search (**) using
+os.walk, and the matching using fnmatch, are both not using iterators,
+something like /** currently needs to read the whole filesystem into
+memory before returning anything.
diff --git a/glob2/__init__.py b/glob2/__init__.py
new file mode 100644
index 0000000..e350b3b
--- /dev/null
+++ b/glob2/__init__.py
@@ -0,0 +1,5 @@
+from __future__ import absolute_import
+from .impl import *
+
+
+__version__ = (0, 5)
diff --git a/glob2/compat.py b/glob2/compat.py
new file mode 100644
index 0000000..b4df988
--- /dev/null
+++ b/glob2/compat.py
@@ -0,0 +1,167 @@
+# Back-port functools.lru_cache to Python 2 (and <= 3.2)
+# {{{ http://code.activestate.com/recipes/578078/ (r6)
+
+from collections import namedtuple
+from functools import update_wrapper
+from threading import RLock
+
+_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
+
+class _HashedSeq(list):
+    __slots__ = 'hashvalue'
+
+    def __init__(self, tup, hash=hash):
+        self[:] = tup
+        self.hashvalue = hash(tup)
+
+    def __hash__(self):
+        return self.hashvalue
+
+def _make_key(args, kwds, typed,
+             kwd_mark = (object(),),
+             fasttypes = set((int, str, frozenset, type(None))),
+             sorted=sorted, tuple=tuple, type=type, len=len):
+    'Make a cache key from optionally typed positional and keyword arguments'
+    key = args
+    if kwds:
+        sorted_items = sorted(kwds.items())
+        key += kwd_mark
+        for item in sorted_items:
+            key += item
+    if typed:
+        key += tuple(type(v) for v in args)
+        if kwds:
+            key += tuple(type(v) for k, v in sorted_items)
+    elif len(key) == 1 and type(key[0]) in fasttypes:
+        return key[0]
+    return _HashedSeq(key)
+
+def lru_cache(maxsize=100, typed=False):
+    """Least-recently-used cache decorator.
+
+    If *maxsize* is set to None, the LRU features are disabled and the cache
+    can grow without bound.
+
+    If *typed* is True, arguments of different types will be cached separately.
+    For example, f(3.0) and f(3) will be treated as distinct calls with
+    distinct results.
+
+    Arguments to the cached function must be hashable.
+
+    View the cache statistics named tuple (hits, misses, maxsize, currsize) with
+    f.cache_info().  Clear the cache and statistics with f.cache_clear().
+    Access the underlying function with f.__wrapped__.
+
+    See:  http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+
+    """
+
+    # Users should only access the lru_cache through its public API:
+    #       cache_info, cache_clear, and f.__wrapped__
+    # The internals of the lru_cache are encapsulated for thread safety and
+    # to allow the implementation to change (including a possible C version).
+
+    def decorating_function(user_function):
+
+        cache = dict()
+        stats = [0, 0]                  # make statistics updateable non-locally
+        HITS, MISSES = 0, 1             # names for the stats fields
+        make_key = _make_key
+        cache_get = cache.get           # bound method to lookup key or return None
+        _len = len                      # localize the global len() function
+        lock = RLock()                  # because linkedlist updates aren't threadsafe
+        root = []                       # root of the circular doubly linked list
+        root[:] = [root, root, None, None]      # initialize by pointing to self
+        nonlocal_root = [root]                  # make updateable non-locally
+        PREV, NEXT, KEY, RESULT = 0, 1, 2, 3    # names for the link fields
+
+        if maxsize == 0:
+
+            def wrapper(*args, **kwds):
+                # no caching, just do a statistics update after a successful call
+                result = user_function(*args, **kwds)
+                stats[MISSES] += 1
+                return result
+
+        elif maxsize is None:
+
+            def wrapper(*args, **kwds):
+                # simple caching without ordering or size limit
+                key = make_key(args, kwds, typed)
+                result = cache_get(key, root)   # root used here as a unique not-found sentinel
+                if result is not root:
+                    stats[HITS] += 1
+                    return result
+                result = user_function(*args, **kwds)
+                cache[key] = result
+                stats[MISSES] += 1
+                return result
+
+        else:
+
+            def wrapper(*args, **kwds):
+                # size limited caching that tracks accesses by recency
+                key = make_key(args, kwds, typed) if kwds or typed else args
+                with lock:
+                    link = cache_get(key)
+                    if link is not None:
+                        # record recent use of the key by moving it to the front of the list
+                        root, = nonlocal_root
+                        link_prev, link_next, key, result = link
+                        link_prev[NEXT] = link_next
+                        link_next[PREV] = link_prev
+                        last = root[PREV]
+                        last[NEXT] = root[PREV] = link
+                        link[PREV] = last
+                        link[NEXT] = root
+                        stats[HITS] += 1
+                        return result
+                result = user_function(*args, **kwds)
+                with lock:
+                    root, = nonlocal_root
+                    if key in cache:
+                        # getting here means that this same key was added to the
+                        # cache while the lock was released.  since the link
+                        # update is already done, we need only return the
+                        # computed result and update the count of misses.
+                        pass
+                    elif _len(cache) >= maxsize:
+                        # use the old root to store the new key and result
+                        oldroot = root
+                        oldroot[KEY] = key
+                        oldroot[RESULT] = result
+                        # empty the oldest link and make it the new root
+                        root = nonlocal_root[0] = oldroot[NEXT]
+                        oldkey = root[KEY]
+                        oldvalue = root[RESULT]
+                        root[KEY] = root[RESULT] = None
+                        # now update the cache dictionary for the new links
+                        del cache[oldkey]
+                        cache[key] = oldroot
+                    else:
+                        # put result in a new link at the front of the list
+                        last = root[PREV]
+                        link = [last, root, key, result]
+                        last[NEXT] = root[PREV] = cache[key] = link
+                    stats[MISSES] += 1
+                return result
+
+        def cache_info():
+            """Report cache statistics"""
+            with lock:
+                return _CacheInfo(stats[HITS], stats[MISSES], maxsize, len(cache))
+
+        def cache_clear():
+            """Clear the cache and cache statistics"""
+            with lock:
+                cache.clear()
+                root = nonlocal_root[0]
+                root[:] = [root, root, None, None]
+                stats[:] = [0, 0]
+
+        wrapper.__wrapped__ = user_function
+        wrapper.cache_info = cache_info
+        wrapper.cache_clear = cache_clear
+        return update_wrapper(wrapper, user_function)
+
+    return decorating_function
\ No newline at end of file
diff --git a/glob2/fnmatch.py b/glob2/fnmatch.py
new file mode 100644
index 0000000..47db550
--- /dev/null
+++ b/glob2/fnmatch.py
@@ -0,0 +1,114 @@
+"""Filename matching with shell patterns.
+
+fnmatch(FILENAME, PATTERN) matches according to the local convention.
+fnmatchcase(FILENAME, PATTERN) always takes case in account.
+
+The functions operate by translating the pattern into a regular
+expression.  They cache the compiled regular expressions for speed.
+
+The function translate(PATTERN) returns a regular expression
+corresponding to PATTERN.  (It does not compile it.)
+"""
+import os
+import posixpath
+import re
+try:
+    from functools import lru_cache
+except ImportError:
+    from .compat import lru_cache
+
+__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]
+
+def fnmatch(name, pat):
+    """Test whether FILENAME matches PATTERN.
+
+    Patterns are Unix shell style:
+
+    *       matches everything
+    ?       matches any single character
+    [seq]   matches any character in seq
+    [!seq]  matches any char not in seq
+
+    An initial period in FILENAME is not special.
+    Both FILENAME and PATTERN are first case-normalized
+    if the operating system requires it.
+    If you don't want this, use fnmatchcase(FILENAME, PATTERN).
+    """
+    name = os.path.normcase(name)
+    pat = os.path.normcase(pat)
+    return fnmatchcase(name, pat)
+
+lru_cache(maxsize=256, typed=True)
+def _compile_pattern(pat):
+    if isinstance(pat, bytes):
+        pat_str = pat.decode('ISO-8859-1')
+        res_str = translate(pat_str)
+        res = res_str.encode('ISO-8859-1')
+    else:
+        res = translate(pat)
+    return re.compile(res).match
+
+def filter(names, pat):
+    """Return the subset of the list NAMES that match PAT."""
+    result = []
+    pat = os.path.normcase(pat)
+    match = _compile_pattern(pat)
+    if os.path is posixpath:
+        # normcase on posix is NOP. Optimize it away from the loop.
+        for name in names:
+            m = match(name)
+            if m:
+                result.append((name, m.groups()))
+    else:
+        for name in names:
+            m = match(os.path.normcase(name))
+            if m:
+                result.append((name, m.groups()))
+    return result
+
+def fnmatchcase(name, pat):
+    """Test whether FILENAME matches PATTERN, including case.
+
+    This is a version of fnmatch() which doesn't case-normalize
+    its arguments.
+    """
+    match = _compile_pattern(pat)
+    return match(name) is not None
+
+
+def translate(pat):
+    """Translate a shell PATTERN to a regular expression.
+
+    There is no way to quote meta-characters.
+    """
+
+    i, n = 0, len(pat)
+    res = ''
+    while i < n:
+        c = pat[i]
+        i = i+1
+        if c == '*':
+            res = res + '(.*)'
+        elif c == '?':
+            res = res + '(.)'
+        elif c == '[':
+            j = i
+            if j < n and pat[j] == '!':
+                j = j+1
+            if j < n and pat[j] == ']':
+                j = j+1
+            while j < n and pat[j] != ']':
+                j = j+1
+            if j >= n:
+                res = res + '\\['
+            else:
+                stuff = pat[i:j].replace('\\','\\\\')
+                i = j+1
+                if stuff[0] == '!':
+                    stuff = '^' + stuff[1:]
+                elif stuff[0] == '^':
+                    stuff = '\\' + stuff
+                res = '%s([%s])' % (res, stuff)
+        else:
+            res = res + re.escape(c)
+    return res + '\Z(?ms)'
diff --git a/glob2/impl.py b/glob2/impl.py
new file mode 100644
index 0000000..6c0845e
--- /dev/null
+++ b/glob2/impl.py
@@ -0,0 +1,197 @@
+"""Filename globbing utility."""
+
+from __future__ import absolute_import
+
+import sys
+import os
+import re
+from . import fnmatch
+
+try:
+    from itertools import imap
+except ImportError:
+    imap = map
+
+
+class Globber(object):
+
+    listdir = staticmethod(os.listdir)
+    isdir = staticmethod(os.path.isdir)
+    islink = staticmethod(os.path.islink)
+    exists = staticmethod(os.path.lexists)
+
+    def walk(self, top, followlinks=False):
+        """A simplified version of os.walk (code copied) that uses
+        ``self.listdir``, and the other local filesystem methods.
+
+        Because we don't care about file/directory distinctions, only
+        a single list is returned.
+        """
+        try:
+            names = self.listdir(top)
+        except os.error as err:
+            return
+
+        items = []
+        for name in names:
+            items.append(name)
+
+        yield top, items
+
+        for name in items:
+            new_path = os.path.join(top, name)
+            if followlinks or not self.islink(new_path):
+                for x in self.walk(new_path, followlinks):
+                    yield x
+
+    def glob(self, pathname, with_matches=False, include_hidden=False):
+        """Return a list of paths matching a pathname pattern.
+
+        The pattern may contain simple shell-style wildcards a la
+        fnmatch. However, unlike fnmatch, filenames starting with a
+        dot are special cases that are not matched by '*' and '?'
+        patterns.
+
+        If ``include_hidden`` is True, then files and folders starting with
+        a dot are also returned.
+        """
+        return list(self.iglob(pathname, with_matches, include_hidden))
+
+    def iglob(self, pathname, with_matches=False, include_hidden=False):
+        """Return an iterator which yields the paths matching a pathname
+        pattern.
+
+        The pattern may contain simple shell-style wildcards a la
+        fnmatch. However, unlike fnmatch, filenames starting with a
+        dot are special cases that are not matched by '*' and '?'
+        patterns.
+
+        If ``with_matches`` is True, then for each matching path
+        a 2-tuple will be returned; the second element if the tuple
+        will be a list of the parts of the path that matched the individual
+        wildcards.
+
+        If ``include_hidden`` is True, then files and folders starting with
+        a dot are also returned.
+        """
+        result = self._iglob(pathname, include_hidden=include_hidden)
+        if with_matches:
+            return result
+        return imap(lambda s: s[0], result)
+
+    def _iglob(self, pathname, rootcall=True, include_hidden=False):
+        """Internal implementation that backs :meth:`iglob`.
+
+        ``rootcall`` is required to differentiate between the user's call to
+        iglob(), and subsequent recursive calls, for the purposes of resolving
+        certain special cases of ** wildcards. Specifically, "**" is supposed
+        to include the current directory for purposes of globbing, but the
+        directory itself should never be returned. So if ** is the lastmost
+        part of the ``pathname`` given the user to the root call, we want to
+        ignore the current directory. For this, we need to know which the root
+        call is.
+        """
+
+        # Short-circuit if no glob magic
+        if not has_magic(pathname):
+            if self.exists(pathname):
+                yield pathname, ()
+            return
+
+        # If no directory part is left, assume the working directory
+        dirname, basename = os.path.split(pathname)
+
+        # If the directory is globbed, recurse to resolve.
+        # If at this point there is no directory part left, we simply
+        # continue with dirname="", which will search the current dir.
+        # `os.path.split()` returns the argument itself as a dirname if it is a
+        # drive or UNC path.  Prevent an infinite recursion if a drive or UNC path
+        # contains magic characters (i.e. r'\\?\C:').
+        if dirname != pathname and has_magic(dirname):
+            # Note that this may return files, which will be ignored
+            # later when we try to use them as directories.
+            # Prefiltering them here would only require more IO ops.
+            dirs = self._iglob(dirname, False, include_hidden)
+        else:
+            dirs = [(dirname, ())]
+
+        # Resolve ``basename`` expr for every directory found
+        for dirname, dir_groups in dirs:
+            for name, groups in self.resolve_pattern(
+                    dirname, basename, not rootcall, include_hidden):
+                yield os.path.join(dirname, name), dir_groups + groups
+
+    def resolve_pattern(self, dirname, pattern, globstar_with_root, include_hidden):
+        """Apply ``pattern`` (contains no path elements) to the
+        literal directory in ``dirname``.
+
+        If pattern=='', this will filter for directories. This is
+        a special case that happens when the user's glob expression ends
+        with a slash (in which case we only want directories). It simpler
+        and faster to filter here than in :meth:`_iglob`.
+        """
+
+        if sys.version_info[0] == 3:
+            if isinstance(pattern, bytes):
+                dirname = bytes(os.curdir, 'ASCII')
+        else:
+            if isinstance(pattern, unicode) and not isinstance(dirname, unicode):
+                dirname = unicode(dirname, sys.getfilesystemencoding() or
+                                           sys.getdefaultencoding())
+
+        # If no magic, short-circuit, only check for existence
+        if not has_magic(pattern):
+            if pattern == '':
+                if self.isdir(dirname):
+                    return [(pattern, ())]
+            else:
+                if self.exists(os.path.join(dirname, pattern)):
+                    return [(pattern, ())]
+            return []
+
+        if not dirname:
+            dirname = os.curdir
+
+        try:
+            if pattern == '**':
+                # Include the current directory in **, if asked; by adding
+                # an empty string as opposed to '.', we spare ourselves
+                # having to deal with os.path.normpath() later.
+                names = [''] if globstar_with_root else []
+                for top, entries in self.walk(dirname):
+                    _mkabs = lambda s: os.path.join(top[len(dirname)+1:], s)
+                    names.extend(map(_mkabs, entries))
+                # Reset pattern so that fnmatch(), which does not understand
+                # ** specifically, will only return a single group match.
+                pattern = '*'
+            else:
+                names = self.listdir(dirname)
+        except os.error:
+            return []
+
+        if not include_hidden and not _ishidden(pattern):
+            # Remove hidden files, but take care to ensure
+            # that the empty string we may have added earlier remains.
+            # Do not filter out the '' that we might have added earlier
+            names = filter(lambda x: not x or not _ishidden(x), names)
+        return fnmatch.filter(names, pattern)
+
+
+default_globber = Globber()
+glob = default_globber.glob
+iglob = default_globber.iglob
+del default_globber
+
+
+magic_check = re.compile('[*?[]')
+magic_check_bytes = re.compile(b'[*?[]')
+
+def has_magic(s):
+    if isinstance(s, bytes):
+        match = magic_check_bytes.search(s)
+    else:
+        match = magic_check.search(s)
+    return match is not None
+
+def _ishidden(path):
+    return path[0] in ('.', b'.'[0])
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..2a9acf1
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal = 1
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..89c65d6
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+import os
+from setuptools import setup, find_packages
+
+
+# Figure out the version
+import re
+here = os.path.dirname(os.path.abspath(__file__))
+version_re = re.compile(
+    r'__version__ = (\(.*?\))')
+fp = open(os.path.join(here, 'glob2', '__init__.py'))
+version = None
+for line in fp:
+    match = version_re.search(line)
+    if match:
+        version = eval(match.group(1))
+        break
+else:
+    raise Exception("Cannot find version in __init__.py")
+fp.close()
+
+
+setup(
+    name = 'glob2',
+    version = ".".join(map(str, version)),
+    description = 'Version of the glob module that can capture patterns '+
+                  'and supports recursive wildcards',
+    author = 'Michael Elsdoerfer',
+    author_email = 'michael at elsdoerfer.com',
+    license='BSD',
+    url = 'http://github.com/miracle2k/python-glob2/',
+    classifiers = [
+        'Development Status :: 3 - Alpha',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 3',
+        'Topic :: Software Development :: Libraries',
+        ],
+    packages = find_packages()
+)
diff --git a/test.py b/test.py
new file mode 100644
index 0000000..c709ee3
--- /dev/null
+++ b/test.py
@@ -0,0 +1,174 @@
+import os
+from os import path
+import shutil
+import tempfile
+
+import glob2
+from glob2 import fnmatch
+
+
+class TestFnmatch(object):
+
+    def test_filter_everything(self):
+        names = (
+            'fooABC', 'barABC', 'foo',)
+        assert fnmatch.filter(names, 'foo*') == [
+            ('fooABC', ('ABC',)),
+            ('foo', ('',))
+        ]
+        assert fnmatch.filter(names, '*AB*') == [
+            ('fooABC', ('foo', 'C')),
+            ('barABC', ('bar', 'C'))
+        ]
+
+    def test_filter_single_character(self):
+        names = (
+            'fooA', 'barA', 'foo',)
+        assert fnmatch.filter(names, 'foo?') == [
+            ('fooA', ('A',)),
+        ]
+        assert fnmatch.filter(names, '???A') == [
+            ('fooA', ('f', 'o', 'o',)),
+            ('barA', ('b', 'a', 'r',)),
+        ]
+
+    def test_sequence(self):
+        names = (
+            'fooA', 'fooB', 'fooC', 'foo',)
+        assert fnmatch.filter(names, 'foo[AB]') == [
+            ('fooA', ('A',)),
+            ('fooB', ('B',)),
+        ]
+        assert fnmatch.filter(names, 'foo[!AB]') == [
+            ('fooC', ('C',)),
+        ]
+
+
+class BaseTest(object):
+
+    def setup(self):
+        self.basedir = tempfile.mkdtemp()
+        self._old_cwd = os.getcwd()
+        os.chdir(self.basedir)
+
+        self.setup_files()
+
+    def setup_files(self):
+        pass
+
+    def teardown(self):
+        os.chdir(self._old_cwd)
+        shutil.rmtree(self.basedir)
+
+    def makedirs(self, *names):
+        for name in names:
+            os.makedirs(path.join(self.basedir, name))
+
+    def touch(self, *names):
+        for name in names:
+            open(path.join(self.basedir, name), 'w').close()
+
+
+class TestPatterns(BaseTest):
+
+    def test(self):
+        self.makedirs('dir1', 'dir22')
+        self.touch(
+            'dir1/a-file', 'dir1/b-file', 'dir22/a-file', 'dir22/b-file')
+        assert glob2.glob('dir?/a-*', True) == [
+            ('dir1/a-file', ('1', 'file'))
+        ]
+
+
+class TestRecursive(BaseTest):
+
+    def setup_files(self):
+        self.makedirs('a', 'b', 'a/foo')
+        self.touch('file.py', 'file.txt', 'a/bar.py', 'README', 'b/py',
+                   'b/bar.py', 'a/foo/hello.py', 'a/foo/world.txt')
+
+    def test_recursive(self):
+        # ** includes the current directory
+        assert sorted(glob2.glob('**/*.py', True)) == [
+            ('a/bar.py', ('a', 'bar')),
+            ('a/foo/hello.py', ('a/foo', 'hello')),
+            ('b/bar.py', ('b', 'bar')),
+            ('file.py', ('', 'file')),
+        ]
+
+    def test_exclude_root_directory(self):
+        # If files from the root directory should not be included,
+        # this is the syntax to use:
+        assert sorted(glob2.glob('*/**/*.py', True)) == [
+            ('a/bar.py', ('a', '', 'bar')),
+            ('a/foo/hello.py', ('a', 'foo', 'hello')),
+            ('b/bar.py', ('b', '', 'bar'))
+        ]
+
+    def test_only_directories(self):
+        # Return directories only
+        assert sorted(glob2.glob('**/', True)) == [
+            ('a/', ('a',)),
+            ('a/foo/', ('a/foo',)),
+            ('b/', ('b',)),
+        ]
+
+    def test_parent_dir(self):
+        # Make sure ".." can be used
+        os.chdir(path.join(self.basedir, 'b'))
+        assert sorted(glob2.glob('../a/**/*.py', True)), [
+            ('../a/bar.py', ('', 'bar')),
+            ('../a/foo/hello.py', ('foo', 'hello'))
+        ]
+
+    def test_fixed_basename(self):
+        assert sorted(glob2.glob('**/bar.py', True)) == [
+            ('a/bar.py', ('a',)),
+            ('b/bar.py', ('b',)),
+        ]
+
+    def test_all_files(self):
+        # Return all files
+        os.chdir(path.join(self.basedir, 'a'))
+        assert sorted(glob2.glob('**', True)) == [
+            ('bar.py', ('bar.py',)),
+            ('foo', ('foo',)),
+            ('foo/hello.py', ('foo/hello.py',)),
+            ('foo/world.txt', ('foo/world.txt',)),
+        ]
+
+    def test_root_directory_not_returned(self):
+        # Ensure that a certain codepath (when the basename is globbed
+        # with ** as opposed to the dirname) does not cause
+        # the root directory to be part of the result.
+        # -> b/ is NOT in the result!
+        assert sorted(glob2.glob('b/**', True)) == [
+            ('b/bar.py', ('bar.py',)),
+            ('b/py', ('py',)),
+        ]
+
+    def test_non_glob(self):
+        # Test without patterns.
+        assert glob2.glob(__file__, True) == [
+            (__file__, ())
+        ]
+        assert glob2.glob(__file__) == [
+            (__file__)
+        ]
+
+
+class TestIncludeHidden(BaseTest):
+
+    def setup_files(self):
+        self.makedirs('a', 'b', 'a/.foo')
+        self.touch('file.py', 'file.txt', 'a/.bar', 'README', 'b/py',
+                   'b/.bar', 'a/.foo/hello.py', 'a/.foo/world.txt')
+
+    def test_hidden(self):
+        # ** includes the current directory
+        assert sorted(glob2.glob('*/*', True, include_hidden=True)), [
+            ('a/.bar', ('a', '.bar')),
+            ('a/.foo', ('a', '.foo')),
+            ('b/.bar', ('b', '.bar')),
+            ('b/py', ('b', 'py')),
+        ]

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-glob2.git



More information about the Python-modules-commits mailing list