Passed
Push — master ( 541fe2...1f8075 )
by Fabien
01:56
created

RunCommand::writeResult()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 16
rs 9.9666
cc 3
nc 4
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Churn\Command;
6
7
use Churn\Configuration\Config;
8
use Churn\Configuration\Loader;
9
use Churn\File\FileFinder;
10
use Churn\File\FileHelper;
11
use Churn\Process\Observer\OnSuccess;
12
use Churn\Process\Observer\OnSuccessAccumulate;
13
use Churn\Process\Observer\OnSuccessCollection;
14
use Churn\Process\Observer\OnSuccessProgress;
15
use Churn\Process\ProcessFactory;
16
use Churn\Process\ProcessHandlerFactory;
17
use Churn\Result\ResultAccumulator;
18
use Churn\Result\ResultsRendererFactory;
19
use InvalidArgumentException;
20
use Symfony\Component\Console\Command\Command;
21
use Symfony\Component\Console\Helper\ProgressBar;
22
use Symfony\Component\Console\Input\InputArgument;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
use Symfony\Component\Console\Output\StreamOutput;
27
use Webmozart\Assert\Assert;
28
29
/** @SuppressWarnings(PHPMD.CouplingBetweenObjects) */
30
class RunCommand extends Command
31
{
32
33
    public const LOGO = "
34
    ___  _   _  __  __  ____  _  _     ____  _   _  ____
35
   / __)( )_( )(  )(  )(  _ \( \( )___(  _ \( )_( )(  _ \
36
  ( (__  ) _ (  )(__)(  )   / )  ((___))___/ ) _ (  )___/
37
   \___)(_) (_)(______)(_)\_)(_)\_)   (__)  (_) (_)(__)
38
";
39
40
    /**
41
     * The process handler factory.
42
     *
43
     * @var ProcessHandlerFactory
44
     */
45
    private $processHandlerFactory;
46
47
    /**
48
     * The renderer factory.
49
     *
50
     * @var ResultsRendererFactory
51
     */
52
    private $renderFactory;
53
54
    /**
55
     * Class constructor.
56
     *
57
     * @param ProcessHandlerFactory $processHandlerFactory The process handler factory.
58
     * @param ResultsRendererFactory $renderFactory The Results Renderer Factory.
59
     */
60
    public function __construct(ProcessHandlerFactory $processHandlerFactory, ResultsRendererFactory $renderFactory)
61
    {
62
        parent::__construct();
63
64
        $this->processHandlerFactory = $processHandlerFactory;
65
        $this->renderFactory = $renderFactory;
66
    }
67
68
    /**
69
     * Returns a new instance of the command.
70
     */
71
    public static function newInstance(): self
72
    {
73
        return new self(new ProcessHandlerFactory(), new ResultsRendererFactory());
74
    }
75
76
    /**
77
     * Configure the command
78
     */
79
    protected function configure(): void
80
    {
81
        $this->setName('run')
82
            ->addArgument('paths', InputArgument::IS_ARRAY, 'Path to source to check.')
83
            ->addOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'Path to the configuration file', 'churn.yml') // @codingStandardsIgnoreLine
84
            ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format to use', 'text')
85
            ->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'The path where to write the result')
86
            ->addOption('parallel', null, InputOption::VALUE_REQUIRED, 'Number of parallel jobs')
87
            ->addOption('progress', 'p', InputOption::VALUE_NONE, 'Show progress bar')
88
            ->setDescription('Check files')
89
            ->setHelp('Checks the churn on the provided path argument(s).');
90
    }
91
92
    /**
93
     * Execute the command.
94
     *
95
     * @param InputInterface $input Input.
96
     * @param OutputInterface $output Output.
97
     */
98
    protected function execute(InputInterface $input, OutputInterface $output): int
99
    {
100
        $this->displayLogo($input, $output);
101
        $accumulator = $this->analyze($input, $output, $this->getConfiguration($input));
102
        $this->writeResult($input, $output, $accumulator);
103
104
        return 0;
105
    }
106
107
    /**
108
     * @param InputInterface $input Input.
109
     * @throws InvalidArgumentException If paths argument invalid.
110
     */
111
    private function getConfiguration(InputInterface $input): Config
