Skip to content

Commit 8695fc7

Browse files
committed
[TASK] Improve CLI output
1 parent b704f8a commit 8695fc7

File tree

16 files changed

+381
-21
lines changed

16 files changed

+381
-21
lines changed

e2e/typo3-typoscript/expected-output.txt

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
2+
1 file with changes
3+
===================
4+
5+
1) typo3-typoscript/result/cache-hash.typoscript
6+
17
---------- begin diff ----------
28
@@ @@
39
page.10.value = Link to page 23
@@ -13,5 +19,8 @@
1319
----------- end diff -----------
1420

1521
Applied rules:
16-
RemoveUseCacheHashFromTypolinkTypoScriptFractor (https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/10.0/Deprecation-88406-SetCacheHashnoCacheHashOptionsInViewHelpersAndUriBuilder.html)
22+
* RemoveUseCacheHashFromTypolinkTypoScriptFractor (https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/10.0/Deprecation-88406-SetCacheHashnoCacheHashOptionsInViewHelpersAndUriBuilder.html)
23+
24+
25+
[OK] 1 file has been changed by Fractor
1726

e2e/typo3-yaml/expected-output.txt

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
2+
1 file with changes
3+
===================
4+
5+
1) typo3-yaml/result/my_form.form.yaml
6+
17
---------- begin diff ----------
28
@@ @@
39
-
@@ -64,5 +70,8 @@
6470
----------- end diff -----------
6571

6672
Applied rules:
67-
EmailFinisherYamlFractor (https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/10.0/Feature-80420-AllowMultipleRecipientsInEmailFinisher.html)
73+
* EmailFinisherYamlFractor (https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/10.0/Feature-80420-AllowMultipleRecipientsInEmailFinisher.html)
74+
75+
76+
[OK] 1 file has been changed by Fractor
6877

packages/fractor/config/application.php

+9
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
use a9f\Fractor\Application\Contract\FileProcessor;
66
use a9f\Fractor\Application\FractorRunner;
7+
use a9f\Fractor\ChangesReporting\Contract\Output\OutputFormatterInterface;
78
use a9f\Fractor\Configuration\AllowedFileExtensionsResolver;
89
use a9f\Fractor\Configuration\SkipConfigurationFactory;
910
use a9f\Fractor\Configuration\ValueObject\SkipConfiguration;
11+
use a9f\Fractor\Console\Output\OutputFormatterCollector;
1012
use a9f\Fractor\Differ\ConsoleDiffer;
1113
use a9f\Fractor\Differ\Contract\Differ;
1214
use a9f\Fractor\FractorApplication;
@@ -88,6 +90,13 @@ static function (ChildDefinition $definition, AsCommand $attribute): void {
8890
$services->set(SkipConfiguration::class)->factory([service(SkipConfigurationFactory::class), 'create']);
8991
$services->set(FractorRunner::class)->arg('$processors', tagged_iterator('fractor.file_processor'));
9092
$services->set(AllowedFileExtensionsResolver::class)->arg('$processors', tagged_iterator('fractor.file_processor'));
93+
$services->set(OutputFormatterCollector::class)->arg(
94+
'$outputFormatters',
95+
tagged_iterator('fractor.output_formatter')
96+
);
9197
$services->set(Filesystem::class);
9298
$containerBuilder->registerForAutoconfiguration(FileProcessor::class)->addTag('fractor.file_processor');
99+
$containerBuilder->registerForAutoconfiguration(OutputFormatterInterface::class)->addTag(
100+
'fractor.output_formatter'
101+
);
93102
};

packages/fractor/src/Application/FractorRunner.php

