Skip to content

Commit c38b115

Browse files
authored
Merge pull request #3 from pug-php/consecutive-statement-wrapping
Consecutive statement wrapping
2 parents 2c9c83d + f3aa034 commit c38b115

File tree

3 files changed

+255
-49
lines changed

3 files changed

+255
-49
lines changed

.travis.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ php:
1111
- 7.1
1212
- 7.2
1313

14-
matrix:
15-
include:
16-
- php: hhvm
17-
dist: trusty
18-
sudo: required
19-
2014
install:
2115
- travis_retry composer self-update
2216
- travis_retry composer install

src/Phug/Formatter/AbstractTwigFormat.php

Lines changed: 151 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,44 @@
33
namespace Phug\Formatter;
44

55
use Phug\Formatter;
6+
use Phug\Formatter\Element\CodeElement;
67
use Phug\Formatter\Element\MarkupElement;
78
use Phug\Formatter\Format\XhtmlFormat;
89

910
abstract class AbstractTwigFormat extends XhtmlFormat
1011
{
12+
protected $codeBlocks = [];
13+
protected $phpMode = true;
14+
protected $statements = [
15+
'if',
16+
'else',
17+
'elseif',
18+
'for',
19+
'autoescape',
20+
'block',
21+
'do',
22+
'embed',
23+
'extends',
24+
'filter',
25+
'flush',
26+
'from',
27+
'import',
28+
'include',
29+
'macro',
30+
'sandbox',
31+
'set',
32+
'spaceless',
33+
'use',
34+
'verbatim',
35+
'with',
36+
];
37+
1138
public function __construct(Formatter $formatter = null)
1239
{
1340
parent::__construct($formatter);
1441

15-
$nestedCodes = [];
16-
$codeBlocks = [];
42+
$this->codeBlocks = [];
43+
$this->phpMode = true;
1744
$this
1845
->setOptionsRecursive([
1946
'php_token_handlers' => [
@@ -27,48 +54,18 @@ public function __construct(Formatter $formatter = null)
2754
'string_attribute' => '%s',
2855
'expression_in_text' => '%s',
2956
'html_expression_escape' => '%s | e',
30-
'php_handle_code' => function ($input) use (&$formatter, &$nestedCodes, &$codeBlocks) {
31-
$pugModuleName = '$'.$formatter->getOption('dependencies_storage');
32-
if ($this->mustBeHandleWithPhp($input, $pugModuleName)) {
33-
$input = preg_replace_callback(
34-
'/\{\[block:(\d+)\]\}/',
35-
function ($match) use (&$codeBlocks) {
36-
return ' ?>'.$codeBlocks[intval($match[1])].'<?php ';
37-
},
38-
$input
39-
);
40-
41-
return "<?php $input ?>";
42-
}
43-
44-
list($statement, $input) = explode(' ', $input, 2);
45-
$statement = $statement === 'each' ? 'for' : $statement;
46-
$input = $statement.' '.$input;
47-
$hasBlocks = false;
48-
$input = preg_replace_callback(
49-
'/\{\[block:(\d+)\]\}/',
50-
function ($match) use (&$codeBlocks, &$hasBlocks) {
51-
$hasBlocks = true;
52-
53-
return ' %}'.$codeBlocks[intval($match[1])].'{% ';
54-
},
55-
$input
56-
);
57-
if ($hasBlocks) {
58-
$input .= 'end'.$statement;
59-
}
60-
61-
return "{% $input %}";
57+
'php_handle_code' => function ($input) {
58+
return $this->replaceTwigBlocks($input, true);
6259
},
6360
'php_nested_html' => '%s',
64-
'php_block_code' => function ($input) use (&$codeBlocks) {
65-
$id = count($codeBlocks);
66-
$codeBlocks[] = $input;
61+
'php_block_code' => function ($input) {
62+
$id = count($this->codeBlocks);
63+
$this->codeBlocks[] = $input;
6764

6865
return '{[block:'.$id.']}';
6966
},
70-
'php_display_code' => function ($input) use (&$formatter) {
71-
$pugModuleName = '$'.$formatter->getOption('dependencies_storage');
67+
'php_display_code' => function ($input) {
68+
$pugModuleName = '$'.$this->formatter->getOption('dependencies_storage');
7269
if ($this->mustBeHandleWithPhp($input, $pugModuleName)) {
7370
return "<?= $input ?>";
7471
}
@@ -100,4 +97,119 @@ protected function formatAttributes(MarkupElement $element)
10097

10198
return $code;
10299
}
100+
101+
protected function deduplicatePhpTags($commentPattern, &$content, &$childContent)
102+
{
103+
// @codeCoverageIgnoreStart
104+
$content = preg_replace('/\\s\\?>$/', '', $content);
105+
$childContent = preg_replace('/^<\\?(?:php)?\\s/', '', $childContent);
106+
if ($commentPattern &&
107+
($pos = mb_strpos($childContent, $commentPattern)) !== false && (
108+
($end = mb_strpos($childContent, '?>')) === false ||
109+
$pos < $end
110+
) &&
111+
preg_match('/\\}\\s*$/', $content)
112+
) {
113+
$content = preg_replace(
114+
'/\\}\\s*$/',
115+
preg_replace('/\\?><\\?php(?:php)?(\s+\\?><\\?php(?:php)?)*/', '\\\\0', $childContent, 1),
116+
$content
117+
);
118+
$childContent = '';
119+
}
120+
// @codeCoverageIgnoreEnd
121+
}
122+
123+
protected function formatTwigChildElement($child, $previous, &$content, $commentPattern)
124+
{
125+
$childContent = $this->formatter->format($child);
126+
127+
if ($child instanceof CodeElement &&
128+
$previous instanceof CodeElement &&
129+
$previous->isCodeBlock()
130+
) {
131+
$this->deduplicatePhpTags($commentPattern, $content, $childContent);
132+
}
133+
134+
if (preg_match('/^\{% else/', $childContent)) {
135+
$content = preg_replace('/\{% end(if)+ %\}$/', '', $content);
136+
}
137+
138+
return $this->replaceTwigBlocks($childContent, false);
139+
}
140+
141+
protected function formatElementChildren(ElementInterface $element, $indentStep = 1)
142+
{
143+
$indentLevel = $this->formatter->getLevel();
144+
$this->formatter->setLevel($indentLevel + $indentStep);
145+
$content = '';
146+
$previous = null;
147+
$commentPattern = $this->getOption('debug');
148+
foreach ($this->getChildrenIterator($element) as $child) {
149+
if ($child instanceof ElementInterface) {
150+
$content .= $this->formatTwigChildElement($child, $previous, $content, $commentPattern);
151+
$previous = $child;
152+
}
153+
}
154+
$this->formatter->setLevel($indentLevel);
155+
156+
return $content;
157+
}
158+
159+
protected function replaceTwigPhpBlocks($input, $wrap)
160+
{
161+
$input = $this->restoreBlockSubstitutes($input);
162+
$this->phpMode = true;
163+
164+
return $wrap ? "<?php $input ?>" : $input;
165+
}
166+
167+
protected function extractStatement(&$input)
168+
{
169+
$statement = $input;
170+
$parts = explode(' ', $input, 2);
171+
if (count($parts) === 2) {
172+
list($statement, $input) = $parts;
173+
$statement = $statement === 'each' ? 'for' : $statement;
174+
$input = "$statement $input";
175+
}
176+
177+
return $statement;
178+
}
179+
180+
protected function restoreBlockSubstitutes($input, $pattern = ' ?>%s<?php ', &$hasBlocks = null)
181+
{
182+
return preg_replace_callback(
183+
'/\{\[block:(\d+)\]\}/',
184+
function ($match) use ($pattern, &$hasBlocks) {
185+
$hasBlocks = true;
186+
187+
return sprintf($pattern, $this->codeBlocks[intval($match[1])]);
188+
},
189+
$input
190+
);
191+
}
192+
193+
protected function replaceTwigTemplateBlocks($input, $wrap)
194+
{
195+
$this->phpMode = false;
196+
$statement = $this->extractStatement($input);
197+
$hasBlocks = false;
198+
$input = $this->restoreBlockSubstitutes($input, ' %%}%s{%% ', $hasBlocks);
199+
if ($hasBlocks) {
200+
$statement = preg_replace('/^([^\\{]+)\\{.*$/', '$1', $statement);
201+
if (in_array($statement, $this->statements)) {
202+
$input .= 'end'.preg_replace('/^else/', 'if', $statement);
203+
}
204+
}
205+
206+
return $wrap && trim($input) !== '' ? "{% $input %}" : $input;
207+
}
208+
209+
protected function replaceTwigBlocks($input, $wrap)
210+
{
211+
return $this->mustBeHandleWithPhp($input, '$'.$this->formatter->getOption('dependencies_storage'))
212+
? $this->replaceTwigPhpBlocks($input, $wrap)
213+
: $this->replaceTwigTemplateBlocks($input, $wrap);
214+
}
103215
}

0 commit comments

Comments
 (0)