Skip to content

Commit 5d534e2

Browse files
committed
up: update some logic for finder
1 parent 9b95ed0 commit 5d534e2

File tree

1 file changed

+127
-43
lines changed

1 file changed

+127
-43
lines changed

src/FileFinder.php

Lines changed: 127 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
use Iterator;
2020
use IteratorAggregate;
2121
use LogicException;
22-
use RecursiveArrayIterator;
2322
use RecursiveDirectoryIterator;
2423
use RecursiveIterator;
2524
use RecursiveIteratorIterator;
@@ -28,7 +27,6 @@
2827
use Toolkit\Stdlib\Str;
2928
use Traversable;
3029
use UnexpectedValueException;
31-
use function array_flip;
3230
use function array_merge;
3331
use function closedir;
3432
use function count;
@@ -61,12 +59,19 @@
6159
*/
6260
final class FileFinder implements IteratorAggregate, Countable
6361
{
64-
public const MODE_ALL = 0;
65-
public const ONLY_FILE = 1;
66-
public const ONLY_DIR = 2;
62+
public const MODE_ALL = 0;
63+
public const ONLY_FILE = 1;
64+
public const ONLY_DIR = 2;
6765

6866
public const IGNORE_VCS_FILES = 1;
6967
public const IGNORE_DOT_FILES = 2;
68+
public const IGNORE_DOT_DIRS = 4;
69+
70+
public const MODE2DESC = [
71+
self::MODE_ALL => 'ALL',
72+
self::ONLY_DIR => 'DIR',
73+
self::ONLY_FILE => 'FILE',
74+
];
7075

7176
/** @var array */
7277
private static array $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];
@@ -77,15 +82,24 @@ final class FileFinder implements IteratorAggregate, Countable
7782
/** @var int */
7883
private int $ignore;
7984

85+
/** @var bool */
86+
private bool $initialized = false;
87+
88+
/** @var bool recursive sub-dirs */
89+
private bool $recursive = true;
90+
8091
/** @var bool */
8192
private bool $ignoreVcsAdded = false;
8293

8394
/** @var bool */
8495
private bool $skipUnreadableDirs = true;
8596

86-
/** @var array */
97+
/** @var array The find dirs */
8798
private array $dirs = [];
8899

100+
/** @var array<string> exclude pattern for directory names and each sub-dirs */
101+
private array $excludes = [];
102+
89103
/**
90104
* add include file,dir name match.
91105
*
@@ -96,7 +110,7 @@ final class FileFinder implements IteratorAggregate, Countable
96110
private array $names = [];
97111

98112
/**
99-
* add exclude file,dir name patterns
113+
* add exclude file,dir name patterns, but sub-dir will not be exclude.
100114
*
101115
* eg: '.php' '*.php' 'test.php'
102116
*
@@ -110,17 +124,14 @@ final class FileFinder implements IteratorAggregate, Countable
110124
/** @var array<string> exclude paths pattern */
111125
private array $notPaths = [];
112126

113-
/** @var array<string> exclude directory names */
114-
private array $excludes = [];
115-
116127
/**
117128
* path filters. each filter like: `Closure(SplFileInfo):bool`, return FALSE to exclude.
118129
*
119130
* @var array
120131
*/
121132
private array $filters = [];
122133

123-
/** @var array */
134+
/** @var Traversable[] */
124135
private array $iterators = [];
125136

126137
/** @var bool */
@@ -363,7 +374,7 @@ public function addNotPaths(array|string $patterns): self
363374
}
364375

