Completed
Pull Request — master (#122)
by Bill
01:29
created

ChurnCommand::displayResultsJson()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 9
nc 1
nop 2
1
<?php declare(strict_types = 1);
2
3
namespace Churn\Commands;
4
5
use Churn\Collections\FileCollection;
6
use Churn\Results\Result;
7
use Churn\Results\ResultCollection;
8
use Illuminate\Support\Collection;
9
use Churn\Factories\ProcessFactory;
10
use Churn\Managers\FileManager;
11
use Churn\Results\ResultsParser;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Helper\Table;
14
use Symfony\Component\Console\Input\InputArgument;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Churn\Configuration\Config;
19
use Symfony\Component\Yaml\Yaml;
20
use InvalidArgumentException;
21
22
class ChurnCommand extends Command
23
{
24
    const FORMAT_JSON = 'json';
25
    const FORMAT_TEXT = 'text';
26
27
    /**
28
     * The config values.
29
     * @var Config
30
     */
31
    private $config;
32
33
    /**
34
     * The process factory.
35
     * @var ProcessFactory
36
     */
37
    private $processFactory;
38
39
    /**
40
     * Th results parser.
41
     * @var ResultsParser
42
     */
43
    private $resultsParser;
44
45
    /**
46
     * Collection of files to run the processes on.
47
     * @var FileCollection
48
     */
49
    private $filesCollection;
50
51
    /**
52
     * Collection of processes currently running.
53
     * @var Collection
54
     */
55
    private $runningProcesses;
56
57
    /**
58
     * Array of completed processes.
59
     * @var array
60
     */
61
    private $completedProcessesArray;
62
63
    /**
64
     * The start time.
65
     * @var float
66
     */
67
    private $startTime;
68
69
    /**
70
     * Keeps track of how many files were processed.
71
     * @var integer
72
     */
73
    private $filesCount;
74
75
    /**
76
     * ChurnCommand constructor.
77
     */
78
    public function __construct()
79
    {
80
        parent::__construct();
81
82
        $this->resultsParser = new ResultsParser();
83
    }
84
85
    /**
86
     * Configure the command
87
     * @return void
88
     */
89
    protected function configure()
90
    {
91
        $this->setName('run')
92
            ->addArgument('paths', InputArgument::IS_ARRAY, 'Path to source to check.')
93
            ->addOption('configuration', 'c', InputOption::VALUE_OPTIONAL, 'Path to the configuration file', 'churn.yml')  // @codingStandardsIgnoreLine
94
            ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format to use', 'text')
95
            ->setDescription('Check files')
96
            ->setHelp('Checks the churn on the provided path argument(s).');
97
    }
98
99
    /**
100
     * Execute the command
101
     * @param  InputInterface  $input  Input.
102
     * @param  OutputInterface $output Output.
103
     * @return void
104
     */
105
    protected function execute(InputInterface $input, OutputInterface $output)
106
    {
107
        $this->startTime = microtime(true);
108
        $this->setupProcessor($input->getOption('configuration'));
109
110
        $this->filesCollection = $this->getPhpFiles($this->getDirectoriesToScan($input));
111
        $this->filesCount = $this->filesCollection->count();
112
        $this->runningProcesses = new Collection;
113
        $this->completedProcessesArray = [];
114
        while ($this->filesCollection->hasFiles() || $this->runningProcesses->count()) {
115
            $this->getProcessResults();
116
        }
117
        $completedProcesses = new Collection($this->completedProcessesArray);
118
119
        $results = $this->resultsParser
120
            ->parse($completedProcesses)
121
            ->normalizeAgainst($this->config);
122
        $this->displayResults($input->getOption('format'), $output, $results);
123
    }
124
125
    /**
126
     * Gets the output from processes and stores them in the completedProcessArray member.
127
     * @return void
128
     */
129
    private function getProcessResults()
130
    {
131
        for ($index = $this->runningProcesses->count(); $this->filesCollection->hasFiles() > 0 && $index < $this->config->getParallelJobs(); $index++) {
132
            $file = $this->filesCollection->getNextFile();
133
134
            $process = $this->processFactory->createGitCommitProcess($file);
135
            $process->start();
136
            $this->runningProcesses->put($process->getKey(), $process);
137
138
            $process = $this->processFactory->createCyclomaticComplexityProcess($file);
139
            $process->start();
140
            $this->runningProcesses->put($process->getKey(), $process);
141
        }
142
143
        foreach ($this->runningProcesses as $file => $process) {
144
            if ($process->isSuccessful()) {
145
                $this->runningProcesses->forget($process->getKey());
146
                $this->completedProcessesArray[$process->getFileName()][$process->getType()] = $process;
147
            }
148
        }
149
    }
150
151
    /**
152
     * Displays the results in a table.
153
     * @param  OutputInterface  $output  Output.
154
     * @param  ResultCollection $results Results Collection.
155
     * @return void
156
     */
157
    protected function displayResults(string $format, OutputInterface $output, ResultCollection $results)
158
    {
159
        if ($format === self::FORMAT_JSON) {
160
            $this->displayResultsJson($output, $results);
161
        } elseif ($format === self::FORMAT_TEXT) {
162
            $this->displayResultsText($output, $results);
163
        } else {
164
            throw new \InvalidArgumentException('Invalid output format provided');
165
        }
166
    }
167
168
    /**
169
     * @param string $configFile Relative path churn.yml configuration file.
170
     * @return void
171
     */
172
    private function setupProcessor(string $configFile)
173
    {
174
        $this->config = Config::create(Yaml::parse(@file_get_contents($configFile)) ?? []);
175
        $this->processFactory = new ProcessFactory($this->config);
176
    }
177
178
    /**
179
     * @param array $directory Directories.
180
     * @return FileCollection
181
     */
182
    private function getPhpFiles(array $directory): FileCollection
183
    {
184
        $fileManager = new FileManager($this->config->getFileExtensions(), $this->config->getFilesToIgnore());
185
186
        return $fileManager->getPhpFiles($directory);
187
    }
188
189
    /**
190
     * @param OutputInterface $output
191
     * @param ResultCollection $results
192
     */
193
    private function displayResultsText(OutputInterface $output, ResultCollection $results)
194
    {
195
        $totalTime = microtime(true) - $this->startTime;
196
        echo "\n
197
    ___  _   _  __  __  ____  _  _     ____  _   _  ____
198
   / __)( )_( )(  )(  )(  _ \( \( )___(  _ \( )_( )(  _ \
199
  ( (__  ) _ (  )(__)(  )   / )  ((___))___/ ) _ (  )___/
200
   \___)(_) (_)(______)(_)\_)(_)\_)   (__)  (_) (_)(__)      https://github.com/bmitch/churn-php\n\n";
201
202
        $table = new Table($output);
203
        $table->setHeaders(['File', 'Times Changed', 'Complexity', 'Score']);
204
        $table->addRows($results->toArray());
205
206
        $table->render();
207
208
        echo "  "
209
            . $this->filesCount
210
            . " files analysed in {$totalTime} seconds using "
211
            . $this->config->getParallelJobs()
212
            . " parallel jobs.\n\n";
213
    }
214
215
    private function displayResultsJson(OutputInterface $output, ResultCollection $results)
216
    {
217
        $data = array_map(function(array $result) {
218
            return [
219
                'file' => $result[0],
220
                'commits' => $result[1],
221
                'complexity' => $result[2],
222
                'score' => $result[3]
223
            ];
224
        }, $results->toArray());
225
226
        $output->write(json_encode($data));
227
    }
228
229
    /**
230
     * Get the directories to scan.
231
     * @param InputInterface $input Input Interface.
232
     * @throws InvalidArgumentException When no directories to scan found.
233
     * @return array
234
     */
235
    private function getDirectoriesToScan(InputInterface $input): array
236
    {
237
        $dirsProvidedAsArgs = $input->getArgument('paths');
238
        if (count($dirsProvidedAsArgs) > 0) {
239
            return $dirsProvidedAsArgs;
240
        }
241
242
        $dirsConfigured = $this->config->getDirectoriesToScan();
243
        if (count($dirsConfigured) > 0) {
244
            return $dirsConfigured;
245
        }
246
247
        throw new InvalidArgumentException(
248
            'Provide the directories you want to scan as arguments, ' .
249
            'or configure them under "directoriesToScan" in your churn.yml file.'
250
        );
251
    }
252
}
253