| 
									
										
										
										
											1995-01-27 02:41:45 +00:00
										 |  |  | """Filename matching with shell patterns.
 | 
					
						
							| 
									
										
										
										
											1992-01-12 23:29:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1995-01-27 02:41:45 +00:00
										 |  |  | fnmatch(FILENAME, PATTERN) matches according to the local convention. | 
					
						
							|  |  |  | fnmatchcase(FILENAME, PATTERN) always takes case in account. | 
					
						
							| 
									
										
										
										
											1992-01-12 23:29:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1995-01-27 02:41:45 +00:00
										 |  |  | The functions operate by translating the pattern into a regular | 
					
						
							|  |  |  | expression.  They cache the compiled regular expressions for speed. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | The function translate(PATTERN) returns a regular expression | 
					
						
							|  |  |  | corresponding to PATTERN.  (It does not compile it.) | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2025-04-08 12:11:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import functools | 
					
						
							|  |  |  | import itertools | 
					
						
							| 
									
										
										
										
											2010-07-23 16:22:25 +00:00
										 |  |  | import os | 
					
						
							|  |  |  | import posixpath | 
					
						
							| 
									
										
										
										
											1997-10-22 21:00:49 +00:00
										 |  |  | import re | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 12:11:25 +02:00
										 |  |  | __all__ = ["filter", "filterfalse", "fnmatch", "fnmatchcase", "translate"] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-07-23 16:22:25 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1991-01-01 18:11:14 +00:00
										 |  |  | def fnmatch(name, pat): | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |     """Test whether FILENAME matches PATTERN.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Patterns are Unix shell style: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     *       matches everything | 
					
						
							|  |  |  |     ?       matches any single character | 
					
						
							|  |  |  |     [seq]   matches any character in seq | 
					
						
							|  |  |  |     [!seq]  matches any char not in seq | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     An initial period in FILENAME is not special. | 
					
						
							|  |  |  |     Both FILENAME and PATTERN are first case-normalized | 
					
						
							|  |  |  |     if the operating system requires it. | 
					
						
							|  |  |  |     If you don't want this, use fnmatchcase(FILENAME, PATTERN). | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     name = os.path.normcase(name) | 
					
						
							|  |  |  |     pat = os.path.normcase(pat) | 
					
						
							|  |  |  |     return fnmatchcase(name, pat) | 
					
						
							| 
									
										
										
										
											1995-01-27 02:41:45 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 12:11:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-15 06:53:26 -04:00
										 |  |  | @functools.lru_cache(maxsize=32768, typed=True) | 
					
						
							| 
									
										
										
										
											2011-10-20 09:22:10 -07:00
										 |  |  | def _compile_pattern(pat): | 
					
						
							|  |  |  |     if isinstance(pat, bytes): | 
					
						
							| 
									
										
										
										
											2010-08-13 16:26:40 +00:00
										 |  |  |         pat_str = str(pat, 'ISO-8859-1') | 
					
						
							|  |  |  |         res_str = translate(pat_str) | 
					
						
							|  |  |  |         res = bytes(res_str, 'ISO-8859-1') | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         res = translate(pat) | 
					
						
							|  |  |  |     return re.compile(res).match | 
					
						
							| 
									
										
										
										
											2010-07-23 16:22:25 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 12:11:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-06-06 06:24:38 +00:00
										 |  |  | def filter(names, pat): | 
					
						
							| 
									
										
										
										
											2020-12-18 16:10:20 -03:00
										 |  |  |     """Construct a list from those elements of the iterable NAMES that match PAT.""" | 
					
						
							| 
									
										
										
										
											2008-10-02 18:55:37 +00:00
										 |  |  |     result = [] | 
					
						
							|  |  |  |     pat = os.path.normcase(pat) | 
					
						
							| 
									
										
										
										
											2011-10-20 09:22:10 -07:00
										 |  |  |     match = _compile_pattern(pat) | 
					
						
							| 
									
										
										
										
											2001-06-06 06:24:38 +00:00
										 |  |  |     if os.path is posixpath: | 
					
						
							|  |  |  |         # normcase on posix is NOP. Optimize it away from the loop. | 
					
						
							|  |  |  |         for name in names: | 
					
						
							|  |  |  |             if match(name): | 
					
						
							|  |  |  |                 result.append(name) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         for name in names: | 
					
						
							|  |  |  |             if match(os.path.normcase(name)): | 
					
						
							|  |  |  |                 result.append(name) | 
					
						
							|  |  |  |     return result | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 12:11:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | def filterfalse(names, pat): | 
					
						
							|  |  |  |     """Construct a list from those elements of the iterable NAMES that do not match PAT.""" | 
					
						
							|  |  |  |     pat = os.path.normcase(pat) | 
					
						
							|  |  |  |     match = _compile_pattern(pat) | 
					
						
							|  |  |  |     if os.path is posixpath: | 
					
						
							|  |  |  |         # normcase on posix is NOP. Optimize it away from the loop. | 
					
						
							|  |  |  |         return list(itertools.filterfalse(match, names)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     result = [] | 
					
						
							|  |  |  |     for name in names: | 
					
						
							|  |  |  |         if match(os.path.normcase(name)) is None: | 
					
						
							|  |  |  |             result.append(name) | 
					
						
							|  |  |  |     return result | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											1995-01-27 02:41:45 +00:00
										 |  |  | def fnmatchcase(name, pat): | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |     """Test whether FILENAME matches PATTERN, including case.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     This is a version of fnmatch() which doesn't case-normalize | 
					
						
							|  |  |  |     its arguments. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2011-10-20 09:22:10 -07:00
										 |  |  |     match = _compile_pattern(pat) | 
					
						
							| 
									
										
										
										
											2008-10-02 18:55:37 +00:00
										 |  |  |     return match(name) is not None | 
					
						
							| 
									
										
										
										
											1991-01-01 18:11:14 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-07-23 16:22:25 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											1992-01-12 23:29:29 +00:00
										 |  |  | def translate(pat): | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |     """Translate a shell PATTERN to a regular expression.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     There is no way to quote meta-characters. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |     parts, star_indices = _translate(pat, '*', '.') | 
					
						
							|  |  |  |     return _join_translated_parts(parts, star_indices) | 
					
						
							| 
									
										
										
										
											2023-11-13 17:15:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 12:11:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  | _re_setops_sub = re.compile(r'([&~|])').sub | 
					
						
							|  |  |  | _re_escape = functools.lru_cache(maxsize=512)(re.escape) | 
					
						
							| 
									
										
										
										
											2023-11-13 17:15:56 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-08 12:11:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  | def _translate(pat, star, question_mark): | 
					
						
							| 
									
										
										
										
											2020-05-05 21:28:24 -05:00
										 |  |  |     res = [] | 
					
						
							|  |  |  |     add = res.append | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |     star_indices = [] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |     i, n = 0, len(pat) | 
					
						
							|  |  |  |     while i < n: | 
					
						
							|  |  |  |         c = pat[i] | 
					
						
							|  |  |  |         i = i+1 | 
					
						
							|  |  |  |         if c == '*': | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |             # store the position of the wildcard | 
					
						
							|  |  |  |             star_indices.append(len(res)) | 
					
						
							|  |  |  |             add(star) | 
					
						
							| 
									
										
										
										
											2020-05-05 21:28:24 -05:00
										 |  |  |             # compress consecutive `*` into one | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |             while i < n and pat[i] == '*': | 
					
						
							|  |  |  |                 i += 1 | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |         elif c == '?': | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |             add(question_mark) | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |         elif c == '[': | 
					
						
							|  |  |  |             j = i | 
					
						
							|  |  |  |             if j < n and pat[j] == '!': | 
					
						
							|  |  |  |                 j = j+1 | 
					
						
							|  |  |  |             if j < n and pat[j] == ']': | 
					
						
							|  |  |  |                 j = j+1 | 
					
						
							|  |  |  |             while j < n and pat[j] != ']': | 
					
						
							|  |  |  |                 j = j+1 | 
					
						
							|  |  |  |             if j >= n: | 
					
						
							| 
									
										
										
										
											2020-05-05 21:28:24 -05:00
										 |  |  |                 add('\\[') | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2018-02-09 13:30:19 +02:00
										 |  |  |                 stuff = pat[i:j] | 
					
						
							| 
									
										
										
										
											2022-06-05 11:46:29 +03:00
										 |  |  |                 if '-' not in stuff: | 
					
						
							| 
									
										
										
										
											2018-02-09 13:30:19 +02:00
										 |  |  |                     stuff = stuff.replace('\\', r'\\') | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     chunks = [] | 
					
						
							|  |  |  |                     k = i+2 if pat[i] == '!' else i+1 | 
					
						
							|  |  |  |                     while True: | 
					
						
							|  |  |  |                         k = pat.find('-', k, j) | 
					
						
							|  |  |  |                         if k < 0: | 
					
						
							|  |  |  |                             break | 
					
						
							|  |  |  |                         chunks.append(pat[i:k]) | 
					
						
							|  |  |  |                         i = k+1 | 
					
						
							|  |  |  |                         k = k+3 | 
					
						
							| 
									
										
										
										
											2022-06-05 11:46:29 +03:00
										 |  |  |                     chunk = pat[i:j] | 
					
						
							|  |  |  |                     if chunk: | 
					
						
							|  |  |  |                         chunks.append(chunk) | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         chunks[-1] += '-' | 
					
						
							|  |  |  |                     # Remove empty ranges -- invalid in RE. | 
					
						
							|  |  |  |                     for k in range(len(chunks)-1, 0, -1): | 
					
						
							|  |  |  |                         if chunks[k-1][-1] > chunks[k][0]: | 
					
						
							|  |  |  |                             chunks[k-1] = chunks[k-1][:-1] + chunks[k][1:] | 
					
						
							|  |  |  |                             del chunks[k] | 
					
						
							| 
									
										
										
										
											2018-02-09 13:30:19 +02:00
										 |  |  |                     # Escape backslashes and hyphens for set difference (--). | 
					
						
							|  |  |  |                     # Hyphens that create ranges shouldn't be escaped. | 
					
						
							|  |  |  |                     stuff = '-'.join(s.replace('\\', r'\\').replace('-', r'\-') | 
					
						
							|  |  |  |                                      for s in chunks) | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |                 i = j+1 | 
					
						
							| 
									
										
										
										
											2022-06-05 11:46:29 +03:00
										 |  |  |                 if not stuff: | 
					
						
							|  |  |  |                     # Empty range: never match. | 
					
						
							|  |  |  |                     add('(?!)') | 
					
						
							|  |  |  |                 elif stuff == '!': | 
					
						
							|  |  |  |                     # Negated empty range: match any character. | 
					
						
							|  |  |  |                     add('.') | 
					
						
							|  |  |  |                 else: | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |                     # Escape set operations (&&, ~~ and ||). | 
					
						
							|  |  |  |                     stuff = _re_setops_sub(r'\\\1', stuff) | 
					
						
							| 
									
										
										
										
											2022-06-05 11:46:29 +03:00
										 |  |  |                     if stuff[0] == '!': | 
					
						
							|  |  |  |                         stuff = '^' + stuff[1:] | 
					
						
							|  |  |  |                     elif stuff[0] in ('^', '['): | 
					
						
							|  |  |  |                         stuff = '\\' + stuff | 
					
						
							|  |  |  |                     add(f'[{stuff}]') | 
					
						
							| 
									
										
										
										
											2001-01-14 23:36:06 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |             add(_re_escape(c)) | 
					
						
							| 
									
										
										
										
											2020-05-05 21:28:24 -05:00
										 |  |  |     assert i == n | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |     return res, star_indices | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def _join_translated_parts(parts, star_indices): | 
					
						
							|  |  |  |     if not star_indices: | 
					
						
							| 
									
										
										
										
											2025-05-03 17:58:21 +03:00
										 |  |  |         return fr'(?s:{"".join(parts)})\z' | 
					
						
							| 
									
										
										
										
											2024-11-27 17:42:45 +01:00
										 |  |  |     iter_star_indices = iter(star_indices) | 
					
						
							|  |  |  |     j = next(iter_star_indices) | 
					
						
							|  |  |  |     buffer = parts[:j]  # fixed pieces at the start | 
					
						
							|  |  |  |     append, extend = buffer.append, buffer.extend | 
					
						
							|  |  |  |     i = j + 1 | 
					
						
							|  |  |  |     for j in iter_star_indices: | 
					
						
							|  |  |  |         # Now deal with STAR fixed STAR fixed ... | 
					
						
							|  |  |  |         # For an interior `STAR fixed` pairing, we want to do a minimal | 
					
						
							|  |  |  |         # .*? match followed by `fixed`, with no possibility of backtracking. | 
					
						
							|  |  |  |         # Atomic groups ("(?>...)") allow us to spell that directly. | 
					
						
							|  |  |  |         # Note: people rely on the undocumented ability to join multiple | 
					
						
							|  |  |  |         # translate() results together via "|" to build large regexps matching | 
					
						
							|  |  |  |         # "one of many" shell patterns. | 
					
						
							|  |  |  |         append('(?>.*?') | 
					
						
							|  |  |  |         extend(parts[i:j]) | 
					
						
							|  |  |  |         append(')') | 
					
						
							|  |  |  |         i = j + 1 | 
					
						
							|  |  |  |     append('.*') | 
					
						
							|  |  |  |     extend(parts[i:]) | 
					
						
							|  |  |  |     res = ''.join(buffer) | 
					
						
							| 
									
										
										
										
											2025-05-03 17:58:21 +03:00
										 |  |  |     return fr'(?s:{res})\z' |