8000 feature #19139 [FrameworkBundle][Yaml] Move YamlLintCommand to the Ya… · symfony/symfony@6ede0b0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 6ede0b0

Browse files
committed
feature #19139 [FrameworkBundle][Yaml] Move YamlLintCommand to the Yaml component (chalasr)
This PR was merged into the 3.2-dev branch. Discussion ---------- [FrameworkBundle][Yaml] Move YamlLintCommand to the Yaml component | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #18987 | License | MIT | Doc PR | ~ See #18987 for the use case. Also I see several things that can be simplified/optimized in the original command, but I think it would be better to propose the changes in a next PR and just propose the exact changes needed to move it outside of the framework. If all should be done here, let me know before reviewing it. Commits ------- 33402ea Add Yaml LintCommand
2 parents e949d34 + 33402ea commit 6ede0b0

File tree

7 files changed

+530
-129
lines changed

7 files changed

+530
-129
lines changed

src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function testLintCorrectFile()
2828
$ret = $tester->execute(array('filename' => array($filename)), array('verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false));
2929

3030
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
31-
$this->assertRegExp('/^\/\/ OK in /', trim($tester->getDisplay()));
31+
$this->assertContains('OK in', trim($tester->getDisplay()));
3232
}
3333

3434
public function testLintIncorrectFile()

src/Symfony/Bundle/FrameworkBundle/Command/YamlLintCommand.php

Lines changed: 17 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -11,155 +11,45 @@
1111

1212
namespace Symfony\Bundle\FrameworkBundle\Command;
1313

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\Finder\Finder;
20-
use Symfony\Component\Yaml\Exception\ParseException;
21-
use Symfony\Component\Yaml\Parser;
14+
use Symfony\Component\Yaml\Command\LintCommand as BaseLintCommand;
2215

2316
/**
2417
* Validates YAML files syntax and outputs encountered errors.
2518
*
2619
* @author Grégoire Pineau <lyrixx@lyrixx.info>
20+
* @author Robin Chalas <robin.chalas@gmail.com>
2721
*/
28-
class YamlLintCommand extends Command
22+
class YamlLintCommand extends BaseLintCommand
2923
{
24+
/**
25+
* {@inheritdoc}
26+
*/
3027
protected function configure()
3128
{
32-
$this
33-
->setName('lint:yaml')
34-
->setDescription('Lints a file and outputs encountered errors')
35-
->addArgument('filename', null, 'A file or a directory or STDIN')
36-
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
37-
->setHelp(<<<EOF
38-
The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT
39-
the first encountered syntax error.
29+
parent::configure();
4030

41-
You can validate the syntax of a file:
31+
$this->setHelp(
32+
$this->getHelp().<<<EOF
4233
43-
<info>php %command.full_name% filename</info>
44-
45-
Or of a whole directory:
46-
47-
<info>php %command.full_name% dirname</info>
48-
<info>php %command.full_name% dirname --format=json</info>
49-
50-
Or all YAML files in a bundle:
34+
Or find all files in a bundle:
5135
5236
<info>php %command.full_name% @AcmeDemoBundle</info>
5337
54-
You can also pass the YAML contents from STDIN:
55-
56-
<info>cat filename | php %command.full_name%</info>
57-
5838
EOF
59-
)
60-
;
61-
}
62-
63-
protected function execute(InputInterface $input, OutputInterface $output)
64-
{
65-
$io = new SymfonyStyle($input, $output);
66-
$filename = $input->getArgument('filename');
67-
68-
if (!$filename) {
69-
if (0 !== ftell(STDIN)) {
70-
throw new \RuntimeException('Please provide a filename or pipe file content to STDIN.');
71-
}
72-
73-
$content = '';
74-
while (!feof(STDIN)) {
75-
$content .= fread(STDIN, 1024);
76-
}
77-
78-
return $this->display($input, $output, $io, array($this->validate($content)));
79-
}
80-
81-
if (0 !== strpos($filename, '@') && !is_readable($filename)) {
82-
throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename));
83-
}
84-
85-
$files = array();
86-
if (is_file($filename)) {
87-
$files = array($filename);
88-
} elseif (is_dir($filename)) {
89-
$files = Finder::create()->files()->in($filename)->name('*.yml');
90-
} else {
91-
$dir = $this->getApplication()->getKernel()->locateResource($filename);
92-
$files = Finder::create()->files()->in($dir)->name('*.yml');
93-
}
94-
95-
$filesInfo = array();
96-
foreach ($files as $file) {
97-
$filesInfo[] = $this->validate(file_get_contents($file), $file);
98-
}
99-
100-
return $this->display($input, $output, $io, $filesInfo);
39+
);
10140
}
10241

