Loading core/lib/Drupal/Core/Command/OutputFormat.php 0 → 100644 +11 −0 Changes for core/lib/Drupal/Core/Command/OutputFormat.php: 11 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\Core\Command; /** * A list of common output formats. */ enum OutputFormat: string { case Yaml = 'yaml'; case Json = 'json'; } core/lib/Drupal/Core/Extension/Command/ExtensionListCommand.php 0 → 100644 +140 −0 Changes for core/lib/Drupal/Core/Extension/Command/ExtensionListCommand.php: 140 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Extension\Command; use Drupal\Component\Serialization\Yaml; use Drupal\Core\Command\OutputFormat; use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ThemeExtensionList; use Drupal\Core\Theme\ExtensionType; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; /** * List extensions. * * @internal */ #[AsCommand( name: 'ex:list', description: 'List extensions.', aliases: [ 'pm:list', 'pml', ], usages: [ 'ex:list --type=module --format=json', 'ex:list --type=theme --format=yaml', ] )] class ExtensionListCommand { public function __construct( protected ModuleExtensionList $extensionListModule, protected ThemeExtensionList $extensionListTheme, ) {} /** * Invoke the command. */ public function __invoke( InputInterface $input, OutputInterface $output, #[Option('Show extensions that belong to Drupal core.')] ?bool $core = NULL, #[Option('Filter extensions to modules or themes.')] ?ExtensionType $type = NULL, #[Option('A comma delimited list of packages to filter by.')] ?string $package = NULL, #[Option('Filter extensions by install status.')] ?bool $installed = NULL, #[Option('An alternative output format such as json or yaml. Defaults to table.')] ?OutputFormat $format = NULL, ): int { $rows = []; $modules = $this->extensionListModule->getList(); $themes = $this->extensionListTheme->getList(); $both = array_merge($modules, $themes); foreach ($both as $key => $extension) { // Populate any missing keys. $extension->info += ['package' => '', 'version' => '']; // Filter out test extensions. if (str_contains($extension->getPath(), 'tests')) { continue; } if ($type && $extension->getType() !== $type->value) { continue; } // Filter out uninstalled if --installed is specified. if ($installed === TRUE && $extension->status !== 1) { continue; } // Filter out installed if --no-installed is specified. if ($installed === FALSE && $extension->status == 1) { continue; } // Filter out core if --no-core specified. // @todo Add origin property to themes. // @see https://www.drupal.org/project/drupal/issues/3605703 if ($core === FALSE && @$extension->origin == 'core') { continue; } // Filter out non-core if --core specified. if ($core === TRUE && $extension->origin !== 'core') { continue; } // Filter by package. if ($package && !in_array(strtolower($extension->info['package']), array_map('strtolower', explode(',', $package)))) { continue; } $row = [ 'package' => $extension->info['package'], 'display_name' => $extension->info['name'] . ' (' . $extension->getName() . ')', 'status' => ucfirst($extension->status ? 'installed' : 'uninstalled'), 'version' => $extension->info['version'], ]; $rows[$key] = $row; } $result = match ($format) { OutputFormat::Json => json_encode($rows, JSON_PRETTY_PRINT), OutputFormat::Yaml => Yaml::encode($rows), default => $this->renderTable($rows), }; $output->writeln($result); return Command::SUCCESS; } /** * Render the table. */ protected function renderTable(array $rows): string { $buffer = new BufferedOutput(); $table = new Table($buffer); $table ->setHeaders(['Package', 'Name', 'Status', 'Version']) ->setRows($rows) ->render(); return $buffer->fetch(); } } core/modules/system/tests/src/Kernel/Command/ExtensionListCommandTest.php 0 → 100644 +75 −0 Changes for core/modules/system/tests/src/Kernel/Command/ExtensionListCommandTest.php: 75 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Tests\system\Kernel\Command; use Drupal\Core\Extension\Command\ExtensionListCommand; use Drupal\KernelTests\DrupalApplicationTesterTrait; use Drupal\KernelTests\KernelTestBase; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use Symfony\Component\Console\Command\Command; /** * Tests the 'ex:list' command. */ #[Group('Console')] #[RunTestsInSeparateProcesses] #[CoversClass(ExtensionListCommand::class)] class ExtensionListCommandTest extends KernelTestBase { use DrupalApplicationTesterTrait; /** * {@inheritdoc} */ protected static $modules = [ 'system', ]; /** * Tests the 'ex:list' command. */ public function testExtensionList(): void { $tester = $this->applicationTester(); // Detailed testing using json output format. $cmd = ['command' => 'ex:list', '--format' => 'json']; $this->assertEquals(Command::SUCCESS, $tester->run($cmd)); $json = json_decode($tester->getDisplay()); $this->assertSame('Field types', $json->datetime->package); $this->assertSame('Datetime (datetime)', $json->datetime->display_name); $this->assertSame('Uninstalled', $json->datetime->status); $this->assertSame(\Drupal::VERSION, $json->datetime->version); $this->assertSame('Installed', $json->system->status); // Exercise the filtering options. $cmd = ['command' => 'ex:list', '--format' => 'json', '--type' => 'module', '--installed' => TRUE]; $this->assertEquals(Command::SUCCESS, $tester->run($cmd)); $json = json_decode($tester->getDisplay()); $this->assertObjectHasProperty('system', $json); $this->assertObjectNotHasProperty('datetime', $json); $cmd = ['command' => 'ex:list', '--format' => 'json', '--no-core' => TRUE]; $this->assertEquals(Command::SUCCESS, $tester->run($cmd)); $json = json_decode($tester->getDisplay()); $this->assertObjectNotHasProperty('system', $json); $cmd = ['command' => 'ex:list', '--format' => 'json', '--type' => 'theme']; $this->assertEquals(Command::SUCCESS, $tester->run($cmd)); $json = json_decode($tester->getDisplay()); $this->assertObjectNotHasProperty('system', $json); // Sanity check the human readable output. $this->assertEquals(Command::SUCCESS, $tester->run(['command' => 'ex:list'])); $display = $tester->getDisplay(); $this->assertStringContainsString('Package', $display); $this->assertStringContainsString('Version', $display); $this->assertStringContainsString('User', $display); $this->assertStringContainsString('Installed', $display); $this->assertStringContainsString('Uninstalled', $display); $this->assertStringContainsString(\Drupal::VERSION, $display); } } Loading
core/lib/Drupal/Core/Command/OutputFormat.php 0 → 100644 +11 −0 Changes for core/lib/Drupal/Core/Command/OutputFormat.php: 11 added lines, 0 removed lines. Original line number Diff line number Diff line <?php namespace Drupal\Core\Command; /** * A list of common output formats. */ enum OutputFormat: string { case Yaml = 'yaml'; case Json = 'json'; }
core/lib/Drupal/Core/Extension/Command/ExtensionListCommand.php 0 → 100644 +140 −0 Changes for core/lib/Drupal/Core/Extension/Command/ExtensionListCommand.php: 140 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Core\Extension\Command; use Drupal\Component\Serialization\Yaml; use Drupal\Core\Command\OutputFormat; use Drupal\Core\Extension\ModuleExtensionList; use Drupal\Core\Extension\ThemeExtensionList; use Drupal\Core\Theme\ExtensionType; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Attribute\Option; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; /** * List extensions. * * @internal */ #[AsCommand( name: 'ex:list', description: 'List extensions.', aliases: [ 'pm:list', 'pml', ], usages: [ 'ex:list --type=module --format=json', 'ex:list --type=theme --format=yaml', ] )] class ExtensionListCommand { public function __construct( protected ModuleExtensionList $extensionListModule, protected ThemeExtensionList $extensionListTheme, ) {} /** * Invoke the command. */ public function __invoke( InputInterface $input, OutputInterface $output, #[Option('Show extensions that belong to Drupal core.')] ?bool $core = NULL, #[Option('Filter extensions to modules or themes.')] ?ExtensionType $type = NULL, #[Option('A comma delimited list of packages to filter by.')] ?string $package = NULL, #[Option('Filter extensions by install status.')] ?bool $installed = NULL, #[Option('An alternative output format such as json or yaml. Defaults to table.')] ?OutputFormat $format = NULL, ): int { $rows = []; $modules = $this->extensionListModule->getList(); $themes = $this->extensionListTheme->getList(); $both = array_merge($modules, $themes); foreach ($both as $key => $extension) { // Populate any missing keys. $extension->info += ['package' => '', 'version' => '']; // Filter out test extensions. if (str_contains($extension->getPath(), 'tests')) { continue; } if ($type && $extension->getType() !== $type->value) { continue; } // Filter out uninstalled if --installed is specified. if ($installed === TRUE && $extension->status !== 1) { continue; } // Filter out installed if --no-installed is specified. if ($installed === FALSE && $extension->status == 1) { continue; } // Filter out core if --no-core specified. // @todo Add origin property to themes. // @see https://www.drupal.org/project/drupal/issues/3605703 if ($core === FALSE && @$extension->origin == 'core') { continue; } // Filter out non-core if --core specified. if ($core === TRUE && $extension->origin !== 'core') { continue; } // Filter by package. if ($package && !in_array(strtolower($extension->info['package']), array_map('strtolower', explode(',', $package)))) { continue; } $row = [ 'package' => $extension->info['package'], 'display_name' => $extension->info['name'] . ' (' . $extension->getName() . ')', 'status' => ucfirst($extension->status ? 'installed' : 'uninstalled'), 'version' => $extension->info['version'], ]; $rows[$key] = $row; } $result = match ($format) { OutputFormat::Json => json_encode($rows, JSON_PRETTY_PRINT), OutputFormat::Yaml => Yaml::encode($rows), default => $this->renderTable($rows), }; $output->writeln($result); return Command::SUCCESS; } /** * Render the table. */ protected function renderTable(array $rows): string { $buffer = new BufferedOutput(); $table = new Table($buffer); $table ->setHeaders(['Package', 'Name', 'Status', 'Version']) ->setRows($rows) ->render(); return $buffer->fetch(); } }
core/modules/system/tests/src/Kernel/Command/ExtensionListCommandTest.php 0 → 100644 +75 −0 Changes for core/modules/system/tests/src/Kernel/Command/ExtensionListCommandTest.php: 75 added lines, 0 removed lines. Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Tests\system\Kernel\Command; use Drupal\Core\Extension\Command\ExtensionListCommand; use Drupal\KernelTests\DrupalApplicationTesterTrait; use Drupal\KernelTests\KernelTestBase; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use Symfony\Component\Console\Command\Command; /** * Tests the 'ex:list' command. */ #[Group('Console')] #[RunTestsInSeparateProcesses] #[CoversClass(ExtensionListCommand::class)] class ExtensionListCommandTest extends KernelTestBase { use DrupalApplicationTesterTrait; /** * {@inheritdoc} */ protected static $modules = [ 'system', ]; /** * Tests the 'ex:list' command. */ public function testExtensionList(): void { $tester = $this->applicationTester(); // Detailed testing using json output format. $cmd = ['command' => 'ex:list', '--format' => 'json']; $this->assertEquals(Command::SUCCESS, $tester->run($cmd)); $json = json_decode($tester->getDisplay()); $this->assertSame('Field types', $json->datetime->package); $this->assertSame('Datetime (datetime)', $json->datetime->display_name); $this->assertSame('Uninstalled', $json->datetime->status); $this->assertSame(\Drupal::VERSION, $json->datetime->version); $this->assertSame('Installed', $json->system->status); // Exercise the filtering options. $cmd = ['command' => 'ex:list', '--format' => 'json', '--type' => 'module', '--installed' => TRUE]; $this->assertEquals(Command::SUCCESS, $tester->run($cmd)); $json = json_decode($tester->getDisplay()); $this->assertObjectHasProperty('system', $json); $this->assertObjectNotHasProperty('datetime', $json); $cmd = ['command' => 'ex:list', '--format' => 'json', '--no-core' => TRUE]; $this->assertEquals(Command::SUCCESS, $tester->run($cmd)); $json = json_decode($tester->getDisplay()); $this->assertObjectNotHasProperty('system', $json); $cmd = ['command' => 'ex:list', '--format' => 'json', '--type' => 'theme']; $this->assertEquals(Command::SUCCESS, $tester->run($cmd)); $json = json_decode($tester->getDisplay()); $this->assertObjectNotHasProperty('system', $json); // Sanity check the human readable output. $this->assertEquals(Command::SUCCESS, $tester->run(['command' => 'ex:list'])); $display = $tester->getDisplay(); $this->assertStringContainsString('Package', $display); $this->assertStringContainsString('Version', $display); $this->assertStringContainsString('User', $display); $this->assertStringContainsString('Installed', $display); $this->assertStringContainsString('Uninstalled', $display); $this->assertStringContainsString(\Drupal::VERSION, $display); } }