Skip to content

Commit

Permalink
Merge pull request #2064 from hydephp/replace-glob-brace
Browse files Browse the repository at this point in the history
[1.x] Replace `GLOB_BRACE` with a more robust solution hydephp/develop@0d1280c
  • Loading branch information
github-actions committed Dec 19, 2024
1 parent e703c7a commit 98a23ca
Show file tree
Hide file tree
Showing 15 changed files with 615 additions and 229 deletions.
15 changes: 15 additions & 0 deletions src/Facades/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ public static function smartGlob(string $pattern, int $flags = 0): Collection
return self::kernel()->filesystem()->smartGlob($pattern, $flags);
}

/**
* Find files in the project's directory, with optional filtering by extension and recursion.
*
* The returned collection will be a list of paths relative to the project root.
*
* @param string $directory
* @param string|array<string>|false $matchExtensions The file extension(s) to match, or false to match all files.
* @param bool $recursive Whether to search recursively or not.
* @return \Illuminate\Support\Collection<int, string>
*/
public static function findFiles(string $directory, string|array|false $matchExtensions = false, bool $recursive = false): Collection
{
return self::kernel()->filesystem()->findFiles($directory, $matchExtensions, $recursive);
}

/**
* Touch one or more files in the project's directory.
*
Expand Down
2 changes: 1 addition & 1 deletion src/Foundation/Kernel/FileCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ protected function runExtensionHandlers(): void
protected function discoverFilesFor(string $pageClass): void
{
// Scan the source directory, and directories therein, for files that match the model's file extension.
foreach (Filesystem::smartGlob($pageClass::sourcePath('{*,**/*}'), GLOB_BRACE) as $path) {
foreach (Filesystem::findFiles($pageClass::sourceDirectory(), $pageClass::fileExtension(), true) as $path) {
if (! str_starts_with(basename((string) $path), '_')) {
$this->addFile(SourceFile::make($path, $pageClass));
}
Expand Down
13 changes: 13 additions & 0 deletions src/Foundation/Kernel/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Hyde\Foundation\HydeKernel;
use Hyde\Foundation\PharSupport;
use Illuminate\Support\Collection;
use Hyde\Framework\Actions\Internal\FileFinder;

use function collect;
use function Hyde\normalize_slashes;
Expand Down Expand Up @@ -183,4 +184,16 @@ public function smartGlob(string $pattern, int $flags = 0): Collection

return $files->map(fn (string $path): string => $this->pathToRelative($path));
}

/**
* @param string|array<string>|false $matchExtensions
* @return \Illuminate\Support\Collection<int, string>
*/
public function findFiles(string $directory, string|array|false $matchExtensions = false, bool $recursive = false): Collection
{
/** @var \Hyde\Framework\Actions\Internal\FileFinder $finder */
$finder = app(FileFinder::class);

return $finder->handle($directory, $matchExtensions, $recursive);
}
}
66 changes: 66 additions & 0 deletions src/Framework/Actions/Internal/FileFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace Hyde\Framework\Actions\Internal;

use Hyde\Facades\Filesystem;
use Hyde\Hyde;
use Illuminate\Support\Collection;
use SplFileInfo;
use Symfony\Component\Finder\Finder;

/**
* @interal This class is used internally by the framework and is not part of the public API, unless that is requested on GitHub with a valid use case.
*/
class FileFinder
{
/**
* @param array<string>|string|false $matchExtensions
* @return \Illuminate\Support\Collection<int, string>
*/
public static function handle(string $directory, array|string|false $matchExtensions = false, bool $recursive = false): Collection
{
if (! Filesystem::isDirectory($directory)) {
return collect();
}

$finder = Finder::create()->files()->in(Hyde::path($directory));

if ($recursive === false) {
$finder->depth('== 0');
}

if ($matchExtensions !== false) {
$finder->name(static::buildFileExtensionPattern((array) $matchExtensions));
}

return collect($finder)->map(function (SplFileInfo $file): string {
return Hyde::pathToRelative($file->getPathname());
})->sort()->values();
}

/** @param array<string> $extensions */
protected static function buildFileExtensionPattern(array $extensions): string
{
$extensions = self::expandCommaSeparatedValues($extensions);

return '/\.('.self::normalizeExtensionForRegexPattern($extensions).')$/i';
}

/** @param array<string> $extensions */
private static function expandCommaSeparatedValues(array $extensions): array
{
return array_merge(...array_map(function (string $item): array {
return array_map(fn (string $item): string => trim($item), explode(',', $item));
}, $extensions));
}

/** @param array<string> $extensions */
private static function normalizeExtensionForRegexPattern(array $extensions): string
{
return implode('|', array_map(function (string $extension): string {
return preg_quote(ltrim($extension, '.'), '/');
}, $extensions));
}
}
3 changes: 1 addition & 2 deletions src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Hyde\Framework\Features\BuildTasks\PreBuildTask;

