Improve all console commands

This commit is contained in:
Matias Griese 2021-01-06 19:13:19 +02:00
parent 379033aae4
commit 9893792768
13 changed files with 358 additions and 215 deletions

View File

@ -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`

View File

@ -2,12 +2,8 @@
<?php
use Grav\Common\Composer;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Grav\Common\Grav;
use Grav\Common\Filesystem\Folder;
use Grav\Console\Application\PluginApplication;
\define('GRAV_CLI', true);
\define('GRAV_REQUEST_TIME', microtime(true));
@ -43,112 +39,8 @@ if (!file_exists(GRAV_ROOT . '/index.php')) {
exit('FATAL: Must be run from ROOT directory of Grav!');
}
$climate = new League\CLImate\CLImate;
$climate->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('<red>Usage:</red>');
$output->writeln(" {$bin} [slug] [command] [arguments]");
$output->writeln('');
$output->writeln('<red>Example:</red>');
$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('<red>Plugins with CLI available:</red>');
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 . '. <red>' . str_pad($entry, 15) . "</red> <white>{$bin} {$entry} list</white>");
}
}
exit;
}
if (null === $plugin) {
$output->writeln('');
$output->writeln("<red>$name plugin not found</red>");
die;
}
if (!$plugin->enabled) {
$output->writeln('');
$output->writeln("<red>$name not enabled</red>");
die;
}
if ($plugin === null) {
$output->writeln("<red>Grav Plugin <white>'{$name}'</white> is not installed</red>");
exit;
}
$path = 'plugins://' . $name . '/cli';
try {
$commands = Folder::all($path, ['compare' => 'Filename', 'pattern' => '/' . $pattern . '$/usm', 'levels' => 1]);
} catch (\RuntimeException $e) {
$output->writeln("<red>No Console Commands for <white>'{$name}'</white> where found in <white>'{$path}'</white></red>");
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();

View File

@ -173,7 +173,7 @@ class Plugins extends Iterator
/**
* Return list of all plugin data with their blueprints.
*
* @return array
* @return array<string,Data>
*/
public static function all()
{

View File

@ -0,0 +1,82 @@
<?php
/**
* @package Grav\Console
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Application;
use Grav\Common\Grav;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
/**
* Class GpmApplication
* @package Grav\Console\Application
*/
class Application extends \Symfony\Component\Console\Application
{
/** @var string|null */
protected $environment;
/** @var string|null */
protected $language;
/** @var bool */
protected $initialized = false;
/**
* @param InputInterface $input
* @return string|null
*/
public function getCommandName(InputInterface $input): ?string
{
$this->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;
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* @package Grav\Console
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Application\CommandLoader;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* Class GpmApplication
* @package Grav\Console\Application
*/
class PluginCommandLoader implements CommandLoaderInterface
{
/** @var array */
private $commands;
/**
* PluginCommandLoader constructor.
*
* @param string $name
*/
public function __construct(string $name)
{
$this->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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -0,0 +1,99 @@
<?php
/**
* @package Grav\Console
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Application;
use Grav\Common\Data\Data;
use Grav\Common\Grav;
use Grav\Console\Application\CommandLoader\PluginCommandLoader;
use Grav\Console\Plugin\PluginListCommand;
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Throwable;
/**
* Class PluginApplication
* @package Grav\Console\Application
*/
class PluginApplication extends Application
{
/** @var string|null */
protected $pluginName;
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
parent::__construct($name, $version);
$this->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));
}
}

View File

@ -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

View File

@ -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('<red>WARNING</red>: 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.
*

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,70 @@
<?php
/**
* @package Grav\Console\Plugin
*
* @copyright Copyright (C) 2015 - 2020 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Console\Plugin;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Plugins;
use Grav\Console\ConsoleCommand;
/**
* Class InfoCommand
* @package Grav\Console\Gpm
*/
class PluginListCommand extends ConsoleCommand
{
protected static $defaultName = 'plugins:list';
/**
* @return void
*/
protected function configure(): void
{
$this->setHidden(true);
}
/**
* @return int
*/
protected function serve(): int
{
$bin = 'bin/plugin';
$pattern = '([A-Z]\w+Command\.php)';
$output = $this->output;
$output->writeln('');
$output->writeln('<red>Usage:</red>');
$output->writeln(" {$bin} [slug] [command] [arguments]");
$output->writeln('');
$output->writeln('<red>Example:</red>');
$output->writeln(" {$bin} error log -l 1 --trace");
$output->writeln('');
$output->writeln('<red>Plugins with CLI available:</red>');
$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 . '. <red>' . str_pad($name, 15) . "</red> <white>{$bin} {$name} list</white>");
}
return 0;
}
}