PvraBaseCommand::getDefaultAnalysersAliases()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 9.4285
cc 3
eloc 6
nc 3
nop 0
crap 3
1
<?php
2
/**
3
 * PvraBaseCommand.php
4
 *
5
 * MIT LICENSE
6
 *
7
 * LICENSE: This source file is subject to the MIT license.
8
 * A copy of the licenses text was distributed alongside this
9
 * file (usually the repository or package root). The text can also
10
 * be obtained through one of the following sources:
11
 * * http://opensource.org/licenses/MIT
12
 * * https://github.com/suralc/pvra/blob/master/LICENSE
13
 *
14
 * @author     suralc <[email protected]>
15
 * @license    http://opensource.org/licenses/MIT  MIT
16
 */
17
namespace Pvra\Console\Commands;
18
19
20
use Pvra\AnalyserAwareInterface;
21
use Pvra\Analysers\LanguageFeatureAnalyser;
22
use Pvra\FileAnalyser;
23
use Pvra\InformationProvider\LibraryInformation;
24
use Pvra\InformationProvider\LibraryInformationAwareInterface;
25
use Pvra\Result\Collection;
26
use Pvra\Result\CollectionWriter;
27
use Pvra\Result\Exceptions\ResultFileWriterException;
28
use Pvra\Result\MessageLocator;
29
use Pvra\Result\ResultFormatter\Json;
30
use Symfony\Component\Console\Command\Command;
31
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
32
use Symfony\Component\Console\Input\InputArgument;
33
use Symfony\Component\Console\Input\InputInterface;
34
use Symfony\Component\Console\Input\InputOption;
35
use Symfony\Component\Console\Output\OutputInterface;
36
37
/**
38
 * PvraBaseCommand
39
 *
40
 * Base class to give some default options for commands that may run the parser.
41
 *
42
 * @package Pvra\Console\Commands
43
 */