use function basename;
use function glob;
use function in_array;
use function sprintf;

Expand All @@ -21,7 +20,7 @@ class CleanSiteDirectory extends PreBuildTask
public function handle(): void
{
if ($this->isItSafeToCleanOutputDirectory()) {
Filesystem::unlink(glob(Hyde::sitePath('*.{html,json}'), GLOB_BRACE));
Filesystem::unlink(Filesystem::findFiles(Hyde::sitePath(), ['html', 'json'])->all());
Filesystem::cleanDirectory(Hyde::siteMediaPath());
}
}
Expand Down
7 changes: 2 additions & 5 deletions src/Support/DataCollections.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

use function implode;
use function Hyde\path_join;
use function json_decode;
use function sprintf;
use function Hyde\unslash;
use function str_starts_with;

Expand Down Expand Up @@ -102,9 +101,7 @@ public static function json(string $name, bool $asArray = false): static

protected static function findFiles(string $name, array|string $extensions): Collection
{
return Filesystem::smartGlob(sprintf('%s/%s/*.{%s}',
static::$sourceDirectory, $name, implode(',', (array) $extensions)
), GLOB_BRACE);
return Filesystem::findFiles(path_join(static::$sourceDirectory, $name), $extensions);
}

protected static function makeIdentifier(string $path): string
Expand Down
17 changes: 9 additions & 8 deletions src/Support/Filesystem/MediaFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Hyde\Support\Filesystem;

use Hyde\Facades\Filesystem;
use Hyde\Hyde;
use Hyde\Facades\Config;
use Hyde\Framework\Exceptions\FileNotFoundException;
Expand All @@ -14,12 +15,9 @@
use function array_merge;
use function array_keys;
use function filesize;
use function implode;
use function pathinfo;
use function collect;
use function is_file;
use function sprintf;
use function glob;

/**
* File abstraction for a project media file.
Expand Down Expand Up @@ -104,15 +102,18 @@ protected static function discoverMediaAssetFiles(): array
})->all();
}

/** @return array<string> */
protected static function getMediaAssetFiles(): array
{
return glob(Hyde::path(static::getMediaGlobPattern()), GLOB_BRACE) ?: [];
return Filesystem::findFiles(Hyde::getMediaDirectory(), static::getMediaFileExtensions(), true)->all();
}

protected static function getMediaGlobPattern(): string
/** @return array<string>|string */
protected static function getMediaFileExtensions(): array|string
{
return sprintf(Hyde::getMediaDirectory().'/{*,**/*,**/*/*}.{%s}', implode(',',
Config::getArray('hyde.media_extensions', self::EXTENSIONS)
));
/** @var array<string>|string $config */
$config = Config::get('hyde.media_extensions', self::EXTENSIONS);

return $config;
}
}
2 changes: 1 addition & 1 deletion tests/Feature/DiscoveryServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public function testMediaAssetExtensionsCanBeAddedByCommaSeparatedValues()

$this->assertSame([], MediaFile::files());

self::mockConfig(['hyde.media_extensions' => ['1,2,3']]);
self::mockConfig(['hyde.media_extensions' => '1,2,3']);
$this->assertSame(['test.1', 'test.2', 'test.3'], MediaFile::files());
}

Expand Down
16 changes: 16 additions & 0 deletions tests/Feature/FileCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,20 @@ public function testDocumentationPagesAreDiscovered()
$this->assertArrayHasKey('_docs/foo.md', $collection->toArray());
$this->assertEquals(new SourceFile('_docs/foo.md', DocumentationPage::class), $collection->get('_docs/foo.md'));
}