+15-15
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
use a9f\Fractor\Application\ValueObject\File;
1111
use a9f\Fractor\Configuration\ValueObject\Configuration;
1212
use a9f\Fractor\Console\Contract\Output;
13+
use a9f\Fractor\Differ\ValueObject\FileDiff;
1314
use a9f\Fractor\Differ\ValueObjectFactory\FileDiffFactory;
1415
use a9f\Fractor\FileSystem\FilesFinder;
15-
use a9f\Fractor\Reporting\FractorsChangelogLinesResolver;
16+
use a9f\Fractor\ValueObject\FileProcessResult;
17+
use a9f\Fractor\ValueObject\ProcessResult;
1618
use Nette\Utils\FileSystem;
1719
use Webmozart\Assert\Assert;
1820

@@ -26,7 +28,6 @@
2628
* @param iterable<FileProcessor<FractorRule>> $processors
2729
*/
2830
public function __construct(
29-
private FractorsChangelogLinesResolver $fractorsChangelogLinesResolver,
3031
private FilesFinder $fileFinder,
3132
private FilesCollector $fileCollector,
3233
private iterable $processors,
@@ -37,14 +38,17 @@ public function __construct(
3738
Assert::allIsInstanceOf($this->processors, FileProcessor::class);
3839
}
3940

40-
public function run(Output $output, Configuration $configuration): void
41+
public function run(Output $output, Configuration $configuration): ProcessResult
4142
{
4243
$filePaths = $this->fileFinder->findFiles($configuration->getPaths(), $configuration->getFileExtensions());
4344

4445
if (! $configuration->isQuiet()) {
4546
$output->progressStart(count($filePaths));
4647
}
4748

49+
/** @var FileDiff[] $fileDiffs */
50+
$fileDiffs = [];
51+
4852
foreach ($filePaths as $filePath) {
4953
$file = new File($filePath, FileSystem::read($filePath));
5054
$this->fileCollector->addFile($file);
@@ -67,6 +71,12 @@ public function run(Output $output, Configuration $configuration): void
6771
}
6872

6973
$file->setFileDiff($this->fileDiffFactory->createFileDiff($file));
74+
75+
$fileProcessResult = new FileProcessResult($file->getFileDiff());
76+
$currentFileDiff = $fileProcessResult->getFileDiff();
77+
if ($currentFileDiff instanceof FileDiff) {
78+
$fileDiffs[] = $currentFileDiff;
79+
}
7080
}
7181

7282
if (! $configuration->isQuiet()) {
@@ -78,24 +88,14 @@ public function run(Output $output, Configuration $configuration): void
7888
continue;
7989
}
8090

81-
if (! $configuration->isQuiet()) {
82-
$output->write($file->getFileDiff()->getDiffConsoleFormatted());
83-
if ($file->getAppliedRules() !== []) {
84-
$fractorsChangelogsLines = $this->fractorsChangelogLinesResolver->createFractorChangelogLines(
85-
$file->getAppliedRules()
86-
);
87-
$output->write('<options=underscore>Applied rules:</>');
88-
$output->listing($fractorsChangelogsLines);
89-
$output->newLine();
90-
}
91-
}
92-
9391
if ($configuration->isDryRun()) {
9492
continue;
9593
}
9694

9795
$this->fileWriter->write($file);
9896
}
97+
98+
return new ProcessResult($fileDiffs);
9999
}
100100