103-
private function validate($content, $file = null)
42+
protected function getDirectoryIterator($directory)
10443
{
105-
$parser = new Parser();
106-
try {
107-
$parser->parse($content);
108-
} catch (ParseException $e) {
109-
return array('file' => $file, 'valid' => false, 'message' => $e->getMessage());
44+
if (!is_dir($directory)) {
45+
$directory = $this->getApplication()->getKernel()->locateResource($directory);
11046
}
11147

112-
return array('file' => $file, 'valid' => true);
48+
return parent::getDirectoryIterator($directory);
11349
}
11450

115-
private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, $files)
51+
protected function isReadable($fileOrDirectory)
11652
{
117-
switch ($input->getOption('format')) {
118-
case 'txt':
119-
return $this->displayTxt($output, $io, $files);
120-
case 'json':
121-
return $this->displayJson($io, $files);
122-
default:
123-
throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format')));
124-
}
125-
}
126-
127-
private function displayTxt(OutputInterface $output, SymfonyStyle $io, $filesInfo)
128-
{
129-
$errors = 0;
130-
131-
foreach ($filesInfo as $info) {
132-
if ($info['valid'] && $output->isVerbose()) {
133-
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
134-
} elseif (!$info['valid']) {
135-
++$errors;
136-
$io->text(sprintf('<error> ERROR </error> in %s', $info['file']));
137-
$io->text(sprintf('<error> >> %s</error>', $info['message']));
138-
}
139-
}
140-
141-
if ($errors === 0) {
142-
$io->success(sprintf('All %d YAML files contain valid syntax.', count($filesInfo)));
143-
} else {
144-
$io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.', count($filesInfo) - $errors, $errors));
145-
}
146-
147-
return min($errors, 1);
148-
}
149-
150-
private function displayJson(OutputInterface $output, $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-
$output->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT));
162-
163-
return min($errors, 1);
53+
return 0 === strpos($fileOrDirectory, '@') || parent::isReadable($fileOrDirectory);
16454
}
16555
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Bundle\FrameworkBundle\Tests\Command;
13+
14+
use Symfony\Bundle\FrameworkBundle\Command\YamlLintCommand;
15+
use Symfony\Bundle\FrameworkBundle\Console\Application;
16+
use Symfony\Component\Console\Application as BaseApplication;
17+
use Symfony\Component\Console\Output\OutputInterface;
18+
use Symfony\Component\Console\Tester\CommandTester;
19+
use Symfony\Component\Console\Helper\HelperSet;
20+
use Symfony\Component\Console\Input\InputDefinition;
21+
use Symfony\Component\HttpKernel\KernelInterface;
22+
23+
/**
24+
* Tests the YamlLintCommand.
25+
*
26+
* @author Robin Chalas <robin.chalas@gmail.com>
27+
*/
28+
class YamlLintCommandTest extends \PHPUnit_Framework_TestCase
29+
{
30+
private $files;
31+
32+
public function testLintCorrectFile()
33+
{
34+
$tester = $this->createCommandTester();
35+
$filename = $this->createFile('foo: bar');
36+
37+
$tester->execute(
38+
array('filename' => $filename),
39+
array('verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false)
40+
);
41+
42+
$this->assertEquals(0, $tester->getStatusCode(), 'Returns 0 in case of success');
43+
$this->assertContains('OK', trim($tester->getDisplay()));
44+
}
45+
46+
public function testLintIncorrectFile()
47+
{
48+
$incorrectContent = '
49+
foo:
50+
bar';
51+
$tester = $this->createCommandTester();
52+
$filename = $this->createFile($incorrectContent);
53+
54+
$tester->execute(array('filename' => $filename), array('decorated' => false));
55+
56+
$this->assertEquals(1, $tester->getStatusCode(), 'Returns 1 in case of error');
57+
$this->assertContains('Unable to parse at line 3 (near "bar").', trim($tester->getDisplay()));
58+
}
59+
60+
/**
61+
* @expectedException \RuntimeException
62+
*/
63+
public function testLintFileNotReadable()
64+
{
65+
$tester = $this->createCommandTester();
66+
$filename = $this->createFile('');
67+
unlink($filename);
68+
69+
$tester->execute(array('filename' => $filename), array('decorated' => false));
70+
}
71+
72+
public function testGetHelp()
73+
{
74+
$command = new YamlLintCommand();
75+
$expected = <<<EOF
76+
The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT
77+
the first encountered syntax error.
78+
79+
You can validates YAML contents passed from STDIN:
80+
81+
<info>cat filename | php %command.full_name%</info>
82+
83+
You can also validate the syntax of a file:
84+
85+
<info>php %command.full_name% filename</info>
86+
87+
Or of a whole directory:
88+
89+
<info>php %command.full_name% dirname</info>
90+
<info>php %command.full_name% dirname --format=json</info>
91+
92+
Or find all files in a bundle:
93+
94+
<info>php %command.full_name% @AcmeDemoBundle</info>
95+
96+
EOF;
97+
98+
$this->assertEquals($expected, $command->getHelp());
99+
}
100+
101+
public function testLintFilesFromBundleDirectory()
102+
{
103+
$tester = $this->createCommandTester($this->getKernelAwareApplicationMock());
104+
$tester->execute(
105+
array('filename' => '@AppBundle/Resources'),
106+
array('verbosity' => OutputInterface::VERBOSITY_VERBOSE, 'decorated' => false)
107+
);
108+
109+
$this->assertEquals(0, $tester->getStatusCode(), 'Returns 0 in case of success');
110+
$this->assertContains('[OK] All 0 YAML files contain valid syntax', trim($tester->getDisplay()));
111+
}
112+
113+
/**
114+
* @return string Path to the new file
115+
*/
116+
private function createFile($content)
117+
{
118+
$filename = tempnam(sys_get_temp_dir(), 'sf-');
119+
file_put_contents($filename, $content);
120+
121+
$this->files[] = $filename;
122+
123+
return $filename;
124+
}
125+
126+
/**
127+
* @return CommandTester
128+
*/
129+
private function createCommandTester($application = null)
130+
{
131+
if (!$application) {
132+
$application = new BaseApplication();
133+
$application->add(new YamlLintCommand());
134+
}
135+
136+
$command = $application->find('lint:yaml');
137+
138+
if ($application) {
139+
$command->setApplication($application);
140+
}
141+
142+
return new CommandTester($command);
143+
}
144+
145+
private function getKernelAwareApplicationMock()
146+
{
147+
$kernel = $this->getMockBuilder(KernelInterface::class)
148+
->disableOriginalConstructor()
149+
->getMock();
150+
151+
$kernel
152+
->expects($this->once())
153+
->method('locateResource')
154+
->with('@AppBundle/Resources')
155+
->willReturn(sys_get_temp_dir());
156+
157+
$application = $this->getMockBuilder(Application::class)
158+
->disableOriginalConstructor()
159+
->getMock();
160+
161+
$application
162+
->expects($this->once())
163+
->method('getKernel')
164+
->willReturn($kernel);
165+
166+
$application
167+
->expects($this->once())
168+
->method('getHelperSet')
169+
->willReturn(new HelperSet());
170+
171+
$application
172+
->expects($this->any())
173+
->method('getDefinition')
174+
->willReturn(new InputDefinition());
175+
176+
$application
177+
->expects($this->once())
178+
->method('find')
179+
->with('lint:yaml')
180+
->willReturn(new YamlLintCommand());
181+
182+
return $application;
183+
}
184+
185+
protected function setUp()
186+
{
187+
$this->files = array();
188+
}
189+
190+
protected function tearDown()
191+
{
192+
foreach ($this->files as $file) {
193+
if (file_exists($file)) {
194+
unlink($file);
195+
}
196+
}
197+
}
198+
}

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"symfony/process": "~2.8|~3.0",
5050
"symfony/serializer": "~2.8|^3.0",
5151
"symfony/validator": "~3.1",
52-
"symfony/yaml": "~2.8|~3.0",
52+
"symfony/yaml": "~3.2",
5353
"symfony/property-info": "~2.8|~3.0",
5454
"phpdocumentor/reflection-docblock": "^3.0",
5555
"twig/twig": "~1.23|~2.0"

0 commit comments

Comments
 (0)
0