public function testDiscoverFilesForRecursivelyDiscoversFilesInSubdirectories()
{
$this->file('_pages/foo.md');
$this->file('_pages/foo/bar.md');
$this->file('_pages/foo/bar/baz.md');
$collection = FileCollection::init(Hyde::getInstance())->boot();

$this->assertArrayHasKey('_pages/foo.md', $collection->toArray());
$this->assertArrayHasKey('_pages/foo/bar.md', $collection->toArray());
$this->assertArrayHasKey('_pages/foo/bar/baz.md', $collection->toArray());

$this->assertEquals(new SourceFile('_pages/foo.md', MarkdownPage::class), $collection->get('_pages/foo.md'));
$this->assertEquals(new SourceFile('_pages/foo/bar.md', MarkdownPage::class), $collection->get('_pages/foo/bar.md'));
$this->assertEquals(new SourceFile('_pages/foo/bar/baz.md', MarkdownPage::class), $collection->get('_pages/foo/bar/baz.md'));
}
}
82 changes: 82 additions & 0 deletions tests/Feature/Foundation/FilesystemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,28 @@
use Hyde\Foundation\HydeKernel;
use Hyde\Foundation\Kernel\Filesystem;
use Hyde\Foundation\PharSupport;
use Hyde\Framework\Actions\Internal\FileFinder;
use Hyde\Hyde;
use Hyde\Pages\BladePage;
use Hyde\Pages\DocumentationPage;
use Hyde\Pages\HtmlPage;
use Hyde\Pages\MarkdownPage;
use Hyde\Pages\MarkdownPost;
use Hyde\Testing\CreatesTemporaryFiles;
use Hyde\Testing\UnitTestCase;
use Illuminate\Support\Collection;

use function Hyde\normalize_slashes;

/**
* @covers \Hyde\Foundation\HydeKernel
* @covers \Hyde\Foundation\Kernel\Filesystem
* @covers \Hyde\Facades\Filesystem
*/
class FilesystemTest extends UnitTestCase
{
use CreatesTemporaryFiles;

protected string $originalBasePath;

protected Filesystem $filesystem;
Expand Down Expand Up @@ -361,4 +367,80 @@ public function testPathToRelativeHelperDoesNotModifyNonProjectPaths()
$this->assertSame(normalize_slashes($testString), Hyde::pathToRelative($testString));
}
}

public function testFindFileMethodFindsFilesInDirectory()
{
$this->files(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md']);
$files = $this->filesystem->findFiles('directory');

$this->assertCount(3, $files);
$this->assertContains('directory/apple.md', $files);
$this->assertContains('directory/banana.md', $files);
$this->assertContains('directory/cherry.md', $files);

$this->cleanUpFilesystem();
}

public function testFindFileMethodTypes()
{
$this->file('directory/apple.md');
$files = $this->filesystem->findFiles('directory');

$this->assertInstanceOf(Collection::class, $files);
$this->assertContainsOnly('int', $files->keys());
$this->assertContainsOnly('string', $files->all());
$this->assertSame('directory/apple.md', $files->first());

$this->cleanUpFilesystem();
}

public function testFindFileMethodTypesWithArguments()
{
$this->file('directory/apple.md');

$this->assertInstanceOf(Collection::class, $this->filesystem->findFiles('directory', false, false));
$this->assertInstanceOf(Collection::class, $this->filesystem->findFiles('directory', 'md', false));
$this->assertInstanceOf(Collection::class, $this->filesystem->findFiles('directory', false, true));
$this->assertInstanceOf(Collection::class, $this->filesystem->findFiles('directory', 'md', true));

$this->cleanUpFilesystem();
}

public function testFindFilesFromFilesystemFacade()
{
$this->files(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md']);
$files = \Hyde\Facades\Filesystem::findFiles('directory');

$this->assertSame(['directory/apple.md', 'directory/banana.md', 'directory/cherry.md'], $files->sort()->values()->all());

$this->cleanUpFilesystem();
}

public function testFindFilesFromFilesystemFacadeWithArguments()
{
$this->files(['directory/apple.md', 'directory/banana.txt', 'directory/cherry.blade.php', 'directory/nested/dates.md']);

$files = \Hyde\Facades\Filesystem::findFiles('directory', 'md');
$this->assertSame(['directory/apple.md'], $files->all());

$files = \Hyde\Facades\Filesystem::findFiles('directory', false, true);
$this->assertSame(['directory/apple.md', 'directory/banana.txt', 'directory/cherry.blade.php', 'directory/nested/dates.md'], $files->sort()->values()->all());

$this->cleanUpFilesystem();
}

public function testCanSwapOutFileFinder()
{
app()->bind(FileFinder::class, function () {
return new class
{
public static function handle(): Collection
{
return collect(['mocked']);
}
};
});

$this->assertSame(['mocked'], \Hyde\Facades\Filesystem::findFiles('directory')->toArray());
}
}
Loading

0 comments on commit 98a23ca

Please sign in to comment.