Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Add PathSkipper and use it in FilesFinder #20

Merged
merged 2 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion fractor-xml/src/Contract/XmlFractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace a9f\FractorXml\Contract;

use a9f\Fractor\Application\Contract\FractorRule;
use a9f\FractorXml\DomDocumentIterator;

interface XmlFractor extends DomNodeVisitor
interface XmlFractor extends DomNodeVisitor, FractorRule
{
public function canHandle(\DOMNode $node): bool;

Expand Down
6 changes: 3 additions & 3 deletions fractor-xml/src/XmlFileProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
use a9f\Fractor\Application\ValueObject\File;
use a9f\FractorXml\Contract\XmlFractor;

final class XmlFileProcessor implements FileProcessor
final readonly class XmlFileProcessor implements FileProcessor
{
/**
* @param list<XmlFractor> $rules
* @param XmlFractor[] $rules
*/
public function __construct(private readonly iterable $rules)
public function __construct(private iterable $rules)
{
}

Expand Down
1 change: 1 addition & 0 deletions fractor/config/application.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
return static function (ContainerConfigurator $containerConfigurator, ContainerBuilder $containerBuilder): void {
$parameters = $containerConfigurator->parameters();
$parameters->set(Option::PATHS, [__DIR__]);
$parameters->set(Option::SKIP, []);
$services = $containerConfigurator->services();
$services->defaults()
->autowire()
Expand Down
9 changes: 9 additions & 0 deletions fractor/src/Application/Contract/FractorRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

declare(strict_types=1);

namespace a9f\Fractor\Application\Contract;

interface FractorRule
{
}
11 changes: 4 additions & 7 deletions fractor/src/Configuration/ConfigResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,19 @@ final class ConfigResolver
{
public static function resolveConfigsFromInput(ArgvInput $input): ?string
{
$configurationFile = self::getConfigFileFromInput($input) ?? getcwd() . '/fractor.php';

return $configurationFile;
return self::getConfigFileFromInput($input) ?? getcwd() . '/fractor.php';
}

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

/**
* @param list<string> $nameCandidates
*/
private static function getOptionValue(ArgvInput $input, array $nameCandidates): ?string
private static function getOptionValue(ArgvInput $input): ?string
{
$nameCandidates = ['--config', '-c'];
foreach ($nameCandidates as $name) {
if ($input->hasParameterOption($name, true)) {
return $input->getParameterOption($name, null, true);
Expand Down
1 change: 1 addition & 0 deletions fractor/src/Configuration/ConfigurationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public function create(): Configuration
return new Configuration(
$this->allowedFileExtensionsResolver->resolve(),
$this->parameterBag->get(Option::PATHS),
$this->parameterBag->get(Option::SKIP),
);
}
}
1 change: 1 addition & 0 deletions fractor/src/Configuration/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
final class Option
{
public const PATHS = 'paths';
public const SKIP = 'skip';
}
15 changes: 12 additions & 3 deletions fractor/src/Configuration/ValueObject/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,32 @@
final readonly class Configuration
{
/**
* @param list<non-empty-string> $fileExtensions
* @param string[] $fileExtensions
* @param list<non-empty-string> $paths
* @param string[] $skip
*/
public function __construct(private array $fileExtensions, private array $paths)
public function __construct(private array $fileExtensions, private array $paths, private array $skip)
{
Assert::notEmpty($this->paths, 'No directories given');
Assert::allStringNotEmpty($this->paths, 'No directories given');
}

/**
* @return list<non-empty-string>
* @return string[]
*/
public function getFileExtensions(): array
{
return $this->fileExtensions;
}

/**
* @return string[]
*/
public function getSkip(): array
{
return $this->skip;
}

/**
* @return list<non-empty-string>
*/
Expand Down
13 changes: 9 additions & 4 deletions fractor/src/DependencyInjection/ContainerContainerBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace a9f\Fractor\DependencyInjection;

use a9f\Fractor\DependencyInjection\CompilerPass\CommandsCompilerPass;
use a9f\FractorExtensionInstaller\Generated\InstalledPackages;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;

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

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

foreach ($additionalConfigFiles as $additionalConfigFile) {
if (!file_exists($additionalConfigFile)) {
continue;
}
$this->loadFile($containerBuilder, $additionalConfigFile);
}

Expand All @@ -30,19 +35,19 @@ public function createDependencyInjectionContainer(array $additionalConfigFiles
}


private function loadFile(\Symfony\Component\DependencyInjection\ContainerBuilder $containerBuilder, string $pathToFile): void
private function loadFile(ContainerBuilder $containerBuilder, string $pathToFile): void
{
$fileLoader = new PhpFileLoader($containerBuilder, new FileLocator(dirname($pathToFile)));
$fileLoader->load($pathToFile);
}

private function importExtensionConfigurations(\Symfony\Component\DependencyInjection\ContainerBuilder $containerBuilder): void
private function importExtensionConfigurations(ContainerBuilder $containerBuilder): void
{
if (!class_exists('a9f\\FractorExtensionInstaller\\Generated\\InstalledPackages')) {
return;
}

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

if (file_exists($filePath)) {
Expand Down
9 changes: 6 additions & 3 deletions fractor/src/FileSystem/FilesFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

namespace a9f\Fractor\FileSystem;

use a9f\Fractor\Skipper\Skipper\PathSkipper;
use Symfony\Component\Finder\Finder;

final readonly class FilesFinder
{
public function __construct(private FilesystemTweaker $filesystemTweaker, private FileAndDirectoryFilter $fileAndDirectoryFilter)
public function __construct(private FilesystemTweaker $filesystemTweaker, private FileAndDirectoryFilter $fileAndDirectoryFilter, private PathSkipper $pathSkipper)
{
}

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

$filteredFilePaths = array_filter(
$files,
fn (string $filePath): bool => true // TODO: Add skipper here
fn (string $filePath): bool => !$this->pathSkipper->shouldSkip($filePath)
);

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

// TODO: Add skipper here
if ($this->pathSkipper->shouldSkip($path)) {
continue;
}

$filePaths[] = $path;
}
Expand Down
116 changes: 116 additions & 0 deletions fractor/src/Skipper/FileSystem/FilePathHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

declare(strict_types=1);

namespace a9f\Fractor\Skipper\FileSystem;

use Nette\Utils\Strings;
use Symfony\Component\Filesystem\Filesystem;
use Webmozart\Assert\Assert;

final class FilePathHelper
{
/**
* @see https://regex101.com/r/d4F5Fm/1
* @var string
*/
private const SCHEME_PATH_REGEX = '#^([a-z]+)\\:\\/\\/(.+)#';

/**
* @see https://regex101.com/r/no28vw/1
* @var string
*/
private const TWO_AND_MORE_SLASHES_REGEX = '#/{2,}#';

/**
* @var string
*/
private const SCHEME_UNDEFINED = 'undefined';
private Filesystem $filesystem;

public function __construct(

) {
$this->filesystem = new Filesystem();
}

public function relativePath(string $fileRealPath): string
{
if (!$this->filesystem->isAbsolutePath($fileRealPath)) {
return $fileRealPath;
}

return $this->relativeFilePathFromDirectory($fileRealPath, (string)getcwd());
}

/**
* Used from
* https://github.com/phpstan/phpstan-src/blob/02425e61aa48f0668b4efb3e73d52ad544048f65/src/File/FileHelper.php#L40, with custom modifications
*/
public function normalizePathAndSchema(string $originalPath): string
{
$directorySeparator = DIRECTORY_SEPARATOR;

$matches = Strings::match($originalPath, self::SCHEME_PATH_REGEX);
if ($matches !== null) {
[, $scheme, $path] = $matches;
} else {
$scheme = self::SCHEME_UNDEFINED;
$path = $originalPath;
}

$normalizedPath = PathNormalizer::normalize((string) $path);
$path = Strings::replace($normalizedPath, self::TWO_AND_MORE_SLASHES_REGEX, '/');

$pathRoot = str_starts_with($path, '/') ? $directorySeparator : '';
$pathParts = explode('/', trim($path, '/'));

$normalizedPathParts = $this->normalizePathParts($pathParts, $scheme);

$pathStart = ($scheme !== self::SCHEME_UNDEFINED ? $scheme . '://' : '');
return PathNormalizer::normalize($pathStart . $pathRoot . implode($directorySeparator, $normalizedPathParts));
}

private function relativeFilePathFromDirectory(string $fileRealPath, string $directory): string
{
Assert::directory($directory);
$normalizedFileRealPath = PathNormalizer::normalize($fileRealPath);

$relativeFilePath = $this->filesystem->makePathRelative($normalizedFileRealPath, $directory);
return rtrim($relativeFilePath, '/');
}

/**
* @param string[] $pathParts
* @return string[]
*/
private function normalizePathParts(array $pathParts, string $scheme): array
{
$normalizedPathParts = [];

foreach ($pathParts as $pathPart) {
if ($pathPart === '.') {
continue;
}

if ($pathPart !== '..') {
$normalizedPathParts[] = $pathPart;
continue;
}

/** @var string $removedPart */
$removedPart = array_pop($normalizedPathParts);
if ($scheme !== 'phar') {
continue;
}

if (!\str_ends_with($removedPart, '.phar')) {
continue;
}

$scheme = self::SCHEME_UNDEFINED;
}

return $normalizedPathParts;
}
}
27 changes: 27 additions & 0 deletions fractor/src/Skipper/FileSystem/FnMatchPathNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace a9f\Fractor\Skipper\FileSystem;

final class FnMatchPathNormalizer
{
public function normalizeForFnmatch(string $path): string
{
if (str_ends_with($path, '*') || str_starts_with($path, '*')) {
return '*' . trim($path, '*') . '*';
}

if (\str_contains($path, '..')) {
/** @var string|false $realPath */
$realPath = realpath($path);
if ($realPath === false) {
return '';
}

return PathNormalizer::normalize($realPath);
}

return $path;
}
}
13 changes: 13 additions & 0 deletions fractor/src/Skipper/FileSystem/PathNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace a9f\Fractor\Skipper\FileSystem;

final class PathNormalizer
{
public static function normalize(string $path): string
{
return str_replace('\\', '/', $path);
}
}
18 changes: 18 additions & 0 deletions fractor/src/Skipper/Fnmatcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace a9f\Fractor\Skipper;

final class Fnmatcher
{
public function match(string $matchingPath, string $filePath): bool
{
if (\fnmatch($matchingPath, $filePath)) {
return \true;
}

// in case of relative compare
return \fnmatch('*/' . $matchingPath, $filePath);
}
}
Loading