diff --git a/generator/src/Commands/GenerateCommand.php b/generator/src/Commands/GenerateCommand.php index 0e7efba7..0ce02359 100644 --- a/generator/src/Commands/GenerateCommand.php +++ b/generator/src/Commands/GenerateCommand.php @@ -83,7 +83,7 @@ protected function execute( $modules[$function->getModuleName()] = true; } - $genDir = FileCreator::getSafeRootDir() . "/generated/$version"; + $genDir = Filesystem::outputDir() . "/$version"; $fileCreator = new FileCreator(); $fileCreator->generatePhpFile($res->methods, "$genDir/"); $fileCreator->generateFunctionsList($res->methods, "$genDir/functionsList.php"); @@ -91,10 +91,11 @@ protected function execute( } foreach (\array_keys($modules) as $moduleName) { - $fileCreator->generateVersionSplitters($moduleName, FileCreator::getSafeRootDir() . "/generated/", \array_keys($versions)); + $fileCreator->generateVersionSplitters($moduleName, Filesystem::outputDir() . "/", \array_keys($versions)); $fileCreator->createExceptionFile((string) $moduleName); } - $fileCreator->generateVersionSplitters("functionsList", FileCreator::getSafeRootDir() . "/generated/", \array_keys($versions), true); + + $fileCreator->generateVersionSplitters("functionsList", Filesystem::outputDir() . "/", \array_keys($versions), true); $this->runCsFix($output); diff --git a/generator/src/Generator/ComposerJsonEditor.php b/generator/src/Generator/ComposerJsonEditor.php index 94ee9a04..7721bd07 100644 --- a/generator/src/Generator/ComposerJsonEditor.php +++ b/generator/src/Generator/ComposerJsonEditor.php @@ -4,6 +4,8 @@ namespace Safe\Generator; +use Safe\Templating\Filesystem; + /** * This class will edit the main composer.json file to add the list of files generated from modules. */ @@ -15,7 +17,7 @@ class ComposerJsonEditor public static function editComposerFileForGeneration(array $modules): void { - $composerContent = file_get_contents(FileCreator::getSafeRootDir() . '/composer.json'); + $composerContent = file_get_contents(Filesystem::projectRootDir() . '/composer.json'); if ($composerContent === false) { throw new \RuntimeException('Error while loading composer.json file for edition.'); } @@ -24,7 +26,7 @@ public static function editComposerFileForGeneration(array $modules): void $composerJson['autoload']['files'] = self::editFilesListForGeneration($composerJson['autoload']['files'], $modules); $newContent = \json_encode($composerJson, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES) . "\n"; - \file_put_contents(FileCreator::getSafeRootDir() . '/composer.json', $newContent); + \file_put_contents(Filesystem::projectRootDir() . '/composer.json', $newContent); } /** diff --git a/generator/src/Generator/FileCreator.php b/generator/src/Generator/FileCreator.php index 47935d0a..c5c8da27 100644 --- a/generator/src/Generator/FileCreator.php +++ b/generator/src/Generator/FileCreator.php @@ -4,6 +4,8 @@ namespace Safe\Generator; +use Safe\Templating\Engine; +use Safe\Templating\Filesystem; use Safe\XmlDocParser\ErrorType; use Safe\XmlDocParser\Scanner; use Safe\XmlDocParser\Method; @@ -13,6 +15,12 @@ class FileCreator { + private Engine $engine; + + public function __construct(?Engine $engine = null) + { + $this->engine = $engine ?? new Engine(); + } /** * This function generate an improved php lib function in a php file @@ -34,26 +42,14 @@ public function generatePhpFile( foreach ($phpFunctionsByModule as $module => $phpFunctions) { $lcModule = \lcfirst($module); - if (!is_dir($path)) { + if (!\is_dir($path)) { \mkdir($path); } - $stream = \fopen($path.$lcModule.'.php', 'w'); - if ($stream === false) { - throw new \RuntimeException('Unable to write to '.$path); - } - - // Write file header - \fwrite($stream, "engine->generate('Module.php.tpl', [ + '{{exceptionName}}' => self::toExceptionName($module), + '{{functions}}' => \implode(PHP_EOL, $phpFunctions), + ])); } } @@ -107,18 +103,9 @@ private function getFunctionsNameList(array $functions): array */ public function generateFunctionsList(array $functions, string $path): void { - $functionNames = $this->getFunctionsNameList($functions); - $stream = fopen($path, 'w'); - if ($stream === false) { - throw new \RuntimeException('Unable to write to '.$path); - } - fwrite($stream, "engine->generate('FunctionList.php.tpl', [ + '{{functionNames}}' => \implode(PHP_EOL, \array_map(static fn(string $name): string => \sprintf('\'%s\',', $name), $this->getFunctionsNameList($functions))), + ])); } /** @@ -128,66 +115,29 @@ public function generateFunctionsList(array $functions, string $path): void */ public function generateRectorFile(array $functions, string $path): void { - $functionNames = $this->getFunctionsNameList($functions); - - $stream = fopen($path, 'w'); - - if ($stream === false) { - throw new \RuntimeException('Unable to write to '.$path); - } - - $header = <<<'TXT' -ruleWithConfiguration( - RenameFunctionRector::class, - [ -TXT; - - fwrite($stream, $header); - - foreach ($functionNames as $functionName) { - fwrite($stream, " '$functionName' => 'Safe\\$functionName',\n"); - } - - fwrite($stream, " ]\n );\n};\n"); - fclose($stream); + Filesystem::dumpFile($path, $this->engine->generate('RectorConfig.php.tpl', [ + '{{functionNames}}' => \implode(PHP_EOL, \array_map(static fn(string $name): string => \sprintf('\'%1$s\' => \'Safe\\%1$s\',', $name), $this->getFunctionsNameList($functions))), + ])); } public function createExceptionFile(string $moduleName): void { $exceptionName = self::toExceptionName($moduleName); - if (!file_exists(FileCreator::getSafeRootDir() . '/lib/Exceptions/'.$exceptionName.'.php')) { - \file_put_contents( - FileCreator::getSafeRootDir() . '/generated/Exceptions/'.$exceptionName.'.php', - <<engine->generate('Exception.php.tpl', [ + '{{exceptionName}}' => $exceptionName, + ])); } public static function getSafeRootDir(): string { - return __DIR__ . '/../../..'; + $path = realpath(__DIR__ . '/../../..'); + + if (false === $path) { + throw new \RuntimeException('Unable to locate root directory'); + } + + return $path; } /** diff --git a/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php b/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php index f4471b07..9882614f 100644 --- a/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php +++ b/generator/src/PhpStanFunctions/PhpStanFunctionMapReader.php @@ -5,6 +5,7 @@ namespace Safe\PhpStanFunctions; use Safe\Generator\FileCreator; +use Safe\Templating\Filesystem; class PhpStanFunctionMapReader { @@ -20,8 +21,8 @@ class PhpStanFunctionMapReader public function __construct() { - $this->functionMap = require 'phar://' . FileCreator::getSafeRootDir() . '/generator/vendor/phpstan/phpstan/phpstan.phar/resources/functionMap.php'; - $this->customFunctionMap = require FileCreator::getSafeRootDir() . '/generator/config/CustomPhpStanFunctionMap.php'; + $this->functionMap = require 'phar://' . Filesystem::generatorDir() . '/vendor/phpstan/phpstan/phpstan.phar/resources/functionMap.php'; + $this->customFunctionMap = require Filesystem::generatorDir() . '/config/CustomPhpStanFunctionMap.php'; } public function getFunction(string $functionName): ?PhpStanFunction diff --git a/generator/src/Templating/Engine.php b/generator/src/Templating/Engine.php new file mode 100644 index 00000000..f990f755 --- /dev/null +++ b/generator/src/Templating/Engine.php @@ -0,0 +1,61 @@ +files()->name('*.php.tpl')->in(self::basePath()); + $this->templates = array_map(fn(\SplFileInfo $file) => str_replace(self::basePath() . '/', '', $file->getRealPath()), \iterator_to_array($finder->getIterator())); + } + + /** + * @param array $context + * + * @throws TemplatingException + */ + public function generate(string $template, array $context = []): string + { + if (!$this->hasTemplate($template)) { + throw new TemplateNotFoundException(\sprintf('Template "%s" not found.', $template)); + } + + if (false === $content = file_get_contents(self::basePath() . '/' . $template)) { + throw new UnreadableTemplateException(\sprintf('Could not read template "%s".', $template)); + } + + foreach ($context as $placeholder => $replacement) { + if (!\is_string($replacement)) { + throw new InvalidPlaceholderException(\sprintf('Placeholder "%s" must be a string.', $placeholder)); + } + + $content = str_replace($placeholder, $replacement, $content); + } + + return $content; + } + + private function hasTemplate(string $name): bool + { + return 0 < \count(\array_filter($this->templates, static fn(string $template) => $template === $name)); + } + + private static function basePath(): string + { + return Filesystem::generatorDir().'/templates'; + } +} diff --git a/generator/src/Templating/Exception/InvalidPlaceholderException.php b/generator/src/Templating/Exception/InvalidPlaceholderException.php new file mode 100644 index 00000000..f5c7d089 --- /dev/null +++ b/generator/src/Templating/Exception/InvalidPlaceholderException.php @@ -0,0 +1,9 @@ +extractSection('returnvalues', $file); - $detectErrorType = require FileCreator::getSafeRootDir() . '/generator/config/detectErrorType.php'; + $detectErrorType = require Filesystem::generatorDir() . '/config/detectErrorType.php'; return $detectErrorType($returnDocs); } diff --git a/generator/src/XmlDocParser/Scanner.php b/generator/src/XmlDocParser/Scanner.php index 3ac8438f..61a4aaa7 100644 --- a/generator/src/XmlDocParser/Scanner.php +++ b/generator/src/XmlDocParser/Scanner.php @@ -4,6 +4,8 @@ namespace Safe\XmlDocParser; +use Safe\Templating\Filesystem; +use Symfony\Component\Console\Style\SymfonyStyle; use function array_merge; use function iterator_to_array; use Safe\PhpStanFunctions\PhpStanFunctionMapReader; @@ -58,7 +60,7 @@ public function getMethodsPaths(): array private function getIgnoredFunctions(): array { if ($this->ignoredFunctions === null) { - $ignoredFunctions = require FileCreator::getSafeRootDir() . '/generator/config/ignoredFunctions.php'; + $ignoredFunctions = require Filesystem::generatorDir() . '/config/ignoredFunctions.php'; $specialCaseFunctions = $this->getSpecialCases(); $this->ignoredFunctions = array_merge($ignoredFunctions, $specialCaseFunctions); @@ -73,7 +75,7 @@ private function getIgnoredFunctions(): array private function getIgnoredModules(): array { if ($this->ignoredModules === null) { - $this->ignoredModules = require FileCreator::getSafeRootDir() . '/generator/config/ignoredModules.php'; + $this->ignoredModules = require Filesystem::generatorDir() . '/config/ignoredModules.php'; assert(!is_null($this->ignoredModules)); } return $this->ignoredModules; @@ -87,7 +89,7 @@ private function getIgnoredModules(): array */ public static function getSpecialCases(): array { - $data = file_get_contents(FileCreator::getSafeRootDir() . '/lib/special_cases.php'); + $data = file_get_contents(Filesystem::projectRootDir() . '/lib/special_cases.php'); if ($data === false) { throw new \RuntimeException('Unable to read special cases'); } @@ -104,14 +106,14 @@ public static function getSpecialCases(): array */ public static function getHiddenFunctions(): array { - return require FileCreator::getSafeRootDir() . '/generator/config/hiddenFunctions.php'; + return require Filesystem::generatorDir() . '/config/hiddenFunctions.php'; } /** * @param SplFileInfo[] $paths * @param string[] $pastFunctionNames */ - public function getMethods(array $paths, array $pastFunctionNames, OutputInterface $output): ScannerResponse + public function getMethods(array $paths, array $pastFunctionNames, SymfonyStyle $io): ScannerResponse { /** @var Method[] $functions */ $functions = []; @@ -124,9 +126,7 @@ public function getMethods(array $paths, array $pastFunctionNames, OutputInterfa $ignoredModules = $this->getIgnoredModules(); $ignoredModules = \array_combine($ignoredModules, $ignoredModules); - ProgressBar::setFormatDefinition('custom', ' %current%/%max% [%bar%] %message%'); - $progressBar = new ProgressBar($output, count($paths)); - $progressBar->setFormat("custom"); + $progressBar = $io->createProgressBar(\count($paths)); foreach ($paths as $path) { $module = \basename(\dirname($path->getPath())); $progressBar->setMessage($path->getFilename()); @@ -167,8 +167,8 @@ public function getMethods(array $paths, array $pastFunctionNames, OutputInterfa } } } + $progressBar->finish(); - $output->writeln(""); return new ScannerResponse($functions, $overloadedFunctions); } diff --git a/generator/src/XmlDocParser/ScannerResponse.php b/generator/src/XmlDocParser/ScannerResponse.php index e82af9c4..3c8ce42c 100644 --- a/generator/src/XmlDocParser/ScannerResponse.php +++ b/generator/src/XmlDocParser/ScannerResponse.php @@ -15,4 +15,9 @@ public function __construct( public readonly array $overloadedFunctions ) { } + + public function hasOverloadedFunctions(): bool + { + return \count($this->overloadedFunctions) > 0; + } } diff --git a/generator/templates/Exception.php.tpl b/generator/templates/Exception.php.tpl new file mode 100644 index 00000000..d664d65a --- /dev/null +++ b/generator/templates/Exception.php.tpl @@ -0,0 +1,15 @@ +ruleWithConfiguration( + RenameFunctionRector::class, + [ + {{functionNames}} + ], + ); +}; \ No newline at end of file