Passed
Pull Request — master (#301)
by Fabien
02:07
created

RunCommand::getDirectoriesToScan()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
dl 0
loc 9
rs 10
c 1
b 0
f 0
cc 2
nc 2
nop 1
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($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 Config $config The configuration object.
157
     * @return array<string> Array of absolute paths.
158
     */
159
    private function getDirectoriesToScan(Config $config): array
160
    {
161
        $paths = [];
162
163
        foreach ($config->getDirectoriesToScan() as $path) {
164
            $paths[] = FileHelper::toAbsolutePath($path, $config->getPath());
0 ignored issues
show
Bug introduced by
It seems like $config->getPath() can also be of type null; however, parameter $configurationPath of Churn\File\FileHelper::toAbsolutePath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

164
            $paths[] = FileHelper::toAbsolutePath($path, /** @scrutinizer ignore-type */ $config->getPath());
Loading history...
165
        }
166
167
        return $paths;
168
    }
169
170
    /**
171
     * @param InputInterface $input Input.
172
     * @param OutputInterface $output Output.
173
     * @param ResultAccumulator $accumulator The object accumulating the results.
174
     */
175
    private function getOnSuccessObserver(
176
        InputInterface $input,
177
        OutputInterface $output,
178
        ResultAccumulator $accumulator
179
    ): OnSuccess {
180
        $observer = new OnSuccessAccumulate($accumulator);
181
182
        if (true === $input->getOption('progress')) {
183
            $progressBar = new ProgressBar($output);
184
            $progressBar->start();
185
            $observer = new OnSuccessCollection($observer, new OnSuccessProgress($progressBar));
186
        }
187
188
        return $observer;
189
    }
190
191
    /**
192
     * @param InputInterface $input Input.
193
     * @param OutputInterface $output Output.
194
     */
195
    private function displayLogo(InputInterface $input, OutputInterface $output): void
196
    {
197
        if ('text' !== $input->getOption('format') && empty($input->getOption('output'))) {
198
            return;
199
        }
200
201
        $output->writeln(self::LOGO);
202
    }
203
204
    /**
205
     * @param InputInterface $input Input.
206
     * @param OutputInterface $output Output.
207
     * @param ResultAccumulator $accumulator The results to write.
208
     */
209
    private function writeResult(InputInterface $input, OutputInterface $output, ResultAccumulator $accumulator): void
210
    {
211
        if (true === $input->getOption('progress')) {
212
            $output->writeln("\n");
213
        }
214
215
        if (!empty($input->getOption('output'))) {
216
            $output = new StreamOutput(
217
                \fopen((string) $input->getOption('output'), 'w+'),
218
                OutputInterface::VERBOSITY_NORMAL,
219
                false
220
            );
221
        }
222
223
        $renderer = $this->renderFactory->getRenderer($input->getOption('format'));
224
        $renderer->render($output, $accumulator->toArray());
225
    }
226
}
227