101101
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\ChangesReporting\Contract\Output;
6+
7+
use a9f\Fractor\Configuration\ValueObject\Configuration;
8+
use a9f\Fractor\ValueObject\ProcessResult;
9+
use Symfony\Component\Console\Style\SymfonyStyle;
10+
11+
interface OutputFormatterInterface
12+
{
13+
public function getName(): string;
14+
15+
public function report(ProcessResult $processResult, Configuration $configuration): void;
16+
17+
public function setSymfonyStyle(SymfonyStyle $symfonyStyle): void;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\ChangesReporting\Output;
6+
7+
use a9f\Fractor\ChangesReporting\Contract\Output\OutputFormatterInterface;
8+
use a9f\Fractor\Configuration\ValueObject\Configuration;
9+
use a9f\Fractor\Differ\ValueObject\FileDiff;
10+
use a9f\Fractor\ValueObject\ProcessResult;
11+
use Symfony\Component\Console\Style\SymfonyStyle;
12+
13+
class ConsoleOutputFormatter implements OutputFormatterInterface
14+
{
15+
/**
16+
* @var string
17+
*/
18+
public const NAME = 'console';
19+
20+
/**
21+
* @readonly
22+
*/
23+
private SymfonyStyle $symfonyStyle;
24+
25+
public function setSymfonyStyle(SymfonyStyle $symfonyStyle): void
26+
{
27+
$this->symfonyStyle = $symfonyStyle;
28+
}
29+
30+
public function getName(): string
31+
{
32+
return self::NAME;
33+
}
34+
35+
public function report(ProcessResult $processResult, Configuration $configuration): void
36+
{
37+
$this->reportFileDiffs($processResult->getFileDiffs(), false);
38+
39+
// to keep space between progress bar and success message
40+
if ($processResult->getFileDiffs() === []) {
41+
$this->symfonyStyle->newLine();
42+
}
43+
44+
$message = $this->createSuccessMessage($processResult, $configuration);
45+
$this->symfonyStyle->success($message);
46+
}
47+
48+
/**
49+
* @param FileDiff[] $fileDiffs
50+
*/
51+
private function reportFileDiffs(array $fileDiffs, bool $absoluteFilePath): void
52+
{
53+
if (\count($fileDiffs) <= 0) {
54+
return;
55+
}
56+
// normalize
57+
\ksort($fileDiffs);
58+
$message = \sprintf('%d file%s with changes', \count($fileDiffs), \count($fileDiffs) === 1 ? '' : 's');
59+
$this->symfonyStyle->title($message);
60+
61+
$i = 0;
62+
foreach ($fileDiffs as $fileDiff) {
63+
$filePath = $absoluteFilePath ? $fileDiff->getAbsoluteFilePath() ?? '' : $fileDiff->getRelativeFilePath();
64+
$message = \sprintf('<options=bold>%d) %s</>', ++$i, $filePath);
65+
$this->symfonyStyle->writeln($message);
66+
$this->symfonyStyle->newLine();
67+
$this->symfonyStyle->writeln($fileDiff->getDiffConsoleFormatted());
68+
69+
if ($fileDiff->getAppliedRules() !== []) {
70+
$this->symfonyStyle->writeln('<options=underscore>Applied rules:</>');
71+
$this->symfonyStyle->listing($fileDiff->getAppliedRules());
72+
$this->symfonyStyle->newLine();
73+
}
74+
}
75+
}
76+
77+
private function createSuccessMessage(ProcessResult $processResult, Configuration $configuration): string
78+
{
79+
$changeCount = \count($processResult->getFileDiffs());
80+
if ($changeCount === 0) {
81+
return 'Fractor is done!';
82+
}
83+
return \sprintf(
84+
'%d file%s %s by Fractor',
85+
$changeCount,
86+
$changeCount > 1 ? 's' : '',
87+
$configuration->isDryRun()
88+
? 'would have been changed (dry-run)'
89+
: ($changeCount === 1 ? 'has' : 'have') . ' been changed'
90+
);
91+
}
92+
}

packages/fractor/src/Console/Command/ProcessCommand.php

+30-3
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,25 @@
77
use a9f\Fractor\Application\FractorRunner;
88
use a9f\Fractor\Configuration\ConfigurationFactory;
99
use a9f\Fractor\Configuration\Option;
10+
use a9f\Fractor\Configuration\ValueObject\Configuration;
11+
use a9f\Fractor\Console\ExitCode;
12+
use a9f\Fractor\Console\Output\OutputFormatterCollector;
1013
use a9f\Fractor\Console\Output\SymfonyConsoleOutput;
14+
use a9f\Fractor\ValueObject\ProcessResult;
1115
use Symfony\Component\Console\Attribute\AsCommand;
1216
use Symfony\Component\Console\Command\Command;
1317
use Symfony\Component\Console\Input\InputInterface;
1418
use Symfony\Component\Console\Input\InputOption;
1519
use Symfony\Component\Console\Output\OutputInterface;
20+
use Symfony\Component\Console\Style\SymfonyStyle;
1621

1722
#[AsCommand(name: 'process', description: 'Runs Fractor with the given configuration file')]
1823
final class ProcessCommand extends Command
1924
{
2025
public function __construct(
2126
private readonly FractorRunner $runner,
22-
private readonly ConfigurationFactory $configurationFactory
27+
private readonly ConfigurationFactory $configurationFactory,
28+
private readonly OutputFormatterCollector $outputFormatterCollector
2329
) {
2430
parent::__construct();
2531
}
@@ -49,8 +55,29 @@ protected function configure(): void
4955

5056
protected function execute(InputInterface $input, OutputInterface $output): int
5157
{
52-
$this->runner->run(new SymfonyConsoleOutput($output), $this->configurationFactory->createFromInput($input));
58+
$configuration = $this->configurationFactory->createFromInput($input);
59+
$processResult = $this->runner->run(new SymfonyConsoleOutput($output), $configuration);
5360

54-
return Command::SUCCESS;
61+
$outputFormat = 'console';
62+
$outputFormatter = $this->outputFormatterCollector->getByName($outputFormat);
63+
$outputFormatter->setSymfonyStyle(new SymfonyStyle($input, $output));
64+
$outputFormatter->report($processResult, $configuration);
65+
66+
return $this->resolveReturnCode($processResult, $configuration);
67+
}
68+
69+
/**
70+
* @return ExitCode::*
71+
*/
72+
private function resolveReturnCode(ProcessResult $processResult, Configuration $configuration): int
73+
{
74+
// inverse error code for CI dry-run
75+
if (! $configuration->isDryRun()) {
76+
return ExitCode::SUCCESS;
77+
}
78+
if ($processResult->getFileDiffs() !== []) {
79+
return ExitCode::CHANGED_CODE;
80+
}
81+
return ExitCode::SUCCESS;
5582
}
5683
}
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Console;
6+
7+
use Symfony\Component\Console\Command\Command;
8+
9+
final class ExitCode
10+
{
11+
public const SUCCESS = Command::SUCCESS;
12+
13+
/**
14+
* @var int
15+
*/
16+
public const CHANGED_CODE = 2;
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace a9f\Fractor\Console\Output;
6+
7+
use a9f\Fractor\ChangesReporting\Contract\Output\OutputFormatterInterface;
8+
use a9f\Fractor\Exception\Configuration\InvalidConfigurationException;
9+
10+
final class OutputFormatterCollector
11+
{
12+
/**
13+
* @var array<string, OutputFormatterInterface>
14+
*/
15+
private array $outputFormatters = [];
16+
17+
/**
18+
* @param OutputFormatterInterface[] $outputFormatters
19+
*/
20+
public function __construct(iterable $outputFormatters)
21+
{
22+
foreach ($outputFormatters as $outputFormatter) {
23+
$this->outputFormatters[$outputFormatter->getName()] = $outputFormatter;
24+
}
25+
}
26+
27+
public function getByName(string $name): OutputFormatterInterface
28+
{
29+
$this->ensureOutputFormatExists($name);
30+
return $this->outputFormatters[$name];
31+
}
32+
33+
private function ensureOutputFormatExists(string $name): void
34+
{
35+
if (isset($this->outputFormatters[$name])) {
36+
return;
37+
}
38+
$outputFormatterNames = \array_keys($this->outputFormatters);
39+
throw new InvalidConfigurationException(\sprintf(
40+
'Output formatter "%s" was not found. Pick one of "%s".',
41+
$name,
42+
\implode('", "', $outputFormatterNames)
43+
));
44+
}
45+
}

0 commit comments

Comments
 (0)