add trashes
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\CLI;
|
||||
|
||||
use Config\Exceptions;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ReflectionException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* BaseCommand is the base class used in creating CLI commands.
|
||||
*
|
||||
* @property array<string, string> $arguments
|
||||
* @property Commands $commands
|
||||
* @property string $description
|
||||
* @property string $group
|
||||
* @property LoggerInterface $logger
|
||||
* @property string $name
|
||||
* @property array<string, string> $options
|
||||
* @property string $usage
|
||||
*/
|
||||
abstract class BaseCommand
|
||||
{
|
||||
/**
|
||||
* The group the command is lumped under
|
||||
* when listing commands.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $group;
|
||||
|
||||
/**
|
||||
* The Command's name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name;
|
||||
|
||||
/**
|
||||
* the Command's usage description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $usage;
|
||||
|
||||
/**
|
||||
* the Command's short description
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description;
|
||||
|
||||
/**
|
||||
* the Command's options description
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $options = [];
|
||||
|
||||
/**
|
||||
* the Command's Arguments description
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* The Logger to use for a command
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Instance of Commands so
|
||||
* commands can call other commands.
|
||||
*
|
||||
* @var Commands
|
||||
*/
|
||||
protected $commands;
|
||||
|
||||
public function __construct(LoggerInterface $logger, Commands $commands)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->commands = $commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Actually execute a command.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*
|
||||
* @return int|void
|
||||
*/
|
||||
abstract public function run(array $params);
|
||||
|
||||
/**
|
||||
* Can be used by a command to run other commands.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*
|
||||
* @return int|void
|
||||
*
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
protected function call(string $command, array $params = [])
|
||||
{
|
||||
return $this->commands->run($command, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple method to display an error with line/file, in child commands.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function showError(Throwable $e)
|
||||
{
|
||||
$exception = $e;
|
||||
$message = $e->getMessage();
|
||||
$config = config(Exceptions::class);
|
||||
|
||||
require $config->errorViewPath . '/cli/error_exception.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Help includes (Usage, Arguments, Description, Options).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function showHelp()
|
||||
{
|
||||
CLI::write(lang('CLI.helpUsage'), 'yellow');
|
||||
|
||||
if (isset($this->usage)) {
|
||||
$usage = $this->usage;
|
||||
} else {
|
||||
$usage = $this->name;
|
||||
|
||||
if ($this->arguments !== []) {
|
||||
$usage .= ' [arguments]';
|
||||
}
|
||||
}
|
||||
|
||||
CLI::write($this->setPad($usage, 0, 0, 2));
|
||||
|
||||
if (isset($this->description)) {
|
||||
CLI::newLine();
|
||||
CLI::write(lang('CLI.helpDescription'), 'yellow');
|
||||
CLI::write($this->setPad($this->description, 0, 0, 2));
|
||||
}
|
||||
|
||||
if ($this->arguments !== []) {
|
||||
CLI::newLine();
|
||||
CLI::write(lang('CLI.helpArguments'), 'yellow');
|
||||
$length = max(array_map(strlen(...), array_keys($this->arguments)));
|
||||
|
||||
foreach ($this->arguments as $argument => $description) {
|
||||
CLI::write(CLI::color($this->setPad($argument, $length, 2, 2), 'green') . $description);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->options !== []) {
|
||||
CLI::newLine();
|
||||
CLI::write(lang('CLI.helpOptions'), 'yellow');
|
||||
$length = max(array_map(strlen(...), array_keys($this->options)));
|
||||
|
||||
foreach ($this->options as $option => $description) {
|
||||
CLI::write(CLI::color($this->setPad($option, $length, 2, 2), 'green') . $description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads our string out so that all titles are the same length to nicely line up descriptions.
|
||||
*
|
||||
* @param int $extra How many extra spaces to add at the end
|
||||
*/
|
||||
public function setPad(string $item, int $max, int $extra = 2, int $indent = 0): string
|
||||
{
|
||||
$max += $extra + $indent;
|
||||
|
||||
return str_pad(str_repeat(' ', $indent) . $item, $max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pad for $key => $value array output
|
||||
*
|
||||
* @param array<string, string> $array
|
||||
*
|
||||
* @deprecated Use setPad() instead.
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public function getPad(array $array, int $pad): int
|
||||
{
|
||||
$max = 0;
|
||||
|
||||
foreach (array_keys($array) as $key) {
|
||||
$max = max($max, strlen($key));
|
||||
}
|
||||
|
||||
return $max + $pad;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it simple to access our protected properties.
|
||||
*
|
||||
* @return array<string, string>|Commands|LoggerInterface|string|null
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->{$key} ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes it simple to check our protected properties.
|
||||
*/
|
||||
public function __isset(string $key): bool
|
||||
{
|
||||
return isset($this->{$key});
|
||||
}
|
||||
}
|
||||
+1155
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\CLI;
|
||||
|
||||
use CodeIgniter\Autoloader\FileLocatorInterface;
|
||||
use CodeIgniter\Events\Events;
|
||||
use CodeIgniter\Log\Logger;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* Core functionality for running, listing, etc commands.
|
||||
*
|
||||
* @phpstan-type commands_list array<string, array{'class': class-string<BaseCommand>, 'file': string, 'group': string,'description': string}>
|
||||
*/
|
||||
class Commands
|
||||
{
|
||||
/**
|
||||
* The found commands.
|
||||
*
|
||||
* @var commands_list
|
||||
*/
|
||||
protected $commands = [];
|
||||
|
||||
/**
|
||||
* Logger instance.
|
||||
*
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Logger|null $logger
|
||||
*/
|
||||
public function __construct($logger = null)
|
||||
{
|
||||
$this->logger = $logger ?? service('logger');
|
||||
$this->discoverCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a command given
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*
|
||||
* @return int Exit code
|
||||
*/
|
||||
public function run(string $command, array $params)
|
||||
{
|
||||
if (! $this->verifyCommand($command, $this->commands)) {
|
||||
return EXIT_ERROR;
|
||||
}
|
||||
|
||||
// The file would have already been loaded during the
|
||||
// createCommandList function...
|
||||
$className = $this->commands[$command]['class'];
|
||||
$class = new $className($this->logger, $this);
|
||||
|
||||
Events::trigger('pre_command');
|
||||
|
||||
$exit = $class->run($params);
|
||||
|
||||
Events::trigger('post_command');
|
||||
|
||||
return $exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to the list of commands.
|
||||
*
|
||||
* @return commands_list
|
||||
*/
|
||||
public function getCommands()
|
||||
{
|
||||
return $this->commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers all commands in the framework and within user code,
|
||||
* and collects instances of them to work with.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function discoverCommands()
|
||||
{
|
||||
if ($this->commands !== []) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var FileLocatorInterface */
|
||||
$locator = service('locator');
|
||||
$files = $locator->listFiles('Commands/');
|
||||
|
||||
// If no matching command files were found, bail
|
||||
// This should never happen in unit testing.
|
||||
if ($files === []) {
|
||||
return; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
// Loop over each file checking to see if a command with that
|
||||
// alias exists in the class.
|
||||
foreach ($files as $file) {
|
||||
/** @var class-string<BaseCommand>|false */
|
||||
$className = $locator->findQualifiedNameFromPath($file);
|
||||
|
||||
if ($className === false || ! class_exists($className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$class = new ReflectionClass($className);
|
||||
|
||||
if (! $class->isInstantiable() || ! $class->isSubclassOf(BaseCommand::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class = new $className($this->logger, $this);
|
||||
|
||||
if (isset($class->group) && ! isset($this->commands[$class->name])) {
|
||||
$this->commands[$class->name] = [
|
||||
'class' => $className,
|
||||
'file' => $file,
|
||||
'group' => $class->group,
|
||||
'description' => $class->description,
|
||||
];
|
||||
}
|
||||
|
||||
unset($class);
|
||||
} catch (ReflectionException $e) {
|
||||
$this->logger->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
asort($this->commands);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the command being sought is found
|
||||
* in the commands list.
|
||||
*
|
||||
* @param commands_list $commands
|
||||
*/
|
||||
public function verifyCommand(string $command, array $commands): bool
|
||||
{
|
||||
if (isset($commands[$command])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$message = lang('CLI.commandNotFound', [$command]);
|
||||
$alternatives = $this->getCommandAlternatives($command, $commands);
|
||||
|
||||
if ($alternatives !== []) {
|
||||
if (count($alternatives) === 1) {
|
||||
$message .= "\n\n" . lang('CLI.altCommandSingular') . "\n ";
|
||||
} else {
|
||||
$message .= "\n\n" . lang('CLI.altCommandPlural') . "\n ";
|
||||
}
|
||||
|
||||
$message .= implode("\n ", $alternatives);
|
||||
}
|
||||
|
||||
CLI::error($message);
|
||||
CLI::newLine();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds alternative of `$name` among collection
|
||||
* of commands.
|
||||
*
|
||||
* @param commands_list $collection
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function getCommandAlternatives(string $name, array $collection): array
|
||||
{
|
||||
/** @var array<string, int> */
|
||||
$alternatives = [];
|
||||
|
||||
/** @var string $commandName */
|
||||
foreach (array_keys($collection) as $commandName) {
|
||||
$lev = levenshtein($name, $commandName);
|
||||
|
||||
if ($lev <= strlen($commandName) / 3 || str_contains($commandName, $name)) {
|
||||
$alternatives[$commandName] = $lev;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($alternatives, SORT_NATURAL | SORT_FLAG_CASE);
|
||||
|
||||
return array_keys($alternatives);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\CLI;
|
||||
|
||||
use CodeIgniter\CodeIgniter;
|
||||
use Config\App;
|
||||
use Config\Services;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Console
|
||||
*
|
||||
* @see \CodeIgniter\CLI\ConsoleTest
|
||||
*/
|
||||
class Console
|
||||
{
|
||||
/**
|
||||
* Runs the current command discovered on the CLI.
|
||||
*
|
||||
* @return int|void Exit code
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// Create CLIRequest
|
||||
$appConfig = config(App::class);
|
||||
Services::createRequest($appConfig, true);
|
||||
// Load Routes
|
||||
service('routes')->loadRoutes();
|
||||
|
||||
$params = array_merge(CLI::getSegments(), CLI::getOptions());
|
||||
$params = $this->parseParamsForHelpOption($params);
|
||||
$command = array_shift($params) ?? 'list';
|
||||
|
||||
return service('commands')->run($command, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays basic information about the Console.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function showHeader(bool $suppress = false)
|
||||
{
|
||||
if ($suppress) {
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::write(sprintf(
|
||||
'CodeIgniter v%s Command Line Tool - Server Time: %s UTC%s',
|
||||
CodeIgniter::CI_VERSION,
|
||||
date('Y-m-d H:i:s'),
|
||||
date('P'),
|
||||
), 'green');
|
||||
CLI::newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Introspects the `$params` passed for presence of the
|
||||
* `--help` option.
|
||||
*
|
||||
* If present, it will be found as `['help' => null]`.
|
||||
* We'll remove that as an option from `$params` and
|
||||
* unshift it as argument instead.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
private function parseParamsForHelpOption(array $params): array
|
||||
{
|
||||
if (array_key_exists('help', $params)) {
|
||||
unset($params['help']);
|
||||
|
||||
$params = $params === [] ? ['list'] : $params;
|
||||
array_unshift($params, 'help');
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\CLI\Exceptions;
|
||||
|
||||
use CodeIgniter\Exceptions\DebugTraceableTrait;
|
||||
use CodeIgniter\Exceptions\RuntimeException;
|
||||
|
||||
/**
|
||||
* CLIException
|
||||
*/
|
||||
class CLIException extends RuntimeException
|
||||
{
|
||||
use DebugTraceableTrait;
|
||||
|
||||
/**
|
||||
* Thrown when `$color` specified for `$type` is not within the
|
||||
* allowed list of colors.
|
||||
*
|
||||
* @return CLIException
|
||||
*/
|
||||
public static function forInvalidColor(string $type, string $color)
|
||||
{
|
||||
return new static(lang('CLI.invalidColor', [$type, $color]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,527 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\CLI;
|
||||
|
||||
use Config\Generators;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* GeneratorTrait contains a collection of methods
|
||||
* to build the commands that generates a file.
|
||||
*/
|
||||
trait GeneratorTrait
|
||||
{
|
||||
/**
|
||||
* Component Name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $component;
|
||||
|
||||
/**
|
||||
* File directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* (Optional) View template path
|
||||
*
|
||||
* We use special namespaced paths like:
|
||||
* `CodeIgniter\Commands\Generators\Views\cell.tpl.php`.
|
||||
*/
|
||||
protected ?string $templatePath = null;
|
||||
|
||||
/**
|
||||
* View template name for fallback
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $template;
|
||||
|
||||
/**
|
||||
* Language string key for required class names.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $classNameLang = '';
|
||||
|
||||
/**
|
||||
* Namespace to use for class.
|
||||
* Leave null to use the default namespace.
|
||||
*/
|
||||
protected ?string $namespace = null;
|
||||
|
||||
/**
|
||||
* Whether to require class name.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $hasClassName = true;
|
||||
|
||||
/**
|
||||
* Whether to sort class imports.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $sortImports = true;
|
||||
|
||||
/**
|
||||
* Whether the `--suffix` option has any effect.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $enabledSuffixing = true;
|
||||
|
||||
/**
|
||||
* The params array for easy access by other methods.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var array<int|string, string|null>
|
||||
*/
|
||||
private $params = [];
|
||||
|
||||
/**
|
||||
* Execute the command.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*
|
||||
* @deprecated use generateClass() instead
|
||||
*/
|
||||
protected function execute(array $params): void
|
||||
{
|
||||
$this->generateClass($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a class file from an existing template.
|
||||
*
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
protected function generateClass(array $params): void
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
// Get the fully qualified class name from the input.
|
||||
$class = $this->qualifyClassName();
|
||||
|
||||
// Get the file path from class name.
|
||||
$target = $this->buildPath($class);
|
||||
|
||||
// Check if path is empty.
|
||||
if ($target === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->generateFile($target, $this->buildContent($class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a view file from an existing template.
|
||||
*
|
||||
* @param string $view namespaced view name that is generated
|
||||
* @param array<int|string, string|null> $params
|
||||
*/
|
||||
protected function generateView(string $view, array $params): void
|
||||
{
|
||||
$this->params = $params;
|
||||
|
||||
$target = $this->buildPath($view);
|
||||
|
||||
// Check if path is empty.
|
||||
if ($target === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->generateFile($target, $this->buildContent($view));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles writing the file to disk, and all of the safety checks around that.
|
||||
*
|
||||
* @param string $target file path
|
||||
*/
|
||||
private function generateFile(string $target, string $content): void
|
||||
{
|
||||
if ($this->getOption('namespace') === 'CodeIgniter') {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::write(lang('CLI.generator.usingCINamespace'), 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
if (
|
||||
CLI::prompt(
|
||||
'Are you sure you want to continue?',
|
||||
['y', 'n'],
|
||||
'required',
|
||||
) === 'n'
|
||||
) {
|
||||
CLI::newLine();
|
||||
CLI::write(lang('CLI.generator.cancelOperation'), 'yellow');
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::newLine();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$isFile = is_file($target);
|
||||
|
||||
// Overwriting files unknowingly is a serious annoyance, So we'll check if
|
||||
// we are duplicating things, If 'force' option is not supplied, we bail.
|
||||
if (! $this->getOption('force') && $isFile) {
|
||||
CLI::error(
|
||||
lang('CLI.generator.fileExist', [clean_path($target)]),
|
||||
'light_gray',
|
||||
'red',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the directory to save the file is existing.
|
||||
$dir = dirname($target);
|
||||
|
||||
if (! is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
helper('filesystem');
|
||||
|
||||
// Build the class based on the details we have, We'll be getting our file
|
||||
// contents from the template, and then we'll do the necessary replacements.
|
||||
if (! write_file($target, $content)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
CLI::error(
|
||||
lang('CLI.generator.fileError', [clean_path($target)]),
|
||||
'light_gray',
|
||||
'red',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
if ($this->getOption('force') && $isFile) {
|
||||
CLI::write(
|
||||
lang('CLI.generator.fileOverwrite', [clean_path($target)]),
|
||||
'yellow',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
CLI::write(
|
||||
lang('CLI.generator.fileCreate', [clean_path($target)]),
|
||||
'green',
|
||||
);
|
||||
CLI::newLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare options and do the necessary replacements.
|
||||
*
|
||||
* @param string $class namespaced classname or namespaced view.
|
||||
*
|
||||
* @return string generated file content
|
||||
*/
|
||||
protected function prepare(string $class): string
|
||||
{
|
||||
return $this->parseTemplate($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change file basename before saving.
|
||||
*
|
||||
* Useful for components where the file name has a date.
|
||||
*/
|
||||
protected function basename(string $filename): string
|
||||
{
|
||||
return basename($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the class name and checks if it is already qualified.
|
||||
*/
|
||||
protected function qualifyClassName(): string
|
||||
{
|
||||
$class = $this->normalizeInputClassName();
|
||||
|
||||
// Gets the namespace from input. Don't forget the ending backslash!
|
||||
$namespace = $this->getNamespace() . '\\';
|
||||
|
||||
if (str_starts_with($class, $namespace)) {
|
||||
return $class; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$directoryString = ($this->directory !== null) ? $this->directory . '\\' : '';
|
||||
|
||||
return $namespace . $directoryString . str_replace('/', '\\', $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize input classname.
|
||||
*/
|
||||
private function normalizeInputClassName(): string
|
||||
{
|
||||
// Gets the class name from input.
|
||||
$class = $this->params[0] ?? CLI::getSegment(2);
|
||||
|
||||
if ($class === null && $this->hasClassName) {
|
||||
// @codeCoverageIgnoreStart
|
||||
$nameLang = $this->classNameLang !== ''
|
||||
? $this->classNameLang
|
||||
: 'CLI.generator.className.default';
|
||||
$class = CLI::prompt(lang($nameLang), null, 'required');
|
||||
CLI::newLine();
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
helper('inflector');
|
||||
|
||||
$component = singular($this->component);
|
||||
|
||||
/**
|
||||
* @see https://regex101.com/r/a5KNCR/2
|
||||
*/
|
||||
$pattern = sprintf('/([a-z][a-z0-9_\/\\\\]+)(%s)$/i', $component);
|
||||
|
||||
if (preg_match($pattern, $class, $matches) === 1) {
|
||||
$class = $matches[1] . ucfirst($matches[2]);
|
||||
}
|
||||
|
||||
if (
|
||||
$this->enabledSuffixing && $this->getOption('suffix')
|
||||
&& preg_match($pattern, $class) !== 1
|
||||
) {
|
||||
$class .= ucfirst($component);
|
||||
}
|
||||
|
||||
// Trims input, normalize separators, and ensure that all paths are in Pascalcase.
|
||||
return ltrim(
|
||||
implode(
|
||||
'\\',
|
||||
array_map(
|
||||
pascalize(...),
|
||||
explode('\\', str_replace('/', '\\', trim($class))),
|
||||
),
|
||||
),
|
||||
'\\/',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the generator view as defined in the `Config\Generators::$views`,
|
||||
* with fallback to `$template` when the defined view does not exist.
|
||||
*
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
protected function renderTemplate(array $data = []): string
|
||||
{
|
||||
try {
|
||||
$template = $this->templatePath ?? config(Generators::class)->views[$this->name];
|
||||
|
||||
return view($template, $data, ['debug' => false]);
|
||||
} catch (Throwable $e) {
|
||||
log_message('error', (string) $e);
|
||||
|
||||
return view(
|
||||
"CodeIgniter\\Commands\\Generators\\Views\\{$this->template}",
|
||||
$data,
|
||||
['debug' => false],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs pseudo-variables contained within view file.
|
||||
*
|
||||
* @param string $class namespaced classname or namespaced view.
|
||||
* @param list<string> $search
|
||||
* @param list<string> $replace
|
||||
* @param array<string, bool|string|null> $data
|
||||
*
|
||||
* @return string generated file content
|
||||
*/
|
||||
protected function parseTemplate(
|
||||
string $class,
|
||||
array $search = [],
|
||||
array $replace = [],
|
||||
array $data = [],
|
||||
): string {
|
||||
// Retrieves the namespace part from the fully qualified class name.
|
||||
$namespace = trim(
|
||||
implode(
|
||||
'\\',
|
||||
array_slice(explode('\\', $class), 0, -1),
|
||||
),
|
||||
'\\',
|
||||
);
|
||||
$search[] = '<@php';
|
||||
$search[] = '{namespace}';
|
||||
$search[] = '{class}';
|
||||
$replace[] = '<?php';
|
||||
$replace[] = $namespace;
|
||||
$replace[] = str_replace($namespace . '\\', '', $class);
|
||||
|
||||
return str_replace($search, $replace, $this->renderTemplate($data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the contents for class being generated, doing all
|
||||
* the replacements necessary, and alphabetically sorts the
|
||||
* imports for a given template.
|
||||
*/
|
||||
protected function buildContent(string $class): string
|
||||
{
|
||||
$template = $this->prepare($class);
|
||||
|
||||
if (
|
||||
$this->sortImports
|
||||
&& preg_match(
|
||||
'/(?P<imports>(?:^use [^;]+;$\n?)+)/m',
|
||||
$template,
|
||||
$match,
|
||||
)
|
||||
) {
|
||||
$imports = explode("\n", trim($match['imports']));
|
||||
sort($imports);
|
||||
|
||||
return str_replace(trim($match['imports']), implode("\n", $imports), $template);
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the file path from the class name.
|
||||
*
|
||||
* @param string $class namespaced classname or namespaced view.
|
||||
*/
|
||||
protected function buildPath(string $class): string
|
||||
{
|
||||
$namespace = $this->getNamespace();
|
||||
|
||||
// Check if the namespace is actually defined and we are not just typing gibberish.
|
||||
$base = service('autoloader')->getNamespace($namespace);
|
||||
|
||||
if (! $base = reset($base)) {
|
||||
CLI::error(
|
||||
lang('CLI.namespaceNotDefined', [$namespace]),
|
||||
'light_gray',
|
||||
'red',
|
||||
);
|
||||
CLI::newLine();
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
$realpath = realpath($base);
|
||||
$base = ($realpath !== false) ? $realpath : $base;
|
||||
|
||||
$file = $base . DIRECTORY_SEPARATOR
|
||||
. str_replace(
|
||||
'\\',
|
||||
DIRECTORY_SEPARATOR,
|
||||
trim(str_replace($namespace . '\\', '', $class), '\\'),
|
||||
) . '.php';
|
||||
|
||||
return implode(
|
||||
DIRECTORY_SEPARATOR,
|
||||
array_slice(
|
||||
explode(DIRECTORY_SEPARATOR, $file),
|
||||
0,
|
||||
-1,
|
||||
),
|
||||
) . DIRECTORY_SEPARATOR . $this->basename($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace from the command-line option,
|
||||
* or the default namespace if the option is not set.
|
||||
* Can be overridden by directly setting $this->namespace.
|
||||
*/
|
||||
protected function getNamespace(): string
|
||||
{
|
||||
return $this->namespace ?? trim(
|
||||
str_replace(
|
||||
'/',
|
||||
'\\',
|
||||
$this->getOption('namespace') ?? APP_NAMESPACE,
|
||||
),
|
||||
'\\',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows child generators to modify the internal `$hasClassName` flag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setHasClassName(bool $hasClassName)
|
||||
{
|
||||
$this->hasClassName = $hasClassName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows child generators to modify the internal `$sortImports` flag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setSortImports(bool $sortImports)
|
||||
{
|
||||
$this->sortImports = $sortImports;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows child generators to modify the internal `$enabledSuffixing` flag.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function setEnabledSuffixing(bool $enabledSuffixing)
|
||||
{
|
||||
$this->enabledSuffixing = $enabledSuffixing;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single command-line option. Returns TRUE if the option exists,
|
||||
* but doesn't have a value, and is simply acting as a flag.
|
||||
*/
|
||||
protected function getOption(string $name): bool|string|null
|
||||
{
|
||||
if (! array_key_exists($name, $this->params)) {
|
||||
return CLI::getOption($name);
|
||||
}
|
||||
|
||||
return $this->params[$name] ?? true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of CodeIgniter 4 framework.
|
||||
*
|
||||
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace CodeIgniter\CLI;
|
||||
|
||||
/**
|
||||
* Input and Output for CLI.
|
||||
*/
|
||||
class InputOutput
|
||||
{
|
||||
/**
|
||||
* Is the readline library on the system?
|
||||
*/
|
||||
private readonly bool $readlineSupport;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Readline is an extension for PHP that makes interactivity with PHP
|
||||
// much more bash-like.
|
||||
// http://www.php.net/manual/en/readline.installation.php
|
||||
$this->readlineSupport = extension_loaded('readline');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get input from the shell, using readline or the standard STDIN
|
||||
*
|
||||
* Named options must be in the following formats:
|
||||
* php index.php user -v --v -name=John --name=John
|
||||
*
|
||||
* @param string|null $prefix You may specify a string with which to prompt the user.
|
||||
*/
|
||||
public function input(?string $prefix = null): string
|
||||
{
|
||||
// readline() can't be tested.
|
||||
if ($this->readlineSupport && ENVIRONMENT !== 'testing') {
|
||||
return readline($prefix); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
echo $prefix;
|
||||
|
||||
$input = fgets(fopen('php://stdin', 'rb'));
|
||||
|
||||
if ($input === false) {
|
||||
$input = '';
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* While the library is intended for use on CLI commands,
|
||||
* commands can be called from controllers and elsewhere
|
||||
* so we need a way to allow them to still work.
|
||||
*
|
||||
* For now, just echo the content, but look into a better
|
||||
* solution down the road.
|
||||
*
|
||||
* @param resource $handle
|
||||
*/
|
||||
public function fwrite($handle, string $string): void
|
||||
{
|
||||
if (! is_cli()) {
|
||||
echo $string;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
fwrite($handle, $string);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user