jpayne@69
|
1 """
|
jpayne@69
|
2 Filename globbing utility. Mostly a copy of `glob` from Python 3.5.
|
jpayne@69
|
3
|
jpayne@69
|
4 Changes include:
|
jpayne@69
|
5 * `yield from` and PEP3102 `*` removed.
|
jpayne@69
|
6 * Hidden files are not ignored.
|
jpayne@69
|
7 """
|
jpayne@69
|
8
|
jpayne@69
|
9 import fnmatch
|
jpayne@69
|
10 import os
|
jpayne@69
|
11 import re
|
jpayne@69
|
12
|
jpayne@69
|
13 __all__ = ["glob", "iglob", "escape"]
|
jpayne@69
|
14
|
jpayne@69
|
15
|
jpayne@69
|
16 def glob(pathname, recursive: bool = False):
|
jpayne@69
|
17 """Return a list of paths matching a pathname pattern.
|
jpayne@69
|
18
|
jpayne@69
|
19 The pattern may contain simple shell-style wildcards a la
|
jpayne@69
|
20 fnmatch. However, unlike fnmatch, filenames starting with a
|
jpayne@69
|
21 dot are special cases that are not matched by '*' and '?'
|
jpayne@69
|
22 patterns.
|
jpayne@69
|
23
|
jpayne@69
|
24 If recursive is true, the pattern '**' will match any files and
|
jpayne@69
|
25 zero or more directories and subdirectories.
|
jpayne@69
|
26 """
|
jpayne@69
|
27 return list(iglob(pathname, recursive=recursive))
|
jpayne@69
|
28
|
jpayne@69
|
29
|
jpayne@69
|
30 def iglob(pathname, recursive: bool = False):
|
jpayne@69
|
31 """Return an iterator which yields the paths matching a pathname pattern.
|
jpayne@69
|
32
|
jpayne@69
|
33 The pattern may contain simple shell-style wildcards a la
|
jpayne@69
|
34 fnmatch. However, unlike fnmatch, filenames starting with a
|
jpayne@69
|
35 dot are special cases that are not matched by '*' and '?'
|
jpayne@69
|
36 patterns.
|
jpayne@69
|
37
|
jpayne@69
|
38 If recursive is true, the pattern '**' will match any files and
|
jpayne@69
|
39 zero or more directories and subdirectories.
|
jpayne@69
|
40 """
|
jpayne@69
|
41 it = _iglob(pathname, recursive)
|
jpayne@69
|
42 if recursive and _isrecursive(pathname):
|
jpayne@69
|
43 s = next(it) # skip empty string
|
jpayne@69
|
44 assert not s
|
jpayne@69
|
45 return it
|
jpayne@69
|
46
|
jpayne@69
|
47
|
jpayne@69
|
48 def _iglob(pathname, recursive):
|
jpayne@69
|
49 dirname, basename = os.path.split(pathname)
|
jpayne@69
|
50 glob_in_dir = glob2 if recursive and _isrecursive(basename) else glob1
|
jpayne@69
|
51
|
jpayne@69
|
52 if not has_magic(pathname):
|
jpayne@69
|
53 if basename:
|
jpayne@69
|
54 if os.path.lexists(pathname):
|
jpayne@69
|
55 yield pathname
|
jpayne@69
|
56 else:
|
jpayne@69
|
57 # Patterns ending with a slash should match only directories
|
jpayne@69
|
58 if os.path.isdir(dirname):
|
jpayne@69
|
59 yield pathname
|
jpayne@69
|
60 return
|
jpayne@69
|
61
|
jpayne@69
|
62 if not dirname:
|
jpayne@69
|
63 yield from glob_in_dir(dirname, basename)
|
jpayne@69
|
64 return
|
jpayne@69
|
65 # `os.path.split()` returns the argument itself as a dirname if it is a
|
jpayne@69
|
66 # drive or UNC path. Prevent an infinite recursion if a drive or UNC path
|
jpayne@69
|
67 # contains magic characters (i.e. r'\\?\C:').
|
jpayne@69
|
68 if dirname != pathname and has_magic(dirname):
|
jpayne@69
|
69 dirs = _iglob(dirname, recursive)
|
jpayne@69
|
70 else:
|
jpayne@69
|
71 dirs = [dirname]
|
jpayne@69
|
72 if not has_magic(basename):
|
jpayne@69
|
73 glob_in_dir = glob0
|
jpayne@69
|
74 for dirname in dirs:
|
jpayne@69
|
75 for name in glob_in_dir(dirname, basename):
|
jpayne@69
|
76 yield os.path.join(dirname, name)
|
jpayne@69
|
77
|
jpayne@69
|
78
|
jpayne@69
|
79 # These 2 helper functions non-recursively glob inside a literal directory.
|
jpayne@69
|
80 # They return a list of basenames. `glob1` accepts a pattern while `glob0`
|
jpayne@69
|
81 # takes a literal basename (so it only has to check for its existence).
|
jpayne@69
|
82
|
jpayne@69
|
83
|
jpayne@69
|
84 def glob1(dirname, pattern):
|
jpayne@69
|
85 if not dirname:
|
jpayne@69
|
86 if isinstance(pattern, bytes):
|
jpayne@69
|
87 dirname = os.curdir.encode('ASCII')
|
jpayne@69
|
88 else:
|
jpayne@69
|
89 dirname = os.curdir
|
jpayne@69
|
90 try:
|
jpayne@69
|
91 names = os.listdir(dirname)
|
jpayne@69
|
92 except OSError:
|
jpayne@69
|
93 return []
|
jpayne@69
|
94 return fnmatch.filter(names, pattern)
|
jpayne@69
|
95
|
jpayne@69
|
96
|
jpayne@69
|
97 def glob0(dirname, basename):
|
jpayne@69
|
98 if not basename:
|
jpayne@69
|
99 # `os.path.split()` returns an empty basename for paths ending with a
|
jpayne@69
|
100 # directory separator. 'q*x/' should match only directories.
|
jpayne@69
|
101 if os.path.isdir(dirname):
|
jpayne@69
|
102 return [basename]
|
jpayne@69
|
103 else:
|
jpayne@69
|
104 if os.path.lexists(os.path.join(dirname, basename)):
|
jpayne@69
|
105 return [basename]
|
jpayne@69
|
106 return []
|
jpayne@69
|
107
|
jpayne@69
|
108
|
jpayne@69
|
109 # This helper function recursively yields relative pathnames inside a literal
|
jpayne@69
|
110 # directory.
|
jpayne@69
|
111
|
jpayne@69
|
112
|
jpayne@69
|
113 def glob2(dirname, pattern):
|
jpayne@69
|
114 assert _isrecursive(pattern)
|
jpayne@69
|
115 yield pattern[:0]
|
jpayne@69
|
116 yield from _rlistdir(dirname)
|
jpayne@69
|
117
|
jpayne@69
|
118
|
jpayne@69
|
119 # Recursively yields relative pathnames inside a literal directory.
|
jpayne@69
|
120 def _rlistdir(dirname):
|
jpayne@69
|
121 if not dirname:
|
jpayne@69
|
122 if isinstance(dirname, bytes):
|
jpayne@69
|
123 dirname = os.curdir.encode('ASCII')
|
jpayne@69
|
124 else:
|
jpayne@69
|
125 dirname = os.curdir
|
jpayne@69
|
126 try:
|
jpayne@69
|
127 names = os.listdir(dirname)
|
jpayne@69
|
128 except OSError:
|
jpayne@69
|
129 return
|
jpayne@69
|
130 for x in names:
|
jpayne@69
|
131 yield x
|
jpayne@69
|
132 path = os.path.join(dirname, x) if dirname else x
|
jpayne@69
|
133 for y in _rlistdir(path):
|
jpayne@69
|
134 yield os.path.join(x, y)
|
jpayne@69
|
135
|
jpayne@69
|
136
|
jpayne@69
|
137 magic_check = re.compile('([*?[])')
|
jpayne@69
|
138 magic_check_bytes = re.compile(b'([*?[])')
|
jpayne@69
|
139
|
jpayne@69
|
140
|
jpayne@69
|
141 def has_magic(s):
|
jpayne@69
|
142 if isinstance(s, bytes):
|
jpayne@69
|
143 match = magic_check_bytes.search(s)
|
jpayne@69
|
144 else:
|
jpayne@69
|
145 match = magic_check.search(s)
|
jpayne@69
|
146 return match is not None
|
jpayne@69
|
147
|
jpayne@69
|
148
|
jpayne@69
|
149 def _isrecursive(pattern):
|
jpayne@69
|
150 if isinstance(pattern, bytes):
|
jpayne@69
|
151 return pattern == b'**'
|
jpayne@69
|
152 else:
|
jpayne@69
|
153 return pattern == '**'
|
jpayne@69
|
154
|
jpayne@69
|
155
|
jpayne@69
|
156 def escape(pathname):
|
jpayne@69
|
157 """Escape all special characters."""
|
jpayne@69
|
158 # Escaping is done by wrapping any of "*?[" between square brackets.
|
jpayne@69
|
159 # Metacharacters do not work in the drive part and shouldn't be escaped.
|
jpayne@69
|
160 drive, pathname = os.path.splitdrive(pathname)
|
jpayne@69
|
161 if isinstance(pathname, bytes):
|
jpayne@69
|
162 pathname = magic_check_bytes.sub(rb'[\1]', pathname)
|
jpayne@69
|
163 else:
|
jpayne@69
|
164 pathname = magic_check.sub(r'[\1]', pathname)
|
jpayne@69
|
165 return drive + pathname
|