Completed
Push — master ( 819344...58056e )
by Carlos
02:20
created

LintCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 47
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 47
rs 9.0303
cc 1
eloc 38
nc 1
nop 0
1
<?php
2
3
/*
4
 * This file is part of the overtrue/phplint.
5
 *
6
 * (c) 2016 overtrue <[email protected]>
7
 */
8
9
namespace Overtrue\PHPLint\Command;
10
11
use Overtrue\PHPLint\Linter;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Helper\Helper;
14
use Symfony\Component\Console\Helper\ProgressBar;
15
use Symfony\Component\Console\Input\InputArgument;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Output\OutputInterface;
19
use Symfony\Component\Yaml\Exception\ParseException;
20
use Symfony\Component\Yaml\Yaml;
21
22
/**
23
 * Class LintCommand.
24
 */
25
class LintCommand extends Command
26
{
27
    /**
28
     * @var array
29
     */
30
    protected $defaults = [
31
        'jobs' => 5,
32
        'exclude' => [],
33
        'extensions' => ['php'],
34
    ];
35
36
    /**
37
     * @var \Symfony\Component\Console\Output\InputInterface
38
     */
39
    protected $input;
40
41
    /**
42
     * @var \Symfony\Component\Console\Output\OutputInterface
43
     */
44
    protected $ouput;
45
46
    /**
47
     * Configures the current command.
48
     */
49
    protected function configure()
50
    {
51
        $this
52
            ->setName('phplint')
53
            ->setDescription('Lint something')
54
            ->addArgument(
55
                'path',
56
                InputArgument::OPTIONAL,
57
                'Path to file or directory to lint'
58
            )
59
            ->addOption(
60
               'exclude',
61
               null,
62
               InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
63
               'Path to file or directory to exclude from linting'
64
            )
65
            ->addOption(
66
               'extensions',
67
               null,
68
               InputOption::VALUE_REQUIRED,
69
               'Check only files with selected extensions (default: php)'
70
            )
71
            ->addOption(
72
               'jobs',
73
               'j',
74
               InputOption::VALUE_REQUIRED,
75
               'Number of parraled jobs to run (default: 5)'
76
            )
77
            ->addOption(
78
               'configuration',
79
               'c',
80
               InputOption::VALUE_REQUIRED,
81
               'Read configuration from config file (default: .phplint.yml).'
82
            )
83
            ->addOption(
84
               'no-configuration',
85
               null,
86
               InputOption::VALUE_NONE,
87
               'Ignore default configuration file (default: .phplint.yml).'
88
            )
89
            ->addOption(
90
               'no-cache',
91
               null,
92
               InputOption::VALUE_NONE,
93
               'Ignore cached data.'
94
            );
95
    }
96
97
    /**
98
     * Initializes the command just after the input has been validated.
99
     *
100
     * This is mainly useful when a lot of commands extends one main command
101
     * where some things need to be initialized based on the input arguments and options.
102
     *
103
     * @param InputInterface  $input  An InputInterface instance
104
     * @param OutputInterface $output An OutputInterface instance
105
     */
106
    public function initialize(InputInterface $input, OutputInterface $output)
107
    {
108
        $this->input = $input;
0 ignored issues
show
Documentation Bug introduced by
It seems like $input of type object<Symfony\Component...e\Input\InputInterface> is incompatible with the declared type object<Symfony\Component...\Output\InputInterface> of property $input.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
109
        $this->output = $output;
0 ignored issues
show
Bug introduced by
The property output does not seem to exist. Did you mean ouput?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
110
    }
111
112
    /**
113
     * Executes the current command.
114
     *
115
     * This method is not abstract because you can use this class
116
     * as a concrete class. In this case, instead of defining the
117
     * execute() method, you set the code to execute by passing
118
     * a Closure to the setCode() method.
119
     *
120
     * @param InputInterface  $input  An InputInterface instance
121
     * @param OutputInterface $output An OutputInterface instance
122
     *
123
     * @throws LogicException When this abstract method is not implemented
124
     *
125
     * @return null|int null or 0 if everything went fine, or an error code
126
     *
127
     * @see setCode()
128
     */
129
    protected function execute(InputInterface $input, OutputInterface $output)
130
    {
131
        $startTime = microtime(true);
132
        $startMemUsage = memory_get_usage(true);
133
134
        $output->writeln($this->getApplication()->getLongVersion()." by overtrue and contributors.\n");
135
136
        $options = $this->mergeOptions();
137
138
        $linter = new Linter($options['path'], $options['exclude'], $options['extensions']);
139
140
        $files = $linter->getFiles();
141
        $fileCount = count($files);
142
143
        if ($fileCount <= 0) {
144
            $output->writeln('<info>Could not find files to lint</info>');
145
146
            return 0;
147
        }
148
149
        $linter->setProcessLimit($options['jobs']);
150
151
        if (!$input->getOption('no-cache') && file_exists(__DIR__.'/../../.phplint-cache')) {
152
            $linter->setCache(json_decode(file_get_contents(__DIR__.'/../../.phplint-cache'), true));
153
        }
154
155
        $progress = new ProgressBar($output, $fileCount);
156
        $progress->setBarWidth(50);
157
        $progress->setBarCharacter('<info>.</info>');
158
        $progress->setEmptyBarCharacter('<comment>.</comment>');
159
        $progress->setProgressCharacter('|');
160
        $progress->setMessage('', 'filename');
161
        $progress->setFormat("%filename%\n\n%current%/%max% [%bar%] %percent:3s%% %elapsed:6s%\n");
162
        $progress->start();
163
164
        $linter->setProcessCallback(function ($status, $filename) use ($progress) {
165
            $progress->setMessage("<info>Checking: </info>{$filename}", 'filename');
166
            $progress->advance();
167
        });
168
169
        $errors = $linter->lint($files);
170
        $progress->finish();
171
172
        $timeUsage = Helper::formatTime(microtime(true) - $startTime);
173
        $memUsage = Helper::formatMemory(memory_get_usage(true) - $startMemUsage);
174
175
        $code = 0;
176
        $errCount = count($errors);
177
178
        $output->writeln("\nTime: {$timeUsage}, Memory: {$memUsage}MB\n");
179
180
        if ($errCount > 0) {
181
            $output->writeln('<error>FAILURES!</error>');
182
            $output->writeln("<error>Files: {$fileCount}, Failures: {$errCount}</error>");
183
            $this->showErrors($errors);
184
            $code = 1;
185
        } else {
186
            $output->writeln("<info>OK! (Files: {$fileCount}, Success: {$fileCount})</info>");
187
        }
188
189
        return $code;
190
    }
191
192
    /**
193
     * Show errors detail.
194
     *
195
     * @param array $errors
196
     */
197
    protected function showErrors($errors)
198
    {
199
        $i = 0;
200
        $this->output->writeln("\nThere was ".count($errors).' errors:');
0 ignored issues
show
Bug introduced by
The property output does not seem to exist. Did you mean ouput?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
201
202
        foreach ($errors as $filename => $error) {
203
            $this->output->writeln('<comment>'.++$i.". {$filename}:{$error['line']}".'</comment>');
0 ignored issues
show
Bug introduced by
The property output does not seem to exist. Did you mean ouput?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
204
            $error = preg_replace('~in\s+'.preg_quote($filename).'~', '', $error);
205
            $this->output->writeln("<error> {$error['error']}</error>");
0 ignored issues
show
Bug introduced by
The property output does not seem to exist. Did you mean ouput?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
206
        }
207
    }
208
209
    /**
210
     * Merge options.
211
     *
212
     * @return array
213
     */
214
    protected function mergeOptions()
215
    {
216
        $options = $this->input->getOptions();
217
        $options['path'] = $this->input->getArgument('path') ?: './';
218
219
        $config = [];
220
221
        if (!$this->input->getOption('no-configuration')) {
222
            $filename = rtrim($options['path'], DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'.phplint.yml';
223
224
            if (!$options['configuration'] && file_exists($filename)) {
225
                $options['configuration'] = realpath($filename);
226
            }
227
228
            if (!empty($options['configuration'])) {
229
                $this->output->writeln("<comment>Loaded config from \"{$options['configuration']}\"</comment>\n");
0 ignored issues
show
Bug introduced by
The property output does not seem to exist. Did you mean ouput?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
230
                $config = $this->loadConfiguration($options['configuration']);
231
            }
232
        }
233
234
        $options = array_merge($this->defaults, array_filter($config), array_filter($options));
235
236
        is_array($options['extensions']) || $options['extensions'] = explode(',', $options['extensions']);
237
238
        return $options;
239
    }
240
241
    /**
242
     * Load configuration from yaml.
243
     *
244
     * @param string $path
245
     *
246
     * @return array
247
     */
248
    protected function loadConfiguration($path)
249
    {
250
        try {
251
            $configuration = Yaml::parse(file_get_contents($path));
252
            if (!is_array($configuration)) {
253
                throw new ParseException('Invalid content.', 1);
254
            }
255
256
            return $configuration;
257
        } catch (ParseException $e) {
258
            $this->output->writeln(sprintf('<error>Unable to parse the YAML string: %s<error>', $e->getMessage()));
0 ignored issues
show
Bug introduced by
The property output does not seem to exist. Did you mean ouput?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
259
        }
260
    }
261
}
262