add Standalone

This commit is contained in:
Tomas Votruba 2019-10-22 14:38:49 +02:00
parent 4fd551bdad
commit 0a5efeaf84
8 changed files with 305 additions and 29 deletions

View File

@ -8,17 +8,14 @@ function includeProjectsAutoload(string $composerJsonPath, string $cwd): void
$composerSettings = json_decode($contents, true);
if (! is_array($composerSettings)) {
fwrite(STDERR, "Failed to load '${composerJsonPath}'\n");
exit(1);
die(sprintf('Failed to load "%s"', $composerJsonPath));
}
$vendorPath = $composerSettings['config']['vendor-dir'] ?? $cwd . '/vendor';
if (! is_dir($vendorPath)) {
fwrite(STDERR, "Please check if 'composer.phar install' was run already (expected to find '${vendorPath}')\n");
exit(1);
die(sprintf('Please check if "composer install" was run already (expected to find "%s")', $vendorPath));
}
/** @noinspection PhpIncludeInspection */
require $vendorPath . '/autoload.php';
}
@ -29,6 +26,7 @@ if (is_file($projectAutoload)) {
require $projectAutoload;
}
// is autolaod successful?
if (class_exists('Rector\HttpKernel\RectorKernel')) {
return;
}
@ -53,11 +51,9 @@ if (file_exists($composerJsonPath)) {
return;
}
fwrite(
STDERR,
die(
sprintf(
'Composer autoload.php was not found in paths "%s". Have you ran "composer update"?',
implode('", "', $possibleAutoloadPaths)
)
);
exit(1);

View File

@ -11,6 +11,7 @@
"bin": ["bin/rector"],
"require": {
"php": "^7.1",
"ext-json": "*",
"composer/xdebug-handler": "^1.3",
"doctrine/annotations": "^1.6",
"doctrine/inflector": "^1.3",

View File

@ -40,6 +40,11 @@ final class SetOptionResolver
return null;
}
return $this->detectFromNameAndDirectory($setName, $configDirectory);
}
public function detectFromNameAndDirectory(string $setName, string $configDirectory): ?string
{
$nearestMatches = $this->findNearestMatchingFiles($configDirectory, $setName);
if (count($nearestMatches) === 0) {
$this->reportSetNotFound($configDirectory, $setName);
@ -57,18 +62,16 @@ final class SetOptionResolver
$suggestedSet = ObjectHelpers::getSuggestion($allSets, $setName);
$hasSetVersion = (bool) Strings::match($setName, '#[\d]#');
[$versionedSets, $unversionedSets] = $this->separateVersionedAndUnversionedSets($allSets);
$setsListInString = $this->createSetListInString($hasSetVersion, $unversionedSets, $versionedSets);
$setsListInString = $this->createSetListInString($unversionedSets, $versionedSets);
$setNotFoundMessage = sprintf(
'%s "%s" was not found.%s%s',
ucfirst($this->keyName),
$setName,
PHP_EOL,
$suggestedSet ? sprintf('Did you mean "%s"?', $suggestedSet) . PHP_EOL : 'Pick one of above.'
$suggestedSet ? sprintf('Did you mean "%s"?', $suggestedSet) . PHP_EOL : ''
);
$pickOneOfMessage = sprintf('Pick "--%s" of:%s%s', $this->keyName, PHP_EOL . PHP_EOL, $setsListInString);
@ -152,24 +155,18 @@ final class SetOptionResolver
* @param string[] $unversionedSets
* @param string[] $versionedSets
*/
private function createSetListInString(
bool $hasSetVersion,
array $unversionedSets,
array $versionedSets
): string {
private function createSetListInString(array $unversionedSets, array $versionedSets): string
{
$setsListInString = '';
if ($hasSetVersion === false) {
foreach ($unversionedSets as $unversionedSet) {
$setsListInString .= ' * ' . $unversionedSet . PHP_EOL;
}
foreach ($unversionedSets as $unversionedSet) {
$setsListInString .= ' * ' . $unversionedSet . PHP_EOL;
}
if ($hasSetVersion) {
foreach ($versionedSets as $groupName => $configName) {
$setsListInString .= ' * ' . $groupName . ': ' . implode(', ', $configName) . PHP_EOL;
}
foreach ($versionedSets as $groupName => $configName) {
$setsListInString .= ' * ' . $groupName . ': ' . implode(', ', $configName) . PHP_EOL;
}
return $setsListInString;
}
@ -183,12 +180,16 @@ final class SetOptionResolver
$unversionedSets = [];
foreach ($allSets as $set) {
$match = Strings::match($set, '#^[A-Za-z\-]+#');
if ($match === null) {
$hasVersion = (bool) Strings::match($set, '#\d#');
if ($hasVersion === false) {
$unversionedSets[] = $set;
continue;
}
$setWithoutVersion = rtrim($match[0], '-');
$match = Strings::match($set, '#^(?<set>[A-Za-z\-]+)#');
$setWithoutVersion = $match['set'];
if ($setWithoutVersion !== $set) {
$versionedSets[$setWithoutVersion][] = $set;
}

View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Rector\DependencyInjection;
use Psr\Container\ContainerInterface;
use Rector\Console\Option\SetOptionResolver;
use Rector\Exception\ShouldNotHappenException;
use Rector\HttpKernel\RectorKernel;
use Rector\Set\Set;
use Symplify\PackageBuilder\Configuration\ConfigFileFinder;
final class RectorContainerFactory
{
/**
* @var SetOptionResolver
*/
private $setOptionResolver;
public function __construct()
{
$this->setOptionResolver = new SetOptionResolver();
}
public function createFromSet(string $set): ContainerInterface
{
$configFiles = $this->resolveConfigs($set);
return $this->createFromConfigs($configFiles);
}
/**
* @return string[]
*/
private function resolveConfigs(string $set): array
{
$config = $this->setOptionResolver->detectFromNameAndDirectory($set, Set::SET_DIRECTORY);
if ($config === null) {
throw new ShouldNotHappenException(sprintf('Config file for "%s" set was not found', $set));
}
// copied mostly from https://github.com/rectorphp/rector/blob/master/bin/container.php
$configFiles = [];
$configFiles[] = $config;
// local config has priority
$configFiles[] = ConfigFileFinder::provide('rector', ['rector.yml', 'rector.yaml']);
// remove empty values
return array_filter($configFiles);
}
/**
* @param string[] $configFiles
*/
private function createFromConfigs(array $configFiles): ContainerInterface
{
// to override the configs without clearing cache
$environment = 'prod' . random_int(1, 10000000);
$isDebug = true;
$rectorKernel = new RectorKernel($environment, $isDebug);
if ($configFiles) {
$rectorKernel->setConfigs($configFiles);
}
$rectorKernel->boot();
return $rectorKernel->getContainer();
}
}

13
src/Set/Set.php Normal file
View File

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Rector\Set;
final class Set
{
/**
* @var string
*/
public const SET_DIRECTORY = __DIR__ . '/../../config/set';
}

View File

@ -14,7 +14,7 @@ final class SetProvider
public function provide(): array
{
$finder = Finder::create()->files()
->in(__DIR__ . '/../../config/set');
->in(Set::SET_DIRECTORY);
$sets = [];
foreach ($finder->getIterator() as $fileInfo) {

View File

@ -0,0 +1,165 @@
<?php
declare(strict_types=1);
namespace Rector\Standalone;
use Psr\Container\ContainerInterface;
use Rector\Application\ErrorAndDiffCollector;
use Rector\Application\RectorApplication;
use Rector\Autoloading\AdditionalAutoloader;
use Rector\Configuration\Configuration;
use Rector\Configuration\Option;
use Rector\Console\Command\ProcessCommand;
use Rector\Console\Output\ConsoleOutputFormatter;
use Rector\DependencyInjection\RectorContainerFactory;
use Rector\Exception\FileSystem\FileNotFoundException;
use Rector\Extension\FinishingExtensionRunner;
use Rector\Extension\ReportingExtensionRunner;
use Rector\FileSystem\FilesFinder;
use Rector\Guard\RectorGuard;
use Rector\PhpParser\NodeTraverser\RectorNodeTraverser;
use Rector\Stubs\StubLoader;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* This class is needed over process/cli run to get console output in sane way;
* without it, it's not possible to get inside output closed stream.
*/
final class RectorStandaloneRunner
{
/**
* @var RectorContainerFactory
*/
private $rectorContainerFactory;
/**
* @var SymfonyStyle
*/
private $nativeSymfonyStyle;
public function __construct(RectorContainerFactory $rectorContainerFactory, SymfonyStyle $symfonyStyle)
{
$this->rectorContainerFactory = $rectorContainerFactory;
$this->nativeSymfonyStyle = $symfonyStyle;
}
/**
* @param string[] $source
*/
public function processSourceWithSet(array $source, string $set, bool $isDryRun): void
{
$source = $this->absolutizeSource($source);
$container = $this->rectorContainerFactory->createFromSet($set);
$this->prepare($container, $source, $isDryRun);
/** @var FilesFinder $filesFinder */
$filesFinder = $container->get(FilesFinder::class);
$phpFileInfos = $filesFinder->findInDirectoriesAndFiles($source, ['php']);
/** @var RectorApplication $rectorApplication */
$rectorApplication = $container->get(RectorApplication::class);
$rectorApplication->runOnFileInfos($phpFileInfos);
$this->reportErrors($container);
$this->finish($container);
}
/**
* Mostly copied from: https://github.com/rectorphp/rector/blob/master/src/Console/Command/ProcessCommand.php.
* @param string[] $source
*/
private function prepare(ContainerInterface $container, array $source, bool $isDryRun): void
{
ini_set('memory_limit', '4096M');
/** @var RectorNodeTraverser $rectorNodeTraverser */
$rectorNodeTraverser = $container->get(RectorNodeTraverser::class);
$this->prepareConfiguration($container, $rectorNodeTraverser, $isDryRun);
/** @var RectorGuard $rectorGuard */
$rectorGuard = $container->get(RectorGuard::class);
$rectorGuard->ensureSomeRectorsAreRegistered();
// setup verbosity from the current run
/** @var SymfonyStyle $symfonyStyle */
$symfonyStyle = $container->get(SymfonyStyle::class);
$symfonyStyle->setVerbosity($this->nativeSymfonyStyle->getVerbosity());
/** @var AdditionalAutoloader $additionalAutoloader */
$additionalAutoloader = $container->get(AdditionalAutoloader::class);
$additionalAutoloader->autoloadWithInputAndSource(new ArrayInput([]), $source);
/** @var StubLoader $stubLoader */
$stubLoader = $container->get(StubLoader::class);
$stubLoader->loadStubs();
}
private function reportErrors(ContainerInterface $container): void
{
/** @var ErrorAndDiffCollector $errorAndDiffCollector */
$errorAndDiffCollector = $container->get(ErrorAndDiffCollector::class);
/** @var ConsoleOutputFormatter $consoleOutputFormatter */
$consoleOutputFormatter = $container->get(ConsoleOutputFormatter::class);
$consoleOutputFormatter->report($errorAndDiffCollector);
}
/**
* @param string[] $source
* @return string[]
*/
private function absolutizeSource(array $source): array
{
foreach ($source as $key => $singleSource) {
/** @var string $singleSource */
if (! file_exists($singleSource)) {
throw new FileNotFoundException($singleSource);
}
/** @var string $realpath */
$realpath = realpath($singleSource);
$source[$key] = $realpath;
}
return $source;
}
private function finish(ContainerInterface $container): void
{
/** @var FinishingExtensionRunner $finishingExtensionRunner */
$finishingExtensionRunner = $container->get(FinishingExtensionRunner::class);
$finishingExtensionRunner->run();
/** @var ReportingExtensionRunner $reportingExtensionRunner */
$reportingExtensionRunner = $container->get(ReportingExtensionRunner::class);
$reportingExtensionRunner->run();
}
private function prepareConfiguration(
ContainerInterface $container,
RectorNodeTraverser $rectorNodeTraverser,
bool $isDryRun
): void {
/** @var Configuration $configuration */
$configuration = $container->get(Configuration::class);
$configuration->setAreAnyPhpRectorsLoaded((bool) $rectorNodeTraverser->getPhpRectorCount());
// definition mimics @see ProcessCommand definition
/** @var ProcessCommand $processCommand */
$processCommand = $container->get(ProcessCommand::class);
$definition = clone $processCommand->getDefinition();
// reset arguments to prevent "source is missing"
$definition->setArguments([]);
$configuration->resolveFromInput(new ArrayInput([
'--' . Option::OPTION_DRY_RUN => $isDryRun,
'--' . Option::OPTION_OUTPUT_FORMAT => 'console',
], $definition));
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Rector\Tests\DependencyInjection;
use PHPUnit\Framework\TestCase;
use Rector\DependencyInjection\RectorContainerFactory;
use Rector\Exception\Configuration\SetNotFoundException;
use Symfony\Component\DependencyInjection\ContainerInterface;
final class RectorContainerFactoryTest extends TestCase
{
public function test(): void
{
$rectorContainerFactory = new RectorContainerFactory();
$rectorContainer = $rectorContainerFactory->createFromSet('doctrine');
$this->assertInstanceOf(ContainerInterface::class, $rectorContainer);
}
public function testMissingSet(): void
{
$rectorContainerFactory = new RectorContainerFactory();
$this->expectException(SetNotFoundException::class);
$rectorContainerFactory->createFromSet('bla');
}
}