[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