-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
659be22
commit 7d27f64
Showing
47 changed files
with
2,246 additions
and
1,828 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,325 @@ | ||
<?php | ||
|
||
/** | ||
* This file is part of the CodeIgniter 4 framework. | ||
* | ||
* (c) CodeIgniter Foundation <[email protected]> | ||
* | ||
* 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 Config\Services; | ||
use RuntimeException; | ||
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; | ||
|
||
/** | ||
* View template name | ||
* | ||
* @var string | ||
*/ | ||
protected $template; | ||
|
||
/** | ||
* Whether to sort class imports. | ||
* | ||
* @internal | ||
* | ||
* @var boolean | ||
*/ | ||
private $sortImports = true; | ||
|
||
/** | ||
* The params array for easy access by other methods. | ||
* | ||
* @internal | ||
* | ||
* @var array | ||
*/ | ||
private $params = []; | ||
private $nm = true; | ||
|
||
/** | ||
* Execute the command. | ||
* | ||
* @param array $params | ||
* | ||
* @return void | ||
*/ | ||
protected function execute(array $params): void | ||
{ | ||
$this->params = $params; | ||
|
||
if ($this->getOption('namespace') === 'CodeIgniter' && $this->nm) | ||
{ | ||
CLI::write(lang('CLI.generator.usingCINamespace'), 'yellow'); | ||
CLI::newLine(); | ||
|
||
// @codeCoverageIgnoreStart | ||
if (CLI::prompt('Are you sure you want to continue?', ['y', 'n'], 'required') === 'n') | ||
{ | ||
CLI::newLine(); | ||
CLI::write(lang('CLI.cancelOperation'), 'light_yellow'); | ||
CLI::newLine(); | ||
|
||
return; | ||
} | ||
// @codeCoverageIgnoreEnd | ||
|
||
CLI::newLine(); | ||
} | ||
|
||
$this->nm = false; | ||
|
||
// Get the fully qualified class name from the input. | ||
$class = $this->qualifyClassName(); | ||
|
||
// Get the file path from class name. | ||
$path = $this->buildPath($class); | ||
|
||
// Overwriting files unknowingly is a serious annoyance. | ||
// So we'll check if we are duplicating things. | ||
// If the 'force' option is not supplied, we bail. | ||
if (! $this->getOption('force') && file_exists($path)) | ||
{ | ||
CLI::write(lang('CLI.generator.fileExist', [CLI::color(clean_path($path), 'red')])); | ||
CLI::newLine(); | ||
|
||
return; | ||
} | ||
|
||
// Check if the directory to save the file is existing. | ||
$dir = dirname($path); | ||
|
||
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($path, $this->buildContent($class))) | ||
{ | ||
CLI::error(lang('CLI.generator.fileError', [clean_path($path)]), 'light_gray', 'red'); | ||
CLI::newLine(); | ||
|
||
return; | ||
} | ||
|
||
if ($this->getOption('force') && file_exists($path)) | ||
{ | ||
CLI::write(lang('CLI.generator.fileOverwrite', [CLI::color(clean_path($path), 'yellow')])); | ||
CLI::newLine(); | ||
|
||
return; | ||
} | ||
|
||
CLI::write(lang('CLI.generator.fileCreate', [CLI::color(clean_path($path), 'light_green')])); | ||
CLI::newLine(); | ||
} | ||
|
||
/** | ||
* Prepare options and do the necessary replacements. | ||
* | ||
* @param string $class | ||
* | ||
* @return string | ||
*/ | ||
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. | ||
* | ||
* @param string $filename | ||
* | ||
* @return string | ||
*/ | ||
protected function basename(string $filename): string | ||
{ | ||
return basename($filename); | ||
} | ||
|
||
/** | ||
* Parses the class name and checks if it is already qualified. | ||
* | ||
* @return string | ||
*/ | ||
private function qualifyClassName(): string | ||
{ | ||
// Gets the class name from input. | ||
$class = $this->params[0] ?? CLI::getSegment(2) ?? ''; | ||
|
||
if (empty($class)) | ||
{ | ||
$class = CLI::prompt(lang('CLI.generator.className'), null, 'required'); // @codeCoverageIgnore | ||
CLI::newLine(); | ||
} | ||
|
||
helper('inflector'); | ||
|
||
$component = strtolower(singular($this->component)); | ||
$class = strtolower($class); | ||
$class = strpos($class, $component) ? str_replace($component, ucfirst($component), $class) : $class; | ||
|
||
if ($this->getOption('suffix') && ! stripos($class, $component)) | ||
{ | ||
$class .= ucfirst($component); | ||
} | ||
|
||
// Trims input, normalize separators, and ensure that all paths are in Pascalcase. | ||
$class = ltrim(implode('\\', array_map('pascalize', explode('\\', str_replace('/', '\\', trim($class))))), '\\/'); | ||
|
||
// Gets the root namespace from input. | ||
$root = trim(str_replace('/', '\\', $this->getOption('namespace') ?? APP_NAMESPACE), '\\'); | ||
|
||
if (strncmp($class, $root, strlen($root)) === 0) | ||
{ | ||
return $class; | ||
} | ||
|
||
return $root . '\\' . $this->directory . '\\' . str_replace('/', '\\', $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 $data Data to be passed to the view. | ||
* | ||
* @return string | ||
*/ | ||
private function renderTemplate(array $data = []): string | ||
{ | ||
try | ||
{ | ||
return view(config('Generators')->views[$this->name], $data, ['debug' => false]); | ||
} | ||
catch (Throwable $e) | ||
{ | ||
log_message('error', $e->getMessage()); | ||
|
||
return view("CodeIgniter\Commands\Generators\Views\\{$this->template}", $data, ['debug' => false]); | ||
} | ||
} | ||
|
||
/** | ||
* Performs pseudo-variables contained within view file. | ||
* | ||
* @param string $class | ||
* @param array $search | ||
* @param array $replace | ||
* @param array $data | ||
* | ||
* @return string | ||
*/ | ||
private 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)), '\\'); | ||
|
||
array_push($search, '<@php', '{namespace}', '{class}'); | ||
array_push($replace, '<?php', $namespace, 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. | ||
* | ||
* @param string $class | ||
* | ||
* @return string | ||
*/ | ||
private 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 | ||
* | ||
* @return string | ||
*/ | ||
private function buildPath(string $class): string | ||
{ | ||
$root = trim(str_replace('/', '\\', $this->getOption('namespace') ?? APP_NAMESPACE), '\\'); | ||
|
||
// Check if the namespace is actually defined and we are not just typing gibberish. | ||
$base = Services::autoloader()->getNamespace($root); | ||
|
||
if (! $base = reset($base)) | ||
{ | ||
throw new RuntimeException(lang('CLI.namespaceNotDefined', [$root])); | ||
} | ||
|
||
$base = realpath($base) ?: $base; | ||
$file = $base . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, trim(str_replace($root, '', $class), '\\')) . '.php'; | ||
|
||
return implode(DIRECTORY_SEPARATOR, array_slice(explode(DIRECTORY_SEPARATOR, $file), 0, -1)) . DIRECTORY_SEPARATOR . $this->basename($file); | ||
} | ||
|
||
/** | ||
* 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. | ||
* | ||
* @param string $name | ||
* | ||
* @return boolean|mixed|null | ||
*/ | ||
protected function getOption(string $name) | ||
{ | ||
if (! array_key_exists($name, $this->params)) | ||
{ | ||
return CLI::getOption($name); | ||
} | ||
|
||
// If the param doesn't have a value, simply return TRUE | ||
// so they know it was set, otherwise return the actual value. | ||
return is_null($this->params[$name]) ? true : $this->params[$name]; | ||
} | ||
} |
Oops, something went wrong.