From 8806e24dd888d93aca700e57fbbac895ac8c71dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Sun, 1 Sep 2024 05:36:07 +0200 Subject: [PATCH] [AssetMapper] Search & filter assets in `debug:asset-mapper` command --- .../Command/DebugAssetMapperCommand.php | 121 ++++++++++++++---- .../Command/DebugAssetsMapperCommandTest.php | 53 ++++++++ 2 files changed, 150 insertions(+), 24 deletions(-) diff --git a/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php b/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php index 7021bba762cb6..a81857b5c14b2 100644 --- a/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php +++ b/src/Symfony/Component/AssetMapper/Command/DebugAssetMapperCommand.php @@ -15,7 +15,9 @@ use Symfony\Component\AssetMapper\AssetMapperRepository; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -40,10 +42,41 @@ public function __construct( protected function configure(): void { $this + ->addArgument('name', InputArgument::OPTIONAL, 'An asset name (or a path) to search for (e.g. "app")') + ->addOption('ext', null, InputOption::VALUE_REQUIRED, 'Filter assets by extension (e.g. "css")', null, ['js', 'css', 'png']) ->addOption('full', null, null, 'Whether to show the full paths') + ->addOption('vendor', null, InputOption::VALUE_NEGATABLE, 'Only show assets from vendor packages') ->setHelp(<<<'EOT' -The %command.name% command outputs all of the assets in -asset mapper for debugging purposes. +The %command.name% command displays information about the Asset +Mapper for debugging purposes. + +To list all configured paths (with local paths and their namespace prefixes) and +all mapped assets (with their logical path and filesystem path), run: + + php %command.full_name% + +You can filter the results by providing a name to search for in the asset name +or path: + + php %command.full_name% bootstrap.js + php %command.full_name% style/ + +To filter the assets by extension, use the --ext option: + + php %command.full_name% --ext=css + +To show only assets from vendor packages, use the --vendor option: + + php %command.full_name% --vendor + +To exclude assets from vendor packages, use the --no-vendor option: + + php %command.full_name% --no-vendor + +To see the full paths, use the --full option: + + php %command.full_name% --full + EOT ); } @@ -52,43 +85,83 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); - $allAssets = $this->assetMapper->allAssets(); + $name = $input->getArgument('name'); + $extensionFilter = $input->getOption('ext'); + $vendorFilter = $input->getOption('vendor'); + + if (!$extensionFilter) { + $io->section($name ? 'Matched Paths' : 'Asset Mapper Paths'); + $pathRows = []; + foreach ($this->assetMapperRepository->allDirectories() as $path => $namespace) { + $path = $this->relativizePath($path); + if (!$input->getOption('full')) { + $path = $this->shortenPath($path); + } + if ($name && !str_contains($path, $name) && !str_contains($namespace, $name)) { + continue; + } + $pathRows[] = [$path, $namespace]; + } + uasort($pathRows, static function (array $a, array $b): int { + return [(bool) $a[1], ...$a] <=> [(bool) $b[1], ...$b]; + }); + if ($pathRows) { + $io->table(['Path', 'Namespace prefix'], $pathRows); + } else { + $io->warning('No paths found.'); + } + } - $pathRows = []; - foreach ($this->assetMapperRepository->allDirectories() as $path => $namespace) { - $path = $this->relativizePath($path); + $io->section($name ? 'Matched Assets' : 'Mapped Assets'); + $rows = $this->searchAssets($name, $extensionFilter, $vendorFilter); + if ($rows) { if (!$input->getOption('full')) { - $path = $this->shortenPath($path); + $rows = array_map(fn (array $row): array => [ + $this->shortenPath($row[0]), + $this->shortenPath($row[1]), + ], $rows); } - - $pathRows[] = [$path, $namespace]; + uasort($rows, static function (array $a, array $b): int { + return [$a] <=> [$b]; + }); + $io->table(['Logical Path', 'Filesystem Path'], $rows); + if ($this->didShortenPaths) { + $io->note('To see the full paths, re-run with the --full option.'); + } + } else { + $io->warning('No assets found.'); } - $io->section('Asset Mapper Paths'); - $io->table(['Path', 'Namespace prefix'], $pathRows); + return 0; + } + + /** + * @return list + */ + private function searchAssets(?string $name, ?string $extension, ?bool $vendor): array + { $rows = []; - foreach ($allAssets as $asset) { + foreach ($this->assetMapper->allAssets() as $asset) { + if ($extension && $extension !== $asset->publicExtension) { + continue; + } + if (null !== $vendor && $vendor !== $asset->isVendor) { + continue; + } + if ($name && !str_contains($asset->logicalPath, $name) && !str_contains($asset->sourcePath, $name)) { + continue; + } + $logicalPath = $asset->logicalPath; $sourcePath = $this->relativizePath($asset->sourcePath); - if (!$input->getOption('full')) { - $logicalPath = $this->shortenPath($logicalPath); - $sourcePath = $this->shortenPath($sourcePath); - } - $rows[] = [ $logicalPath, $sourcePath, ]; } - $io->section('Mapped Assets'); - $io->table(['Logical Path', 'Filesystem Path'], $rows); - - if ($this->didShortenPaths) { - $io->note('To see the full paths, re-run with the --full option.'); - } - return 0; + return $rows; } private function relativizePath(string $path): string diff --git a/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php b/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php index 5d2530004096c..03fe35450a085 100644 --- a/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php +++ b/src/Symfony/Component/AssetMapper/Tests/Command/DebugAssetsMapperCommandTest.php @@ -31,4 +31,57 @@ public function testCommandDumpsInformation() $this->assertStringContainsString('subdir/file6.js', $tester->getDisplay()); $this->assertStringContainsString('dir2'.\DIRECTORY_SEPARATOR.'subdir'.\DIRECTORY_SEPARATOR.'file6.js', $tester->getDisplay()); } + + public function testCommandFiltersName() + { + $application = new Application(new AssetMapperTestAppKernel('test', true)); + $command = $application->find('debug:asset-map'); + $tester = new CommandTester($command); + $res = $tester->execute(['name' => 'stimulus']); + + $this->assertSame(0, $res); + $this->assertStringContainsString('stimulus', $tester->getDisplay()); + $this->assertStringNotContainsString('lodash', $tester->getDisplay()); + + $res = $tester->execute(['name' => 'lodash']); + $this->assertSame(0, $res); + $this->assertStringNotContainsString('stimulus', $tester->getDisplay()); + $this->assertStringContainsString('lodash', $tester->getDisplay()); + } + + public function testCommandFiltersExtension() + { + $application = new Application(new AssetMapperTestAppKernel('test', true)); + $command = $application->find('debug:asset-map'); + $tester = new CommandTester($command); + $res = $tester->execute(['--ext' => 'css']); + + $this->assertSame(0, $res); + $this->assertStringNotContainsString('.js', $tester->getDisplay()); + + $this->assertStringContainsString('file1.css', $tester->getDisplay()); + $this->assertStringContainsString('file3.css', $tester->getDisplay()); + } + + public function testCommandFiltersVendor() + { + $application = new Application(new AssetMapperTestAppKernel('test', true)); + $command = $application->find('debug:asset-map'); + + $tester = new CommandTester($command); + $res = $tester->execute(['--vendor' => true]); + + $this->assertSame(0, $res); + $this->assertStringContainsString('vendor/lodash/', $tester->getDisplay()); + $this->assertStringContainsString('@hotwired/stimulus', $tester->getDisplay()); + $this->assertStringNotContainsString('dir2'.\DIRECTORY_SEPARATOR, $tester->getDisplay()); + + $tester = new CommandTester($command); + $res = $tester->execute(['--no-vendor' => true]); + + $this->assertSame(0, $res); + $this->assertStringNotContainsString('vendor/lodash/', $tester->getDisplay()); + $this->assertStringNotContainsString('@hotwired/stimulus', $tester->getDisplay()); + $this->assertStringContainsString('dir2'.\DIRECTORY_SEPARATOR, $tester->getDisplay()); + } }