Skip to content

Commit 7a8f40a

Browse files
Merge pull request #19 from andreaswolf/issue-18
[TASK] Enhance FilesFinder with GlobPattern
2 parents 62b7f25 + bcdf181 commit 7a8f40a

File tree

19 files changed

+266
-64
lines changed

19 files changed

+266
-64
lines changed

fractor-xml/src/XmlFileProcessor.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ public function __construct(private readonly iterable $rules)
1515
{
1616
}
1717

18-
public function canHandle(\SplFileInfo $file): bool
18+
public function canHandle(File $file): bool
1919
{
20-
return $file->getExtension() === 'xml';
20+
return $file->getFileExtension() === 'xml';
2121
}
2222

2323
public function handle(File $file): void

fractor/src/Application/Contract/FileProcessor.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
interface FileProcessor
88
{
9-
public function canHandle(\SplFileInfo $file): bool;
9+
public function canHandle(File $file): bool;
1010

1111
public function handle(File $file): void;
1212

fractor/src/Application/FractorRunner.php

+7-6
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,22 @@ public function __construct(private FilesFinder $fileFinder, private FilesCollec
2525

2626
public function run(Output $output, bool $dryRun = false): void
2727
{
28-
$files = $this->fileFinder->findFiles($this->configuration->getPaths(), $this->configuration->getFileExtensions());
28+
$filePaths = $this->fileFinder->findFiles($this->configuration->getPaths(), $this->configuration->getFileExtensions());
2929

30-
$output->progressStart(count($files));
30+
$output->progressStart(count($filePaths));
31+
32+
foreach ($filePaths as $filePath) {
33+
$file = new File($filePath, FileSystem::read($filePath));
3134

32-
foreach ($files as $file) {
3335
foreach ($this->processors as $processor) {
3436
if (!$processor->canHandle($file)) {
3537
$output->progressAdvance();
3638
continue;
3739
}
3840

39-
$fractorFile = new File($file->getRealPath(), FileSystem::read($file->getRealPath()));
40-
$this->fileCollector->addFile($fractorFile);
41+
$this->fileCollector->addFile($file);
4142

42-
$processor->handle($fractorFile);
43+
$processor->handle($file);
4344
$output->progressAdvance();
4445
}
4546
}

fractor/src/Application/ValueObject/File.php

+13-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@ final class File
88
{
99
private bool $hasChanged = false;
1010
private readonly string $originalContent;
11+
private string $directoryName;
12+
private string $fileName;
13+
private string $fileExtension;
1114

1215
public function __construct(private readonly string $filePath, private string $content)
1316
{
1417
$this->originalContent = $this->content;
18+
$this->directoryName = dirname($this->filePath);
19+
$this->fileName = basename($this->filePath);
20+
$this->fileExtension = pathinfo($this->fileName, PATHINFO_EXTENSION);
1521
}
1622

1723
public function getFilePath(): string
@@ -21,19 +27,24 @@ public function getFilePath(): string
2127

2228
public function getDirectoryName(): string
2329
{
24-
return dirname($this->filePath);
30+
return $this->directoryName;
2531
}
2632

2733
public function getFileName(): string
2834
{
29-
return basename($this->filePath);
35+
return $this->fileName;
3036
}
3137

3238
public function getContent(): string
3339
{
3440
return $this->content;
3541
}
3642

43+
public function getFileExtension(): string
44+
{
45+
return $this->fileExtension;
46+
}
47+
3748
public function changeFileContent(string $newFileContent): void
3849
{
3950
if ($this->content === $newFileContent) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\FileSystem;
6+
7+
final class FileAndDirectoryFilter
8+
{
9+
/**
10+
* @param string[] $filesAndDirectories
11+
* @return string[]
12+
*/
13+
public function filterDirectories(array $filesAndDirectories): array
14+
{
15+
$directories = array_filter($filesAndDirectories, static fn (string $path): bool => is_dir($path));
16+
17+
return array_values($directories);
18+
}
19+
20+
/**
21+
* @param string[] $filesAndDirectories
22+
* @return string[]
23+
*/
24+
public function filterFiles(array $filesAndDirectories): array
25+
{
26+
$files = array_filter($filesAndDirectories, static fn (string $path): bool => is_file($path));
27+
28+
return array_values($files);
29+
}
30+
}

fractor/src/FileSystem/FilesFinder.php

+71-11
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,51 @@
44

55
use Symfony\Component\Finder\Finder;
66

7-
final class FilesFinder
7+
final readonly class FilesFinder
88
{
9+
public function __construct(private FilesystemTweaker $filesystemTweaker, private FileAndDirectoryFilter $fileAndDirectoryFilter)
10+
{
11+
}
12+
913
/**
10-
* @param list<non-empty-string> $directories
11-
* @param list<non-empty-string> $fileExtensions
12-
* @return list<\SplFileInfo>
14+
* @param string[] $source
15+
* @param string[] $suffixes
16+
* @return string[]
1317
*/
14-
public function findFiles(array $directories, array $fileExtensions): array
18+
public function findFiles(array $source, array $suffixes, bool $sortByName = true): array
19+
{
20+
$filesAndDirectories = $this->filesystemTweaker->resolveWithFnmatch($source);
21+
22+
$files = $this->fileAndDirectoryFilter->filterFiles($filesAndDirectories);
23+
24+
$filteredFilePaths = array_filter(
25+
$files,
26+
fn (string $filePath): bool => true // TODO: Add skipper here
27+
);
28+
29+
if ($suffixes !== []) {
30+
$fileWithExtensionsFilter = static function (string $filePath) use ($suffixes): bool {
31+
$filePathExtension = pathinfo($filePath, PATHINFO_EXTENSION);
32+
return in_array($filePathExtension, $suffixes, true);
33+
};
34+
$filteredFilePaths = array_filter($filteredFilePaths, $fileWithExtensionsFilter);
35+
}
36+
37+
$directories = $this->fileAndDirectoryFilter->filterDirectories($filesAndDirectories);
38+
$filteredFilePathsInDirectories = $this->findInDirectories($directories, $suffixes, $sortByName);
39+
40+
return [...$filteredFilePaths, ...$filteredFilePathsInDirectories];
41+
}
42+
43+
/**
44+
* @param string[] $directories
45+
* @param string[] $suffixes
46+
* @return string[]
47+
*/
48+
private function findInDirectories(array $directories, array $suffixes, bool $sortByName = true): array
1549
{
1650
if ($directories === []) {
17-
throw new \UnexpectedValueException('Directories must not be an empty array');
51+
return [];
1852
}
1953

2054
$finder = Finder::create()
@@ -23,14 +57,40 @@ public function findFiles(array $directories, array $fileExtensions): array
2357
->size('> 0')
2458
->in($directories);
2559

26-
foreach ($fileExtensions as $fileExtension) {
27-
$finder->name('*.' . $fileExtension);
60+
if ($sortByName) {
61+
$finder->sortByName();
62+
}
63+
64+
if ($suffixes !== []) {
65+
$suffixesPattern = $this->normalizeSuffixesToPattern($suffixes);
66+
$finder->name($suffixesPattern);
2867
}
2968

30-
$files = [];
69+
$filePaths = [];
3170
foreach ($finder as $fileInfo) {
32-
$files[] = $fileInfo;
71+
// getRealPath() function will return false when it checks broken symlinks.
72+
// So we should check if this file exists or we got broken symlink
73+
74+
/** @var string|false $path */
75+
$path = $fileInfo->getRealPath();
76+
if ($path === false) {
77+
continue;
78+
}
79+
80+
// TODO: Add skipper here
81+
82+
$filePaths[] = $path;
3383
}
34-
return $files;
84+
85+
return $filePaths;
86+
}
87+
88+
/**
89+
* @param string[] $suffixes
90+
*/
91+
private function normalizeSuffixesToPattern(array $suffixes): string
92+
{
93+
$suffixesPattern = implode('|', $suffixes);
94+
return '#\.(' . $suffixesPattern . ')$#';
3595
}
3696
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\FileSystem;
6+
7+
final class FilesystemTweaker
8+
{
9+
/**
10+
* This will turn paths like "src/Symfony/Component/*\/Tests" to existing directory paths
11+
*
12+
* @param string[] $paths
13+
*
14+
* @return string[]
15+
*/
16+
public function resolveWithFnmatch(array $paths): array
17+
{
18+
$absolutePathsFound = [];
19+
foreach ($paths as $path) {
20+
if (\str_contains($path, '*')) {
21+
$foundPaths = $this->foundInGlob($path);
22+
$absolutePathsFound = [...$absolutePathsFound, ...$foundPaths];
23+
} else {
24+
$absolutePathsFound[] = $path;
25+
}
26+
}
27+
28+
return $absolutePathsFound;
29+
}
30+
31+
/**
32+
* @return string[]
33+
*/
34+
private function foundInGlob(string $path): array
35+
{
36+
/** @var string[] $paths */
37+
$paths = (array) glob($path);
38+
39+
return array_filter($paths, static fn (string $path): bool => file_exists($path));
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Tests\FileSystem\FileAndDirectoryFilter;
6+
7+
use a9f\Fractor\FileSystem\FileAndDirectoryFilter;
8+
use PHPUnit\Framework\TestCase;
9+
10+
final class FileAndDirectoryFilterTest extends TestCase
11+
{
12+
private FileAndDirectoryFilter $fileAndDirectoryFilter;
13+
14+
protected function setUp(): void
15+
{
16+
$this->fileAndDirectoryFilter = new FileAndDirectoryFilter();
17+
}
18+
19+
public function testSeparateFilesAndDirectories(): void
20+
{
21+
$sources = [__DIR__, __DIR__ . '/FileAndDirectoryFilterTest.php'];
22+
23+
$files = $this->fileAndDirectoryFilter->filterFiles($sources);
24+
$directories = $this->fileAndDirectoryFilter->filterDirectories($sources);
25+
26+
self::assertCount(1, $files);
27+
self::assertCount(1, $directories);
28+
29+
self::assertSame($files, [__DIR__ . '/FileAndDirectoryFilterTest.php']);
30+
self::assertSame($directories, [__DIR__]);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Tests\FileSystem\FilesFinder;
6+
7+
use a9f\Fractor\FileSystem\FilesFinder;
8+
use a9f\Fractor\Testing\PHPUnit\AbstractFractorTestCase;
9+
use PHPUnit\Framework\Attributes\Test;
10+
11+
final class FilesFinderTest extends AbstractFractorTestCase
12+
{
13+
private FilesFinder $subject;
14+
15+
protected function setUp(): void
16+
{
17+
parent::setUp();
18+
$this->subject = $this->getService(FilesFinder::class);
19+
}
20+
21+
#[Test]
22+
public function findAllNonEmptyFilesInGivenDirectories(): void
23+
{
24+
self::assertCount(4, $this->subject->findFiles([__DIR__ . '/Fixtures/Source'], []));
25+
}
26+
27+
#[Test]
28+
public function findAllNonEmptyFilesInGivenDirectoriesWithGivenExtensions(): void
29+
{
30+
self::assertCount(2, $this->subject->findFiles([__DIR__ . '/Fixtures/Source'], ['txt', 'json']));
31+
}
32+
33+
#[Test]
34+
public function withFollowingBrokenSymlinks(): void
35+
{
36+
$foundFiles = $this->subject->findFiles([__DIR__ . '/Fixtures/SourceWithBrokenSymlinks'], []);
37+
self::assertCount(0, $foundFiles);
38+
}
39+
40+
#[Test]
41+
public function directoriesWithGlobPattern(): void
42+
{
43+
$foundDirectories = $this->subject->findFiles([__DIR__ . '/Fixtures/SourceWithSubFolders/folder*/*'], []);
44+
self::assertCount(2, $foundDirectories);
45+
}
46+
47+
#[Test]
48+
public function filesWithGlobPattern(): void
49+
{
50+
$foundFiles = $this->subject->findFiles([__DIR__ . '/Fixtures/SourceWithSubFolders/**/foo.txt'], ['txt']);
51+
self::assertCount(2, $foundFiles);
52+
53+
/** @var string $foundFile */
54+
$foundFile = array_pop($foundFiles);
55+
56+
$fileBasename = $this->getFileBasename($foundFile);
57+
self::assertSame('foo.txt', $fileBasename);
58+
}
59+
60+
private function getFileBasename(string $foundFile): string
61+
{
62+
return pathinfo($foundFile, PATHINFO_BASENAME);
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
folder3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1

0 commit comments

Comments
 (0)