365376
/**
366-
* exclude directory names
377+
* exclude pattern for directory names and each sub-dirs
367378
*
368379
* @param array|string $dirNames
369380
*
@@ -391,7 +402,6 @@ public function ignoreVCS(bool $ignoreVCS): self
391402
} else {
392403
$this->ignore &= ~self::IGNORE_VCS_FILES;
393404
}
394-
395405
return $this;
396406
}
397407

@@ -410,6 +420,21 @@ public function ignoreDotFiles(bool $ignoreDotFiles = true): self
410420
return $this;
411421
}
412422

423+
/**
424+
* @param bool $ignoreDotDirs
425+
*
426+
* @return FileFinder
427+
*/
428+
public function ignoreDotDirs(bool $ignoreDotDirs = true): self
429+
{
430+
if ($ignoreDotDirs) {
431+
$this->ignore |= self::IGNORE_DOT_DIRS;
432+
} else {
433+
$this->ignore &= ~self::IGNORE_DOT_DIRS;
434+
}
435+
return $this;
436+
}
437+
413438
/**
414439
* @param bool $skipUnreadableDirs
415440
*
@@ -451,6 +476,26 @@ public function followLinks(mixed $followLinks = true): self
451476
return $this;
452477
}
453478

479+
/**
480+
* @return $this
481+
*/
482+
public function notRecursive(): self
483+
{
484+
$this->recursive = false;
485+
return $this;
486+
}
487+
488+
/**
489+
* @param bool $recursive
490+
*
491+
* @return $this
492+
*/
493+
public function recursiveDir(bool $recursive): self
494+
{
495+
$this->recursive = $recursive;
496+
return $this;
497+
}
498+
454499
/**
455500
* @param Closure(SplFileInfo): bool $closure
456501
*
@@ -501,7 +546,7 @@ public function append(mixed $iterator): self
501546
$this->iterators[] = $iterator->getIterator();
502547
} elseif ($iterator instanceof Iterator) {
503548
$this->iterators[] = $iterator;
504-
// } elseif (\is_array($iterator) || $iterator instanceof Traversable) {
549+
// } elseif (\is_array($iterator) || $iterator instanceof Traversable) {
505550
} elseif (is_iterable($iterator)) {
506551
$it = new ArrayIterator();
507552
foreach ($iterator as $file) {
@@ -520,18 +565,40 @@ public function append(mixed $iterator): self
520565
*/
521566
public function getInfo(): array
522567
{
568+
$this->initialize();
523569
$info = get_object_vars($this);
524570

525-
$mode2desc = [
526-
self::MODE_ALL => 'ALL',
527-
self::ONLY_DIR => 'DIR',
528-
self::ONLY_FILE => 'FILE',
529-
];
530-
$info['mode'] = $mode2desc[$this->mode];
531-
571+
// change mode value
572+
$info['mode'] = self::MODE2DESC[$this->mode];
532573
return $info;
533574
}
534575

576+
protected function initialize(): void
577+
{
578+
if ($this->initialized) {
579+
return;
580+
}
581+
582+
if (0 === count($this->dirs) && 0 === count($this->iterators)) {
583+
throw new LogicException('You must call one of in() or append() methods before iterating over a Finder.');
584+
}
585+
586+
if (!$this->ignoreVcsAdded && self::IGNORE_VCS_FILES === (self::IGNORE_VCS_FILES & $this->ignore)) {
587+
$this->excludes = array_merge($this->excludes, self::$vcsPatterns);
588+
$this->ignoreVcsAdded = true;
589+
}
590+
591+
if (self::IGNORE_DOT_DIRS === (self::IGNORE_DOT_DIRS & $this->ignore)) {
592+
$this->excludes[] = '.*';
593+
}
594+
595+
if (self::IGNORE_DOT_FILES === (self::IGNORE_DOT_FILES & $this->ignore)) {
596+
$this->notNames[] = '.*';
597+
}
598+
599+
$this->initialized = true;
600+
}
601+
535602
/**
536603
* @return int
537604
*/
@@ -558,27 +625,25 @@ public function each(callable $fn): void
558625
}
559626
}
560627

