Passed
Push — master ( 1f8075...93e91b )
by Fabien
02:17
created

RunCommand::displayLogo()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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