diff --git a/src/Symfony/Bridge/Twig/Command/LintCommand.php b/src/Symfony/Bridge/Twig/Command/LintCommand.php index 5ebd255450d4c..6636f6cce0e82 100644 --- a/src/Symfony/Bridge/Twig/Command/LintCommand.php +++ b/src/Symfony/Bridge/Twig/Command/LintCommand.php @@ -11,8 +11,13 @@ namespace Symfony\Bridge\Twig\Command; +if (!defined('JSON_PRETTY_PRINT')) { + define('JSON_PRETTY_PRINT', 128); +} + use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\Finder; @@ -56,23 +61,24 @@ protected function configure() { $this ->setDescription('Lints a template and outputs encountered errors') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') ->addArgument('filename') ->setHelp(<<%command.name% command lints a template and outputs to stdout +The %command.name% command lints a template and outputs to STDOUT the first encountered syntax error. +You can validate the syntax of a file: + php %command.full_name% filename -The command gets the contents of filename and validates its syntax. +Or of a whole directory: php %command.full_name% dirname +php %command.full_name% dirname --format=json -The command finds all twig templates in dirname and validates the syntax -of each Twig template. +You can also pass the template contents from STDIN: cat filename | php %command.full_name% - -The command gets the template contents from stdin and validates its syntax. EOF ) ; @@ -81,29 +87,27 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { $twig = $this->getTwigEnvironment(); - $template = null; $filename = $input->getArgument('filename'); if (!$filename) { if (0 !== ftell(STDIN)) { - throw new \RuntimeException("Please provide a filename or pipe template content to stdin."); + throw new \RuntimeException("Please provide a filename or pipe template content to STDIN."); } + $template = ''; while (!feof(STDIN)) { $template .= fread(STDIN, 1024); } - return $this->validateTemplate($twig, $output, $template); + return $this->display($input, $output, array($this->validate($twig, $template))); } - $files = $this->findFiles($filename); - - $errors = 0; - foreach ($files as $file) { - $errors += $this->validateTemplate($twig, $output, file_get_contents($file), $file); + $filesInfo = array(); + foreach ($this->findFiles($filename) as $file) { + $filesInfo[] = $this->validate($twig, file_get_contents($file), $file); } - return $errors > 0 ? 1 : 0; + return $this->display($input, $output, $filesInfo); } protected function findFiles($filename) @@ -117,24 +121,69 @@ protected function findFiles($filename) throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename)); } - protected function validateTemplate(\Twig_Environment $twig, OutputInterface $output, $template, $file = null) + private function validate(\Twig_Environment $twig, $template, $file = null) { try { $twig->parse($twig->tokenize($template, $file ? (string) $file : null)); - $output->writeln('OK'.($file ? sprintf(' in %s', $file) : '')); } catch (\Twig_Error $e) { - $this->renderException($output, $template, $e, $file); + return array('template' => $template, 'file' => $file, 'valid' => false, 'exception' => $e); + } + + return array('template' => $template, 'file' => $file, 'valid' => true); + } + + private function display(InputInterface $input, OutputInterface $output, $files) + { + switch ($input->getOption('format')) { + case 'txt': + return $this->displayTxt($output, $files); + case 'json': + return $this->displayJson($output, $files); + default: + throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); + } + } - return 1; + private function displayTxt(OutputInterface $output, $filesInfo) + { + $errors = 0; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $output->isVerbose()) { + $output->writeln('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + $errors++; + $this->renderException($output, $info['template'], $info['exception'], $info['file']); + } } - return 0; + $output->writeln(sprintf('%d/%d valid files', count($filesInfo) - $errors, count($filesInfo))); + + return min($errors, 1); + } + + private function displayJson(OutputInterface $output, $filesInfo) + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + unset($v['template']); + if (!$v['valid']) { + $v['message'] = $v['exception']->getMessage(); + unset($v['exception']); + $errors++; + } + }); + + $output->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT)); + + return min($errors, 1); } - protected function renderException(OutputInterface $output, $template, \Twig_Error $exception, $file = null) + private function renderException(OutputInterface $output, $template, \Twig_Error $exception, $file = null) { $line = $exception->getTemplateLine(); - $lines = $this->getContext($template, $line); if ($file) { $output->writeln(sprintf("KO in %s (line %s)", $file, $line)); @@ -142,7 +191,7 @@ protected function renderException(OutputInterface $output, $template, \Twig_Err $output->writeln(sprintf("KO (line %s)", $line)); } - foreach ($lines as $no => $code) { + foreach ($this->getContext($template, $line) as $no => $code) { $output->writeln(sprintf( "%s %-6s %s", $no == $line ? '>>' : ' ', @@ -155,7 +204,7 @@ protected function renderException(OutputInterface $output, $template, \Twig_Err } } - protected function getContext($template, $line, $context = 3) + private function getContext($template, $line, $context = 3) { $lines = explode("\n", $template); diff --git a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php index da4460ccbe938..3fe54cbabf547 100644 --- a/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php +++ b/src/Symfony/Bridge/Twig/Tests/Command/LintCommandTest.php @@ -11,9 +11,10 @@ namespace Symfony\Bridge\Twig\Tests\Command; -use Symfony\Component\Console\Tester\CommandTester; -use Symfony\Component\Console\Application; use Symfony\Bridge\Twig\Command\LintCommand; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Tester\CommandTester; /** * @covers \Symfony\Bridge\Twig\Command\LintCommand @@ -27,7 +28,7 @@ public function testLintCorrectFile() $tester = $this->createCommandTester(); $filename = $this->createFile('{{ foo }}'); - $ret = $tester->execute(array('filename' => $filename)); + $ret = $tester->execute(array('filename' => $filename), array('verbosity' => OutputInterface::VERBOSITY_VERBOSE)); $this->assertEquals(0, $ret, 'Returns 0 in case of success'); $this->assertRegExp('/^OK in /', $tester->getDisplay()); diff --git a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php index 79a765d768e5f..95c892cb0433b 100644 --- a/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php +++ b/src/Symfony/Bundle/TwigBundle/Command/LintCommand.php @@ -57,10 +57,9 @@ protected function configure() $this->getHelp().<<php %command.full_name% @AcmeMyBundle +Or all template files in a bundle: -The command finds all twig templates in the AcmeMyBundle bundle and validates -the syntax of each Twig template. +php %command.full_name% @AcmeDemoBundle EOF ) ;