Passed
Pull Request — master (#295)
by Fabien
02:25
created

RunCommand::analyze()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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