Skip to content

Commit 7b7cb03

Browse files
Merge pull request #20 from andreaswolf/issue-14
[FEATURE] Add PathSkipper and use it in FilesFinder
2 parents 7a8f40a + 525b125 commit 7b7cb03

File tree

32 files changed

+546
-25
lines changed

32 files changed

+546
-25
lines changed

fractor-xml/src/Contract/XmlFractor.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
namespace a9f\FractorXml\Contract;
44

5+
use a9f\Fractor\Application\Contract\FractorRule;
56
use a9f\FractorXml\DomDocumentIterator;
67

7-
interface XmlFractor extends DomNodeVisitor
8+
interface XmlFractor extends DomNodeVisitor, FractorRule
89
{
910
public function canHandle(\DOMNode $node): bool;
1011

fractor-xml/src/XmlFileProcessor.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
use a9f\Fractor\Application\ValueObject\File;
77
use a9f\FractorXml\Contract\XmlFractor;
88

9-
final class XmlFileProcessor implements FileProcessor
9+
final readonly class XmlFileProcessor implements FileProcessor
1010
{
1111
/**
12-
* @param list<XmlFractor> $rules
12+
* @param XmlFractor[] $rules
1313
*/
14-
public function __construct(private readonly iterable $rules)
14+
public function __construct(private iterable $rules)
1515
{
1616
}
1717

fractor/config/application.php

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
return static function (ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void {
1818
$parameters = $containerConfigurator->parameters();
1919
$parameters->set(Option::PATHS, [__DIR__]);
20+
$parameters->set(Option::SKIP, []);
2021
$services = $containerConfigurator->services();
2122
$services->defaults()
2223
->autowire()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Application\Contract;
6+
7+
interface FractorRule
8+
{
9+
}

fractor/src/Configuration/ConfigResolver.php

+4-7
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,19 @@ final class ConfigResolver
88
{
99
public static function resolveConfigsFromInput(ArgvInput $input): ?string
1010
{
11-
$configurationFile = self::getConfigFileFromInput($input) ?? getcwd() . '/fractor.php';
12-
13-
return $configurationFile;
11+
return self::getConfigFileFromInput($input) ?? getcwd() . '/fractor.php';
1412
}
1513

1614
private static function getConfigFileFromInput(ArgvInput $input): ?string
1715
{
18-
// TODO validate if the file exists
19-
return self::getOptionValue($input, ['--config', '-c']);
16+
return self::getOptionValue($input);
2017
}
2118

2219
/**
23-
* @param list<string> $nameCandidates
2420
*/
25-
private static function getOptionValue(ArgvInput $input, array $nameCandidates): ?string
21+
private static function getOptionValue(ArgvInput $input): ?string
2622
{
23+
$nameCandidates = ['--config', '-c'];
2724
foreach ($nameCandidates as $name) {
2825
if ($input->hasParameterOption($name, true)) {
2926
return $input->getParameterOption($name, null, true);

fractor/src/Configuration/ConfigurationFactory.php

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public function create(): Configuration
1818
return new Configuration(
1919
$this->allowedFileExtensionsResolver->resolve(),
2020
$this->parameterBag->get(Option::PATHS),
21+
$this->parameterBag->get(Option::SKIP),
2122
);
2223
}
2324
}

fractor/src/Configuration/Option.php

+1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
final class Option
88
{
99
public const PATHS = 'paths';
10+
public const SKIP = 'skip';
1011
}

fractor/src/Configuration/ValueObject/Configuration.php

+12-3
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,32 @@
99
final readonly class Configuration
1010
{
1111
/**
12-
* @param list<non-empty-string> $fileExtensions
12+
* @param string[] $fileExtensions
1313
* @param list<non-empty-string> $paths
14+
* @param string[] $skip
1415
*/
15-
public function __construct(private array $fileExtensions, private array $paths)
16+
public function __construct(private array $fileExtensions, private array $paths, private array $skip)
1617
{
1718
Assert::notEmpty($this->paths, 'No directories given');
1819
Assert::allStringNotEmpty($this->paths, 'No directories given');
1920
}
2021

2122
/**
22-
* @return list<non-empty-string>
23+
* @return string[]
2324
*/
2425
public function getFileExtensions(): array
2526
{
2627
return $this->fileExtensions;
2728
}
2829

30+
/**
31+
* @return string[]
32+
*/
33+
public function getSkip(): array
34+
{
35+
return $this->skip;
36+
}
37+
2938
/**
3039
* @return list<non-empty-string>
3140
*/

fractor/src/DependencyInjection/ContainerContainerBuilder.php

+9-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
namespace a9f\Fractor\DependencyInjection;
44

55
use a9f\Fractor\DependencyInjection\CompilerPass\CommandsCompilerPass;
6+
use a9f\FractorExtensionInstaller\Generated\InstalledPackages;
67
use Symfony\Component\Config\FileLocator;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
79
use Symfony\Component\DependencyInjection\ContainerInterface;
810
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
911

@@ -14,13 +16,16 @@ class ContainerContainerBuilder
1416
*/
1517
public function createDependencyInjectionContainer(array $additionalConfigFiles = []): ContainerInterface
1618
{
17-
$containerBuilder = new \Symfony\Component\DependencyInjection\ContainerBuilder();
19+
$containerBuilder = new ContainerBuilder();
1820
$this->loadFile($containerBuilder, __DIR__ . '/../../config/application.php');
1921
$this->importExtensionConfigurations($containerBuilder);
2022

2123
$containerBuilder->addCompilerPass(new CommandsCompilerPass());
2224

2325
foreach ($additionalConfigFiles as $additionalConfigFile) {
26+
if (!file_exists($additionalConfigFile)) {
27+
continue;
28+
}
2429
$this->loadFile($containerBuilder, $additionalConfigFile);
2530
}
2631

@@ -30,19 +35,19 @@ public function createDependencyInjectionContainer(array $additionalConfigFiles
3035
}
3136

3237

33-
private function loadFile(\Symfony\Component\DependencyInjection\ContainerBuilder $containerBuilder, string $pathToFile): void
38+
private function loadFile(ContainerBuilder $containerBuilder, string $pathToFile): void
3439
{
3540
$fileLoader = new PhpFileLoader($containerBuilder, new FileLocator(dirname($pathToFile)));
3641
$fileLoader->load($pathToFile);
3742
}
3843

39-
private function importExtensionConfigurations(\Symfony\Component\DependencyInjection\ContainerBuilder $containerBuilder): void
44+
private function importExtensionConfigurations(ContainerBuilder $containerBuilder): void
4045
{
4146
if (!class_exists('a9f\\FractorExtensionInstaller\\Generated\\InstalledPackages')) {
4247
return;
4348
}
4449

45-
foreach (\a9f\FractorExtensionInstaller\Generated\InstalledPackages::PACKAGES as $package) {
50+
foreach (InstalledPackages::PACKAGES as $package) {
4651
$filePath = $package['path'] . '/config/application.php';
4752

4853
if (file_exists($filePath)) {

fractor/src/FileSystem/FilesFinder.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22

33
namespace a9f\Fractor\FileSystem;
44

5+
use a9f\Fractor\Skipper\Skipper\PathSkipper;
56
use Symfony\Component\Finder\Finder;
67

78
final readonly class FilesFinder
89
{
9-
public function __construct(private FilesystemTweaker $filesystemTweaker, private FileAndDirectoryFilter $fileAndDirectoryFilter)
10+
public function __construct(private FilesystemTweaker $filesystemTweaker, private FileAndDirectoryFilter $fileAndDirectoryFilter, private PathSkipper $pathSkipper)
1011
{
1112
}
1213

@@ -23,7 +24,7 @@ public function findFiles(array $source, array $suffixes, bool $sortByName = tru
2324

2425
$filteredFilePaths = array_filter(
2526
$files,
26-
fn (string $filePath): bool => true // TODO: Add skipper here
27+
fn (string $filePath): bool => !$this->pathSkipper->shouldSkip($filePath)
2728
);
2829

2930
if ($suffixes !== []) {
@@ -77,7 +78,9 @@ private function findInDirectories(array $directories, array $suffixes, bool $so
7778
continue;
7879
}
7980

80-
// TODO: Add skipper here
81+
if ($this->pathSkipper->shouldSkip($path)) {
82+
continue;
83+
}
8184

8285
$filePaths[] = $path;
8386
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Skipper\FileSystem;
6+
7+
use Nette\Utils\Strings;
8+
use Symfony\Component\Filesystem\Filesystem;
9+
use Webmozart\Assert\Assert;
10+
11+
final class FilePathHelper
12+
{
13+
/**
14+
* @see https://regex101.com/r/d4F5Fm/1
15+
* @var string
16+
*/
17+
private const SCHEME_PATH_REGEX = '#^([a-z]+)\\:\\/\\/(.+)#';
18+
19+
/**
20+
* @see https://regex101.com/r/no28vw/1
21+
* @var string
22+
*/
23+
private const TWO_AND_MORE_SLASHES_REGEX = '#/{2,}#';
24+
25+
/**
26+
* @var string
27+
*/
28+
private const SCHEME_UNDEFINED = 'undefined';
29+
private Filesystem $filesystem;
30+
31+
public function __construct(
32+
33+
) {
34+
$this->filesystem = new Filesystem();
35+
}
36+
37+
public function relativePath(string $fileRealPath): string
38+
{
39+
if (!$this->filesystem->isAbsolutePath($fileRealPath)) {
40+
return $fileRealPath;
41+
}
42+
43+
return $this->relativeFilePathFromDirectory($fileRealPath, (string)getcwd());
44+
}
45+
46+
/**
47+
* Used from
48+
* https://github.com/phpstan/phpstan-src/blob/02425e61aa48f0668b4efb3e73d52ad544048f65/src/File/FileHelper.php#L40, with custom modifications
49+
*/
50+
public function normalizePathAndSchema(string $originalPath): string
51+
{
52+
$directorySeparator = DIRECTORY_SEPARATOR;
53+
54+
$matches = Strings::match($originalPath, self::SCHEME_PATH_REGEX);
55+
if ($matches !== null) {
56+
[, $scheme, $path] = $matches;
57+
} else {
58+
$scheme = self::SCHEME_UNDEFINED;
59+
$path = $originalPath;
60+
}
61+
62+
$normalizedPath = PathNormalizer::normalize((string) $path);
63+
$path = Strings::replace($normalizedPath, self::TWO_AND_MORE_SLASHES_REGEX, '/');
64+
65+
$pathRoot = str_starts_with($path, '/') ? $directorySeparator : '';
66+
$pathParts = explode('/', trim($path, '/'));
67+
68+
$normalizedPathParts = $this->normalizePathParts($pathParts, $scheme);
69+
70+
$pathStart = ($scheme !== self::SCHEME_UNDEFINED ? $scheme . '://' : '');
71+
return PathNormalizer::normalize($pathStart . $pathRoot . implode($directorySeparator, $normalizedPathParts));
72+
}
73+
74+
private function relativeFilePathFromDirectory(string $fileRealPath, string $directory): string
75+
{
76+
Assert::directory($directory);
77+
$normalizedFileRealPath = PathNormalizer::normalize($fileRealPath);
78+
79+
$relativeFilePath = $this->filesystem->makePathRelative($normalizedFileRealPath, $directory);
80+
return rtrim($relativeFilePath, '/');
81+
}
82+
83+
/**
84+
* @param string[] $pathParts
85+
* @return string[]
86+
*/
87+
private function normalizePathParts(array $pathParts, string $scheme): array
88+
{
89+
$normalizedPathParts = [];
90+
91+
foreach ($pathParts as $pathPart) {
92+
if ($pathPart === '.') {
93+
continue;
94+
}
95+
96+
if ($pathPart !== '..') {
97+
$normalizedPathParts[] = $pathPart;
98+
continue;
99+
}
100+
101+
/** @var string $removedPart */
102+
$removedPart = array_pop($normalizedPathParts);
103+
if ($scheme !== 'phar') {
104+
continue;
105+
}
106+
107+
if (!\str_ends_with($removedPart, '.phar')) {
108+
continue;
109+
}
110+
111+
$scheme = self::SCHEME_UNDEFINED;
112+
}
113+
114+
return $normalizedPathParts;
115+
}
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Skipper\FileSystem;
6+
7+
final class FnMatchPathNormalizer
8+
{
9+
public function normalizeForFnmatch(string $path): string
10+
{
11+
if (str_ends_with($path, '*') || str_starts_with($path, '*')) {
12+
return '*' . trim($path, '*') . '*';
13+
}
14+
15+
if (\str_contains($path, '..')) {
16+
/** @var string|false $realPath */
17+
$realPath = realpath($path);
18+
if ($realPath === false) {
19+
return '';
20+
}
21+
22+
return PathNormalizer::normalize($realPath);
23+
}
24+
25+
return $path;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Skipper\FileSystem;
6+
7+
final class PathNormalizer
8+
{
9+
public static function normalize(string $path): string
10+
{
11+
return str_replace('\\', '/', $path);
12+
}
13+
}

fractor/src/Skipper/Fnmatcher.php

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Skipper;
6+
7+
final class Fnmatcher
8+
{
9+
public function match(string $matchingPath, string $filePath): bool
10+
{
11+
if (\fnmatch($matchingPath, $filePath)) {
12+
return \true;
13+
}
14+
15+
// in case of relative compare
16+
return \fnmatch('*/' . $matchingPath, $filePath);
17+
}
18+
}

0 commit comments

Comments
 (0)