Completed
Push — master ( d21053...c59b30 )
by
unknown
08:11
created

LintCommand   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 207
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 0
loc 207
rs 9.6
c 0
b 0
f 0
wmc 35
lcom 1
cbo 2

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A configure() 0 28 1
A execute() 0 26 5
A validate() 0 10 2
A display() 0 11 3
B displayTxt() 0 23 8
A displayJson() 0 15 2
A getFiles() 0 16 4
A getStdin() 0 13 3
A getParser() 0 8 2
A getDirectoryIterator() 0 15 2
A isReadable() 0 12 2
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Yaml\Command;
13
14
use Symfony\Component\Console\Command\Command;
15
use Symfony\Component\Console\Input\InputInterface;
16
use Symfony\Component\Console\Input\InputOption;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Symfony\Component\Console\Style\SymfonyStyle;
19
use Symfony\Component\Yaml\Exception\ParseException;
20
use Symfony\Component\Yaml\Parser;
21
22
/**
23
 * Validates YAML files syntax and outputs encountered errors.
24
 *
25
 * @author Grégoire Pineau <[email protected]>
26
 * @author Robin Chalas <[email protected]>
27
 */
28
class LintCommand extends Command
29
{
30
    private $parser;
31
    private $format;
32
    private $displayCorrectFiles;
33
    private $directoryIteratorProvider;
34
    private $isReadableProvider;
35
36
    public function __construct($name = null, $directoryIteratorProvider = null, $isReadableProvider = null)
37
    {
38
        parent::__construct($name);
39
40
        $this->directoryIteratorProvider = $directoryIteratorProvider;
41
        $this->isReadableProvider = $isReadableProvider;
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     */
47
    protected function configure()
48
    {
49
        $this
50
            ->setName('lint:yaml')
51
            ->setDescription('Lints a file and outputs encountered errors')
52
            ->addArgument('filename', null, 'A file or a directory or STDIN')
53
            ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
54
            ->setHelp(<<<EOF
55
The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT
56
the first encountered syntax error.
57
58
You can validates YAML contents passed from STDIN:
59
60
  <info>cat filename | php %command.full_name%</info>
61
62
You can also validate the syntax of a file:
63
64
  <info>php %command.full_name% filename</info>
65
66
Or of a whole directory:
67
68
  <info>php %command.full_name% dirname</info>
69
  <info>php %command.full_name% dirname --format=json</info>
70
71
EOF
72
            )
73
        ;
74
    }
75
76
    protected function execute(InputInterface $input, OutputInterface $output)
77
    {
78
        $io = new SymfonyStyle($input, $output);
79
        $filename = $input->getArgument('filename');
80
        $this->format = $input->getOption('format');
81
        $this->displayCorrectFiles = $output->isVerbose();
82
83
        if (!$filename) {
84
            if (!$stdin = $this->getStdin()) {
85
                throw new \RuntimeException('Please provide a filename or pipe file content to STDIN.');
86
            }
87
88
            return $this->display($io, array($this->validate($stdin)));
89
        }
90
91
        if (!$this->isReadable($filename)) {
92
            throw new \RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
93
        }
94
95
        $filesInfo = array();
96
        foreach ($this->getFiles($filename) as $file) {
97
            $filesInfo[] = $this->validate(file_get_contents($file), $file);
98
        }
99
100
        return $this->display($io, $filesInfo);
101
    }
102
103
    private function validate($content, $file = null)
104
    {
105
        try {
106
            $this->getParser()->parse($content);
107
        } catch (ParseException $e) {
108
            return array('file' => $file, 'valid' => false, 'message' => $e->getMessage());
109
        }
110
111
        return array('file' => $file, 'valid' => true);
112
    }
113
114
    private function display(SymfonyStyle $io, array $files)
115
    {
116
        switch ($this->format) {
117
            case 'txt':
118
                return $this->displayTxt($io, $files);
119
            case 'json':
120
                return $this->displayJson($io, $files);
121
            default:
122
                throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
123
        }
124
    }
125
126
    private function displayTxt(SymfonyStyle $io, array $filesInfo)
127
    {
128
        $countFiles = count($filesInfo);
129
        $erroredFiles = 0;
130
131
        foreach ($filesInfo as $info) {
132
            if ($info['valid'] && $this->displayCorrectFiles) {
133
                $io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
134
            } elseif (!$info['valid']) {
135
                ++$erroredFiles;
136
                $io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
137
                $io->text(sprintf('<error> >> %s</error>', $info['message']));
138
            }
139
        }
140
141
        if ($erroredFiles === 0) {
142
            $io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles));
143
        } else {
144
            $io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles));
145
        }
146
147
        return min($erroredFiles, 1);
148
    }
149
150
    private function displayJson(SymfonyStyle $io, array $filesInfo)
151
    {
152
        $errors = 0;
153
154
        array_walk($filesInfo, function (&$v) use (&$errors) {
155
            $v['file'] = (string) $v['file'];
156
            if (!$v['valid']) {
157
                ++$errors;
158
            }
159
        });
160
161
        $io->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
162
163
        return min($errors, 1);
164
    }
165
166
    private function getFiles($fileOrDirectory)
167
    {
168
        if (is_file($fileOrDirectory)) {
169
            yield new \SplFileInfo($fileOrDirectory);
170
171
            return;
172
        }
173
174
        foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
175
            if (!in_array($file->getExtension(), array('yml', 'yaml'))) {
176
                continue;
177
            }
178
179
            yield $file;
180
        }
181
    }
182
183
    private function getStdin()
184
    {
185
        if (0 !== ftell(STDIN)) {
186
            return;
187
        }
188
189
        $inputs = '';
190
        while (!feof(STDIN)) {
191
            $inputs .= fread(STDIN, 1024);
192
        }
193
194
        return $inputs;
195
    }
196
197
    private function getParser()
198
    {
199
        if (!$this->parser) {
200
            $this->parser = new Parser();
201
        }
202
203
        return $this->parser;
204
    }
205
206
    private function getDirectoryIterator($directory)
207
    {
208
        $default = function ($directory) {
209
            return new \RecursiveIteratorIterator(
210
                new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
211
                \RecursiveIteratorIterator::LEAVES_ONLY
212
            );
213
        };
214
215
        if (null !== $this->directoryIteratorProvider) {
216
            return call_user_func($this->directoryIteratorProvider, $directory, $default);
217
        }
218
219
        return $default($directory);
220
    }
221
222
    private function isReadable($fileOrDirectory)
223
    {
224
        $default = function ($fileOrDirectory) {
225
            return is_readable($fileOrDirectory);
226
        };
227
228
        if (null !== $this->isReadableProvider) {
229
            return call_user_func($this->isReadableProvider, $fileOrDirectory, $default);
230
        }
231
232
        return $default($fileOrDirectory);
233
    }
234
}
235