Skip to content

Commit 702e2a4

Browse files
committed
feature #9855 [Twig] Decouple Twig commands from the Famework (GromNaN)
This PR was squashed before being merged into the 2.5-dev branch (closes #9855). Discussion ---------- [Twig] Decouple Twig commands from the Famework I want to use the command `twig:lint` in a Silex project. In this PR, I've moved the class `Symfony\Bundle\TwigBundle\Command\LintCommand` to `Symfony\Bridge\Twig\Command\LintCommand` and removed dependency to the `ContainerAwareCommand`. | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | yes (renamed class) | Deprecations? | no | Tests pass? | yes | Fixed tickets | #9818 (comment) | License | MIT | Doc PR | n/a - [ ] Move command `twig:debug` once merged. - [x] Lazy load twig service Commits ------- 907748d [Twig] Decouple Twig commands from the Famework
2 parents 3203793 + 907748d commit 702e2a4

File tree

6 files changed

+319
-110
lines changed

6 files changed

+319
-110
lines changed

src/Symfony/Bridge/Twig/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
2.5.0
5+
-----
6+
7+
* moved command `twig:lint` from `TwigBundle`
8+
49
2.4.0
510
-----
611

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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\Bridge\Twig\Command;
13+
14+
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
use Symfony\Component\Finder\Finder;
18+
19+
/**
20+
* Command that will validate your template syntax and output encountered errors.
21+
*
22+
* @author Marc Weistroff <marc.weistroff@sensiolabs.com>
23+
* @author Jérôme Tamarelle <jerome@tamarelle.net>
24+
*/
25+
class LintCommand extends Command
26+
{
27+
private $twig;
28+
29+
/**
30+
* {@inheritDoc}
31+
*/
32+
public function __construct($name = 'twig:lint')
33+
{
34+
parent::__construct($name);
35+
}
36+
37+
/**
38+
* Sets the twig environment
39+
*
40+
* @param \Twig_Environment $twig
41+
*/
42+
public function setTwigEnvironment(\Twig_Environment $twig)
43+
{
44+
$this->twig = $twig;
45+
}
46+
47+
/**
48+
* @return \Twig_Environment $twig
49+
*/
50+
protected function getTwigEnvironment()
51+
{
52+
return $this->twig;
53+
}
54+
55+
protected function configure()
56+
{
57+
$this
58+
->setDescription('Lints a template and outputs encountered errors')
59+
->addArgument('filename')
60+
->setHelp(<<<EOF
61+
The <info>%command.name%</info> command lints a template and outputs to stdout
62+
the first encountered syntax error.
63+
64+
<info>php %command.full_name% filename</info>
65+
66+
The command gets the contents of <comment>filename</comment> and validates its syntax.
67+
68+
<info>php %command.full_name% dirname</info>
69+
70+
The command finds all twig templates in <comment>dirname</comment> and validates the syntax
71+
of each Twig template.
72+
73+
<info>cat filename | php %command.full_name%</info>
74+
75+
The command gets the template contents from stdin and validates its syntax.
76+
EOF
77+
)
78+
;
79+
}
80+
81+
protected function execute(InputInterface $input, OutputInterface $output)
82+
{
83+
$twig = $this->getTwigEnvironment();
84+
$template = null;
85+
$filename = $input->getArgument('filename');
86+
87+
if (!$filename) {
88+
if (0 !== ftell(STDIN)) {
89+
throw new \RuntimeException("Please provide a filename or pipe template content to stdin.");
90+
}
91+
92+
while (!feof(STDIN)) {
93+
$template .= fread(STDIN, 1024);
94+
}
95+
96+
return $this->validateTemplate($twig, $output, $template);
97+
}
98+
99+
$files = $this->findFiles($filename);
100+
101+
$errors = 0;
102+
foreach ($files as $file) {
103+
$errors += $this->validateTemplate($twig, $output, file_get_contents($file), $file);
104+
}
105+
106+
return $errors > 0 ? 1 : 0;
107+
}
108+
109+
protected function findFiles($filename)
110+
{
111+
if (is_file($filename)) {
112+
return array($filename);
113+
} elseif (is_dir($filename)) {
114+
return Finder::create()->files()->in($filename)->name('*.twig');
115+
}
116+
117+
throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename));
118+
}
119+
120+
protected function validateTemplate(\Twig_Environment $twig, OutputInterface $output, $template, $file = null)
121+
{
122+
try {
123+
$twig->parse($twig->tokenize($template, $file ? (string) $file : null));
124+
$output->writeln('<info>OK</info>'.($file ? sprintf(' in %s', $file) : ''));
125+
} catch (\Twig_Error $e) {
126+
$this->renderException($output, $template, $e, $file);
127+
128+
return 1;
129+
}
130+
131+
return 0;
132+
}
133+
134+
protected function renderException(OutputInterface $output, $template, \Twig_Error $exception, $file = null)
135+
{
136+
$line = $exception->getTemplateLine();
137+
$lines = $this->getContext($template, $line);
138+
139+
if ($file) {
140+
$output->writeln(sprintf("<error>KO</error> in %s (line %s)", $file, $line));
141+
} else {
142+
$output->writeln(sprintf("<error>KO</error> (line %s)", $line));
143+
}
144+
145+
foreach ($lines as $no => $code) {
146+
$output->writeln(sprintf(
147+
"%s %-6s %s",
148+
$no == $line ? '<error>>></error>' : ' ',
149+
$no,
150+
$code
151+
));
152+
if ($no == $line) {
153+
$output->writeln(sprintf('<error>>> %s</error> ', $exception->getRawMessage()));
154+
}
155+
}
156+
}
157+
158+
protected function getContext($template, $line, $context = 3)
159+
{
160+
$lines = explode("\n", $template);
161+
162+
$position = max(0, $line - $context);
163+
$max = min(count($lines), $line - 1 + $context);
164+
165+
$result = array();
166+
while ($position < $max) {
167+
$result[$position + 1] = $lines[$position];
168+
$position++;
169+
}
170+
171+
return $result;
172+
}
173+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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\Bridge\Twig\Tests\Command;
13+
14+
use Symfony\Component\Console\Tester\CommandTester;
15+
use Symfony\Component\Console\Application;
16+
use Symfony\Bridge\Twig\Command\LintCommand;
17+
18+
/**
19+
* @covers \Symfony\Bridge\Twig\Command\LintCommand
20+
*/
21+
class LintCommandTest extends \PHPUnit_Framework_TestCase
22+
{
23+
private $files;
24+
25+
public function testLintCorrectFile()
26+
{
27+
$tester = $this->createCommandTester();
28+
$filename = $this->createFile('{{ foo }}');
29+
30+
$ret = $tester->execute(array('filename' => $filename));
31+
32+
$this->assertEquals(0, $ret, 'Returns 0 in case of success');
33+
$this->assertRegExp('/^OK in /', $tester->getDisplay());
34+
}
35+
36+
public function testLintIncorrectFile()
37+
{
38+
$tester = $this->createCommandTester();
39+
$filename = $this->createFile('{{ foo');
40+
41+
$ret = $tester->execute(array('filename' => $filename));
42+
43+
$this->assertEquals(1, $ret, 'Returns 1 in case of error');
44+
$this->assertRegExp('/^KO in /', $tester->getDisplay());
45+
}
46+
47+
/**
48+
* @expectedException \RuntimeException
49+
*/
50+
public function testLintFileNotReadable()
51+
{
52+
$tester = $this->createCommandTester();
53+
$filename = $this->createFile('');
54+
unlink($filename);
55+
56+
$ret = $tester->execute(array('filename' => $filename));
57+
}
58+
59+
/**
60+
* @return CommandTester
61+
*/
62+
private function createCommandTester()
63+
{
64+
$twig = new \Twig_Environment(new \Twig_Loader_Filesystem());
65+
66+
$command = new LintCommand();
67+
$command->setTwigEnvironment($twig);
68+
69+
$application = new Application();
70+
$application->add($command);
71+
$command = $application->find('twig:lint');
72+
73+
return new CommandTester($command);
74+
}
75+
76+
/**
77+
* @return string Path to the new file
78+
*/
79+
private function createFile($content)
80+
{
81+
$filename = tempnam(sys_get_temp_dir(), 'sf-');
82+
file_put_contents($filename, $content);
83+
84+
$this->files[] = $filename;
85+
86+
return $filename;
87+
}
88+
89+
public function setUp()
90+
{
91+
$this->files = array();
92+
}
93+
94+
public function tearDown()
95+
{
96+
foreach ($this->files as $file) {
97+
if (file_exists($file)) {
98+
unlink($file);
99+
}
100+
}
101+
}
102+
}

src/Symfony/Bridge/Twig/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"symfony/translation": "~2.2",
2929
"symfony/yaml": "~2.0",
3030
"symfony/security": "~2.4",
31-
"symfony/stopwatch": "~2.2"
31+
"symfony/stopwatch": "~2.2",
32+
"symfony/console": "~2.2"
3233
},
3334
"suggest": {
3435
"symfony/form": "For using the FormExtension",

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy