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