summaryrefslogtreecommitdiffstats
path: root/Lib/glob.py
diff options
context:
space:
mode:
authorSerhiy Storchaka <storchaka@gmail.com>2014-09-11 09:17:37 (GMT)
committerSerhiy Storchaka <storchaka@gmail.com>2014-09-11 09:17:37 (GMT)
commitc2edcdd194356b26873e5304216c313e847b4159 (patch)
tree4f49e9e163484968b129ae625f5d1dcc9cee43f5 /Lib/glob.py
parentd5fd6188e286a52334e24ef1ce4467618c2c1271 (diff)
downloadcpython-c2edcdd194356b26873e5304216c313e847b4159.zip
cpython-c2edcdd194356b26873e5304216c313e847b4159.tar.gz
cpython-c2edcdd194356b26873e5304216c313e847b4159.tar.bz2
Issue #13968: The glob module now supports recursive search in
subdirectories using the "**" pattern.
Diffstat (limited to 'Lib/glob.py')
-rw-r--r--Lib/glob.py56
1 files changed, 50 insertions, 6 deletions
diff --git a/Lib/glob.py b/Lib/glob.py
index d6eca24..56d6704 100644
--- a/Lib/glob.py
+++ b/Lib/glob.py
@@ -6,7 +6,7 @@ import fnmatch
__all__ = ["glob", "iglob"]
-def glob(pathname):
+def glob(pathname, *, recursive=False):
"""Return a list of paths matching a pathname pattern.
The pattern may contain simple shell-style wildcards a la
@@ -14,10 +14,12 @@ def glob(pathname):
dot are special cases that are not matched by '*' and '?'
patterns.
+ If recursive is true, the pattern '**' will match any files and
+ zero or more directories and subdirectories.
"""
- return list(iglob(pathname))
+ return list(iglob(pathname, recursive=recursive))
-def iglob(pathname):
+def iglob(pathname, *, recursive=False):
"""Return an iterator which yields the paths matching a pathname pattern.
The pattern may contain simple shell-style wildcards a la
@@ -25,6 +27,8 @@ def iglob(pathname):
dot are special cases that are not matched by '*' and '?'
patterns.
+ If recursive is true, the pattern '**' will match any files and
+ zero or more directories and subdirectories.
"""
dirname, basename = os.path.split(pathname)
if not has_magic(pathname):
@@ -37,17 +41,23 @@ def iglob(pathname):
yield pathname
return
if not dirname:
- yield from glob1(None, basename)
+ if recursive and _isrecursive(basename):
+ yield from glob2(dirname, basename)
+ else:
+ yield from glob1(dirname, basename)
return
# `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):
- dirs = iglob(dirname)
+ dirs = iglob(dirname, recursive=recursive)
else:
dirs = [dirname]
if has_magic(basename):
- glob_in_dir = glob1
+ if recursive and _isrecursive(basename):
+ glob_in_dir = glob2
+ else:
+ glob_in_dir = glob1
else:
glob_in_dir = glob0
for dirname in dirs:
@@ -83,6 +93,34 @@ def glob0(dirname, basename):
return [basename]
return []
+# This helper function recursively yields relative pathnames inside a literal
+# directory.
+
+def glob2(dirname, pattern):
+ assert _isrecursive(pattern)
+ if dirname:
+ yield pattern[:0]
+ yield from _rlistdir(dirname)
+
+# Recursively yields relative pathnames inside a literal directory.
+
+def _rlistdir(dirname):
+ if not dirname:
+ if isinstance(dirname, bytes):
+ dirname = bytes(os.curdir, 'ASCII')
+ else:
+ dirname = os.curdir
+ try:
+ names = os.listdir(dirname)
+ except os.error:
+ return
+ for x in names:
+ if not _ishidden(x):
+ yield x
+ path = os.path.join(dirname, x) if dirname else x
+ for y in _rlistdir(path):
+ yield os.path.join(x, y)
+
magic_check = re.compile('([*?[])')
magic_check_bytes = re.compile(b'([*?[])')
@@ -97,6 +135,12 @@ def has_magic(s):
def _ishidden(path):
return path[0] in ('.', b'.'[0])
+def _isrecursive(pattern):
+ if isinstance(pattern, bytes):
+ return pattern == b'**'
+ else:
+ return pattern == '**'
+
def escape(pathname):
"""Escape all special characters.
"""