codekingpro/portable-devtools
114k
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 os10import re11import fnmatch12 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 la20 fnmatch. However, unlike fnmatch, filenames starting with a21 dot are special cases that are not matched by '*' and '?'22 patterns.23 24 If recursive is true, the pattern '**' will match any files and25 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 la34 fnmatch. However, unlike fnmatch, filenames starting with a35 dot are special cases that are not matched by '*' and '?'36 patterns.37 38 If recursive is true, the pattern '**' will match any files and39 zero or more directories and subdirectories.40 """41 it = _iglob(pathname, recursive)42 if recursive and _isrecursive(pathname):43 s = next(it) # skip empty string44 assert not s45 return it46 47 48def _iglob(pathname, recursive):49 dirname, basename = os.path.split(pathname)50 glob_in_dir = glob2 if recursive and _isrecursive(basename) else glob151 52 if not has_magic(pathname):53 if basename:54 if os.path.lexists(pathname):55 yield pathname56 else:57 # Patterns ending with a slash should match only directories58 if os.path.isdir(dirname):59 yield pathname60 return61 62 if not dirname:63 yield from glob_in_dir(dirname, basename)64 return65 # `os.path.split()` returns the argument itself as a dirname if it is a66 # drive or UNC path. Prevent an infinite recursion if a drive or UNC path67 # 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 = glob074 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.curdir90 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 a100 # 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 literal110# directory.111 112 113def glob2(dirname, pattern):114 assert _isrecursive(pattern)115 yield pattern[:0]116 yield from _rlistdir(dirname)117 118 119# Recursively yields relative pathnames inside a literal directory.120def _rlistdir(dirname):121 if not dirname:122 if isinstance(dirname, bytes):123 dirname = os.curdir.encode('ASCII')124 else:125 dirname = os.curdir126 try:127 names = os.listdir(dirname)128 except OSError:129 return130 for x in names:131 yield x132 path = os.path.join(dirname, x) if dirname else x133 for y in _rlistdir(path):134 yield os.path.join(x, y)135 136 137magic_check = re.compile('([*?[])')138magic_check_bytes = re.compile(b'([*?[])')139 140 141def has_magic(s):142 if isinstance(s, bytes):143 match = magic_check_bytes.search(s)144 else:145 match = magic_check.search(s)146 return match is not None147 148 149def _isrecursive(pattern):150 if isinstance(pattern, bytes):151 return pattern == b'**'152 else:153 return pattern == '**'154 155 156def escape(pathname):157 """Escape all special characters."""158 # Escaping is done by wrapping any of "*?[" between square brackets.159 # Metacharacters do not work in the drive part and shouldn't be escaped.160 drive, pathname = os.path.splitdrive(pathname)161 if isinstance(pathname, bytes):162 pathname = magic_check_bytes.sub(rb'[\1]', pathname)163 else:164 pathname = magic_check.sub(r'[\1]', pathname)165 return drive + pathname166 