Skip to content

Commit

Permalink
DX: Progress output refactor (#6848)
Browse files Browse the repository at this point in the history
  • Loading branch information
Wirone committed Jul 4, 2023
1 parent 1ab962b commit bc9d96f
Show file tree
Hide file tree
Showing 14 changed files with 327 additions and 83 deletions.
27 changes: 16 additions & 11 deletions src/Console/Command/FixCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
use PhpCsFixer\Console\ConfigurationResolver;
use PhpCsFixer\Console\Output\ErrorOutput;
use PhpCsFixer\Console\Output\NullOutput;
use PhpCsFixer\Console\Output\ProcessOutput;
use PhpCsFixer\Console\Output\OutputContext;
use PhpCsFixer\Console\Output\Progress\ProgressOutputFactory;
use PhpCsFixer\Console\Output\Progress\ProgressOutputType;
use PhpCsFixer\Console\Report\FixReport\ReportSummary;
use PhpCsFixer\Error\ErrorsManager;
use PhpCsFixer\FixerFileProcessedEvent;
use PhpCsFixer\Runner\Runner;
use PhpCsFixer\ToolInfoInterface;
use Symfony\Component\Console\Attribute\AsCommand;
Expand Down Expand Up @@ -61,6 +63,8 @@ final class FixCommand extends Command

private ToolInfoInterface $toolInfo;

private ProgressOutputFactory $progressOutputFactory;

public function __construct(ToolInfoInterface $toolInfo)
{
parent::__construct();
Expand All @@ -70,6 +74,7 @@ public function __construct(ToolInfoInterface $toolInfo)
$this->stopwatch = new Stopwatch();
$this->defaultConfig = new Config();
$this->toolInfo = $toolInfo;
$this->progressOutputFactory = new ProgressOutputFactory();
}

/**
Expand Down Expand Up @@ -264,7 +269,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
}

$progressType = $resolver->getProgress();
$finder = new \ArrayIterator(iterator_to_array($resolver->getFinder()));

if (null !== $stdErr && $resolver->configFinderIsOverridden()) {
Expand All @@ -273,22 +277,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int
);
}

if ('none' === $progressType || null === $stdErr) {
$progressOutput = new NullOutput();
} else {
$progressOutput = new ProcessOutput(
$progressType = $resolver->getProgressType();
$progressOutput = $this->progressOutputFactory->create(
$progressType,
new OutputContext(
$stdErr,
$this->eventDispatcher,
(new Terminal())->getWidth(),
\count($finder)
);
}
)
);

$runner = new Runner(
$finder,
$resolver->getFixers(),
$resolver->getDiffer(),
'none' !== $progressType ? $this->eventDispatcher : null,
ProgressOutputType::NONE !== $progressType ? $this->eventDispatcher : null,
$this->errorsManager,
$resolver->getLinter(),
$resolver->isDryRun(),
Expand All @@ -297,9 +300,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$resolver->shouldStopOnViolation()
);

$this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$progressOutput, 'onFixerFileProcessed']);
$this->stopwatch->start('fixFiles');
$changed = $runner->fix();
$this->stopwatch->stop('fixFiles');
$this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$progressOutput, 'onFixerFileProcessed']);

$progressOutput->printLegend();

Expand Down
14 changes: 8 additions & 6 deletions src/Console/ConfigurationResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use PhpCsFixer\Cache\Signature;
use PhpCsFixer\ConfigInterface;
use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
use PhpCsFixer\Console\Output\Progress\ProgressOutputType;
use PhpCsFixer\Console\Report\FixReport\ReporterFactory;
use PhpCsFixer\Console\Report\FixReport\ReporterInterface;
use PhpCsFixer\Differ\DifferInterface;
Expand Down Expand Up @@ -406,26 +407,27 @@ static function (string $rawPath) use ($cwd, $filesystem): string {
/**
* @throws InvalidConfigurationException
*/
public function getProgress(): string
public function getProgressType(): string
{
if (null === $this->progress) {
if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) {
$progressType = $this->options['show-progress'];
$progressTypes = ['none', 'dots'];

if (null === $progressType) {
$progressType = $this->getConfig()->getHideProgress() ? 'none' : 'dots';
} elseif (!\in_array($progressType, $progressTypes, true)) {
$progressType = $this->getConfig()->getHideProgress()
? ProgressOutputType::NONE
: ProgressOutputType::DOTS;
} elseif (!\in_array($progressType, ProgressOutputType::AVAILABLE, true)) {
throw new InvalidConfigurationException(sprintf(
'The progress type "%s" is not defined, supported are %s.',
$progressType,
Utils::naturalLanguageJoin($progressTypes)
Utils::naturalLanguageJoin(ProgressOutputType::AVAILABLE)
));
}

$this->progress = $progressType;
} else {
$this->progress = 'none';
$this->progress = ProgressOutputType::NONE;
}
}

Expand Down
52 changes: 52 additions & 0 deletions src/Console/Output/OutputContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Console\Output;

use Symfony\Component\Console\Output\OutputInterface;

/**
* @internal
*/
final class OutputContext
{
private ?OutputInterface $output;
private int $terminalWidth;
private int $filesCount;

public function __construct(
?OutputInterface $output,
int $terminalWidth,
int $filesCount
) {
$this->output = $output;
$this->terminalWidth = $terminalWidth;
$this->filesCount = $filesCount;
}

public function getOutput(): ?OutputInterface
{
return $this->output;
}

public function getTerminalWidth(): int
{
return $this->terminalWidth;
}

public function getFilesCount(): int
{
return $this->filesCount;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Console\Output;
namespace PhpCsFixer\Console\Output\Progress;

use PhpCsFixer\Console\Output\OutputContext;
use PhpCsFixer\FixerFileProcessedEvent;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

/**
* Output writer to show the process of a FixCommand.
* Output writer to show the progress of a FixCommand using dots and meaningful letters.
*
* @internal
*/
final class ProcessOutput implements ProcessOutputInterface
final class DotsOutput implements ProgressOutputInterface
{
/**
* File statuses map.
Expand All @@ -39,11 +39,8 @@ final class ProcessOutput implements ProcessOutputInterface
FixerFileProcessedEvent::STATUS_LINT => ['symbol' => 'E', 'format' => '<bg=red>%s</bg=red>', 'description' => 'error'],
];

private OutputInterface $output;

private EventDispatcherInterface $eventDispatcher;

private int $files;
/** @readonly */
private OutputContext $context;

private int $processedFiles = 0;

Expand All @@ -52,22 +49,14 @@ final class ProcessOutput implements ProcessOutputInterface
*/
private $symbolsPerLine;

public function __construct(OutputInterface $output, EventDispatcherInterface $dispatcher, int $width, int $nbFiles)
public function __construct(OutputContext $context)
{
$this->output = $output;
$this->eventDispatcher = $dispatcher;
$this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']);
$this->files = $nbFiles;
$this->context = $context;

// max number of characters per line
// - total length x 2 (e.g. " 1 / 123" => 6 digits and padding spaces)
// - 11 (extra spaces, parentheses and percentage characters, e.g. " x / x (100%)")
$this->symbolsPerLine = max(1, $width - \strlen((string) $this->files) * 2 - 11);
}

public function __destruct()
{
$this->eventDispatcher->removeListener(FixerFileProcessedEvent::NAME, [$this, 'onFixerFileProcessed']);
$this->symbolsPerLine = max(1, $context->getTerminalWidth() - \strlen((string) $context->getFilesCount()) * 2 - 11);
}

/**
Expand All @@ -93,24 +82,24 @@ public function __wakeup(): void
public function onFixerFileProcessed(FixerFileProcessedEvent $event): void
{
$status = self::$eventStatusMap[$event->getStatus()];
$this->output->write($this->output->isDecorated() ? sprintf($status['format'], $status['symbol']) : $status['symbol']);
$this->getOutput()->write($this->getOutput()->isDecorated() ? sprintf($status['format'], $status['symbol']) : $status['symbol']);

++$this->processedFiles;

$symbolsOnCurrentLine = $this->processedFiles % $this->symbolsPerLine;
$isLast = $this->processedFiles === $this->files;
$isLast = $this->processedFiles === $this->context->getFilesCount();

if (0 === $symbolsOnCurrentLine || $isLast) {
$this->output->write(sprintf(
'%s %'.\strlen((string) $this->files).'d / %d (%3d%%)',
$this->getOutput()->write(sprintf(
'%s %'.\strlen((string) $this->context->getFilesCount()).'d / %d (%3d%%)',
$isLast && 0 !== $symbolsOnCurrentLine ? str_repeat(' ', $this->symbolsPerLine - $symbolsOnCurrentLine) : '',
$this->processedFiles,
$this->files,
round($this->processedFiles / $this->files * 100)
$this->context->getFilesCount(),
round($this->processedFiles / $this->context->getFilesCount() * 100)
));

if (!$isLast) {
$this->output->writeln('');
$this->getOutput()->writeln('');
}
}
}
Expand All @@ -125,9 +114,14 @@ public function printLegend(): void
continue;
}

$symbols[$symbol] = sprintf('%s-%s', $this->output->isDecorated() ? sprintf($status['format'], $symbol) : $symbol, $status['description']);
$symbols[$symbol] = sprintf('%s-%s', $this->getOutput()->isDecorated() ? sprintf($status['format'], $symbol) : $symbol, $status['description']);
}

$this->output->write(sprintf("\nLegend: %s\n", implode(', ', $symbols)));
$this->getOutput()->write(sprintf("\nLegend: %s\n", implode(', ', $symbols)));
}

private function getOutput(): OutputInterface
{
return $this->context->getOutput();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Console\Output;
namespace PhpCsFixer\Console\Output\Progress;

use PhpCsFixer\FixerFileProcessedEvent;

/**
* @internal
*/
final class NullOutput implements ProcessOutputInterface
final class NullOutput implements ProgressOutputInterface
{
public function printLegend(): void
{
}

public function onFixerFileProcessed(FixerFileProcessedEvent $event): void
{
}
}
51 changes: 51 additions & 0 deletions src/Console/Output/Progress/ProgressOutputFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

/*
* This file is part of PHP CS Fixer.
*
* (c) Fabien Potencier <fabien@symfony.com>
* Dariusz Rumiński <dariusz.ruminski@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Console\Output\Progress;

use PhpCsFixer\Console\Output\OutputContext;

/**
* @internal
*/
final class ProgressOutputFactory
{
public function create(string $outputType, OutputContext $context): ProgressOutputInterface
{
if (null === $context->getOutput()) {
$outputType = ProgressOutputType::NONE;
}

if (!$this->isBuiltInType($outputType)) {
throw new \InvalidArgumentException(
sprintf(
'Something went wrong, "%s" output type is not supported',
$outputType
)
);
}

return ProgressOutputType::NONE === $outputType
? new NullOutput()
: new DotsOutput($context);
}

private function isBuiltInType(string $outputType): bool
{
return \in_array($outputType, [
ProgressOutputType::NONE,
ProgressOutputType::DOTS,
], true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
* with this source code in the file LICENSE.
*/

namespace PhpCsFixer\Console\Output;
namespace PhpCsFixer\Console\Output\Progress;

use PhpCsFixer\FixerFileProcessedEvent;

/**
* @internal
*/
interface ProcessOutputInterface
interface ProgressOutputInterface
{
public function printLegend(): void;

public function onFixerFileProcessed(FixerFileProcessedEvent $event): void;
}
Loading

0 comments on commit bc9d96f

Please sign in to comment.