├── lang └── ru │ └── install │ └── index.php ├── install ├── version.php ├── components │ └── olegpro │ │ └── olegpro.csscompiler │ │ ├── lang │ │ └── ru │ │ │ ├── class.php │ │ │ ├── .description.php │ │ │ └── .parameters.php │ │ ├── .description.php │ │ ├── .parameters.php │ │ └── class.php └── index.php ├── include.php ├── libs └── scssphp │ ├── src │ ├── Version.php │ ├── Exception │ │ ├── ServerException.php │ │ ├── ParserException.php │ │ └── CompilerException.php │ ├── Node.php │ ├── Compiler │ │ └── Environment.php │ ├── Formatter │ │ ├── OutputBlock.php │ │ ├── Compact.php │ │ ├── Crunched.php │ │ ├── Compressed.php │ │ ├── Expanded.php │ │ ├── Debug.php │ │ └── Nested.php │ ├── Base │ │ └── Range.php │ ├── Block.php │ ├── Util.php │ ├── Type.php │ ├── Formatter.php │ ├── Colors.php │ ├── Node │ │ └── Number.php │ ├── Server.php │ └── Parser.php │ ├── LICENSE.md │ ├── composer.json │ ├── scss.inc.php │ └── README.md ├── .gitattributes ├── lib ├── lesscompiler.php ├── scsscompiler.php └── compiler.php ├── README.md └── .gitignore /lang/ru/install/index.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olegpro/bitrix-csscompiler/HEAD/lang/ru/install/index.php -------------------------------------------------------------------------------- /install/version.php: -------------------------------------------------------------------------------- 1 | "1.2.1", 4 | "VERSION_DATE" => "2016-06-27 02:46:05" 5 | ); 6 | ?> -------------------------------------------------------------------------------- /include.php: -------------------------------------------------------------------------------- 1 | 5 | * Date: 05.09.14 0:45 6 | */ -------------------------------------------------------------------------------- /install/components/olegpro/olegpro.csscompiler/lang/ru/class.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olegpro/bitrix-csscompiler/HEAD/install/components/olegpro/olegpro.csscompiler/lang/ru/class.php -------------------------------------------------------------------------------- /install/components/olegpro/olegpro.csscompiler/lang/ru/.description.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olegpro/bitrix-csscompiler/HEAD/install/components/olegpro/olegpro.csscompiler/lang/ru/.description.php -------------------------------------------------------------------------------- /libs/scssphp/src/Version.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Version 20 | { 21 | const VERSION = 'v0.6.5'; 22 | } 23 | -------------------------------------------------------------------------------- /libs/scssphp/src/Exception/ServerException.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ServerException extends \Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /libs/scssphp/src/Exception/ParserException.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class ParserException extends \Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /libs/scssphp/src/Exception/CompilerException.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class CompilerException extends \Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /install/components/olegpro/olegpro.csscompiler/.description.php: -------------------------------------------------------------------------------- 1 | Loc::getMessage('OP_CS_DESC_NAME'), 11 | 'DESCRIPTION' => Loc::getMessage('OP_CS_DESC_DESCRIPTION'), 12 | 'PATH' => array( 13 | 'ID' => 'utility', 14 | ), 15 | 'AREA_BUTTONS' => array( 16 | array( 17 | 'TITLE' => Loc::getMessage('OP_CS_DESC_AREA_BUTTONS_TITLE') 18 | ), 19 | ), 20 | 'CACHE_PATH' => 'Y', 21 | ); 22 | ?> -------------------------------------------------------------------------------- /libs/scssphp/src/Node.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | abstract class Node 20 | { 21 | /** 22 | * @var string 23 | */ 24 | public $type; 25 | 26 | /** 27 | * @var integer 28 | */ 29 | public $sourceIndex; 30 | 31 | /** 32 | * @var integer 33 | */ 34 | public $sourceLine; 35 | 36 | /** 37 | * @var integer 38 | */ 39 | public $sourceColumn; 40 | } 41 | -------------------------------------------------------------------------------- /libs/scssphp/src/Compiler/Environment.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Environment 20 | { 21 | /** 22 | * @var \Leafo\ScssPhp\Block 23 | */ 24 | public $block; 25 | 26 | /** 27 | * @var \Leafo\ScssPhp\Compiler\Environment 28 | */ 29 | public $parent; 30 | 31 | /** 32 | * @var array 33 | */ 34 | public $store; 35 | 36 | /** 37 | * @var integer 38 | */ 39 | public $depth; 40 | } 41 | -------------------------------------------------------------------------------- /lib/lesscompiler.php: -------------------------------------------------------------------------------- 1 | compiler = new \lessc(); 21 | $this->compiler->setFormatter('compressed'); 22 | } 23 | 24 | /** 25 | * Parse a scssc file to CSS 26 | * @param string $file path to file 27 | * @return string CSS 28 | */ 29 | public function toCss($file) 30 | { 31 | $this->compiler->setImportDir(dirname($file)); 32 | return $this->compiler->compileFile($file); 33 | } 34 | 35 | public static function getExtension() 36 | { 37 | return 'less'; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /libs/scssphp/src/Formatter/OutputBlock.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class OutputBlock 20 | { 21 | /** 22 | * @var string 23 | */ 24 | public $type; 25 | 26 | /** 27 | * @var integer 28 | */ 29 | public $depth; 30 | 31 | /** 32 | * @var array 33 | */ 34 | public $selectors; 35 | 36 | /** 37 | * @var array 38 | */ 39 | public $lines; 40 | 41 | /** 42 | * @var array 43 | */ 44 | public $children; 45 | 46 | /** 47 | * @var \Leafo\ScssPhp\Formatter\OutputBlock 48 | */ 49 | public $parent; 50 | } 51 | -------------------------------------------------------------------------------- /libs/scssphp/src/Base/Range.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Range 20 | { 21 | public $first; 22 | public $last; 23 | 24 | /** 25 | * Initialize range 26 | * 27 | * @param integer|float $first 28 | * @param integer|float $last 29 | */ 30 | public function __construct($first, $last) 31 | { 32 | $this->first = $first; 33 | $this->last = $last; 34 | } 35 | 36 | /** 37 | * Test for inclusion in range 38 | * 39 | * @param integer|float $value 40 | * 41 | * @return boolean 42 | */ 43 | public function includes($value) 44 | { 45 | return $value >= $this->first && $value <= $this->last; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /libs/scssphp/src/Formatter/Compact.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class Compact extends Formatter 22 | { 23 | /** 24 | * {@inheritdoc} 25 | */ 26 | public function __construct() 27 | { 28 | $this->indentLevel = 0; 29 | $this->indentChar = ''; 30 | $this->break = ''; 31 | $this->open = ' {'; 32 | $this->close = "}\n\n"; 33 | $this->tagSeparator = ','; 34 | $this->assignSeparator = ':'; 35 | $this->keepSemicolons = true; 36 | } 37 | 38 | /** 39 | * {@inheritdoc} 40 | */ 41 | public function indentStr() 42 | { 43 | return ' '; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /install/components/olegpro/olegpro.csscompiler/lang/ru/.parameters.php: -------------------------------------------------------------------------------- 1 | addCss()'; 7 | $MESS['OP_CS_REMOVE_OLD_CSS_FILES'] = 'Удалять старые скопилированные css файлы?'; 8 | $MESS['OP_CS_TARGET_FILE_MASK'] = 'Маска файла для записи css файла. (%s обязателен, он заменится на таймштамп файла)'; 9 | $MESS['OP_CS_SHOW_ERRORS_IN_DISPLAY'] = 'Выводить ошибки работы компонента на экран. Если не выбрать, то ошибки будут писаться в лог файл функцией AddMessage2Log()'; 10 | $MESS['OP_CS_ADD_CSS_TO_THE_END'] = 'Добавлять стили в конец если используется Main\Page\Asset::getInstance()->addCss()'; -------------------------------------------------------------------------------- /libs/scssphp/src/Block.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Block 20 | { 21 | /** 22 | * @var string 23 | */ 24 | public $type; 25 | 26 | /** 27 | * @var \Leafo\ScssPhp\Block 28 | */ 29 | public $parent; 30 | 31 | /** 32 | * @var integer 33 | */ 34 | public $sourceIndex; 35 | 36 | /** 37 | * @var integer 38 | */ 39 | public $sourceLine; 40 | 41 | /** 42 | * @var integer 43 | */ 44 | public $sourceColumn; 45 | 46 | /** 47 | * @var array 48 | */ 49 | public $selectors; 50 | 51 | /** 52 | * @var array 53 | */ 54 | public $comments; 55 | 56 | /** 57 | * @var array 58 | */ 59 | public $children; 60 | } 61 | -------------------------------------------------------------------------------- /libs/scssphp/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Leaf Corcoran, http://leafo.github.io/scssphp 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /libs/scssphp/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leafo/scssphp", 3 | "type": "library", 4 | "description": "scssphp is a compiler for SCSS written in PHP.", 5 | "keywords": ["css", "stylesheet", "scss", "sass", "less"], 6 | "homepage": "http://leafo.github.io/scssphp/", 7 | "license": [ 8 | "MIT" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Leaf Corcoran", 13 | "email": "leafot@gmail.com", 14 | "homepage": "http://leafo.net" 15 | } 16 | ], 17 | "autoload": { 18 | "psr-4": { "Leafo\\ScssPhp\\": "src/" } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { "Leafo\\ScssPhp\\Test\\": "tests/" } 22 | }, 23 | "require": { 24 | "php": ">=5.4.0" 25 | }, 26 | "require-dev": { 27 | "squizlabs/php_codesniffer": "~2.5", 28 | "phpunit/phpunit": "~3.7", 29 | "kherge/box": "~2.5" 30 | }, 31 | "bin": ["bin/pscss"], 32 | "archive": { 33 | "exclude": [ 34 | "/Makefile", 35 | "/.gitattributes", 36 | "/.gitignore", 37 | "/.travis.yml", 38 | "/box.json.dist", 39 | "/phpunit.xml.dist", 40 | "/tests" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Csscompiler sass, scss for bitrix 2 | =========== 3 | 4 | Компонент для Bitrix. Компилирует scss, sass или less файлы в готовый css. 5 | 6 | Можно также реализовать, например, компиляцию less файлов. Для этого нужно написать класс, например, LessCompiler(пример класс SCSSCompiler). Унаследовать его от класса \Olegpro\Csscompiler\Compiler, реализовать в нем метод `toCss($file)` и сохранить в папке lib модуля `olegpro.csscompiler` под одноименным названием, в нижнем регистре. И в вызове компонента параметром `CLASS_HANDLER` указать `\Olegpro\Csscompiler\LessCompiler`. 7 | 8 | 9 | ## Пример использования 10 | 11 | ```php 12 | IncludeComponent( 13 | "olegpro:olegpro.csscompiler", 14 | "", 15 | array( 16 | "PATH" => "/bitrix/templates/eshop_adapt_blue/scss/", 17 | "FILES" => array( 18 | 0 => "style.scss", 19 | ), 20 | "PATH_CSS" => "/bitrix/templates/eshop_adapt_blue/", 21 | "CLASS_HANDLER" => "\\Olegpro\\Csscompiler\\SCSSCompiler", 22 | "USE_SETADDITIONALCSS" => "Y", 23 | "ADD_CSS_TO_THE_END" => "Y", 24 | "REMOVE_OLD_CSS_FILES" => "Y", 25 | "TARGET_FILE_MASK" => "styles_%s.css" 26 | ), 27 | false, 28 | array( 29 | "HIDE_ICONS" => "N" 30 | ) 31 | );?> 32 | ``` 33 | 34 | Модуль в [Маркетплейсе](http://marketplace.1c-bitrix.ru/solutions/olegpro.csscompiler/). -------------------------------------------------------------------------------- /lib/scsscompiler.php: -------------------------------------------------------------------------------- 1 | compiler = new \Leafo\ScssPhp\Compiler(); 22 | 23 | $devEnvironment = (isset($_SERVER['ENV']) && in_array(strtolower($_SERVER['ENV']), array('dev', 'demo'))); 24 | 25 | $this->compiler->setFormatter( 26 | !$devEnvironment 27 | ? '\Leafo\ScssPhp\Formatter\Crunched' 28 | : '\Leafo\ScssPhp\Formatter\Expanded' 29 | ); 30 | 31 | if ($devEnvironment) { 32 | $this->compiler->setLineNumberStyle(\Leafo\ScssPhp\Compiler::LINE_COMMENTS); 33 | } 34 | } 35 | 36 | /** 37 | * Parse a scssc file to CSS 38 | * @param string $file path to file 39 | * @return string CSS 40 | */ 41 | public function toCss($file) 42 | { 43 | $this->compiler->addImportPath(dirname($file)); 44 | 45 | return ($css = @ file_get_contents($file)) !== false ? $this->compiler->compile($css) : ''; 46 | } 47 | 48 | public static function getExtension() 49 | { 50 | return 'scss'; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /libs/scssphp/scss.inc.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Crunched extends Formatter 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function __construct() 28 | { 29 | $this->indentLevel = 0; 30 | $this->indentChar = ' '; 31 | $this->break = ''; 32 | $this->open = '{'; 33 | $this->close = '}'; 34 | $this->tagSeparator = ','; 35 | $this->assignSeparator = ':'; 36 | $this->keepSemicolons = false; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function blockLines(OutputBlock $block) 43 | { 44 | $inner = $this->indentStr(); 45 | 46 | $glue = $this->break . $inner; 47 | 48 | foreach ($block->lines as $index => $line) { 49 | if (substr($line, 0, 2) === '/*') { 50 | unset($block->lines[$index]); 51 | } 52 | } 53 | 54 | echo $inner . implode($glue, $block->lines); 55 | 56 | if (! empty($block->children)) { 57 | echo $this->break; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /libs/scssphp/src/Util.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class Util 22 | { 23 | /** 24 | * Asserts that `value` falls within `range` (inclusive), leaving 25 | * room for slight floating-point errors. 26 | * 27 | * @param string $name The name of the value. Used in the error message. 28 | * @param Range $range Range of values. 29 | * @param array $value The value to check. 30 | * @param string $unit The unit of the value. Used in error reporting. 31 | * 32 | * @return mixed `value` adjusted to fall within range, if it was outside by a floating-point margin. 33 | * 34 | * @throws \Exception 35 | */ 36 | public static function checkRange($name, Range $range, $value, $unit = '') 37 | { 38 | $val = $value[1]; 39 | $grace = new Range(-0.00001, 0.00001); 40 | 41 | if ($range->includes($val)) { 42 | return $val; 43 | } 44 | 45 | if ($grace->includes($val - $range->first)) { 46 | return $range->first; 47 | } 48 | 49 | if ($grace->includes($val - $range->last)) { 50 | return $range->last; 51 | } 52 | 53 | throw new \Exception("$name {$val} must be between {$range->first} and {$range->last}$unit"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /libs/scssphp/src/Formatter/Compressed.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Compressed extends Formatter 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function __construct() 28 | { 29 | $this->indentLevel = 0; 30 | $this->indentChar = ' '; 31 | $this->break = ''; 32 | $this->open = '{'; 33 | $this->close = '}'; 34 | $this->tagSeparator = ','; 35 | $this->assignSeparator = ':'; 36 | $this->keepSemicolons = false; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function blockLines(OutputBlock $block) 43 | { 44 | $inner = $this->indentStr(); 45 | 46 | $glue = $this->break . $inner; 47 | 48 | foreach ($block->lines as $index => $line) { 49 | if (substr($line, 0, 2) === '/*' && substr($line, 2, 1) !== '!') { 50 | unset($block->lines[$index]); 51 | } elseif (substr($line, 0, 3) === '/*!') { 52 | $block->lines[$index] = '/*' . substr($line, 3); 53 | } 54 | } 55 | 56 | echo $inner . implode($glue, $block->lines); 57 | 58 | if (! empty($block->children)) { 59 | echo $this->break; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libs/scssphp/src/Formatter/Expanded.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Expanded extends Formatter 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function __construct() 28 | { 29 | $this->indentLevel = 0; 30 | $this->indentChar = ' '; 31 | $this->break = "\n"; 32 | $this->open = ' {'; 33 | $this->close = '}'; 34 | $this->tagSeparator = ', '; 35 | $this->assignSeparator = ': '; 36 | $this->keepSemicolons = true; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | protected function indentStr() 43 | { 44 | return str_repeat($this->indentChar, $this->indentLevel); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | protected function blockLines(OutputBlock $block) 51 | { 52 | $inner = $this->indentStr(); 53 | 54 | $glue = $this->break . $inner; 55 | 56 | foreach ($block->lines as $index => $line) { 57 | if (substr($line, 0, 2) === '/*') { 58 | $block->lines[$index] = preg_replace('/(\r|\n)+/', $glue, $line); 59 | } 60 | } 61 | 62 | echo $inner . implode($glue, $block->lines); 63 | 64 | if (empty($block->selectors) || ! empty($block->children)) { 65 | echo $this->break; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/compiler.php: -------------------------------------------------------------------------------- 1 | 5 | * Date: 05.09.14 1:00 6 | */ 7 | 8 | namespace Olegpro\Csscompiler; 9 | 10 | use \Bitrix\Main\SystemException as SystemException; 11 | 12 | abstract class Compiler 13 | implements CompilerInterface 14 | { 15 | 16 | /** 17 | * Class compiler 18 | */ 19 | private $compiler; 20 | 21 | 22 | /** 23 | * @param string $target 24 | * @param string $content 25 | * @throws \Bitrix\Main\SystemException 26 | */ 27 | public function saveToFile($target, $content) 28 | { 29 | if (@ file_put_contents($target, $content) !== false) { 30 | @ chmod($target, 0666); 31 | } else { 32 | throw new SystemException(sprintf('Cannot write file: %s', $target)); 33 | } 34 | } 35 | 36 | 37 | /** 38 | * @param string $pattern 39 | * @param string $currentCss 40 | */ 41 | public function removeOldCss($pattern, $currentCss) 42 | { 43 | foreach (glob($pattern) as $filename) { 44 | if (is_file($filename) && ($basename = pathinfo($filename, PATHINFO_BASENAME)) != $currentCss) { 45 | @ unlink($filename); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * Clear cache composite site 52 | * @return void 53 | */ 54 | public function clearAllCHTMLPagesCache() 55 | { 56 | \CHTMLPagesCache::CleanAll(); 57 | \CHTMLPagesCache::writeStatistic(0, 0, 0, 0, 0); 58 | } 59 | 60 | } 61 | 62 | 63 | interface CompilerInterface { 64 | 65 | /** 66 | * @param string $file 67 | */ 68 | public function toCss($file); 69 | 70 | /** 71 | * @return string 72 | */ 73 | public static function getExtension(); 74 | 75 | } -------------------------------------------------------------------------------- /libs/scssphp/README.md: -------------------------------------------------------------------------------- 1 | # scssphp 2 | ### 3 | 4 | [![Build](https://travis-ci.org/leafo/scssphp.svg?branch=master)](http://travis-ci.org/leafo/scssphp) 5 | [![License](https://poser.pugx.org/leafo/scssphp/license.svg)](https://packagist.org/packages/leafo/scssphp) 6 | 7 | `scssphp` is a compiler for SCSS written in PHP. 8 | 9 | Checkout the homepage, , for directions on how to use. 10 | 11 | ## Running Tests 12 | 13 | `scssphp` uses [PHPUnit](https://github.com/sebastianbergmann/phpunit) for testing. 14 | 15 | Run the following command from the root directory to run every test: 16 | 17 | vendor/bin/phpunit tests 18 | 19 | There are several tests in the `tests/` directory: 20 | 21 | * `ApiTest.php` contains various unit tests that test the PHP interface. 22 | * `ExceptionTest.php` contains unit tests that test for exceptions thrown by the parser and compiler. 23 | * `FailingTest.php` contains tests reported in Github issues that demonstrate compatibility bugs. 24 | * `InputTest.php` compiles every `.scss` file in the `tests/inputs` directory 25 | then compares to the respective `.css` file in the `tests/outputs` directory. 26 | * `ScssTest.php` extracts (ruby) `scss` tests from the `tests/scss_test.rb` file. 27 | * `ServerTest.php` contains functional tests for the `Server` class. 28 | 29 | When changing any of the tests in `tests/inputs`, the tests will most likely 30 | fail because the output has changed. Once you verify that the output is correct 31 | you can run the following command to rebuild all the tests: 32 | 33 | BUILD=1 vendor/bin/phpunit tests 34 | 35 | This will compile all the tests, and save results into `tests/outputs`. 36 | 37 | To enable the `scss` compatibility tests: 38 | 39 | TEST_SCSS_COMPAT=1 vendor/bin/phpunit tests 40 | 41 | ## Coding Standard 42 | 43 | `scssphp` source conforms to [PSR2](http://www.php-fig.org/psr/psr-2/). 44 | 45 | Run the following command from the root directory to check the code for "sniffs". 46 | 47 | vendor/bin/phpcs --standard=PSR2 bin src tests 48 | -------------------------------------------------------------------------------- /libs/scssphp/src/Type.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Type 20 | { 21 | const T_ASSIGN = 'assign'; 22 | const T_AT_ROOT = 'at-root'; 23 | const T_BLOCK = 'block'; 24 | const T_BREAK = 'break'; 25 | const T_CHARSET = 'charset'; 26 | const T_COLOR = 'color'; 27 | const T_COMMENT = 'comment'; 28 | const T_CONTINUE = 'continue'; 29 | const T_CONTROL = 'control'; 30 | const T_DEBUG = 'debug'; 31 | const T_DIRECTIVE = 'directive'; 32 | const T_EACH = 'each'; 33 | const T_ELSE = 'else'; 34 | const T_ELSEIF = 'elseif'; 35 | const T_ERROR = 'error'; 36 | const T_EXPRESSION = 'exp'; 37 | const T_EXTEND = 'extend'; 38 | const T_FOR = 'for'; 39 | const T_FUNCTION = 'function'; 40 | const T_FUNCTION_CALL = 'fncall'; 41 | const T_HSL = 'hsl'; 42 | const T_IF = 'if'; 43 | const T_IMPORT = 'import'; 44 | const T_INCLUDE = 'include'; 45 | const T_INTERPOLATE = 'interpolate'; 46 | const T_INTERPOLATED = 'interpolated'; 47 | const T_KEYWORD = 'keyword'; 48 | const T_LIST = 'list'; 49 | const T_MAP = 'map'; 50 | const T_MEDIA = 'media'; 51 | const T_MEDIA_EXPRESSION = 'mediaExp'; 52 | const T_MEDIA_TYPE = 'mediaType'; 53 | const T_MEDIA_VALUE = 'mediaValue'; 54 | const T_MIXIN = 'mixin'; 55 | const T_MIXIN_CONTENT = 'mixin_content'; 56 | const T_NESTED_PROPERTY = 'nestedprop'; 57 | const T_NOT = 'not'; 58 | const T_NULL = 'null'; 59 | const T_NUMBER = 'number'; 60 | const T_RETURN = 'return'; 61 | const T_ROOT = 'root'; 62 | const T_SCSSPHP_IMPORT_ONCE = 'scssphp-import-once'; 63 | const T_SELF = 'self'; 64 | const T_STRING = 'string'; 65 | const T_UNARY = 'unary'; 66 | const T_VARIABLE = 'var'; 67 | const T_WARN = 'warn'; 68 | const T_WHILE = 'while'; 69 | } 70 | -------------------------------------------------------------------------------- /libs/scssphp/src/Formatter/Debug.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Debug extends Formatter 23 | { 24 | /** 25 | * {@inheritdoc} 26 | */ 27 | public function __construct() 28 | { 29 | $this->indentLevel = 0; 30 | $this->indentChar = ''; 31 | $this->break = "\n"; 32 | $this->open = ' {'; 33 | $this->close = ' }'; 34 | $this->tagSeparator = ', '; 35 | $this->assignSeparator = ': '; 36 | $this->keepSemicolons = true; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | protected function indentStr() 43 | { 44 | return str_repeat(' ', $this->indentLevel); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | protected function blockLines(OutputBlock $block) 51 | { 52 | $indent = $this->indentStr(); 53 | 54 | if (empty($block->lines)) { 55 | echo "{$indent}block->lines: []\n"; 56 | 57 | return; 58 | } 59 | 60 | foreach ($block->lines as $index => $line) { 61 | echo "{$indent}block->lines[{$index}]: $line\n"; 62 | } 63 | } 64 | 65 | /** 66 | * {@inheritdoc} 67 | */ 68 | protected function blockSelectors(OutputBlock $block) 69 | { 70 | $indent = $this->indentStr(); 71 | 72 | if (empty($block->selectors)) { 73 | echo "{$indent}block->selectors: []\n"; 74 | 75 | return; 76 | } 77 | 78 | foreach ($block->selectors as $index => $selector) { 79 | echo "{$indent}block->selectors[{$index}]: $selector\n"; 80 | } 81 | } 82 | 83 | /** 84 | * {@inheritdoc} 85 | */ 86 | protected function blockChildren(OutputBlock $block) 87 | { 88 | $indent = $this->indentStr(); 89 | 90 | if (empty($block->children)) { 91 | echo "{$indent}block->children: []\n"; 92 | 93 | return; 94 | } 95 | 96 | $this->indentLevel++; 97 | 98 | foreach ($block->children as $i => $child) { 99 | $this->block($child); 100 | } 101 | 102 | $this->indentLevel--; 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | protected function block(OutputBlock $block) 109 | { 110 | $indent = $this->indentStr(); 111 | 112 | echo "{$indent}block->type: {$block->type}\n" . 113 | "{$indent}block->depth: {$block->depth}\n"; 114 | 115 | $this->blockSelectors($block); 116 | $this->blockLines($block); 117 | $this->blockChildren($block); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /install/components/olegpro/olegpro.csscompiler/.parameters.php: -------------------------------------------------------------------------------- 1 | isExists()) 14 | ) { 15 | 16 | foreach ($directory->getChildren() as $ioEntry) { 17 | if ($ioEntry->isFile()) { 18 | $files[$ioEntry->getName()] = $ioEntry->getName(); 19 | } 20 | } 21 | } 22 | 23 | 24 | $arComponentParameters = array( 25 | 'GROUPS' => array(), 26 | 'PARAMETERS' => array( 27 | 28 | 'PATH' => array( 29 | 'PARENT' => 'BASE', 30 | 'NAME' => Loc::getMessage('OP_CS_PATH'), 31 | 'TYPE' => 'STRING', 32 | 'MULTIPLE' => 'N', 33 | 'DEFAULT' => '', 34 | 'REFRESH' => 'Y', 35 | ), 36 | 37 | 'FILES' => array( 38 | 'PARENT' => 'BASE', 39 | 'NAME' => Loc::getMessage('OP_CS_FILES'), 40 | 'TYPE' => 'LIST', 41 | 'MULTIPLE' => 'Y', 42 | 'DEFAULT' => '', 43 | 'VALUES' => $files, 44 | ), 45 | 46 | 'PATH_CSS' => array( 47 | 'PARENT' => 'BASE', 48 | 'NAME' => Loc::getMessage('OP_CS_PATH_CSS'), 49 | 'TYPE' => 'STRING', 50 | 'MULTIPLE' => 'N', 51 | 'DEFAULT' => '', 52 | ), 53 | 54 | 'CLASS_HANDLER' => array( 55 | 'PARENT' => 'BASE', 56 | 'NAME' => Loc::getMessage('OP_CS_CLASS_HANDLER'), 57 | 'TYPE' => 'STRING', 58 | 'VALUE' => 'STRING', 59 | 'MULTIPLE' => 'N', 60 | 'DEFAULT' => '\Olegpro\Csscompiler\SCSSCompiler', 61 | 'REFRESH' => 'N', 62 | ), 63 | 64 | 'USE_SETADDITIONALCSS' => array( 65 | 'PARENT' => 'BASE', 66 | 'NAME' => Loc::getMessage('OP_CS_USE_SETADDITIONALCSS'), 67 | 'TYPE' => 'CHECKBOX', 68 | 'DEFAULT' => 'Y', 69 | ), 70 | 71 | 'ADD_CSS_TO_THE_END' => array( 72 | 'PARENT' => 'BASE', 73 | 'NAME' => Loc::getMessage('OP_CS_ADD_CSS_TO_THE_END'), 74 | 'TYPE' => 'CHECKBOX', 75 | 'DEFAULT' => 'Y', 76 | ), 77 | 'REMOVE_OLD_CSS_FILES' => array( 78 | 'PARENT' => 'BASE', 79 | 'NAME' => Loc::getMessage('OP_CS_REMOVE_OLD_CSS_FILES'), 80 | 'TYPE' => 'CHECKBOX', 81 | 'DEFAULT' => 'Y', 82 | ), 83 | 84 | 'TARGET_FILE_MASK' => array( 85 | 'PARENT' => 'BASE', 86 | 'NAME' => Loc::getMessage('OP_CS_TARGET_FILE_MASK'), 87 | 'TYPE' => 'STRING', 88 | 'MULTIPLE' => 'N', 89 | 'DEFAULT' => 'styles-compiled-%s.css', 90 | ), 91 | 92 | 'SHOW_ERRORS_IN_DISPLAY' => array( 93 | 'PARENT' => 'BASE', 94 | 'NAME' => Loc::getMessage('OP_CS_SHOW_ERRORS_IN_DISPLAY'), 95 | 'TYPE' => 'CHECKBOX', 96 | 'DEFAULT' => 'Y', 97 | ), 98 | 99 | ), 100 | ); 101 | 102 | ?> -------------------------------------------------------------------------------- /install/index.php: -------------------------------------------------------------------------------- 1 | 5 | * Date: 05.09.14 0:47 6 | */ 7 | 8 | use Bitrix\Main\Localization\Loc; 9 | 10 | Loc::loadMessages(__FILE__); 11 | 12 | class olegpro_csscompiler extends CModule 13 | { 14 | var $MODULE_ID = 'olegpro.csscompiler'; 15 | var $MODULE_VERSION; 16 | var $MODULE_VERSION_DATE; 17 | var $MODULE_NAME; 18 | var $MODULE_DESCRIPTION; 19 | var $MODULE_CSS; 20 | var $strError = ''; 21 | 22 | function olegpro_csscompiler() 23 | { 24 | $arModuleVersion = array(); 25 | $path = str_replace("\\", "/", __FILE__); 26 | $path = substr($path, 0, strlen($path) - strlen("/index.php")); 27 | include($path."/version.php"); 28 | $this->MODULE_VERSION = $arModuleVersion["VERSION"]; 29 | $this->MODULE_VERSION_DATE = $arModuleVersion["VERSION_DATE"]; 30 | $this->MODULE_NAME = Loc::getMessage("OLEGPRO_CSSCOMPILER_MODULE_NAME"); 31 | $this->MODULE_DESCRIPTION = Loc::getMessage("OLEGPRO_CSSCOMPILER_MODULE_DESCRIPTION"); 32 | 33 | $this->PARTNER_NAME = GetMessage("OLEGPRO_CSSCOMPILER_PARTNER_NAME"); 34 | $this->PARTNER_URI = GetMessage("OLEGPRO_CSSCOMPILER_PARTNER_URI"); 35 | } 36 | 37 | function GetModuleTasks() 38 | { 39 | return array(); 40 | } 41 | 42 | function InstallDB($arParams = array()) 43 | { 44 | global $DB, $DBType, $APPLICATION; 45 | 46 | $this->InstallTasks(); 47 | RegisterModule($this->MODULE_ID); 48 | CModule::IncludeModule($this->MODULE_ID); 49 | 50 | return true; 51 | } 52 | 53 | function UnInstallDB($arParams = array()) 54 | { 55 | global $DB, $DBType, $APPLICATION; 56 | $this->errors = false; 57 | 58 | UnRegisterModule($this->MODULE_ID); 59 | 60 | if ($this->errors !== false) 61 | { 62 | $APPLICATION->ThrowException(implode("
", $this->errors)); 63 | return false; 64 | } 65 | return true; 66 | } 67 | 68 | function InstallEvents() 69 | { 70 | return true; 71 | } 72 | 73 | function UnInstallEvents() 74 | { 75 | return true; 76 | } 77 | 78 | function InstallFiles($arParams = array()) 79 | { 80 | CopyDirFiles($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/".$this->MODULE_ID."/install/components/", $_SERVER["DOCUMENT_ROOT"]."/bitrix/components", true, true); 81 | return true; 82 | } 83 | 84 | function UnInstallFiles() 85 | { 86 | DeleteDirFiles($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/".$this->MODULE_ID."/install/components/", $_SERVER["DOCUMENT_ROOT"]."/bitrix/components"); 87 | return true; 88 | } 89 | 90 | function DoInstall() 91 | { 92 | global $USER, $APPLICATION; 93 | 94 | if ($USER->IsAdmin()) 95 | { 96 | if ($this->InstallDB()) 97 | { 98 | $this->InstallEvents(); 99 | $this->InstallFiles(); 100 | } 101 | $GLOBALS["errors"] = $this->errors; 102 | } 103 | } 104 | 105 | function DoUninstall() 106 | { 107 | global $DB, $USER, $DOCUMENT_ROOT, $APPLICATION, $step; 108 | 109 | if ($USER->IsAdmin()) 110 | { 111 | if ($this->UnInstallDB()) 112 | { 113 | $this->UnInstallEvents(); 114 | $this->UnInstallFiles(); 115 | } 116 | $GLOBALS["errors"] = $this->errors; 117 | } 118 | } 119 | } 120 | ?> -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /libs/scssphp/src/Formatter.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | abstract class Formatter 22 | { 23 | /** 24 | * @var integer 25 | */ 26 | public $indentLevel; 27 | 28 | /** 29 | * @var string 30 | */ 31 | public $indentChar; 32 | 33 | /** 34 | * @var string 35 | */ 36 | public $break; 37 | 38 | /** 39 | * @var string 40 | */ 41 | public $open; 42 | 43 | /** 44 | * @var string 45 | */ 46 | public $close; 47 | 48 | /** 49 | * @var string 50 | */ 51 | public $tagSeparator; 52 | 53 | /** 54 | * @var string 55 | */ 56 | public $assignSeparator; 57 | 58 | /** 59 | * @var boolea 60 | */ 61 | public $keepSemicolons; 62 | 63 | /** 64 | * Initialize formatter 65 | * 66 | * @api 67 | */ 68 | abstract public function __construct(); 69 | 70 | /** 71 | * Return indentation (whitespace) 72 | * 73 | * @return string 74 | */ 75 | protected function indentStr() 76 | { 77 | return ''; 78 | } 79 | 80 | /** 81 | * Return property assignment 82 | * 83 | * @api 84 | * 85 | * @param string $name 86 | * @param mixed $value 87 | * 88 | * @return string 89 | */ 90 | public function property($name, $value) 91 | { 92 | return rtrim($name) . $this->assignSeparator . $value . ';'; 93 | } 94 | 95 | /** 96 | * Strip semi-colon appended by property(); it's a separator, not a terminator 97 | * 98 | * @api 99 | * 100 | * @param array $lines 101 | */ 102 | public function stripSemicolon(&$lines) 103 | { 104 | if ($this->keepSemicolons) { 105 | return; 106 | } 107 | 108 | if (($count = count($lines)) 109 | && substr($lines[$count - 1], -1) === ';' 110 | ) { 111 | $lines[$count - 1] = substr($lines[$count - 1], 0, -1); 112 | } 113 | } 114 | 115 | /** 116 | * Output lines inside a block 117 | * 118 | * @param \Leafo\ScssPhp\Formatter\OutputBlock $block 119 | */ 120 | protected function blockLines(OutputBlock $block) 121 | { 122 | $inner = $this->indentStr(); 123 | 124 | $glue = $this->break . $inner; 125 | 126 | echo $inner . implode($glue, $block->lines); 127 | 128 | if (! empty($block->children)) { 129 | echo $this->break; 130 | } 131 | } 132 | 133 | /** 134 | * Output block selectors 135 | * 136 | * @param \Leafo\ScssPhp\Formatter\OutputBlock $block 137 | */ 138 | protected function blockSelectors(OutputBlock $block) 139 | { 140 | $inner = $this->indentStr(); 141 | 142 | echo $inner 143 | . implode($this->tagSeparator, $block->selectors) 144 | . $this->open . $this->break; 145 | } 146 | 147 | /** 148 | * Output block children 149 | * 150 | * @param \Leafo\ScssPhp\Formatter\OutputBlock $block 151 | */ 152 | protected function blockChildren(OutputBlock $block) 153 | { 154 | foreach ($block->children as $child) { 155 | $this->block($child); 156 | } 157 | } 158 | 159 | /** 160 | * Output non-empty block 161 | * 162 | * @param \Leafo\ScssPhp\Formatter\OutputBlock $block 163 | */ 164 | protected function block(OutputBlock $block) 165 | { 166 | if (empty($block->lines) && empty($block->children)) { 167 | return; 168 | } 169 | 170 | $pre = $this->indentStr(); 171 | 172 | if (! empty($block->selectors)) { 173 | $this->blockSelectors($block); 174 | 175 | $this->indentLevel++; 176 | } 177 | 178 | if (! empty($block->lines)) { 179 | $this->blockLines($block); 180 | } 181 | 182 | if (! empty($block->children)) { 183 | $this->blockChildren($block); 184 | } 185 | 186 | if (! empty($block->selectors)) { 187 | $this->indentLevel--; 188 | 189 | if (empty($block->children)) { 190 | echo $this->break; 191 | } 192 | 193 | echo $pre . $this->close . $this->break; 194 | } 195 | } 196 | 197 | /** 198 | * Entry point to formatting a block 199 | * 200 | * @api 201 | * 202 | * @param \Leafo\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree 203 | * 204 | * @return string 205 | */ 206 | public function format(OutputBlock $block) 207 | { 208 | ob_start(); 209 | 210 | $this->block($block); 211 | 212 | $out = ob_get_clean(); 213 | 214 | return $out; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /libs/scssphp/src/Formatter/Nested.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Nested extends Formatter 23 | { 24 | /** 25 | * @var integer 26 | */ 27 | private $depth; 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function __construct() 33 | { 34 | $this->indentLevel = 0; 35 | $this->indentChar = ' '; 36 | $this->break = "\n"; 37 | $this->open = ' {'; 38 | $this->close = ' }'; 39 | $this->tagSeparator = ', '; 40 | $this->assignSeparator = ': '; 41 | $this->keepSemicolons = true; 42 | } 43 | 44 | /** 45 | * {@inheritdoc} 46 | */ 47 | protected function indentStr() 48 | { 49 | $n = $this->depth - 1; 50 | 51 | return str_repeat($this->indentChar, max($this->indentLevel + $n, 0)); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | protected function blockLines(OutputBlock $block) 58 | { 59 | $inner = $this->indentStr(); 60 | 61 | $glue = $this->break . $inner; 62 | 63 | foreach ($block->lines as $index => $line) { 64 | if (substr($line, 0, 2) === '/*') { 65 | $block->lines[$index] = preg_replace('/(\r|\n)+/', $glue, $line); 66 | } 67 | } 68 | 69 | echo $inner . implode($glue, $block->lines); 70 | 71 | if (! empty($block->children)) { 72 | echo $this->break; 73 | } 74 | } 75 | 76 | /** 77 | * {@inheritdoc} 78 | */ 79 | protected function blockSelectors(OutputBlock $block) 80 | { 81 | $inner = $this->indentStr(); 82 | 83 | echo $inner 84 | . implode($this->tagSeparator, $block->selectors) 85 | . $this->open . $this->break; 86 | } 87 | 88 | /** 89 | * {@inheritdoc} 90 | */ 91 | protected function blockChildren(OutputBlock $block) 92 | { 93 | foreach ($block->children as $i => $child) { 94 | $this->block($child); 95 | 96 | if ($i < count($block->children) - 1) { 97 | echo $this->break; 98 | 99 | if (isset($block->children[$i + 1])) { 100 | $next = $block->children[$i + 1]; 101 | 102 | if ($next->depth === max($block->depth, 1) && $child->depth >= $next->depth) { 103 | echo $this->break; 104 | } 105 | } 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | */ 113 | protected function block(OutputBlock $block) 114 | { 115 | if ($block->type === 'root') { 116 | $this->adjustAllChildren($block); 117 | } 118 | 119 | if (empty($block->lines) && empty($block->children)) { 120 | return; 121 | } 122 | 123 | $this->depth = $block->depth; 124 | 125 | if (! empty($block->selectors)) { 126 | $this->blockSelectors($block); 127 | 128 | $this->indentLevel++; 129 | } 130 | 131 | if (! empty($block->lines)) { 132 | $this->blockLines($block); 133 | } 134 | 135 | if (! empty($block->children)) { 136 | $this->blockChildren($block); 137 | } 138 | 139 | if (! empty($block->selectors)) { 140 | $this->indentLevel--; 141 | 142 | echo $this->close; 143 | } 144 | 145 | if ($block->type === 'root') { 146 | echo $this->break; 147 | } 148 | } 149 | 150 | /** 151 | * Adjust the depths of all children, depth first 152 | * 153 | * @param \Leafo\ScssPhp\Formatter\OutputBlock $block 154 | */ 155 | private function adjustAllChildren(OutputBlock $block) 156 | { 157 | // flatten empty nested blocks 158 | $children = []; 159 | 160 | foreach ($block->children as $i => $child) { 161 | if (empty($child->lines) && empty($child->children)) { 162 | if (isset($block->children[$i + 1])) { 163 | $block->children[$i + 1]->depth = $child->depth; 164 | } 165 | 166 | continue; 167 | } 168 | 169 | $children[] = $child; 170 | } 171 | 172 | $count = count($children); 173 | 174 | for ($i = 0; $i < $count; $i++) { 175 | $depth = $children[$i]->depth; 176 | $j = $i + 1; 177 | 178 | if (isset($children[$j]) && $depth < $children[$j]->depth) { 179 | $childDepth = $children[$j]->depth; 180 | 181 | for (; $j < $count; $j++) { 182 | if ($depth < $children[$j]->depth && $childDepth >= $children[$j]->depth) { 183 | $children[$j]->depth = $depth + 1; 184 | } 185 | } 186 | } 187 | } 188 | 189 | $block->children = $children; 190 | 191 | // make relative to parent 192 | foreach ($block->children as $child) { 193 | $this->adjustAllChildren($child); 194 | 195 | $child->depth = $child->depth - $block->depth; 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /libs/scssphp/src/Colors.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | class Colors 20 | { 21 | /** 22 | * CSS Colors 23 | * 24 | * @see http://www.w3.org/TR/css3-color 25 | * 26 | * @var array 27 | */ 28 | public static $cssColors = [ 29 | 'aliceblue' => '240,248,255', 30 | 'antiquewhite' => '250,235,215', 31 | 'aqua' => '0,255,255', 32 | 'aquamarine' => '127,255,212', 33 | 'azure' => '240,255,255', 34 | 'beige' => '245,245,220', 35 | 'bisque' => '255,228,196', 36 | 'black' => '0,0,0', 37 | 'blanchedalmond' => '255,235,205', 38 | 'blue' => '0,0,255', 39 | 'blueviolet' => '138,43,226', 40 | 'brown' => '165,42,42', 41 | 'burlywood' => '222,184,135', 42 | 'cadetblue' => '95,158,160', 43 | 'chartreuse' => '127,255,0', 44 | 'chocolate' => '210,105,30', 45 | 'coral' => '255,127,80', 46 | 'cornflowerblue' => '100,149,237', 47 | 'cornsilk' => '255,248,220', 48 | 'crimson' => '220,20,60', 49 | 'cyan' => '0,255,255', 50 | 'darkblue' => '0,0,139', 51 | 'darkcyan' => '0,139,139', 52 | 'darkgoldenrod' => '184,134,11', 53 | 'darkgray' => '169,169,169', 54 | 'darkgreen' => '0,100,0', 55 | 'darkgrey' => '169,169,169', 56 | 'darkkhaki' => '189,183,107', 57 | 'darkmagenta' => '139,0,139', 58 | 'darkolivegreen' => '85,107,47', 59 | 'darkorange' => '255,140,0', 60 | 'darkorchid' => '153,50,204', 61 | 'darkred' => '139,0,0', 62 | 'darksalmon' => '233,150,122', 63 | 'darkseagreen' => '143,188,143', 64 | 'darkslateblue' => '72,61,139', 65 | 'darkslategray' => '47,79,79', 66 | 'darkslategrey' => '47,79,79', 67 | 'darkturquoise' => '0,206,209', 68 | 'darkviolet' => '148,0,211', 69 | 'deeppink' => '255,20,147', 70 | 'deepskyblue' => '0,191,255', 71 | 'dimgray' => '105,105,105', 72 | 'dimgrey' => '105,105,105', 73 | 'dodgerblue' => '30,144,255', 74 | 'firebrick' => '178,34,34', 75 | 'floralwhite' => '255,250,240', 76 | 'forestgreen' => '34,139,34', 77 | 'fuchsia' => '255,0,255', 78 | 'gainsboro' => '220,220,220', 79 | 'ghostwhite' => '248,248,255', 80 | 'gold' => '255,215,0', 81 | 'goldenrod' => '218,165,32', 82 | 'gray' => '128,128,128', 83 | 'green' => '0,128,0', 84 | 'greenyellow' => '173,255,47', 85 | 'grey' => '128,128,128', 86 | 'honeydew' => '240,255,240', 87 | 'hotpink' => '255,105,180', 88 | 'indianred' => '205,92,92', 89 | 'indigo' => '75,0,130', 90 | 'ivory' => '255,255,240', 91 | 'khaki' => '240,230,140', 92 | 'lavender' => '230,230,250', 93 | 'lavenderblush' => '255,240,245', 94 | 'lawngreen' => '124,252,0', 95 | 'lemonchiffon' => '255,250,205', 96 | 'lightblue' => '173,216,230', 97 | 'lightcoral' => '240,128,128', 98 | 'lightcyan' => '224,255,255', 99 | 'lightgoldenrodyellow' => '250,250,210', 100 | 'lightgray' => '211,211,211', 101 | 'lightgreen' => '144,238,144', 102 | 'lightgrey' => '211,211,211', 103 | 'lightpink' => '255,182,193', 104 | 'lightsalmon' => '255,160,122', 105 | 'lightseagreen' => '32,178,170', 106 | 'lightskyblue' => '135,206,250', 107 | 'lightslategray' => '119,136,153', 108 | 'lightslategrey' => '119,136,153', 109 | 'lightsteelblue' => '176,196,222', 110 | 'lightyellow' => '255,255,224', 111 | 'lime' => '0,255,0', 112 | 'limegreen' => '50,205,50', 113 | 'linen' => '250,240,230', 114 | 'magenta' => '255,0,255', 115 | 'maroon' => '128,0,0', 116 | 'mediumaquamarine' => '102,205,170', 117 | 'mediumblue' => '0,0,205', 118 | 'mediumorchid' => '186,85,211', 119 | 'mediumpurple' => '147,112,219', 120 | 'mediumseagreen' => '60,179,113', 121 | 'mediumslateblue' => '123,104,238', 122 | 'mediumspringgreen' => '0,250,154', 123 | 'mediumturquoise' => '72,209,204', 124 | 'mediumvioletred' => '199,21,133', 125 | 'midnightblue' => '25,25,112', 126 | 'mintcream' => '245,255,250', 127 | 'mistyrose' => '255,228,225', 128 | 'moccasin' => '255,228,181', 129 | 'navajowhite' => '255,222,173', 130 | 'navy' => '0,0,128', 131 | 'oldlace' => '253,245,230', 132 | 'olive' => '128,128,0', 133 | 'olivedrab' => '107,142,35', 134 | 'orange' => '255,165,0', 135 | 'orangered' => '255,69,0', 136 | 'orchid' => '218,112,214', 137 | 'palegoldenrod' => '238,232,170', 138 | 'palegreen' => '152,251,152', 139 | 'paleturquoise' => '175,238,238', 140 | 'palevioletred' => '219,112,147', 141 | 'papayawhip' => '255,239,213', 142 | 'peachpuff' => '255,218,185', 143 | 'peru' => '205,133,63', 144 | 'pink' => '255,192,203', 145 | 'plum' => '221,160,221', 146 | 'powderblue' => '176,224,230', 147 | 'purple' => '128,0,128', 148 | 'rebeccapurple' => '102,51,153', 149 | 'red' => '255,0,0', 150 | 'rosybrown' => '188,143,143', 151 | 'royalblue' => '65,105,225', 152 | 'saddlebrown' => '139,69,19', 153 | 'salmon' => '250,128,114', 154 | 'sandybrown' => '244,164,96', 155 | 'seagreen' => '46,139,87', 156 | 'seashell' => '255,245,238', 157 | 'sienna' => '160,82,45', 158 | 'silver' => '192,192,192', 159 | 'skyblue' => '135,206,235', 160 | 'slateblue' => '106,90,205', 161 | 'slategray' => '112,128,144', 162 | 'slategrey' => '112,128,144', 163 | 'snow' => '255,250,250', 164 | 'springgreen' => '0,255,127', 165 | 'steelblue' => '70,130,180', 166 | 'tan' => '210,180,140', 167 | 'teal' => '0,128,128', 168 | 'thistle' => '216,191,216', 169 | 'tomato' => '255,99,71', 170 | 'transparent' => '0,0,0,0', 171 | 'turquoise' => '64,224,208', 172 | 'violet' => '238,130,238', 173 | 'wheat' => '245,222,179', 174 | 'white' => '255,255,255', 175 | 'whitesmoke' => '245,245,245', 176 | 'yellow' => '255,255,0', 177 | 'yellowgreen' => '154,205,50', 178 | ]; 179 | } 180 | -------------------------------------------------------------------------------- /install/components/olegpro/olegpro.csscompiler/class.php: -------------------------------------------------------------------------------- 1 | 5 | * Date: 03.07.14 23:33 6 | */ 7 | 8 | use \Bitrix\Main; 9 | use \Bitrix\Main\Localization\Loc as Loc; 10 | use \Bitrix\Main\SystemException as SystemException; 11 | 12 | if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die(); 13 | 14 | class OlegproCSSCompilerComponent extends CBitrixComponent 15 | { 16 | 17 | /** 18 | * Check Required Modules 19 | * @throws Exception 20 | */ 21 | protected function checkModules() 22 | { 23 | if (!Main\Loader::includeModule('olegpro.csscompiler')) { 24 | throw new SystemException(Loc::getMessage('CVP_OLEGPRO_CSSCOMPILER_MODULE_NOT_INSTALLED')); 25 | } 26 | } 27 | 28 | 29 | /** 30 | * Load language file 31 | */ 32 | public function onIncludeComponentLang() 33 | { 34 | $this->includeComponentLang(basename(__FILE__)); 35 | Loc::loadMessages(__FILE__); 36 | } 37 | 38 | 39 | /** 40 | * Prepare Component Params 41 | */ 42 | public function onPrepareComponentParams($params) 43 | { 44 | $params['USE_SET_ADDITIONAL_CSS'] = ($params['USE_SETADDITIONALCSS'] == 'Y'); 45 | 46 | $params['ADD_CSS_TO_THE_END'] = isset($params['ADD_CSS_TO_THE_END']) && ($params['ADD_CSS_TO_THE_END'] == 'Y'); 47 | 48 | $params['REMOVE_OLD_CSS_FILES'] = ($params['REMOVE_OLD_CSS_FILES'] == 'Y'); 49 | 50 | $params['FILES'] = is_array($params['FILES']) ? $params['FILES'] : array(); 51 | 52 | $params['PATH_TO_FILES'] = isset($params['PATH']) && strlen(trim($params['PATH'])) 53 | ? preg_replace(array('~^/~', '~/$~'), '/', trim($params['PATH'])) 54 | : null; 55 | 56 | $params['PATH_TO_FILES_CSS'] = isset($params['PATH_CSS']) && strlen(trim($params['PATH_CSS'])) 57 | ? preg_replace(array('~^/~', '~/$~'), '/', trim($params['PATH_CSS'])) 58 | : SITE_TEMPLATE_PATH . '/'; 59 | 60 | $params['CLASS_COMPILER'] = isset($params['CLASS_HANDLER']) && strlen($params['CLASS_HANDLER']) 61 | ? $params['CLASS_HANDLER'] 62 | : '\Olegpro\Csscompiler\SCSSCompiler'; 63 | 64 | $params['TARGET_FILE_MASK'] = trim($params['TARGET_FILE_MASK']); 65 | if (!strlen($params['TARGET_FILE_MASK']) || (strpos($params['TARGET_FILE_MASK'], '%s')) === false) { 66 | $params['TARGET_FILE_MASK'] = 'styles-compiled-%s.css'; 67 | } 68 | 69 | $params['SHOW_ERRORS_IN_DISPLAY'] = ($params['SHOW_ERRORS_IN_DISPLAY'] == 'Y'); 70 | 71 | return $params; 72 | } 73 | 74 | 75 | /** 76 | * @throws SystemException 77 | */ 78 | protected function checkCompilerClass() 79 | { 80 | if (!class_exists($this->arParams['CLASS_COMPILER'])) { 81 | throw new SystemException(sprintf('Class "%s" doesn\'t exist.', $this->arParams['CLASS_COMPILER'])); 82 | } 83 | } 84 | 85 | 86 | /** 87 | * @return \Olegpro\Csscompiler\Compiler 88 | * @throws SystemException 89 | */ 90 | protected function getCompiler() 91 | { 92 | if (!class_exists($this->arParams['CLASS_COMPILER'])) { 93 | throw new SystemException(sprintf('Class "%s" doesn\'t exist.', $this->arParams['CLASS_COMPILER'])); 94 | } 95 | 96 | $compiler = new $this->arParams['CLASS_COMPILER']; 97 | 98 | if (!($compiler instanceof \Olegpro\Csscompiler\Compiler)) { 99 | throw new SystemException(sprintf('Class "%s" is not a subclass of \Olegpro\Csscompiler\Compiler', $this->arParams['CLASS_COMPILER'])); 100 | } 101 | 102 | return $compiler; 103 | } 104 | 105 | 106 | /* 107 | * Check the directory needed for component 108 | */ 109 | protected function checkDirs() 110 | { 111 | if (!is_readable($_SERVER['DOCUMENT_ROOT'] . $this->arParams['PATH_TO_FILES'])) { 112 | throw new SystemException(Loc::getMessage('OCSS_ERROR_DIR_NOT_AVAILABLE', array('#DIR#' => $this->arParams['PATH_TO_FILES']))); 113 | } 114 | 115 | if (!is_readable($_SERVER['DOCUMENT_ROOT'] . $this->arParams['PATH_TO_FILES_CSS'])) { 116 | throw new SystemException(Loc::getMessage('OCSS_ERROR_DIR_NOT_AVAILABLE', array('#DIR#' => $this->arParams['PATH_TO_FILES_CSS']))); 117 | } elseif (!is_writable($_SERVER['DOCUMENT_ROOT'] . $this->arParams['PATH_TO_FILES_CSS'])) { 118 | throw new SystemException(Loc::getMessage('OCSS_ERROR_DIR_NOT_WRITABLE', array('#DIR#' => $this->arParams['PATH_TO_FILES_CSS']))); 119 | } 120 | } 121 | 122 | 123 | /** 124 | * Start Component 125 | */ 126 | public function executeComponent() 127 | { 128 | 129 | try { 130 | 131 | $this->checkModules(); 132 | 133 | $this->checkCompilerClass(); 134 | 135 | $this->checkDirs(); 136 | 137 | /** @var \Olegpro\Csscompiler\Compiler $compilerClass */ 138 | $compilerClass = $this->arParams['CLASS_COMPILER']; 139 | 140 | $extCompiler = $compilerClass::getExtension(); 141 | 142 | $lastModified = time(); 143 | 144 | $modified = 0; 145 | 146 | $iterator = new \RecursiveIteratorIterator( 147 | new \RecursiveDirectoryIterator($_SERVER['DOCUMENT_ROOT'] . $this->arParams['PATH_TO_FILES']) 148 | ); 149 | 150 | foreach ($iterator as $file) { 151 | 152 | /** @var \SplFileInfo $file */ 153 | if ( 154 | $file->isFile() 155 | && $file->isReadable() 156 | && $file->getExtension() === $extCompiler 157 | && ($lastModified = $file->getMTime()) > $modified 158 | ) { 159 | $modified = $lastModified; 160 | } 161 | } 162 | 163 | if ($modified) $lastModified = $modified; 164 | 165 | $target = $this->arParams['PATH_TO_FILES_CSS'] . sprintf($this->arParams['TARGET_FILE_MASK'], $lastModified); 166 | 167 | if (!file_exists($_SERVER['DOCUMENT_ROOT'] . $target)) { 168 | 169 | /** @var \Olegpro\Csscompiler\Compiler $compiler */ 170 | $compiler = $this->getCompiler(); 171 | 172 | $css = Loc::getMessage('OCSS_FILE_AUTO_GENERATED', array('#PATH#' => $this->arParams['PATH_TO_FILES'])); 173 | foreach ($this->arParams['FILES'] as $file) { 174 | $css .= $compiler->toCss($_SERVER['DOCUMENT_ROOT'] . $this->arParams['PATH_TO_FILES'] . $file); 175 | } 176 | 177 | if (!empty($css)) { 178 | $compiler->saveToFile($_SERVER['DOCUMENT_ROOT'] . $target, $css); 179 | } 180 | 181 | if ($this->arParams['REMOVE_OLD_CSS_FILES']) { 182 | $compiler->removeOldCss( 183 | $_SERVER['DOCUMENT_ROOT'] . $this->arParams['PATH_TO_FILES_CSS'] . sprintf($this->arParams['TARGET_FILE_MASK'], '*'), 184 | sprintf($this->arParams['TARGET_FILE_MASK'], $lastModified) 185 | ); 186 | } 187 | 188 | if (\CHTMLPagesCache::IsCompositeEnabled()) { 189 | $compiler->clearAllCHTMLPagesCache(); 190 | } 191 | 192 | } 193 | 194 | if ($this->arParams['USE_SETADDITIONALCSS']) { 195 | Main\Page\Asset::getInstance()->addCss($target, $this->arParams['ADD_CSS_TO_THE_END']); 196 | } else { 197 | echo sprintf('', $target); 198 | } 199 | 200 | } catch (SystemException $e) { 201 | if ($this->arParams['SHOW_ERRORS_IN_DISPLAY']) { 202 | ShowError($e->getMessage()); 203 | } else { 204 | AddMessage2Log($e->getMessage(), 'olegpro.csscompiler'); 205 | } 206 | } 207 | 208 | } 209 | 210 | } -------------------------------------------------------------------------------- /libs/scssphp/src/Node/Number.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | class Number extends Node implements \ArrayAccess 30 | { 31 | /** 32 | * @var integer 33 | */ 34 | static public $precision = 5; 35 | 36 | /** 37 | * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/ 38 | * 39 | * @var array 40 | */ 41 | static protected $unitTable = [ 42 | 'in' => [ 43 | 'in' => 1, 44 | 'pc' => 6, 45 | 'pt' => 72, 46 | 'px' => 96, 47 | 'cm' => 2.54, 48 | 'mm' => 25.4, 49 | 'q' => 101.6, 50 | ], 51 | 'turn' => [ 52 | 'deg' => 360, 53 | 'grad' => 400, 54 | 'rad' => 6.28318530717958647692528676, // 2 * M_PI 55 | 'turn' => 1, 56 | ], 57 | 's' => [ 58 | 's' => 1, 59 | 'ms' => 1000, 60 | ], 61 | 'Hz' => [ 62 | 'Hz' => 1, 63 | 'kHz' => 0.001, 64 | ], 65 | 'dpi' => [ 66 | 'dpi' => 1, 67 | 'dpcm' => 2.54, 68 | 'dppx' => 96, 69 | ], 70 | ]; 71 | 72 | /** 73 | * @var integer|float 74 | */ 75 | public $dimension; 76 | 77 | /** 78 | * @var array 79 | */ 80 | public $units; 81 | 82 | /** 83 | * Initialize number 84 | * 85 | * @param mixed $dimension 86 | * @param mixed $initialUnit 87 | */ 88 | public function __construct($dimension, $initialUnit) 89 | { 90 | $this->type = Type::T_NUMBER; 91 | $this->dimension = $dimension; 92 | $this->units = is_array($initialUnit) 93 | ? $initialUnit 94 | : ($initialUnit ? [$initialUnit => 1] 95 | : []); 96 | } 97 | 98 | /** 99 | * Coerce number to target units 100 | * 101 | * @param array $units 102 | * 103 | * @return \Leafo\ScssPhp\Node\Number 104 | */ 105 | public function coerce($units) 106 | { 107 | if ($this->unitless()) { 108 | return new Number($this->dimension, $units); 109 | } 110 | 111 | $dimension = $this->dimension; 112 | 113 | foreach (self::$unitTable['in'] as $unit => $conv) { 114 | $from = isset($this->units[$unit]) ? $this->units[$unit] : 0; 115 | $to = isset($units[$unit]) ? $units[$unit] : 0; 116 | $factor = pow($conv, $from - $to); 117 | $dimension /= $factor; 118 | } 119 | 120 | return new Number($dimension, $units); 121 | } 122 | 123 | /** 124 | * Normalize number 125 | * 126 | * @return \Leafo\ScssPhp\Node\Number 127 | */ 128 | public function normalize() 129 | { 130 | $dimension = $this->dimension; 131 | $units = []; 132 | 133 | $this->normalizeUnits($dimension, $units, 'in'); 134 | 135 | return new Number($dimension, $units); 136 | } 137 | 138 | /** 139 | * {@inheritdoc} 140 | */ 141 | public function offsetExists($offset) 142 | { 143 | if ($offset === -3) { 144 | return $this->sourceColumn !== null; 145 | } 146 | 147 | if ($offset === -2) { 148 | return $this->sourceLine !== null; 149 | } 150 | 151 | if ($offset === -1 152 | || $offset === 0 153 | || $offset === 1 154 | || $offset === 2 155 | ) { 156 | return true; 157 | } 158 | 159 | return false; 160 | } 161 | 162 | /** 163 | * {@inheritdoc} 164 | */ 165 | public function offsetGet($offset) 166 | { 167 | switch ($offset) { 168 | case -3: 169 | return $this->sourceColumn; 170 | 171 | case -2: 172 | return $this->sourceLine; 173 | 174 | case -1: 175 | return $this->sourceIndex; 176 | 177 | case 0: 178 | return $this->type; 179 | 180 | case 1: 181 | return $this->dimension; 182 | 183 | case 2: 184 | return $this->units; 185 | } 186 | } 187 | 188 | /** 189 | * {@inheritdoc} 190 | */ 191 | public function offsetSet($offset, $value) 192 | { 193 | if ($offset === 1) { 194 | $this->dimension = $value; 195 | } elseif ($offset === 2) { 196 | $this->units = $value; 197 | } elseif ($offset == -1) { 198 | $this->sourceIndex = $value; 199 | } elseif ($offset == -2) { 200 | $this->sourceLine = $value; 201 | } elseif ($offset == -3) { 202 | $this->sourceColumn = $value; 203 | } 204 | } 205 | 206 | /** 207 | * {@inheritdoc} 208 | */ 209 | public function offsetUnset($offset) 210 | { 211 | if ($offset === 1) { 212 | $this->dimension = null; 213 | } elseif ($offset === 2) { 214 | $this->units = null; 215 | } elseif ($offset === -1) { 216 | $this->sourceIndex = null; 217 | } elseif ($offset === -2) { 218 | $this->sourceLine = null; 219 | } elseif ($offset === -3) { 220 | $this->sourceColumn = null; 221 | } 222 | } 223 | 224 | /** 225 | * Returns true if the number is unitless 226 | * 227 | * @return boolean 228 | */ 229 | public function unitless() 230 | { 231 | return ! array_sum($this->units); 232 | } 233 | 234 | /** 235 | * Returns unit(s) as the product of numerator units divided by the product of denominator units 236 | * 237 | * @return string 238 | */ 239 | public function unitStr() 240 | { 241 | $numerators = []; 242 | $denominators = []; 243 | 244 | foreach ($this->units as $unit => $unitSize) { 245 | if ($unitSize > 0) { 246 | $numerators = array_pad($numerators, count($numerators) + $unitSize, $unit); 247 | continue; 248 | } 249 | 250 | if ($unitSize < 0) { 251 | $denominators = array_pad($denominators, count($denominators) + $unitSize, $unit); 252 | continue; 253 | } 254 | } 255 | 256 | return implode('*', $numerators) . (count($denominators) ? '/' . implode('*', $denominators) : ''); 257 | } 258 | 259 | /** 260 | * Output number 261 | * 262 | * @param \Leafo\ScssPhp\Compiler $compiler 263 | * 264 | * @return string 265 | */ 266 | public function output(Compiler $compiler = null) 267 | { 268 | $dimension = round($this->dimension, self::$precision); 269 | 270 | $units = array_filter($this->units, function ($unitSize) { 271 | return $unitSize; 272 | }); 273 | 274 | if (count($units) > 1 && array_sum($units) === 0) { 275 | $dimension = $this->dimension; 276 | $units = []; 277 | 278 | $this->normalizeUnits($dimension, $units, 'in'); 279 | 280 | $dimension = round($dimension, self::$precision); 281 | $units = array_filter($units, function ($unitSize) { 282 | return $unitSize; 283 | }); 284 | } 285 | 286 | $unitSize = array_sum($units); 287 | 288 | if ($compiler && ($unitSize > 1 || $unitSize < 0 || count($units) > 1)) { 289 | $compiler->throwError((string) $dimension . $this->unitStr() . " isn't a valid CSS value."); 290 | } 291 | 292 | reset($units); 293 | list($unit, ) = each($units); 294 | 295 | return (string) $dimension . $unit; 296 | } 297 | 298 | /** 299 | * {@inheritdoc} 300 | */ 301 | public function __toString() 302 | { 303 | return $this->output(); 304 | } 305 | 306 | /** 307 | * Normalize units 308 | * 309 | * @param integer|float $dimension 310 | * @param array $units 311 | * @param string $baseUnit 312 | */ 313 | private function normalizeUnits(&$dimension, &$units, $baseUnit = 'in') 314 | { 315 | $dimension = $this->dimension; 316 | $units = []; 317 | 318 | foreach ($this->units as $unit => $exp) { 319 | if (isset(self::$unitTable[$baseUnit][$unit])) { 320 | $factor = pow(self::$unitTable[$baseUnit][$unit], $exp); 321 | 322 | $unit = $baseUnit; 323 | $dimension /= $factor; 324 | } 325 | 326 | $units[$unit] = $exp + (isset($units[$unit]) ? $units[$unit] : 0); 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /libs/scssphp/src/Server.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class Server 24 | { 25 | /** 26 | * @var boolean 27 | */ 28 | private $showErrorsAsCSS; 29 | 30 | /** 31 | * @var string 32 | */ 33 | private $dir; 34 | 35 | /** 36 | * @var string 37 | */ 38 | private $cacheDir; 39 | 40 | /** 41 | * @var \Leafo\ScssPhp\Compiler 42 | */ 43 | private $scss; 44 | 45 | /** 46 | * Join path components 47 | * 48 | * @param string $left Path component, left of the directory separator 49 | * @param string $right Path component, right of the directory separator 50 | * 51 | * @return string 52 | */ 53 | protected function join($left, $right) 54 | { 55 | return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\'); 56 | } 57 | 58 | /** 59 | * Get name of requested .scss file 60 | * 61 | * @return string|null 62 | */ 63 | protected function inputName() 64 | { 65 | switch (true) { 66 | case isset($_GET['p']): 67 | return $_GET['p']; 68 | case isset($_SERVER['PATH_INFO']): 69 | return $_SERVER['PATH_INFO']; 70 | case isset($_SERVER['DOCUMENT_URI']): 71 | return substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME'])); 72 | } 73 | } 74 | 75 | /** 76 | * Get path to requested .scss file 77 | * 78 | * @return string 79 | */ 80 | protected function findInput() 81 | { 82 | if (($input = $this->inputName()) 83 | && strpos($input, '..') === false 84 | && substr($input, -5) === '.scss' 85 | ) { 86 | $name = $this->join($this->dir, $input); 87 | 88 | if (is_file($name) && is_readable($name)) { 89 | return $name; 90 | } 91 | } 92 | 93 | return false; 94 | } 95 | 96 | /** 97 | * Get path to cached .css file 98 | * 99 | * @return string 100 | */ 101 | protected function cacheName($fname) 102 | { 103 | return $this->join($this->cacheDir, md5($fname) . '.css'); 104 | } 105 | 106 | /** 107 | * Get path to meta data 108 | * 109 | * @return string 110 | */ 111 | protected function metadataName($out) 112 | { 113 | return $out . '.meta'; 114 | } 115 | 116 | /** 117 | * Determine whether .scss file needs to be re-compiled. 118 | * 119 | * @param string $out Output path 120 | * @param string $etag ETag 121 | * 122 | * @return boolean True if compile required. 123 | */ 124 | protected function needsCompile($out, &$etag) 125 | { 126 | if (! is_file($out)) { 127 | return true; 128 | } 129 | 130 | $mtime = filemtime($out); 131 | 132 | $metadataName = $this->metadataName($out); 133 | 134 | if (is_readable($metadataName)) { 135 | $metadata = unserialize(file_get_contents($metadataName)); 136 | 137 | foreach ($metadata['imports'] as $import => $originalMtime) { 138 | $currentMtime = filemtime($import); 139 | 140 | if ($currentMtime !== $originalMtime || $currentMtime > $mtime) { 141 | return true; 142 | } 143 | } 144 | 145 | $metaVars = crc32(serialize($this->scss->getVariables())); 146 | 147 | if ($metaVars !== $metadata['vars']) { 148 | return true; 149 | } 150 | 151 | $etag = $metadata['etag']; 152 | 153 | return false; 154 | } 155 | 156 | return true; 157 | } 158 | 159 | /** 160 | * Get If-Modified-Since header from client request 161 | * 162 | * @return string|null 163 | */ 164 | protected function getIfModifiedSinceHeader() 165 | { 166 | $modifiedSince = null; 167 | 168 | if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { 169 | $modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE']; 170 | 171 | if (false !== ($semicolonPos = strpos($modifiedSince, ';'))) { 172 | $modifiedSince = substr($modifiedSince, 0, $semicolonPos); 173 | } 174 | } 175 | 176 | return $modifiedSince; 177 | } 178 | 179 | /** 180 | * Get If-None-Match header from client request 181 | * 182 | * @return string|null 183 | */ 184 | protected function getIfNoneMatchHeader() 185 | { 186 | $noneMatch = null; 187 | 188 | if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { 189 | $noneMatch = $_SERVER['HTTP_IF_NONE_MATCH']; 190 | } 191 | 192 | return $noneMatch; 193 | } 194 | 195 | /** 196 | * Compile .scss file 197 | * 198 | * @param string $in Input path (.scss) 199 | * @param string $out Output path (.css) 200 | * 201 | * @return array 202 | */ 203 | protected function compile($in, $out) 204 | { 205 | $start = microtime(true); 206 | $css = $this->scss->compile(file_get_contents($in), $in); 207 | $elapsed = round((microtime(true) - $start), 4); 208 | 209 | $v = Version::VERSION; 210 | $t = date('r'); 211 | $css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css; 212 | $etag = md5($css); 213 | 214 | file_put_contents($out, $css); 215 | file_put_contents( 216 | $this->metadataName($out), 217 | serialize([ 218 | 'etag' => $etag, 219 | 'imports' => $this->scss->getParsedFiles(), 220 | 'vars' => crc32(serialize($this->scss->getVariables())), 221 | ]) 222 | ); 223 | 224 | return [$css, $etag]; 225 | } 226 | 227 | /** 228 | * Format error as a pseudo-element in CSS 229 | * 230 | * @param \Exception $error 231 | * 232 | * @return string 233 | */ 234 | protected function createErrorCSS(\Exception $error) 235 | { 236 | $message = str_replace( 237 | ["'", "\n"], 238 | ["\\'", "\\A"], 239 | $error->getfile() . ":\n\n" . $error->getMessage() 240 | ); 241 | 242 | return "body { display: none !important; } 243 | html:after { 244 | background: white; 245 | color: black; 246 | content: '$message'; 247 | display: block !important; 248 | font-family: mono; 249 | padding: 1em; 250 | white-space: pre; 251 | }"; 252 | } 253 | 254 | /** 255 | * Render errors as a pseudo-element within valid CSS, displaying the errors on any 256 | * page that includes this CSS. 257 | * 258 | * @param boolean $show 259 | */ 260 | public function showErrorsAsCSS($show = true) 261 | { 262 | $this->showErrorsAsCSS = $show; 263 | } 264 | 265 | /** 266 | * Compile .scss file 267 | * 268 | * @param string $in Input file (.scss) 269 | * @param string $out Output file (.css) optional 270 | * 271 | * @return string|bool 272 | * 273 | * @throws \Leafo\ScssPhp\Exception\ServerException 274 | */ 275 | public function compileFile($in, $out = null) 276 | { 277 | if (! is_readable($in)) { 278 | throw new ServerException('load error: failed to find ' . $in); 279 | } 280 | 281 | $pi = pathinfo($in); 282 | 283 | $this->scss->addImportPath($pi['dirname'] . '/'); 284 | 285 | $compiled = $this->scss->compile(file_get_contents($in), $in); 286 | 287 | if ($out !== null) { 288 | return file_put_contents($out, $compiled); 289 | } 290 | 291 | return $compiled; 292 | } 293 | 294 | /** 295 | * Check if file need compiling 296 | * 297 | * @param string $in Input file (.scss) 298 | * @param string $out Output file (.css) 299 | * 300 | * @return bool 301 | */ 302 | public function checkedCompile($in, $out) 303 | { 304 | if (! is_file($out) || filemtime($in) > filemtime($out)) { 305 | $this->compileFile($in, $out); 306 | 307 | return true; 308 | } 309 | 310 | return false; 311 | } 312 | 313 | /** 314 | * Compile requested scss and serve css. Outputs HTTP response. 315 | * 316 | * @param string $salt Prefix a string to the filename for creating the cache name hash 317 | */ 318 | public function serve($salt = '') 319 | { 320 | $protocol = isset($_SERVER['SERVER_PROTOCOL']) 321 | ? $_SERVER['SERVER_PROTOCOL'] 322 | : 'HTTP/1.0'; 323 | 324 | if ($input = $this->findInput()) { 325 | $output = $this->cacheName($salt . $input); 326 | $etag = $noneMatch = trim($this->getIfNoneMatchHeader(), '"'); 327 | 328 | if ($this->needsCompile($output, $etag)) { 329 | try { 330 | list($css, $etag) = $this->compile($input, $output); 331 | 332 | $lastModified = gmdate('D, d M Y H:i:s', filemtime($output)) . ' GMT'; 333 | 334 | header('Last-Modified: ' . $lastModified); 335 | header('Content-type: text/css'); 336 | header('ETag: "' . $etag . '"'); 337 | 338 | echo $css; 339 | } catch (\Exception $e) { 340 | if ($this->showErrorsAsCSS) { 341 | header('Content-type: text/css'); 342 | 343 | echo $this->createErrorCSS($e); 344 | } else { 345 | header($protocol . ' 500 Internal Server Error'); 346 | header('Content-type: text/plain'); 347 | 348 | echo 'Parse error: ' . $e->getMessage() . "\n"; 349 | } 350 | } 351 | 352 | return; 353 | } 354 | 355 | header('X-SCSS-Cache: true'); 356 | header('Content-type: text/css'); 357 | header('ETag: "' . $etag . '"'); 358 | 359 | if ($etag === $noneMatch) { 360 | header($protocol . ' 304 Not Modified'); 361 | 362 | return; 363 | } 364 | 365 | $modifiedSince = $this->getIfModifiedSinceHeader(); 366 | $mtime = filemtime($output); 367 | 368 | if (strtotime($modifiedSince) === $mtime) { 369 | header($protocol . ' 304 Not Modified'); 370 | 371 | return; 372 | } 373 | 374 | $lastModified = gmdate('D, d M Y H:i:s', $mtime) . ' GMT'; 375 | header('Last-Modified: ' . $lastModified); 376 | 377 | echo file_get_contents($output); 378 | 379 | return; 380 | } 381 | 382 | header($protocol . ' 404 Not Found'); 383 | header('Content-type: text/plain'); 384 | 385 | $v = Version::VERSION; 386 | echo "/* INPUT NOT FOUND scss $v */\n"; 387 | } 388 | 389 | /** 390 | * Based on explicit input/output files does a full change check on cache before compiling. 391 | * 392 | * @param string $in 393 | * @param string $out 394 | * @param boolean $force 395 | * 396 | * @return string Compiled CSS results 397 | * 398 | * @throws \Leafo\ScssPhp\Exception\ServerException 399 | */ 400 | public function checkedCachedCompile($in, $out, $force = false) 401 | { 402 | if (! is_file($in) || ! is_readable($in)) { 403 | throw new ServerException('Invalid or unreadable input file specified.'); 404 | } 405 | 406 | if (is_dir($out) || ! is_writable(file_exists($out) ? $out : dirname($out))) { 407 | throw new ServerException('Invalid or unwritable output file specified.'); 408 | } 409 | 410 | if ($force || $this->needsCompile($out, $etag)) { 411 | list($css, $etag) = $this->compile($in, $out); 412 | } else { 413 | $css = file_get_contents($out); 414 | } 415 | 416 | return $css; 417 | } 418 | 419 | /** 420 | * Constructor 421 | * 422 | * @param string $dir Root directory to .scss files 423 | * @param string $cacheDir Cache directory 424 | * @param \Leafo\ScssPhp\Compiler|null $scss SCSS compiler instance 425 | */ 426 | public function __construct($dir, $cacheDir = null, $scss = null) 427 | { 428 | $this->dir = $dir; 429 | 430 | if (! isset($cacheDir)) { 431 | $cacheDir = $this->join($dir, 'scss_cache'); 432 | } 433 | 434 | $this->cacheDir = $cacheDir; 435 | 436 | if (! is_dir($this->cacheDir)) { 437 | mkdir($this->cacheDir, 0755, true); 438 | } 439 | 440 | if (! isset($scss)) { 441 | $scss = new Compiler(); 442 | $scss->setImportPaths($this->dir); 443 | } 444 | 445 | $this->scss = $scss; 446 | $this->showErrorsAsCSS = false; 447 | 448 | if (! ini_get('date.timezone')) { 449 | date_default_timezone_set('UTC'); 450 | } 451 | } 452 | 453 | /** 454 | * Helper method to serve compiled scss 455 | * 456 | * @param string $path Root path 457 | */ 458 | public static function serveFrom($path) 459 | { 460 | $server = new self($path); 461 | $server->serve(); 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /libs/scssphp/src/Parser.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class Parser 26 | { 27 | const SOURCE_INDEX = -1; 28 | const SOURCE_LINE = -2; 29 | const SOURCE_COLUMN = -3; 30 | 31 | /** 32 | * @var array 33 | */ 34 | protected static $precedence = [ 35 | '=' => 0, 36 | 'or' => 1, 37 | 'and' => 2, 38 | '==' => 3, 39 | '!=' => 3, 40 | '<=>' => 3, 41 | '<=' => 4, 42 | '>=' => 4, 43 | '<' => 4, 44 | '>' => 4, 45 | '+' => 5, 46 | '-' => 5, 47 | '*' => 6, 48 | '/' => 6, 49 | '%' => 6, 50 | ]; 51 | 52 | protected static $commentPattern; 53 | protected static $operatorPattern; 54 | protected static $whitePattern; 55 | 56 | private $sourceName; 57 | private $sourceIndex; 58 | private $sourcePositions; 59 | private $charset; 60 | private $count; 61 | private $env; 62 | private $inParens; 63 | private $eatWhiteDefault; 64 | private $buffer; 65 | private $utf8; 66 | private $encoding; 67 | private $patternModifiers; 68 | 69 | /** 70 | * Constructor 71 | * 72 | * @api 73 | * 74 | * @param string $sourceName 75 | * @param integer $sourceIndex 76 | * @param string $encoding 77 | */ 78 | public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8') 79 | { 80 | $this->sourceName = $sourceName ?: '(stdin)'; 81 | $this->sourceIndex = $sourceIndex; 82 | $this->charset = null; 83 | $this->utf8 = ! $encoding || strtolower($encoding) === 'utf-8'; 84 | $this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais'; 85 | 86 | if (empty(self::$operatorPattern)) { 87 | self::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)'; 88 | 89 | $commentSingle = '\/\/'; 90 | $commentMultiLeft = '\/\*'; 91 | $commentMultiRight = '\*\/'; 92 | 93 | self::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight; 94 | self::$whitePattern = $this->utf8 95 | ? '/' . $commentSingle . '[^\n]*\s*|(' . self::$commentPattern . ')\s*|\s+/AisuS' 96 | : '/' . $commentSingle . '[^\n]*\s*|(' . self::$commentPattern . ')\s*|\s+/AisS'; 97 | } 98 | } 99 | 100 | /** 101 | * Get source file name 102 | * 103 | * @api 104 | * 105 | * @return string 106 | */ 107 | public function getSourceName() 108 | { 109 | return $this->sourceName; 110 | } 111 | 112 | /** 113 | * Throw parser error 114 | * 115 | * @api 116 | * 117 | * @param string $msg 118 | * 119 | * @throws \Leafo\ScssPhp\Exception\ParserException 120 | */ 121 | public function throwParseError($msg = 'parse error') 122 | { 123 | list($line, /* $column */) = $this->getSourcePosition($this->count); 124 | 125 | $loc = empty($this->sourceName) ? "line: $line" : "$this->sourceName on line $line"; 126 | 127 | if ($this->peek("(.*?)(\n|$)", $m, $this->count)) { 128 | throw new ParserException("$msg: failed at `$m[1]` $loc"); 129 | } 130 | 131 | throw new ParserException("$msg: $loc"); 132 | } 133 | 134 | /** 135 | * Parser buffer 136 | * 137 | * @api 138 | * 139 | * @param string $buffer 140 | * 141 | * @return \Leafo\ScssPhp\Block 142 | */ 143 | public function parse($buffer) 144 | { 145 | // strip BOM (byte order marker) 146 | if (substr($buffer, 0, 3) === "\xef\xbb\xbf") { 147 | $buffer = substr($buffer, 3); 148 | } 149 | 150 | $this->buffer = rtrim($buffer, "\x00..\x1f"); 151 | $this->count = 0; 152 | $this->env = null; 153 | $this->inParens = false; 154 | $this->eatWhiteDefault = true; 155 | 156 | $this->saveEncoding(); 157 | $this->extractLineNumbers($buffer); 158 | 159 | $this->pushBlock(null); // root block 160 | $this->whitespace(); 161 | $this->pushBlock(null); 162 | $this->popBlock(); 163 | 164 | while ($this->parseChunk()) { 165 | ; 166 | } 167 | 168 | if ($this->count !== strlen($this->buffer)) { 169 | $this->throwParseError(); 170 | } 171 | 172 | if (! empty($this->env->parent)) { 173 | $this->throwParseError('unclosed block'); 174 | } 175 | 176 | if ($this->charset) { 177 | array_unshift($this->env->children, $this->charset); 178 | } 179 | 180 | $this->env->isRoot = true; 181 | 182 | $this->restoreEncoding(); 183 | 184 | return $this->env; 185 | } 186 | 187 | /** 188 | * Parse a value or value list 189 | * 190 | * @api 191 | * 192 | * @param string $buffer 193 | * @param string $out 194 | * 195 | * @return boolean 196 | */ 197 | public function parseValue($buffer, &$out) 198 | { 199 | $this->count = 0; 200 | $this->env = null; 201 | $this->inParens = false; 202 | $this->eatWhiteDefault = true; 203 | $this->buffer = (string) $buffer; 204 | 205 | $this->saveEncoding(); 206 | 207 | $list = $this->valueList($out); 208 | 209 | $this->restoreEncoding(); 210 | 211 | return $list; 212 | } 213 | 214 | /** 215 | * Parse a selector or selector list 216 | * 217 | * @api 218 | * 219 | * @param string $buffer 220 | * @param string $out 221 | * 222 | * @return boolean 223 | */ 224 | public function parseSelector($buffer, &$out) 225 | { 226 | $this->count = 0; 227 | $this->env = null; 228 | $this->inParens = false; 229 | $this->eatWhiteDefault = true; 230 | $this->buffer = (string) $buffer; 231 | 232 | $this->saveEncoding(); 233 | 234 | $selector = $this->selectors($out); 235 | 236 | $this->restoreEncoding(); 237 | 238 | return $selector; 239 | } 240 | 241 | /** 242 | * Parse a single chunk off the head of the buffer and append it to the 243 | * current parse environment. 244 | * 245 | * Returns false when the buffer is empty, or when there is an error. 246 | * 247 | * This function is called repeatedly until the entire document is 248 | * parsed. 249 | * 250 | * This parser is most similar to a recursive descent parser. Single 251 | * functions represent discrete grammatical rules for the language, and 252 | * they are able to capture the text that represents those rules. 253 | * 254 | * Consider the function Compiler::keyword(). (All parse functions are 255 | * structured the same.) 256 | * 257 | * The function takes a single reference argument. When calling the 258 | * function it will attempt to match a keyword on the head of the buffer. 259 | * If it is successful, it will place the keyword in the referenced 260 | * argument, advance the position in the buffer, and return true. If it 261 | * fails then it won't advance the buffer and it will return false. 262 | * 263 | * All of these parse functions are powered by Compiler::match(), which behaves 264 | * the same way, but takes a literal regular expression. Sometimes it is 265 | * more convenient to use match instead of creating a new function. 266 | * 267 | * Because of the format of the functions, to parse an entire string of 268 | * grammatical rules, you can chain them together using &&. 269 | * 270 | * But, if some of the rules in the chain succeed before one fails, then 271 | * the buffer position will be left at an invalid state. In order to 272 | * avoid this, Compiler::seek() is used to remember and set buffer positions. 273 | * 274 | * Before parsing a chain, use $s = $this->seek() to remember the current 275 | * position into $s. Then if a chain fails, use $this->seek($s) to 276 | * go back where we started. 277 | * 278 | * @return boolean 279 | */ 280 | protected function parseChunk() 281 | { 282 | $s = $this->seek(); 283 | 284 | // the directives 285 | if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') { 286 | if ($this->literal('@at-root') && 287 | ($this->selectors($selector) || true) && 288 | ($this->map($with) || true) && 289 | $this->literal('{') 290 | ) { 291 | $atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s); 292 | $atRoot->selector = $selector; 293 | $atRoot->with = $with; 294 | 295 | return true; 296 | } 297 | 298 | $this->seek($s); 299 | 300 | if ($this->literal('@media') && $this->mediaQueryList($mediaQueryList) && $this->literal('{')) { 301 | $media = $this->pushSpecialBlock(Type::T_MEDIA, $s); 302 | $media->queryList = $mediaQueryList[2]; 303 | 304 | return true; 305 | } 306 | 307 | $this->seek($s); 308 | 309 | if ($this->literal('@mixin') && 310 | $this->keyword($mixinName) && 311 | ($this->argumentDef($args) || true) && 312 | $this->literal('{') 313 | ) { 314 | $mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s); 315 | $mixin->name = $mixinName; 316 | $mixin->args = $args; 317 | 318 | return true; 319 | } 320 | 321 | $this->seek($s); 322 | 323 | if ($this->literal('@include') && 324 | $this->keyword($mixinName) && 325 | ($this->literal('(') && 326 | ($this->argValues($argValues) || true) && 327 | $this->literal(')') || true) && 328 | ($this->end() || 329 | $this->literal('{') && $hasBlock = true) 330 | ) { 331 | $child = [Type::T_INCLUDE, $mixinName, isset($argValues) ? $argValues : null, null]; 332 | 333 | if (! empty($hasBlock)) { 334 | $include = $this->pushSpecialBlock(Type::T_INCLUDE, $s); 335 | $include->child = $child; 336 | } else { 337 | $this->append($child, $s); 338 | } 339 | 340 | return true; 341 | } 342 | 343 | $this->seek($s); 344 | 345 | if ($this->literal('@scssphp-import-once') && 346 | $this->valueList($importPath) && 347 | $this->end() 348 | ) { 349 | $this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s); 350 | 351 | return true; 352 | } 353 | 354 | $this->seek($s); 355 | 356 | if ($this->literal('@import') && 357 | $this->valueList($importPath) && 358 | $this->end() 359 | ) { 360 | $this->append([Type::T_IMPORT, $importPath], $s); 361 | 362 | return true; 363 | } 364 | 365 | $this->seek($s); 366 | 367 | if ($this->literal('@import') && 368 | $this->url($importPath) && 369 | $this->end() 370 | ) { 371 | $this->append([Type::T_IMPORT, $importPath], $s); 372 | 373 | return true; 374 | } 375 | 376 | $this->seek($s); 377 | 378 | if ($this->literal('@extend') && 379 | $this->selectors($selectors) && 380 | $this->end() 381 | ) { 382 | // check for '!flag' 383 | $optional = $this->stripOptionalFlag($selectors); 384 | $this->append([Type::T_EXTEND, $selectors, $optional], $s); 385 | 386 | return true; 387 | } 388 | 389 | $this->seek($s); 390 | 391 | if ($this->literal('@function') && 392 | $this->keyword($fnName) && 393 | $this->argumentDef($args) && 394 | $this->literal('{') 395 | ) { 396 | $func = $this->pushSpecialBlock(Type::T_FUNCTION, $s); 397 | $func->name = $fnName; 398 | $func->args = $args; 399 | 400 | return true; 401 | } 402 | 403 | $this->seek($s); 404 | 405 | if ($this->literal('@break') && $this->end()) { 406 | $this->append([Type::T_BREAK], $s); 407 | 408 | return true; 409 | } 410 | 411 | $this->seek($s); 412 | 413 | if ($this->literal('@continue') && $this->end()) { 414 | $this->append([Type::T_CONTINUE], $s); 415 | 416 | return true; 417 | } 418 | 419 | $this->seek($s); 420 | 421 | 422 | if ($this->literal('@return') && ($this->valueList($retVal) || true) && $this->end()) { 423 | $this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s); 424 | 425 | return true; 426 | } 427 | 428 | $this->seek($s); 429 | 430 | if ($this->literal('@each') && 431 | $this->genericList($varNames, 'variable', ',', false) && 432 | $this->literal('in') && 433 | $this->valueList($list) && 434 | $this->literal('{') 435 | ) { 436 | $each = $this->pushSpecialBlock(Type::T_EACH, $s); 437 | 438 | foreach ($varNames[2] as $varName) { 439 | $each->vars[] = $varName[1]; 440 | } 441 | 442 | $each->list = $list; 443 | 444 | return true; 445 | } 446 | 447 | $this->seek($s); 448 | 449 | if ($this->literal('@while') && 450 | $this->expression($cond) && 451 | $this->literal('{') 452 | ) { 453 | $while = $this->pushSpecialBlock(Type::T_WHILE, $s); 454 | $while->cond = $cond; 455 | 456 | return true; 457 | } 458 | 459 | $this->seek($s); 460 | 461 | if ($this->literal('@for') && 462 | $this->variable($varName) && 463 | $this->literal('from') && 464 | $this->expression($start) && 465 | ($this->literal('through') || 466 | ($forUntil = true && $this->literal('to'))) && 467 | $this->expression($end) && 468 | $this->literal('{') 469 | ) { 470 | $for = $this->pushSpecialBlock(Type::T_FOR, $s); 471 | $for->var = $varName[1]; 472 | $for->start = $start; 473 | $for->end = $end; 474 | $for->until = isset($forUntil); 475 | 476 | return true; 477 | } 478 | 479 | $this->seek($s); 480 | 481 | if ($this->literal('@if') && $this->valueList($cond) && $this->literal('{')) { 482 | $if = $this->pushSpecialBlock(Type::T_IF, $s); 483 | $if->cond = $cond; 484 | $if->cases = []; 485 | 486 | return true; 487 | } 488 | 489 | $this->seek($s); 490 | 491 | if ($this->literal('@debug') && 492 | $this->valueList($value) && 493 | $this->end() 494 | ) { 495 | $this->append([Type::T_DEBUG, $value], $s); 496 | 497 | return true; 498 | } 499 | 500 | $this->seek($s); 501 | 502 | if ($this->literal('@warn') && 503 | $this->valueList($value) && 504 | $this->end() 505 | ) { 506 | $this->append([Type::T_WARN, $value], $s); 507 | 508 | return true; 509 | } 510 | 511 | $this->seek($s); 512 | 513 | if ($this->literal('@error') && 514 | $this->valueList($value) && 515 | $this->end() 516 | ) { 517 | $this->append([Type::T_ERROR, $value], $s); 518 | 519 | return true; 520 | } 521 | 522 | $this->seek($s); 523 | 524 | if ($this->literal('@content') && $this->end()) { 525 | $this->append([Type::T_MIXIN_CONTENT], $s); 526 | 527 | return true; 528 | } 529 | 530 | $this->seek($s); 531 | 532 | $last = $this->last(); 533 | 534 | if (isset($last) && $last[0] === Type::T_IF) { 535 | list(, $if) = $last; 536 | 537 | if ($this->literal('@else')) { 538 | if ($this->literal('{')) { 539 | $else = $this->pushSpecialBlock(Type::T_ELSE, $s); 540 | } elseif ($this->literal('if') && $this->valueList($cond) && $this->literal('{')) { 541 | $else = $this->pushSpecialBlock(Type::T_ELSEIF, $s); 542 | $else->cond = $cond; 543 | } 544 | 545 | if (isset($else)) { 546 | $else->dontAppend = true; 547 | $if->cases[] = $else; 548 | 549 | return true; 550 | } 551 | } 552 | 553 | $this->seek($s); 554 | } 555 | 556 | // only retain the first @charset directive encountered 557 | if ($this->literal('@charset') && 558 | $this->valueList($charset) && 559 | $this->end() 560 | ) { 561 | if (! isset($this->charset)) { 562 | $statement = [Type::T_CHARSET, $charset]; 563 | 564 | list($line, $column) = $this->getSourcePosition($s); 565 | 566 | $statement[self::SOURCE_LINE] = $line; 567 | $statement[self::SOURCE_COLUMN] = $column; 568 | $statement[self::SOURCE_INDEX] = $this->sourceIndex; 569 | 570 | $this->charset = $statement; 571 | } 572 | 573 | return true; 574 | } 575 | 576 | $this->seek($s); 577 | 578 | // doesn't match built in directive, do generic one 579 | if ($this->literal('@', false) && 580 | $this->keyword($dirName) && 581 | ($this->variable($dirValue) || $this->openString('{', $dirValue) || true) && 582 | $this->literal('{') 583 | ) { 584 | if ($dirName === 'media') { 585 | $directive = $this->pushSpecialBlock(Type::T_MEDIA, $s); 586 | } else { 587 | $directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s); 588 | $directive->name = $dirName; 589 | } 590 | 591 | if (isset($dirValue)) { 592 | $directive->value = $dirValue; 593 | } 594 | 595 | return true; 596 | } 597 | 598 | $this->seek($s); 599 | 600 | return false; 601 | } 602 | 603 | // property shortcut 604 | // captures most properties before having to parse a selector 605 | if ($this->keyword($name, false) && 606 | $this->literal(': ') && 607 | $this->valueList($value) && 608 | $this->end() 609 | ) { 610 | $name = [Type::T_STRING, '', [$name]]; 611 | $this->append([Type::T_ASSIGN, $name, $value], $s); 612 | 613 | return true; 614 | } 615 | 616 | $this->seek($s); 617 | 618 | // variable assigns 619 | if ($this->variable($name) && 620 | $this->literal(':') && 621 | $this->valueList($value) && 622 | $this->end() 623 | ) { 624 | // check for '!flag' 625 | $assignmentFlags = $this->stripAssignmentFlags($value); 626 | $this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s); 627 | 628 | return true; 629 | } 630 | 631 | $this->seek($s); 632 | 633 | // misc 634 | if ($this->literal('-->')) { 635 | return true; 636 | } 637 | 638 | // opening css block 639 | if ($this->selectors($selectors) && $this->literal('{')) { 640 | $this->pushBlock($selectors, $s); 641 | 642 | return true; 643 | } 644 | 645 | $this->seek($s); 646 | 647 | // property assign, or nested assign 648 | if ($this->propertyName($name) && $this->literal(':')) { 649 | $foundSomething = false; 650 | 651 | if ($this->valueList($value)) { 652 | $this->append([Type::T_ASSIGN, $name, $value], $s); 653 | $foundSomething = true; 654 | } 655 | 656 | if ($this->literal('{')) { 657 | $propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s); 658 | $propBlock->prefix = $name; 659 | $foundSomething = true; 660 | } elseif ($foundSomething) { 661 | $foundSomething = $this->end(); 662 | } 663 | 664 | if ($foundSomething) { 665 | return true; 666 | } 667 | } 668 | 669 | $this->seek($s); 670 | 671 | // closing a block 672 | if ($this->literal('}')) { 673 | $block = $this->popBlock(); 674 | 675 | if (isset($block->type) && $block->type === Type::T_INCLUDE) { 676 | $include = $block->child; 677 | unset($block->child); 678 | $include[3] = $block; 679 | $this->append($include, $s); 680 | } elseif (empty($block->dontAppend)) { 681 | $type = isset($block->type) ? $block->type : Type::T_BLOCK; 682 | $this->append([$type, $block], $s); 683 | } 684 | 685 | return true; 686 | } 687 | 688 | // extra stuff 689 | if ($this->literal(';') || 690 | $this->literal('