diff --git a/CHANGELOG.md b/CHANGELOG.md index da86516bc..18914951e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ * Make it possible to use an absolute path when loading a blueprint * Make serialize methods final in `ContentBlock`, `AbstractFile`, `FormTrait`, `ObjectCollectionTrait` and `ObjectTrait` * Added support for relative paths in `PageObject::getLevelListing()` [#3110](https://github.com/getgrav/grav/issues/3110) - * Better `--env` support for `bin/grav` and `bin/gpm` console commands + * Better `--env` and `--lang` support for `bin/grav`, `bin/gpm` and `bin/plugin` console commands 1. [](#bugfix) * Fixed port issue with `system.custom_base_url` * Hide errors with `exif_read_data` in `ImageFile` diff --git a/bin/plugin b/bin/plugin index 0f54cfe1a..52729850d 100755 --- a/bin/plugin +++ b/bin/plugin @@ -2,12 +2,8 @@ arguments->add([ - 'environment' => [ - 'prefix' => 'e', - 'longPrefix' => 'env', - 'description' => 'Configuration Environment', - 'defaultValue' => 'localhost' - ] -]); -$climate->arguments->parse(); - -$environment = $climate->arguments->get('environment'); - // Bootstrap Grav container. $grav = Grav::instance(array('loader' => $autoload)); -$grav->setup($environment); -$grav->initializeCli(); -$app = new Application('Grav Plugins Commands', GRAV_VERSION); -$pattern = '([A-Z]\w+Command\.php)'; - -// get arguments and strip the application name -if (null === $argv) { - $argv = $_SERVER['argv']; -} - -$bin = array_shift($argv); -$name = array_shift($argv); -$argv = array_merge([$bin], $argv); - -$input = new ArgvInput($argv); - -/** @var \Grav\Common\Data\Data $plugin */ -$plugin = $grav['plugins']->get($name); - -$output = new ConsoleOutput(); -$output->getFormatter()->setStyle('red', new OutputFormatterStyle('red', null, array('bold'))); -$output->getFormatter()->setStyle('white', new OutputFormatterStyle('white', null, array('bold'))); - -if (!$name) { - $output->writeln(''); - $output->writeln('Usage:'); - $output->writeln(" {$bin} [slug] [command] [arguments]"); - $output->writeln(''); - $output->writeln('Example:'); - $output->writeln(" {$bin} error log -l 1 --trace"); - $list = Folder::all('plugins://', ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm', 'levels' => 2]); - - $total = 0; - if (count($list)) { - $available = []; - $output->writeln(''); - $output->writeln('Plugins with CLI available:'); - foreach ($list as $index => $entry) { - $split = explode('/', $entry); - $entry = array_shift($split); - $index = str_pad($index++ + 1, 2, '0', STR_PAD_LEFT); - $total = str_pad($total++ + 1, 2, '0', STR_PAD_LEFT); - if (\in_array($entry, $available, true)) { - $total--; - continue; - } - - $available[] = $entry; - $commands_count = $index - $total + 1; - $output->writeln(' ' . $total . '. ' . str_pad($entry, 15) . " {$bin} {$entry} list"); - } - } - - exit; -} - -if (null === $plugin) { - $output->writeln(''); - $output->writeln("$name plugin not found"); - die; -} - -if (!$plugin->enabled) { - $output->writeln(''); - $output->writeln("$name not enabled"); - die; -} - - -if ($plugin === null) { - $output->writeln("Grav Plugin '{$name}' is not installed"); - exit; -} - -$path = 'plugins://' . $name . '/cli'; - -try { - $commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm', 'levels' => 1]); -} catch (\RuntimeException $e) { - $output->writeln("No Console Commands for '{$name}' where found in '{$path}'"); - exit; -} - -foreach ($commands as $command_path) { - $full_path = $grav['locator']->findResource("plugins://{$name}/cli/{$command_path}"); - require_once $full_path; - - $command_class = 'Grav\Plugin\Console\\' . preg_replace('/.php$/', '', $command_path); - $command = new $command_class(); - $app->add($command); -} - -$app->run($input); +$app = new PluginApplication('Grav Plugins Commands', GRAV_VERSION); +$app->run(); diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php index 02a189d9b..99a1d70af 100644 --- a/system/src/Grav/Common/Plugins.php +++ b/system/src/Grav/Common/Plugins.php @@ -173,7 +173,7 @@ class Plugins extends Iterator /** * Return list of all plugin data with their blueprints. * - * @return array + * @return array */ public static function all() { diff --git a/system/src/Grav/Console/Application/Application.php b/system/src/Grav/Console/Application/Application.php new file mode 100644 index 000000000..1634a8447 --- /dev/null +++ b/system/src/Grav/Console/Application/Application.php @@ -0,0 +1,82 @@ +environment = $input->getOption('env'); + $this->language = $input->getOption('lang') ?? $this->language; + $this->init(); + + return parent::getCommandName($input); + } + + protected function init(): void + { + if ($this->initialized) { + return; + } + + $this->initialized = true; + + $grav = Grav::instance(); + $grav->setup($this->environment); + } + + /** + * Add global a --env option. + * + * @return InputDefinition + */ + protected function getDefaultInputDefinition(): InputDefinition + { + $inputDefinition = parent::getDefaultInputDefinition(); + $inputDefinition->addOption( + new InputOption( + 'env', + null, + InputOption::VALUE_OPTIONAL, + 'Use environment configuration (defaults to localhost)' + ) + ); + $inputDefinition->addOption( + new InputOption( + 'lang', + null, + InputOption::VALUE_OPTIONAL, + 'Language to be used (defaults to en)' + ) + ); + + return $inputDefinition; + } +} diff --git a/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php b/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php new file mode 100644 index 000000000..bff1e9812 --- /dev/null +++ b/system/src/Grav/Console/Application/CommandLoader/PluginCommandLoader.php @@ -0,0 +1,95 @@ +commands = []; + + try { + $path = "plugins://{$name}/cli"; + $pattern = '([A-Z]\w+Command\.php)'; + + $commands = is_dir($path) ? Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm', 'levels' => 1]) : []; + } catch (RuntimeException $e) { + throw new RuntimeException("Failed to load console commands for plugin {$name}"); + } + + $grav = Grav::instance(); + + /** @var UniformResourceLocator $locator */ + $locator = $grav['locator']; + foreach ($commands as $command_path) { + $full_path = $locator->findResource("plugins://{$name}/cli/{$command_path}"); + require_once $full_path; + + $command_class = 'Grav\Plugin\Console\\' . preg_replace('/.php$/', '', $command_path); + if (class_exists($command_class)) { + $command = new $command_class(); + if ($command instanceof Command) { + $this->commands[$command->getName()] = $command; + } + } + } + } + + /** + * @param string $name + * @return Command + */ + public function get($name): Command + { + $command = $this->commands[$name] ?? null; + if (null === $command) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + return $command; + } + + /** + * @param string $name + * @return bool + */ + public function has($name): bool + { + return isset($this->commands[$name]); + } + + /** + * @return string[] + */ + public function getNames(): array + { + return array_keys($this->commands); + } +} diff --git a/system/src/Grav/Console/Application/GpmApplication.php b/system/src/Grav/Console/Application/GpmApplication.php index fbaf468a8..69bb2344f 100644 --- a/system/src/Grav/Console/Application/GpmApplication.php +++ b/system/src/Grav/Console/Application/GpmApplication.php @@ -9,7 +9,6 @@ namespace Grav\Console\Application; -use Codeception\Application; use Grav\Console\Gpm\DirectInstallCommand; use Grav\Console\Gpm\IndexCommand; use Grav\Console\Gpm\InfoCommand; diff --git a/system/src/Grav/Console/Application/GravApplication.php b/system/src/Grav/Console/Application/GravApplication.php index 73761e1a6..45c8b13eb 100644 --- a/system/src/Grav/Console/Application/GravApplication.php +++ b/system/src/Grav/Console/Application/GravApplication.php @@ -9,7 +9,6 @@ namespace Grav\Console\Application; -use Codeception\Application; use Grav\Console\Cli\BackupCommand; use Grav\Console\Cli\CleanCommand; use Grav\Console\Cli\ClearCacheCommand; diff --git a/system/src/Grav/Console/Application/PluginApplication.php b/system/src/Grav/Console/Application/PluginApplication.php new file mode 100644 index 000000000..aca1c4e11 --- /dev/null +++ b/system/src/Grav/Console/Application/PluginApplication.php @@ -0,0 +1,99 @@ +addCommands([ + new PluginListCommand(), + ]); + } + + public function setPluginName(string $pluginName): void + { + $this->pluginName = $pluginName; + } + + public function getPluginName(): string + { + return $this->pluginName; + } + + /** + * @param InputInterface|null $input + * @param OutputInterface|null $output + * @return int + * @throws Throwable + */ + public function run(InputInterface $input = null, OutputInterface $output = null): int + { + if (null === $input) { + $argv = $_SERVER['argv'] ?? []; + + $bin = array_shift($argv); + $this->pluginName = array_shift($argv); + $argv = array_merge([$bin], $argv); + + $input = new ArgvInput($argv); + } + + return parent::run($input, $output); + } + + protected function init(): void + { + if ($this->initialized) { + return; + } + + parent::init(); + + if (null === $this->pluginName) { + $this->setDefaultCommand('plugins:list'); + + return; + } + + $grav = Grav::instance(); + $grav->initializeCli(); + + /** @var Data $plugin */ + $plugin = $this->pluginName ? $grav['plugins']->get($this->pluginName) : null; + if (null === $plugin) { + throw new NamespaceNotFoundException("Plugin \"{$this->pluginName}\" is not installed."); + } + if (!$plugin->enabled) { + throw new NamespaceNotFoundException("Plugin \"{$this->pluginName}\" is not enabled."); + } + + $this->setCommandLoader(new PluginCommandLoader($this->pluginName)); + } +} diff --git a/system/src/Grav/Console/ConsoleCommand.php b/system/src/Grav/Console/ConsoleCommand.php index a989b5efc..479c6909d 100644 --- a/system/src/Grav/Console/ConsoleCommand.php +++ b/system/src/Grav/Console/ConsoleCommand.php @@ -10,7 +10,6 @@ namespace Grav\Console; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -22,19 +21,6 @@ class ConsoleCommand extends Command { use ConsoleTrait; - /** - * @param string|null $name The name of the command; passing null means it must be set in configure() - * - * @throws LogicException When the command name is empty - */ - public function __construct(string $name = null) - { - parent::__construct($name); - - // Add --env option. - $this->addEnvOption(); - } - /** * @param InputInterface $input * @param OutputInterface $output diff --git a/system/src/Grav/Console/ConsoleTrait.php b/system/src/Grav/Console/ConsoleTrait.php index 150883234..d3f3d30ae 100644 --- a/system/src/Grav/Console/ConsoleTrait.php +++ b/system/src/Grav/Console/ConsoleTrait.php @@ -72,72 +72,21 @@ trait ConsoleTrait $this->setupGrav(); } - /** - * @return $this - */ - final protected function addEnvOption() - { - try { - return $this->addOption( - 'env', - 'e', - InputOption::VALUE_OPTIONAL, - 'Optional environment to trigger a specific configuration.' - ); - } catch (LogicException $e) { - return $this; - } - } - - /** - * @return $this - */ - final protected function addLanguageOption() - { - try { - return $this->addOption( - 'language', - 'l', - InputOption::VALUE_OPTIONAL, - 'Optional language to be used (multi-language sites only).' - ); - } catch (LogicException $e) { - return $this; - } - } - final protected function setupGrav(): void { try { - $environment = $this->input->getOption('env'); - } catch (InvalidArgumentException $e) { - $environment = null; - } - try { - $language = $this->input->getOption('language'); - } catch (InvalidArgumentException $e) { - $language = null; - } - - $grav = Grav::instance(); - if (!$grav->isSetup()) { - // Set environment. - $grav->setup($environment); - } else { - $this->output->writeln(''); - $this->output->writeln('WARNING: Grav environment already set, please update logic in your CLI command'); - } - - if ($language) { - // Set used language. - $this->setLanguage($language); - } + $language = $this->input->getOption('lang'); + if ($language) { + // Set used language. + $this->setLanguage($language); + } + } catch (InvalidArgumentException $e) {} // Initialize cache with CLI compatibility + $grav = Grav::instance(); $grav['config']->set('system.cache.cli_compatibility', true); } - /** * Initialize Grav. * diff --git a/system/src/Grav/Console/GpmCommand.php b/system/src/Grav/Console/GpmCommand.php index 8ca1a7f4d..4e15ebda4 100644 --- a/system/src/Grav/Console/GpmCommand.php +++ b/system/src/Grav/Console/GpmCommand.php @@ -12,7 +12,6 @@ namespace Grav\Console; use Grav\Common\Config\Config; use Grav\Common\Grav; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -24,19 +23,6 @@ class GpmCommand extends Command { use ConsoleTrait; - /** - * @param string|null $name The name of the command; passing null means it must be set in configure() - * - * @throws LogicException When the command name is empty - */ - public function __construct(string $name = null) - { - parent::__construct($name); - - // Add --env option. - $this->addEnvOption(); - } - /** * @param InputInterface $input * @param OutputInterface $output diff --git a/system/src/Grav/Console/GravCommand.php b/system/src/Grav/Console/GravCommand.php index 080bbddf9..bd1ef03ae 100644 --- a/system/src/Grav/Console/GravCommand.php +++ b/system/src/Grav/Console/GravCommand.php @@ -10,7 +10,6 @@ namespace Grav\Console; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\LogicException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -22,19 +21,6 @@ class GravCommand extends Command { use ConsoleTrait; - /** - * @param string|null $name The name of the command; passing null means it must be set in configure() - * - * @throws LogicException When the command name is empty - */ - public function __construct(string $name = null) - { - parent::__construct($name); - - // Add --env option. - $this->addEnvOption(); - } - /** * @param InputInterface $input * @param OutputInterface $output diff --git a/system/src/Grav/Console/Plugin/PluginListCommand.php b/system/src/Grav/Console/Plugin/PluginListCommand.php new file mode 100644 index 000000000..2e301290f --- /dev/null +++ b/system/src/Grav/Console/Plugin/PluginListCommand.php @@ -0,0 +1,70 @@ +setHidden(true); + } + + /** + * @return int + */ + protected function serve(): int + { + $bin = 'bin/plugin'; + $pattern = '([A-Z]\w+Command\.php)'; + + $output = $this->output; + $output->writeln(''); + $output->writeln('Usage:'); + $output->writeln(" {$bin} [slug] [command] [arguments]"); + $output->writeln(''); + $output->writeln('Example:'); + $output->writeln(" {$bin} error log -l 1 --trace"); + + $output->writeln(''); + $output->writeln('Plugins with CLI available:'); + + $plugins = Plugins::all(); + $total = 0; + foreach ($plugins as $name => $plugin) { + if (!$plugin->enabled) { + continue; + } + + $list = Folder::all("plugins://{$name}", ['compare' => 'Pathname', 'pattern' => '/\/cli\/' . $pattern . '$/usm', 'levels' => 1]); + if (!$list) { + continue; + } + + $total++; + $index = str_pad($total, 2, '0', STR_PAD_LEFT); + $output->writeln(' ' . $index . '. ' . str_pad($name, 15) . " {$bin} {$name} list"); + } + + return 0; + } +}