@@ -198,7 +198,9 @@ def writable_candidates(self) -> List[Path]:
198
198
199
199
return self ._writable_candidates
200
200
201
- def make_candidates (self , path : Path , writable_only : bool = False ) -> List [Path ]:
201
+ def make_candidates (
202
+ self , path : Path , writable_only : bool = False , strict : bool = False
203
+ ) -> List [Path ]:
202
204
candidates = self ._candidates if not writable_only else self .writable_candidates
203
205
if path .is_absolute ():
204
206
for candidate in candidates :
@@ -214,7 +216,71 @@ def make_candidates(self, path: Path, writable_only: bool = False) -> List[Path]
214
216
)
215
217
)
216
218
217
- return [candidate / path for candidate in candidates if candidate ]
219
+ results = [candidate / path for candidate in candidates if candidate ]
220
+
221
+ if not results and strict :
222
+ raise RuntimeError (
223
+ 'Unable to find a suitable destination for "{}" in {}' .format (
224
+ str (path ), paths_csv (self ._candidates )
225
+ )
226
+ )
227
+
228
+ return results
229
+
230
+ def _exists_case_insensitive (
231
+ self ,
232
+ path : Union [str , Path ],
233
+ return_first : bool = True ,
234
+ writable_only : bool = False ,
235
+ ) -> Union [Tuple [Path , Any ], List [Tuple [Path , Any ]]]:
236
+ if isinstance (path , str ):
237
+ path = Path (path )
238
+
239
+ candidates = self ._candidates if not writable_only else self .writable_candidates
240
+
241
+ if path .is_absolute ():
242
+ for candidate in candidates :
243
+ try :
244
+ # we set the path to be relative here
245
+ path = path .relative_to (candidate )
246
+ # we consider only one candidate as the path is relative to it
247
+ candidates = [candidate ]
248
+ break
249
+ except ValueError :
250
+ pass
251
+ else :
252
+ raise ValueError (
253
+ "{} is not relative to any discovered {}sites" .format (
254
+ path , "writable " if writable_only else ""
255
+ )
256
+ )
257
+
258
+ results = []
259
+
260
+ for candidate in candidates :
261
+ try :
262
+ # because this is a case insensitive search, we use posix lower form
263
+ path_posix_lower = candidate .joinpath (path ).as_posix ().lower ()
264
+
265
+ for match in candidate .glob ("**/*" ):
266
+ if path_posix_lower == match .as_posix ().lower ():
267
+ # a case insensitive match was found, we return the matched path
268
+ # (case sensitive) as this is used by the caller
269
+ result = match , True
270
+ if return_first :
271
+ return result
272
+ results .append (result )
273
+ break
274
+ else :
275
+ results .append ((candidate .joinpath (path ), False ))
276
+ except OSError :
277
+ # TODO: Replace with PermissionError
278
+ pass
279
+
280
+ if results :
281
+ return results
282
+
283
+ raise OSError ("Unable to access any of {}" .format (paths_csv (candidates )))
218
284
219
285
def _path_method_wrapper (
220
286
self ,
@@ -228,14 +294,9 @@ def _path_method_wrapper(
228
294
if isinstance (path , str ):
229
295
path = Path (path )
230
296
231
- candidates = self .make_candidates (path , writable_only = writable_only )
232
-
233
- if not candidates :
234
- raise RuntimeError (
235
- 'Unable to find a suitable destination for "{}" in {}' .format (
236
- str (path ), paths_csv (self ._candidates )
237
- )
238
- )
297
+ candidates = self .make_candidates (
298
+ path , writable_only = writable_only , strict = True
299
+ )
239
300
240
301
results = []
241
302
@@ -244,8 +305,7 @@ def _path_method_wrapper(
244
305
result = candidate , getattr (candidate , method )(* args , ** kwargs )
245
306
if return_first :
246
307
return result
247
- else :
248
- results .append (result )
308
+ results .append (result )
249
309
except OSError :
250
310
# TODO: Replace with PermissionError
251
311
pass
@@ -261,20 +321,28 @@ def write_text(self, path: Union[str, Path], *args: Any, **kwargs: Any) -> Path:
261
321
def mkdir (self , path : Union [str , Path ], * args : Any , ** kwargs : Any ) -> Path :
262
322
return self ._path_method_wrapper (path , "mkdir" , * args , ** kwargs )[0 ]
263
323
264
- def exists (self , path : Union [str , Path ]) -> bool :
265
- return any (
266
- value [- 1 ]
267
- for value in self ._path_method_wrapper (path , "exists" , return_first = False )
268
- )
324
+ def exists (self , path : Union [str , Path ], case_insensitive : bool = False ) -> bool :
325
+ if case_insensitive :
326
+ results = self ._exists_case_insensitive (path , return_first = False )
327
+ else :
328
+ results = self ._path_method_wrapper (path , "exists" , return_first = False )
329
+ return any (value [- 1 ] for value in results )
269
330
270
- def find (self , path : Union [str , Path ], writable_only : bool = False ) -> List [Path ]:
271
- return [
272
- value [0 ]
273
- for value in self ._path_method_wrapper (
331
+ def find (
332
+ self ,
333
+ path : Union [str , Path ],
334
+ writable_only : bool = False ,
335
+ case_insensitive : bool = False ,
336
+ ) -> List [Path ]:
337
+ if case_insensitive :
338
+ results = self ._exists_case_insensitive (
339
+ path , return_first = False , writable_only = writable_only
340
+ )
341
+ else :
342
+ results = self ._path_method_wrapper (
274
343
path , "exists" , return_first = False , writable_only = writable_only
275
344
)
276
- if value [- 1 ] is True
277
- ]
345
+ return [value [0 ] for value in results if value [- 1 ] is True ]
278
346
279
347
def __getattr__ (self , item : str ) -> Any :
280
348
try :
0 commit comments