Thanks to visit codestin.com
Credit goes to github.com

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 <[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\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 <[email protected]>
23+
* @author Jérôme Tamarelle <[email protected]>
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 <[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\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)