Merge branch 'MDL-76220-master' of https://github.com/ferranrecio/moodle
[moodle.git] / lib / scssphp / Formatter.php
blob6137dc6507137122190fc7ef23ee8f7cb69751a6
1 <?php
3 /**
4 * SCSSPHP
6 * @copyright 2012-2020 Leaf Corcoran
8 * @license http://opensource.org/licenses/MIT MIT
10 * @link http://scssphp.github.io/scssphp
13 namespace ScssPhp\ScssPhp;
15 use ScssPhp\ScssPhp\Formatter\OutputBlock;
16 use ScssPhp\ScssPhp\SourceMap\SourceMapGenerator;
18 /**
19 * Base formatter
21 * @author Leaf Corcoran <leafot@gmail.com>
23 * @internal
25 abstract class Formatter
27 /**
28 * @var int
30 public $indentLevel;
32 /**
33 * @var string
35 public $indentChar;
37 /**
38 * @var string
40 public $break;
42 /**
43 * @var string
45 public $open;
47 /**
48 * @var string
50 public $close;
52 /**
53 * @var string
55 public $tagSeparator;
57 /**
58 * @var string
60 public $assignSeparator;
62 /**
63 * @var bool
65 public $keepSemicolons;
67 /**
68 * @var \ScssPhp\ScssPhp\Formatter\OutputBlock
70 protected $currentBlock;
72 /**
73 * @var int
75 protected $currentLine;
77 /**
78 * @var int
80 protected $currentColumn;
82 /**
83 * @var \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null
85 protected $sourceMapGenerator;
87 /**
88 * @var string
90 protected $strippedSemicolon;
92 /**
93 * Initialize formatter
95 * @api
97 abstract public function __construct();
99 /**
100 * Return indentation (whitespace)
102 * @return string
104 protected function indentStr()
106 return '';
110 * Return property assignment
112 * @api
114 * @param string $name
115 * @param mixed $value
117 * @return string
119 public function property($name, $value)
121 return rtrim($name) . $this->assignSeparator . $value . ';';
125 * Return custom property assignment
126 * differs in that you have to keep spaces in the value as is
128 * @api
130 * @param string $name
131 * @param mixed $value
133 * @return string
135 public function customProperty($name, $value)
137 return rtrim($name) . trim($this->assignSeparator) . $value . ';';
141 * Output lines inside a block
143 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
145 * @return void
147 protected function blockLines(OutputBlock $block)
149 $inner = $this->indentStr();
150 $glue = $this->break . $inner;
152 $this->write($inner . implode($glue, $block->lines));
154 if (! empty($block->children)) {
155 $this->write($this->break);
160 * Output block selectors
162 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
164 * @return void
166 protected function blockSelectors(OutputBlock $block)
168 assert(! empty($block->selectors));
170 $inner = $this->indentStr();
172 $this->write($inner
173 . implode($this->tagSeparator, $block->selectors)
174 . $this->open . $this->break);
178 * Output block children
180 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
182 * @return void
184 protected function blockChildren(OutputBlock $block)
186 foreach ($block->children as $child) {
187 $this->block($child);
192 * Output non-empty block
194 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
196 * @return void
198 protected function block(OutputBlock $block)
200 if (empty($block->lines) && empty($block->children)) {
201 return;
204 $this->currentBlock = $block;
206 $pre = $this->indentStr();
208 if (! empty($block->selectors)) {
209 $this->blockSelectors($block);
211 $this->indentLevel++;
214 if (! empty($block->lines)) {
215 $this->blockLines($block);
218 if (! empty($block->children)) {
219 $this->blockChildren($block);
222 if (! empty($block->selectors)) {
223 $this->indentLevel--;
225 if (! $this->keepSemicolons) {
226 $this->strippedSemicolon = '';
229 if (empty($block->children)) {
230 $this->write($this->break);
233 $this->write($pre . $this->close . $this->break);
238 * Test and clean safely empty children
240 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
242 * @return bool
244 protected function testEmptyChildren($block)
246 $isEmpty = empty($block->lines);
248 if ($block->children) {
249 foreach ($block->children as $k => &$child) {
250 if (! $this->testEmptyChildren($child)) {
251 $isEmpty = false;
252 continue;
255 if ($child->type === Type::T_MEDIA || $child->type === Type::T_DIRECTIVE) {
256 $child->children = [];
257 $child->selectors = null;
262 return $isEmpty;
266 * Entry point to formatting a block
268 * @api
270 * @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree
271 * @param \ScssPhp\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
273 * @return string
275 public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
277 $this->sourceMapGenerator = null;
279 if ($sourceMapGenerator) {
280 $this->currentLine = 1;
281 $this->currentColumn = 0;
282 $this->sourceMapGenerator = $sourceMapGenerator;
285 $this->testEmptyChildren($block);
287 ob_start();
289 try {
290 $this->block($block);
291 } catch (\Exception $e) {
292 ob_end_clean();
293 throw $e;
294 } catch (\Throwable $e) {
295 ob_end_clean();
296 throw $e;
299 $out = ob_get_clean();
300 assert($out !== false);
302 return $out;
306 * Output content
308 * @param string $str
310 * @return void
312 protected function write($str)
314 if (! empty($this->strippedSemicolon)) {
315 echo $this->strippedSemicolon;
317 $this->strippedSemicolon = '';
321 * Maybe Strip semi-colon appended by property(); it's a separator, not a terminator
322 * will be striped for real before a closing, otherwise displayed unchanged starting the next write
324 if (
325 ! $this->keepSemicolons &&
326 $str &&
327 (strpos($str, ';') !== false) &&
328 (substr($str, -1) === ';')
330 $str = substr($str, 0, -1);
332 $this->strippedSemicolon = ';';
335 if ($this->sourceMapGenerator) {
336 $lines = explode("\n", $str);
337 $lastLine = array_pop($lines);
339 foreach ($lines as $line) {
340 // If the written line starts is empty, adding a mapping would add it for
341 // a non-existent column as we are at the end of the line
342 if ($line !== '') {
343 assert($this->currentBlock->sourceLine !== null);
344 assert($this->currentBlock->sourceName !== null);
345 $this->sourceMapGenerator->addMapping(
346 $this->currentLine,
347 $this->currentColumn,
348 $this->currentBlock->sourceLine,
349 //columns from parser are off by one
350 $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
351 $this->currentBlock->sourceName
355 $this->currentLine++;
356 $this->currentColumn = 0;
359 if ($lastLine !== '') {
360 assert($this->currentBlock->sourceLine !== null);
361 assert($this->currentBlock->sourceName !== null);
362 $this->sourceMapGenerator->addMapping(
363 $this->currentLine,
364 $this->currentColumn,
365 $this->currentBlock->sourceLine,
366 //columns from parser are off by one
367 $this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
368 $this->currentBlock->sourceName
372 $this->currentColumn += \strlen($lastLine);
375 echo $str;