44
class PvraBaseCommand extends Command
45
{
46
    const WALKER_DEFAULT_NAMESPACE_ROOT = 'Pvra\\Analysers\\';
47
48
    /**
49
     * A list of `Pvra\PhpParser\RequirementAnalyserAwareInterface` class names.
50
     *
51
     * @var array|string[]
52
     */
53
    protected $expectedWalkers = [];
54
55
    /**
56
     * @var bool
57
     */
58
    private $preferRelativePaths = false;
59
60
    /**
61
     * List of analysers that are loaded if the --analyser option is not set
62
     *
63
     * @var array
64
     */
65
    private static $defaultAnalysers = [
66
        'Php53Features',
67
        'Php54Features',
68
        'Php55Features',
69
        'Php56Features',
70
        'Php70Features',
71
        'LibraryChanges',
72
    ];
73
74
    private static $analyserAliasMap = [
75
        'Php53Features' => 'php-5.3',
76
        'Php54Features' => 'php-5.4',
77
        'Php55Features' => 'php-5.5',
78
        'Php56Features' => 'php-5.6',
79
        'Php70Features' => 'php-7.0',
80
        'LibraryChanges' => 'lib-php',
81
    ];
82
83
    /**
84
     * @inheritdoc
85
     */
86 64
    protected function configure()
87
    {
88 32
        $this
89 64
            ->addOption('preferRelativePaths', 's', InputOption::VALUE_NONE, 'Prefer relative paths in the output')
90 64
            ->addOption('preventNameExpansion', 'p', InputOption::VALUE_NONE,
91 64
                'Prevent name expansion. May increase performance but breaks name based detections in namespaces.')
92 64
            ->addOption('analyser', 'a', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED,
93 64
                'Analysers to run', $this->getDefaultAnalysersAliases())
94 64
            ->addOption('libraryDataSource', 'l', InputOption::VALUE_REQUIRED, 'Source file of library data', false)
95 64
            ->addOption('messageFormatSourceFile', 'm', InputOption::VALUE_REQUIRED, 'File with message formats', false)
96 64
            ->addOption('saveFormat', null, InputOption::VALUE_REQUIRED, 'The format of the save file.', 'json')
97 64
            ->addOption('saveAsFile', null, InputOption::VALUE_REQUIRED,
98 64
                'Save the output as file. Requires usage of the format option. Value is the targets file path.', false);
99
100 64
        $this->addArgument('target', InputArgument::REQUIRED, 'The target of this analysis');
101 64
    }
102
103 64
    private function getDefaultAnalysersAliases()
104
    {
105 64
        $aliasNameMap = [];
106 64
        foreach (self::$defaultAnalysers as $analyser) {
107 64
            if (isset(self::$analyserAliasMap[ $analyser ])) {
108 64
                $aliasNameMap[] = self::$analyserAliasMap[ $analyser ];
109 32
            }
110 32
        }
111
112 64
        return $aliasNameMap;
113
    }
114
115
    /**
116
     * @param $name
117
     * @return bool|string
118
     */
119 62
    private function resolveAnalyserName($name)
120
    {
121 62
        if (($resolved = array_search($name, self::$analyserAliasMap)) !== false) {
122 42
            return $resolved;
123
        }
124 20
        return in_array($name, array_keys(self::$analyserAliasMap)) ? $name : false;
125
    }
126
127
    /**
128
     * @param \Symfony\Component\Console\Input\InputInterface $input
129
     * @param \Symfony\Component\Console\Output\OutputInterface $output
130
     */
131 64
    protected function initialize(InputInterface $input, OutputInterface $output)
132
    {
133 64
        if ($input->getOption('preferRelativePaths')) {
134 4
            $this->preferRelativePaths = true;
135 2
        }
136 64
        $style = new OutputFormatterStyle('red', 'yellow', ['bold', 'blink']);
137 64
        $output->getFormatter()->setStyle('warn', $style);
138 64
        $analysers = $input->getOption('analyser');
139 64
        if (empty($analysers) || !is_array($analysers)) {
140 2
            throw new \InvalidArgumentException('The values given to the "analyser" parameter are not valid.');
141
        }
142 62
        foreach ($analysers as $analyser) {
143 62
            if (($name = $this->resolveAnalyserName($analyser)) !== false) {
144 56
                $analyserName = self::WALKER_DEFAULT_NAMESPACE_ROOT . $name;
145 28
            } else {
146 6
                $analyserName = $analyser;
147
            }
148 62
            if (!class_exists($analyserName)) {
149 4
                throw new \InvalidArgumentException(sprintf('"%s" is not a class.', $analyser));
150 58
            } elseif (!in_array(AnalyserAwareInterface::class, class_implements($analyserName))) {
151 2
                throw new \InvalidArgumentException(sprintf('"%s" does not implement "%s"', $analyserName,
152 2
                    AnalyserAwareInterface::class));
153
            }
154 56
            $this->expectedWalkers[] = $analyserName;
155 28
        }
156 56
        $this->expectedWalkers = array_unique($this->expectedWalkers);
157 56
    }
158
159
    /**
160
     * @param \Symfony\Component\Console\Input\InputInterface|string $input
161
     * @param null|bool $preventNameExpansion
162
     * @return \Pvra\FileAnalyser
163
     */
164 50
    protected function createFileAnalyserInstance($input, $preventNameExpansion = null)
165
    {
166 50
        return new FileAnalyser($input instanceof InputInterface ? $input->getArgument('target') : $input,
167 50
            $input instanceof InputInterface ? $input->getOption('preventNameExpansion') !== true : (bool)$preventNameExpansion);
168
    }
169
170
    /**
171
     * @param \Symfony\Component\Console\Input\InputInterface $input
172
     * @return \Pvra\Result\MessageLocator|static
173
     */
174 50
    protected function createMessageLocatorInstance(InputInterface $input)
175
    {
176 50
        $file = $input->getOption('messageFormatSourceFile');
177 50
        if (is_string($file)) {
178 8
            $locator = MessageLocator::fromArray($this->getArrayFromFile($file)[1]);
179 1
        } else {
180 42
            $locator = MessageLocator::fromPhpFile(__DIR__ . '/../../../data/default_messages.php');
181
        }
182
183 44
        $locator->addMissingMessageHandler(function () {
184
            return 'Message for reason :id: not found. Required version: :version:';
185 44
        });
186
187 44
        return $locator;
188
    }
189
190
    /**
191
     * @param string $librarySourceOption
192
     * @param int $mode
193
     * @return \Pvra\Analysers\LanguageFeatureAnalyser[]
194
     */
195 50
    protected function createNodeWalkerInstances(
196
        $librarySourceOption = null,
197
        $mode = LanguageFeatureAnalyser::MODE_ADDITION
198
    ) {
199 50
        $analysers = [];
200 50
        foreach ($this->expectedWalkers as $walker) {
201 50
            if (in_array(LibraryInformationAwareInterface::class, class_implements($walker))) {
202 38
                if (is_string($librarySourceOption)) {
203 2
                    $info = new LibraryInformation($this->getArrayFromFile($librarySourceOption)[1]);
204 1
                } else {
205 36
                    $info = LibraryInformation::createWithDefaults();
206
                }
207 38
                $analysers[] = (new $walker(['mode' => $mode]))->setLibraryInformation($info);
208 19
            } else {
209 49
                $analysers[] = new $walker(['mode' => $mode]);
210
            }
211 25
        }
212
213 50
        return $analysers;
214
    }
215
216
    /**
217
     * @param string $filePath
218
     * @return array An array having the type of the given file at index `0` and its data at index `1`.
219
     */
220 10
    protected function getArrayFromFile($filePath)
221
    {
222 10
        if (is_file($filePath) && is_readable($filePath)) {
223 8
            $type = pathinfo($filePath, PATHINFO_EXTENSION);
224
            switch ($type) {
225 8
                case 'php':
226 2
                    return [$type, include $filePath];
227 6
                case 'json':
228 4 View Code Duplication
                    if (($data = json_decode(file_get_contents($filePath), true)) === null) {
229 2
                        throw new \RuntimeException(sprintf('Json decoding of file "%s" failed with notice: "%s"',
230 1
                            $filePath,
231 2
                            version_compare(PHP_VERSION, '5.5.0', '>=') ? json_last_error_msg() : json_last_error()));
232
                    }
233 2
                    return [$type, $data];
234 1
                default:
235 2
                    throw new \InvalidArgumentException(sprintf('The %s filetype is not supported. Only php and json files are supported for this operation.',
236 1
                        $type));
237 1
            }
238
        }
239
240 2
        throw new \InvalidArgumentException(sprintf('The file "%s" is not readable or does not exist.', $filePath));
241
    }
242
243
    /**
244
     * @return bool
245
     */
246 4
    protected function hasNameDependentAnalyser()
247
    {
248 4
        foreach ($this->expectedWalkers as $walker) {
249 4
            if (in_array(LibraryInformationAwareInterface::class,
250 2
                class_implements($walker))) {
251 3
                return true;
252
            }
253 2
        }
254
255 2
        return false;
256
    }
257
258
    /**
259
     * @param string $target
260
     * @param string $format
261
     * @param \Pvra\Result\Collection $result
262
     * @param \Symfony\Component\Console\Output\OutputInterface $output
263
     */
264 12
    protected function writeToFile($target, $format, Collection $result, OutputInterface $output)
265
    {
266 12
        $output->writeln('Preparing to write results to ' . $target);
267
        try {
268 12
            $fileWriter = new CollectionWriter($result);
269
            switch ($format) {
270 12
                case 'json':
271 8
                    $formatter = new Json();
272 8
                    break;
273 2
                default:
274 4
                    throw new ResultFileWriterException($format . ' is not a supported save format');
275 2
            }
276 8
            $fileWriter->write($target, $formatter);
277 4
            $output->writeln(sprintf('<info>Generated output file at %s</info>', $target));
278 10
        } catch (ResultFileWriterException $e) {
279 8
            $output->writeln('<error>' . $e->getMessage() . '</error>');
280
        }
281 12
    }
282
283
    /**
284
     * @param string $target
285
     * @return string
286
     */
287 40
    protected function formatOutputPath($target)
288
    {
289 40
        if (!$this->preferRelativePaths) {
290 36
            return $target;
291
        }
292 4
        return \Pvra\Console\makeRelativePath(getcwd(), $target);
293
    }
294
}
295