1"""
2Filename globbing utility.
3"""
4
5from __future__ import absolute_import
6
7import glob as _glob
8import os
9import os.path
10import re
11
12
13_GLOBSTAR = "**"
14_CONTAINS_GLOB_PATTERN = re.compile("[*?[]")
15
16
17def is_glob_pattern(s):
18    """
19    Returns true if 's' represents a glob pattern, and false otherwise.
20    """
21
22    # Copied from glob.has_magic().
23    return _CONTAINS_GLOB_PATTERN.search(s) is not None
24
25
26def glob(globbed_pathname):
27    """
28    Return a list of pathnames matching the 'globbed_pathname' pattern.
29
30    In addition to containing simple shell-style wildcards a la fnmatch,
31    the pattern may also contain globstars ("**"), which is recursively
32    expanded to match zero or more subdirectories.
33    """
34
35    return list(iglob(globbed_pathname))
36
37
38def iglob(globbed_pathname):
39    """
40    Emit a list of pathnames matching the 'globbed_pathname' pattern.
41
42    In addition to containing simple shell-style wildcards a la fnmatch,
43    the pattern may also contain globstars ("**"), which is recursively
44    expanded to match zero or more subdirectories.
45    """
46
47    parts = _split_path(globbed_pathname)
48    parts = _canonicalize(parts)
49
50    index = _find_globstar(parts)
51    if index == -1:
52        for pathname in _glob.iglob(globbed_pathname):
53            # Normalize 'pathname' so exact string comparison can be used later.
54            yield os.path.normpath(pathname)
55        return
56
57    # **, **/, or **/a
58    if index == 0:
59        expand = _expand_curdir
60
61    # a/** or a/**/ or a/**/b
62    else:
63        expand = _expand
64
65    prefix_parts = parts[:index]
66    suffix_parts = parts[index + 1:]
67
68    prefix = os.path.join(*prefix_parts) if prefix_parts else os.curdir
69    suffix = os.path.join(*suffix_parts) if suffix_parts else ""
70
71    for (kind, path) in expand(prefix):
72        if not suffix_parts:
73            yield path
74
75        # Avoid following symlinks to avoid an infinite loop
76        elif suffix_parts and kind == "dir" and not os.path.islink(path):
77            path = os.path.join(path, suffix)
78            for pathname in iglob(path):
79                yield pathname
80
81
82def _split_path(pathname):
83    """
84    Return 'pathname' as a list of path components.
85    """
86
87    parts = []
88
89    while True:
90        (dirname, basename) = os.path.split(pathname)
91        parts.append(basename)
92        if pathname == dirname:
93            parts.append(dirname)
94            break
95        if not dirname:
96            break
97        pathname = dirname
98
99    parts.reverse()
100    return parts
101
102
103def _canonicalize(parts):
104    """
105    Return a copy of 'parts' with consecutive "**"s coalesced.
106    Raise a ValueError for unsupported uses of "**".
107    """
108
109    res = []
110
111    prev_was_globstar = False
112    for p in parts:
113        if p == _GLOBSTAR:
114            # Skip consecutive **'s
115            if not prev_was_globstar:
116                prev_was_globstar = True
117                res.append(p)
118        elif _GLOBSTAR in p:  # a/b**/c or a/**b/c
119            raise ValueError("Can only specify glob patterns of the form a/**/b")
120        else:
121            prev_was_globstar = False
122            res.append(p)
123
124    return res
125
126
127def _find_globstar(parts):
128    """
129    Return the index of the first occurrence of "**" in 'parts'.
130    Return -1 if "**" is not found in the list.
131    """
132
133    for (i, p) in enumerate(parts):
134        if p == _GLOBSTAR:
135            return i
136    return -1
137
138
139def _list_dir(pathname):
140    """
141    Return a pair of the subdirectory names and filenames immediately
142    contained within the 'pathname' directory.
143
144    If 'pathname' does not exist, then None is returned.
145    """
146
147    try:
148        (_root, dirs, files) = os.walk(pathname).next()
149        return (dirs, files)
150    except StopIteration:
151        return None  # 'pathname' directory does not exist
152
153
154def _expand(pathname):
155    """
156    Emit tuples of the form ("dir", dirname) and ("file", filename)
157    of all directories and files contained within the 'pathname' directory.
158    """
159
160    res = _list_dir(pathname)
161    if res is None:
162        return
163
164    (dirs, files) = res
165
166    # Zero expansion
167    if os.path.basename(pathname):
168        yield ("dir", os.path.join(pathname, ""))
169
170    for f in files:
171        path = os.path.join(pathname, f)
172        yield ("file", path)
173
174    for d in dirs:
175        path = os.path.join(pathname, d)
176        for x in _expand(path):
177            yield x
178
179
180def _expand_curdir(pathname):
181    """
182    Emit tuples of the form ("dir", dirname) and ("file", filename)
183    of all directories and files contained within the 'pathname' directory.
184
185    The returned pathnames omit a "./" prefix.
186    """
187
188    res = _list_dir(pathname)
189    if res is None:
190        return
191
192    (dirs, files) = res
193
194    # Zero expansion
195    yield ("dir", "")
196
197    for f in files:
198        yield ("file", f)
199
200    for d in dirs:
201        for x in _expand(d):
202            yield x
203