@@ -327,6 +327,22 @@ def _compile_pattern(pat, sep, case_sensitive, recursive=True):
327
327
return re .compile (regex , flags = flags ).match
328
328
329
329
330
+ def _open_dir (path , dir_fd = None , rel_path = None ):
331
+ """Prepares the directory for scanning. Returns a 3-tuple with parts:
332
+
333
+ 1. A path or fd to supply to `os.scandir()`.
334
+ 2. The file descriptor for the directory, or None.
335
+ 3. Whether the caller should close the fd (bool).
336
+ """
337
+ if dir_fd is None :
338
+ return path , None , False
339
+ elif rel_path == './' :
340
+ return dir_fd , dir_fd , False
341
+ else :
342
+ fd = os .open (rel_path , _dir_open_flags , dir_fd = dir_fd )
343
+ return fd , fd , True
344
+
345
+
330
346
class _Globber :
331
347
"""Class providing shell-style pattern matching and globbing.
332
348
"""
@@ -382,9 +398,11 @@ def special_selector(self, part, parts):
382
398
"""
383
399
select_next = self .selector (parts )
384
400
385
- def select_special (path , exists = False ):
401
+ def select_special (path , dir_fd = None , rel_path = None , exists = False ):
386
402
path = self .concat_path (self .add_slash (path ), part )
387
- return select_next (path , exists )
403
+ if dir_fd is not None :
404
+ rel_path = self .concat_path (self .add_slash (rel_path ), part )
405
+ return select_next (path , dir_fd , rel_path , exists )
388
406
return select_special
389
407
390
408
def wildcard_selector (self , part , parts ):
@@ -397,11 +415,15 @@ def wildcard_selector(self, part, parts):
397
415
if dir_only :
398
416
select_next = self .selector (parts )
399
417
400
- def select_wildcard (path , exists = False ):
418
+ def select_wildcard (path , dir_fd = None , rel_path = None , exists = False ):
419
+ close_fd = False
401
420
try :
421
+ arg , fd , close_fd = _open_dir (path , dir_fd , rel_path )
422
+ if fd is not None :
423
+ prefix = self .add_slash (path )
402
424
# We must close the scandir() object before proceeding to
403
425
# avoid exhausting file descriptors when globbing deep trees.
404
- with self .scandir (path ) as scandir_it :
426
+ with self .scandir (arg ) as scandir_it :
405
427
entries = list (scandir_it )
406
428
except OSError :
407
429
pass
@@ -415,10 +437,16 @@ def select_wildcard(path, exists=False):
415
437
except OSError :
416
438
continue
417
439
entry_path = self .parse_entry (entry )
440
+ if fd is not None :
441
+ entry_path = self .concat_path (prefix , entry_path )
418
442
if dir_only :
419
- yield from select_next (entry_path , exists = True )
443
+ yield from select_next (
444
+ entry_path , fd , entry .name , exists = True )
420
445
else :
421
446
yield entry_path
447
+ finally :
448
+ if close_fd :
449
+ os .close (fd )
422
450
return select_wildcard
423
451
424
452
def recursive_selector (self , part , parts ):
@@ -444,21 +472,31 @@ def recursive_selector(self, part, parts):
444
472
dir_only = bool (parts )
445
473
select_next = self .selector (parts )
446
474
447
- def select_recursive (path , exists = False ):
475
+ def select_recursive (path , dir_fd = None , rel_path = None , exists = False ):
448
476
path = self .add_slash (path )
477
+ if dir_fd is not None :
478
+ rel_path = self .add_slash (rel_path )
449
479
match_pos = len (str (path ))
450
480
if match is None or match (str (path ), match_pos ):
451
- yield from select_next (path , exists )
452
- stack = [path ]
481
+ yield from select_next (path , dir_fd , rel_path , exists )
482
+ stack = [( path , dir_fd , rel_path ) ]
453
483
while stack :
454
484
yield from select_recursive_step (stack , match_pos )
455
485
456
486
def select_recursive_step (stack , match_pos ):
457
- path = stack .pop ()
487
+ path , dir_fd , rel_path = stack .pop ()
458
488
try :
489
+ if path is None :
490
+ os .close (dir_fd )
491
+ return
492
+ arg , fd , close_fd = _open_dir (path , dir_fd , rel_path )
493
+ if fd is not None :
494
+ prefix = self .add_slash (path )
495
+ if close_fd :
496
+ stack .append ((None , fd , None ))
459
497
# We must close the scandir() object before proceeding to
460
498
# avoid exhausting file descriptors when globbing deep trees.
461
- with self .scandir (path ) as scandir_it :
499
+ with self .scandir (arg ) as scandir_it :
462
500
entries = list (scandir_it )
463
501
except OSError :
464
502
pass
@@ -473,28 +511,37 @@ def select_recursive_step(stack, match_pos):
473
511
474
512
if is_dir or not dir_only :
475
513
entry_path = self .parse_entry (entry )
514
+ if fd is not None :
515
+ entry_path = self .concat_path (prefix , entry_path )
476
516
if match is None or match (str (entry_path ), match_pos ):
477
517
if dir_only :
478
- yield from select_next (entry_path , exists = True )
518
+ yield from select_next (
519
+ entry_path , fd , entry .name , exists = True )
479
520
else :
480
521
# Optimization: directly yield the path if this is
481
522
# last pattern part.
482
523
yield entry_path
483
524
if is_dir :
484
- stack .append (entry_path )
525
+ stack .append (( entry_path , fd , entry . name ) )
485
526
486
527
return select_recursive
487
528
488
- def select_exists (self , path , exists = False ):
529
+ def select_exists (self , path , dir_fd = None , rel_path = None , exists = False ):
489
530
"""Yields the given path, if it exists.
490
531
"""
491
532
if exists :
492
533
# Optimization: this path is already known to exist, e.g. because
493
534
# it was returned from os.scandir(), so we skip calling lstat().
494
535
yield path
495
- else :
536
+ elif dir_fd is None :
496
537
try :
497
538
self .lstat (path )
498
539
yield path
499
540
except OSError :
500
541
pass
542
+ else :
543
+ try :
544
+ self .lstat (rel_path , dir_fd = dir_fd )
545
+ yield path
546
+ except OSError :
547
+ pass
0 commit comments