628+
/**
629+
* Retrieve an external iterator
630+
*
631+
* @return Traversable<SplFileInfo> An Traversable
632+
*/
633+
public function all(): Traversable
634+
{
635+
return $this->getIterator();
636+
}
637+
561638
/**
562639
* Retrieve an external iterator
563640
*
564641
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
565-
* @return Traversable An Traversable
566-
* @psalm-return SplFileInfo[]
642+
* @return Traversable<SplFileInfo> An Traversable
567643
*/
568644
public function getIterator(): Traversable
569645
{
570-
if (0 === count($this->dirs) && 0 === count($this->iterators)) {
571-
throw new LogicException('You must call one of in() or append() methods before iterating over a Finder.');
572-
}
573-
574-
if (!$this->ignoreVcsAdded && self::IGNORE_VCS_FILES === (self::IGNORE_VCS_FILES & $this->ignore)) {
575-
$this->excludes = array_merge($this->excludes, self::$vcsPatterns);
576-
$this->ignoreVcsAdded = true;
577-
}
578-
579-
if (self::IGNORE_DOT_FILES === (self::IGNORE_DOT_FILES & $this->ignore)) {
580-
$this->notNames[] = '.*';
581-
}
646+
$this->initialize();
582647

583648
if (1 === count($this->dirs) && 0 === count($this->iterators)) {
584649
return $this->findInDirectory($this->dirs[0]);
@@ -608,21 +673,23 @@ private function findInDirectory(string $dir): Iterator
608673
$flags |= RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
609674
}
610675

611-
$iterator = new class($dir, $flags, $this->skipUnreadableDirs) extends RecursiveDirectoryIterator {
676+
$iterator = new class($dir, $flags, $this->recursive, $this->skipUnreadableDirs) extends RecursiveDirectoryIterator {
612677
private string $rootPath;
613678
private string $subPath = '';
679+
private bool $recursive;
614680
private bool|null $rewindable = null;
615681

616682
private string $directorySep = '/';
617683
private bool $skipUnreadableDirs;
618684

619-
public function __construct(string $path, int $flags, bool $skipUnreadableDirs = true)
685+
public function __construct(string $path, int $flags, bool $recursive, bool $skipUnreadableDirs = true)
620686
{
621687
if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
622688
throw new RuntimeException('This iterator only support returning current as fileInfo.');
623689
}
624690

625691
$this->rootPath = $path;
692+
$this->recursive = $recursive;
626693
$this->skipUnreadableDirs = $skipUnreadableDirs;
627694
parent::__construct($path, $flags);
628695

@@ -654,6 +721,14 @@ public function current(): SplFileInfo
654721
return $fileInfo;
655722
}
656723

724+
public function hasChildren(bool $allowLinks = false): bool
725+
{
726+
if (!$this->recursive) {
727+
return false;
728+
}
729+
return parent::hasChildren($allowLinks);
730+
}
731+
657732
public function getChildren(): RecursiveDirectoryIterator
658733
{
659734
try {
@@ -668,6 +743,7 @@ public function getChildren(): RecursiveDirectoryIterator
668743
} catch (UnexpectedValueException $e) {
669744
if ($this->skipUnreadableDirs) {
670745
return new RecursiveArrayIterator([]);
746+
// return new RecursiveDirectoryIterator([]);
671747
}
672748

673749
throw new RuntimeException($e->getMessage(), $e->getCode(), $e);
@@ -705,23 +781,32 @@ public function isRewindable(): ?bool
705781
// exclude directories
706782
if ($this->excludes) {
707783
$iterator = new class($iterator, $this->excludes) extends FilterIterator implements RecursiveIterator {
708-
/** @var array<string, int> */
784+
/** @var array<string> */
709785
private array $excludes;
710786

711787
private RecursiveIterator $iterator;
712788

713789
public function __construct(RecursiveIterator $iterator, array $excludes)
714790
{
715-
$this->excludes = array_flip($excludes);
791+
$this->excludes = $excludes;
716792
$this->iterator = $iterator;
717793

718794
parent::__construct($iterator);
719795
}
720796

721797
public function accept(): bool
722798
{
723-
$name = $this->current()->getFilename();
724-
return !($this->current()->isDir() && isset($this->excludes[$name]));
799+
if ($this->current()->isDir()) {
800+
$name = $this->current()->getFilename();
801+
802+
foreach ($this->excludes as $not) {
803+
if ($not === $name || fnmatch($not, $name)) {
804+
return false;
805+
}
806+
}
807+
}
808+
809+
return true;
725810
}
726811

727812
public function hasChildren(): bool
@@ -738,7 +823,6 @@ public function getChildren(): ?RecursiveIterator
738823
$children = new self($child, []);
739824
// sync
740825
$children->excludes = $this->excludes;
741-
742826
return $children;
743827
}
744828
};
@@ -769,7 +853,6 @@ public function accept(): bool
769853
if (FileFinder::ONLY_FILE === $this->mode && $info->isDir()) {
770854
return false;
771855
}
772-
773856
return true;
774857
}
775858
};
@@ -791,6 +874,7 @@ public function accept(): bool
791874
{
792875
$filename = $this->current()->getFilename();
793876
foreach ($this->notNames as $not) {
877+
// vdump($not, $this->current()->getPathname(), $filename);
794878
if ($not === $filename || fnmatch($not, $filename)) {
795879
return false;
796880
}

0 commit comments

Comments
 (0)