112
    {
113
        $isDefaultValue = !$input->hasParameterOption('--configuration') && !$input->hasParameterOption('-c');
114
        $config = Loader::fromPath((string) $input->getOption('configuration'), $isDefaultValue);
115
        if ([] !== $input->getArgument('paths')) {
116
            $config->setDirectoriesToScan((array) $input->getArgument('paths'));
117
        }
118
119
        if ([] === $config->getDirectoriesToScan()) {
120
            throw new InvalidArgumentException(
121
                'Provide the directories you want to scan as arguments, ' .
122
                'or configure them under "directoriesToScan" in your churn.yml file.'
123
            );
124
        }
125
126
        if (null !== $input->getOption('parallel')) {
127
            Assert::integerish($input->getOption('parallel'), 'Amount of parallel jobs should be an integer');
128
            $config->setParallelJobs((int) $input->getOption('parallel'));
129
        }
130
131
        return $config;
132
    }
133
134
    /**
135
     * Run the actual analysis.
136
     *
137
     * @param InputInterface $input Input.
138
     * @param OutputInterface $output Output.
139
     * @param Config $config The configuration object.
140
     */
141
    private function analyze(InputInterface $input, OutputInterface $output, Config $config): ResultAccumulator
142
    {
143
        $filesFinder = (new FileFinder($config->getFileExtensions(), $config->getFilesToIgnore()))
144
            ->getPhpFiles($this->getDirectoriesToScan($input, $config));
145
        $accumulator = new ResultAccumulator($config->getFilesToShow(), $config->getMinScoreToShow());
146
        $this->processHandlerFactory->getProcessHandler($config)->process(
147
            $filesFinder,
148
            new ProcessFactory($config->getVCS(), $config->getCommitsSince()),
149
            $this->getOnSuccessObserver($input, $output, $accumulator)
150
        );
151
152
        return $accumulator;
153
    }
154
155
    /**
156
     * @param InputInterface $input Input.
157
     * @param Config $config The configuration object.
158
     * @return array<string> Array of absolute paths.
159
     */
160
    private function getDirectoriesToScan(InputInterface $input, Config $config): array
161
    {
162
        $basePath = null !== $config->getPath() && [] === $input->getArgument('paths')
163
            ? \dirname($config->getPath())
164
            : \getcwd();
165
        $paths = [];
166
167
        foreach ($config->getDirectoriesToScan() as $path) {
168
            $paths[] = FileHelper::toAbsolutePath($path, $basePath);
169
        }
170
171
        return $paths;
172
    }
173
174
    /**
175
     * @param InputInterface $input Input.
176
     * @param OutputInterface $output Output.
177
     * @param ResultAccumulator $accumulator The object accumulating the results.
178
     */
179
    private function getOnSuccessObserver(
180
        InputInterface $input,
181
        OutputInterface $output,
182
        ResultAccumulator $accumulator
183
    ): OnSuccess {
184
        $observer = new OnSuccessAccumulate($accumulator);
185
186
        if (true === $input->getOption('progress')) {
187
            $progressBar = new ProgressBar($output);
188
            $progressBar->start();
189
            $observer = new OnSuccessCollection($observer, new OnSuccessProgress($progressBar));
190
        }
191
192
        return $observer;
193
    }
194
195
    /**
196
     * @param InputInterface $input Input.
197
     * @param OutputInterface $output Output.
198
     */
199
    private function displayLogo(InputInterface $input, OutputInterface $output): void
200
    {
201
        if ('text' !== $input->getOption('format') && empty($input->getOption('output'))) {
202
            return;
203
        }
204
205
        $output->writeln(self::LOGO);
206
    }
207
208
    /**
209
     * @param InputInterface $input Input.
210
     * @param OutputInterface $output Output.
211
     * @param ResultAccumulator $accumulator The results to write.
212
     */
213
    private function writeResult(InputInterface $input, OutputInterface $output, ResultAccumulator $accumulator): void
214
    {
215
        if (true === $input->getOption('progress')) {
216
            $output->writeln("\n");
217
        }
218
219
        if (!empty($input->getOption('output'))) {
220
            $output = new StreamOutput(
221
                \fopen((string) $input->getOption('output'), 'w+'),
222
                OutputInterface::VERBOSITY_NORMAL,
223
                false
224
            );
225
        }
226
227
        $renderer = $this->renderFactory->getRenderer($input->getOption('format'));
228
        $renderer->render($output, $accumulator->toArray());
229
    }
230
}
231