6
6
import os
7
7
import re
8
8
from pathlib import Path , PurePath
9
- from typing import Any , Iterable , Iterator , Sequence , Tuple , Union , cast
9
+ from typing import Any , Iterable , Iterator , Optional , Sequence , Union , cast
10
10
11
11
12
- def _glob_pattern_to_re (pattern : str ) -> Tuple [ str , bool ] :
12
+ def _glob_pattern_to_re (pattern : str ) -> str :
13
13
result = "(?ms)^"
14
14
15
15
in_group = False
16
- only_dirs = False
17
16
18
17
i = 0
19
18
while i < len (pattern ):
20
19
c = pattern [i ]
21
20
if c in "\\ /$^+.()=!|" :
22
- if c == "/" and i == len (pattern ) - 1 :
23
- only_dirs = True
24
- else :
25
- result += "\\ " + c
21
+ result += "\\ " + c
26
22
elif c == "?" :
27
23
result += "."
28
24
elif c in "[]" :
@@ -64,26 +60,40 @@ def _glob_pattern_to_re(pattern: str) -> Tuple[str, bool]:
64
60
65
61
result += "$"
66
62
67
- return result , only_dirs
63
+ return result
68
64
69
65
70
66
@functools .lru_cache (maxsize = 256 )
71
- def _compile_glob_pattern (pattern : str ) -> Tuple [re .Pattern [str ], bool ]:
72
- re_pattern , only_dirs = _glob_pattern_to_re (pattern )
73
- return re .compile (re_pattern ), only_dirs
67
+ def _compile_glob_pattern (pattern : str ) -> re .Pattern [str ]:
68
+ return re .compile (_glob_pattern_to_re (pattern ))
74
69
75
70
76
71
class Pattern :
77
72
def __init__ (self , pattern : str ) -> None :
78
- self .pattern = pattern
79
- self ._re_pattern , self .only_dirs = _compile_glob_pattern (pattern )
73
+ pattern = pattern .strip ()
74
+
75
+ self .only_dirs = pattern .endswith ("/" )
76
+
77
+ path = PurePath (pattern )
78
+ if path .is_absolute ():
79
+ self .pattern = path .relative_to (path .anchor ).as_posix ()
80
+ else :
81
+ self .pattern = path .as_posix ()
82
+
83
+ if "*" in self .pattern or "?" in self .pattern or "[" in self .pattern or "{" in self .pattern :
84
+ self .re_pattern : Optional [re .Pattern [str ]] = _compile_glob_pattern (self .pattern )
85
+ else :
86
+ self .re_pattern = None
80
87
81
88
def matches (self , path : Union [PurePath , str , os .PathLike [str ]]) -> bool :
82
89
if isinstance (path , PurePath ):
83
90
path = path .as_posix ()
84
91
else :
85
92
path = str (os .fspath (path ))
86
- return self ._re_pattern .fullmatch (path ) is not None
93
+ if self .re_pattern is None :
94
+ return path == self .pattern
95
+
96
+ return self .re_pattern .fullmatch (path ) is not None
87
97
88
98
def __str__ (self ) -> str :
89
99
return self .pattern
@@ -104,56 +114,66 @@ def _is_hidden(entry: os.DirEntry[str]) -> bool:
104
114
return True
105
115
106
116
if os .name == "nt" and (
107
- entry .stat ().st_file_attributes & 2 != 0 or entry .name .startswith ("$" ) # type: ignore[attr-defined]
117
+ (not entry .is_symlink () and entry .stat ().st_file_attributes & 2 != 0 ) # type: ignore[attr-defined]
118
+ or entry .name .startswith ("$" )
108
119
):
109
120
return True
110
121
111
122
return False
112
123
113
124
114
125
def iter_files (
115
- path : Union [Path , str , os .PathLike [str ]],
126
+ path : Union [PurePath , str , os .PathLike [str ]],
116
127
patterns : Union [Sequence [Union [Pattern , str ]], Pattern , str , None ] = None ,
117
128
ignore_patterns : Union [Sequence [Union [Pattern , str ]], Pattern , str , None ] = None ,
118
129
* ,
119
130
include_hidden : bool = False ,
120
131
absolute : bool = False ,
121
- _base_path : Union [Path , str , os .PathLike [str ], None ] = None ,
122
132
) -> Iterator [Path ]:
123
- if not isinstance (path , Path ):
124
- path = Path (path or "." )
125
-
126
- if _base_path is None :
127
- _base_path = path
128
- else :
129
- if not isinstance (_base_path , Path ):
130
- path = Path (_base_path )
133
+ if not isinstance (path , PurePath ):
134
+ path = PurePath (path or "." )
131
135
132
136
if patterns is not None and isinstance (patterns , (str , Pattern )):
133
137
patterns = [patterns ]
134
- if patterns is not None :
135
- patterns = [p if isinstance (p , Pattern ) else Pattern (p ) for p in patterns ]
136
138
137
139
if ignore_patterns is not None and isinstance (ignore_patterns , (str , Pattern )):
138
140
ignore_patterns = [ignore_patterns ]
139
- if ignore_patterns is not None :
140
- ignore_patterns = [p if isinstance (p , Pattern ) else Pattern (p ) for p in ignore_patterns ]
141
141
142
+ yield from _iter_files_recursive_re (
143
+ path = path ,
144
+ patterns = [] if patterns is None else [p if isinstance (p , Pattern ) else Pattern (p ) for p in patterns ],
145
+ ignore_patterns = []
146
+ if ignore_patterns is None
147
+ else [p if isinstance (p , Pattern ) else Pattern (p ) for p in ignore_patterns ],
148
+ include_hidden = include_hidden ,
149
+ absolute = absolute ,
150
+ _base_path = path ,
151
+ )
152
+
153
+
154
+ def _iter_files_recursive_re (
155
+ path : PurePath ,
156
+ patterns : Sequence [Pattern ],
157
+ ignore_patterns : Sequence [Pattern ],
158
+ include_hidden : bool ,
159
+ absolute : bool ,
160
+ _base_path : PurePath ,
161
+ ) -> Iterator [Path ]:
142
162
try :
143
163
with os .scandir (path ) as it :
144
164
for f in it :
145
165
if not include_hidden and _is_hidden (f ):
146
166
continue
147
167
148
- relative_path = path / f .name
168
+ relative_path = ( path / f .name ). relative_to ( _base_path )
149
169
150
170
if not ignore_patterns or not any (
151
171
p .matches (relative_path ) and (not p .only_dirs or p .only_dirs and f .is_dir ())
152
172
for p in cast (Iterable [Pattern ], ignore_patterns )
153
173
):
154
174
if f .is_dir ():
155
- yield from iter_files (
156
- f ,
175
+ yield from _iter_files_recursive_re (
176
+ PurePath ( f ) ,
157
177
patterns ,
158
178
ignore_patterns ,
159
179
include_hidden = include_hidden ,
0 commit comments