scssphp 추가
This commit is contained in:
+2
-1
@@ -4,6 +4,7 @@
|
||||
"sergeytsalkov/meekrodb": "^2.3",
|
||||
"league/plates": "^3.3",
|
||||
"phpmailer/phpmailer": "^6.0",
|
||||
"guzzlehttp/guzzle": "^6.3"
|
||||
"guzzlehttp/guzzle": "^6.3",
|
||||
"leafo/scssphp": "^0.7.5"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+53
-1
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "450a6f5772559666e92515aa10280024",
|
||||
"content-hash": "7b9fdde15b3d81d1431847e29f201587",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brandonwamboldt/utilphp",
|
||||
@@ -233,6 +233,58 @@
|
||||
],
|
||||
"time": "2017-03-20T17:10:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "leafo/scssphp",
|
||||
"version": "v0.7.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/leafo/scssphp.git",
|
||||
"reference": "8b538d828bbb75276974605c4a1a435e939da74e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/leafo/scssphp/zipball/8b538d828bbb75276974605c4a1a435e939da74e",
|
||||
"reference": "8b538d828bbb75276974605c4a1a435e939da74e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.6",
|
||||
"squizlabs/php_codesniffer": "~2.5"
|
||||
},
|
||||
"bin": [
|
||||
"bin/pscss"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Leafo\\ScssPhp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Leaf Corcoran",
|
||||
"email": "leafot@gmail.com",
|
||||
"homepage": "http://leafo.net"
|
||||
}
|
||||
],
|
||||
"description": "scssphp is a compiler for SCSS written in PHP.",
|
||||
"homepage": "http://leafo.github.io/scssphp/",
|
||||
"keywords": [
|
||||
"css",
|
||||
"less",
|
||||
"sass",
|
||||
"scss",
|
||||
"stylesheet"
|
||||
],
|
||||
"time": "2018-02-09T02:04:21+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/plates",
|
||||
"version": "3.3.0",
|
||||
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
dir=$(cd "${0%[/\\]*}" > /dev/null; cd "../leafo/scssphp/bin" && pwd)
|
||||
|
||||
if [ -d /proc/cygdrive ] && [[ $(which php) == $(readlink -n /proc/cygdrive)/* ]]; then
|
||||
# We are in Cgywin using Windows php, so the path must be translated
|
||||
dir=$(cygpath -m "$dir");
|
||||
fi
|
||||
|
||||
"${dir}/pscss" "$@"
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
@ECHO OFF
|
||||
setlocal DISABLEDELAYEDEXPANSION
|
||||
SET BIN_TARGET=%~dp0/../leafo/scssphp/bin/pscss
|
||||
php "%BIN_TARGET%" %*
|
||||
Vendored
+1
@@ -9,6 +9,7 @@ return array(
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
|
||||
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
|
||||
'League\\Plates\\' => array($vendorDir . '/league/plates/src'),
|
||||
'Leafo\\ScssPhp\\' => array($vendorDir . '/leafo/scssphp/src'),
|
||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
||||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
|
||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
||||
|
||||
Vendored
+5
@@ -21,6 +21,7 @@ class ComposerStaticInit67b09c83b85c3bc0027caefe5bba171a
|
||||
'L' =>
|
||||
array (
|
||||
'League\\Plates\\' => 14,
|
||||
'Leafo\\ScssPhp\\' => 14,
|
||||
),
|
||||
'G' =>
|
||||
array (
|
||||
@@ -43,6 +44,10 @@ class ComposerStaticInit67b09c83b85c3bc0027caefe5bba171a
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/league/plates/src',
|
||||
),
|
||||
'Leafo\\ScssPhp\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/leafo/scssphp/src',
|
||||
),
|
||||
'GuzzleHttp\\Psr7\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
|
||||
|
||||
Vendored
+54
@@ -234,6 +234,60 @@
|
||||
"url"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "leafo/scssphp",
|
||||
"version": "v0.7.5",
|
||||
"version_normalized": "0.7.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/leafo/scssphp.git",
|
||||
"reference": "8b538d828bbb75276974605c4a1a435e939da74e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/leafo/scssphp/zipball/8b538d828bbb75276974605c4a1a435e939da74e",
|
||||
"reference": "8b538d828bbb75276974605c4a1a435e939da74e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.6",
|
||||
"squizlabs/php_codesniffer": "~2.5"
|
||||
},
|
||||
"time": "2018-02-09T02:04:21+00:00",
|
||||
"bin": [
|
||||
"bin/pscss"
|
||||
],
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Leafo\\ScssPhp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Leaf Corcoran",
|
||||
"email": "leafot@gmail.com",
|
||||
"homepage": "http://leafo.net"
|
||||
}
|
||||
],
|
||||
"description": "scssphp is a compiler for SCSS written in PHP.",
|
||||
"homepage": "http://leafo.github.io/scssphp/",
|
||||
"keywords": [
|
||||
"css",
|
||||
"less",
|
||||
"sass",
|
||||
"scss",
|
||||
"stylesheet"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "league/plates",
|
||||
"version": "3.3.0",
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2015 Leaf Corcoran, http://leafo.github.io/scssphp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
# scssphp
|
||||
### <http://leafo.github.io/scssphp>
|
||||
|
||||
[](http://travis-ci.org/leafo/scssphp)
|
||||
[](https://packagist.org/packages/leafo/scssphp)
|
||||
|
||||
`scssphp` is a compiler for SCSS written in PHP.
|
||||
|
||||
Checkout the homepage, <http://leafo.github.io/scssphp>, for directions on how to use.
|
||||
|
||||
## Running Tests
|
||||
|
||||
`scssphp` uses [PHPUnit](https://github.com/sebastianbergmann/phpunit) for testing.
|
||||
|
||||
Run the following command from the root directory to run every test:
|
||||
|
||||
vendor/bin/phpunit tests
|
||||
|
||||
There are several tests in the `tests/` directory:
|
||||
|
||||
* `ApiTest.php` contains various unit tests that test the PHP interface.
|
||||
* `ExceptionTest.php` contains unit tests that test for exceptions thrown by the parser and compiler.
|
||||
* `FailingTest.php` contains tests reported in Github issues that demonstrate compatibility bugs.
|
||||
* `InputTest.php` compiles every `.scss` file in the `tests/inputs` directory
|
||||
then compares to the respective `.css` file in the `tests/outputs` directory.
|
||||
* `ScssTest.php` extracts (ruby) `scss` tests from the `tests/scss_test.rb` file.
|
||||
* `ServerTest.php` contains functional tests for the `Server` class.
|
||||
|
||||
When changing any of the tests in `tests/inputs`, the tests will most likely
|
||||
fail because the output has changed. Once you verify that the output is correct
|
||||
you can run the following command to rebuild all the tests:
|
||||
|
||||
BUILD=1 vendor/bin/phpunit tests
|
||||
|
||||
This will compile all the tests, and save results into `tests/outputs`.
|
||||
|
||||
To enable the `scss` compatibility tests:
|
||||
|
||||
TEST_SCSS_COMPAT=1 vendor/bin/phpunit tests
|
||||
|
||||
## Coding Standard
|
||||
|
||||
`scssphp` source conforms to [PSR2](http://www.php-fig.org/psr/psr-2/).
|
||||
|
||||
Run the following command from the root directory to check the code for "sniffs".
|
||||
|
||||
vendor/bin/phpcs --standard=PSR2 bin src tests
|
||||
Vendored
+215
@@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2015 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
error_reporting(E_ALL);
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.4') < 0) {
|
||||
die('Requires PHP 5.4 or above');
|
||||
}
|
||||
|
||||
include __DIR__ . '/../scss.inc.php';
|
||||
|
||||
use Leafo\ScssPhp\Compiler;
|
||||
use Leafo\ScssPhp\Parser;
|
||||
use Leafo\ScssPhp\Version;
|
||||
|
||||
$style = null;
|
||||
$loadPaths = null;
|
||||
$precision = null;
|
||||
$dumpTree = false;
|
||||
$inputFile = null;
|
||||
$changeDir = false;
|
||||
$debugInfo = false;
|
||||
$lineNumbers = false;
|
||||
$ignoreErrors = false;
|
||||
$encoding = false;
|
||||
$sourceMap = false;
|
||||
|
||||
/**
|
||||
* Parse argument
|
||||
*
|
||||
* @param integer $i
|
||||
* @param array $options
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
function parseArgument(&$i, $options) {
|
||||
global $argc;
|
||||
global $argv;
|
||||
|
||||
if (! preg_match('/^(?:' . implode('|', (array) $options) . ')=?(.*)/', $argv[$i], $matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (strlen($matches[1])) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
if ($i + 1 < $argc) {
|
||||
$i++;
|
||||
|
||||
return $argv[$i];
|
||||
}
|
||||
}
|
||||
|
||||
for ($i = 1; $i < $argc; $i++) {
|
||||
if ($argv[$i] === '-h' || $argv[$i] === '--help') {
|
||||
$exe = $argv[0];
|
||||
|
||||
$HELP = <<<EOT
|
||||
Usage: $exe [options] [input-file]
|
||||
|
||||
Options include:
|
||||
|
||||
-h, --help Show this message
|
||||
--continue-on-error Continue compilation (as best as possible) when error encountered
|
||||
--debug-info Annotate selectors with CSS referring to the source file and line number
|
||||
-f=format Set the output format (compact, compressed, crunched, expanded, or nested)
|
||||
-i=path Set import path
|
||||
--iso8859-1 Use iso8859-1 encoding instead of utf-8 (default utf-8)
|
||||
--line-numbers Annotate selectors with comments referring to the source file and line number
|
||||
-p=precision Set decimal number precision (default 10)
|
||||
--sourcemap Create source map file
|
||||
-T Dump formatted parse tree
|
||||
-v, --version Print the version
|
||||
|
||||
EOT;
|
||||
exit($HELP);
|
||||
}
|
||||
|
||||
if ($argv[$i] === '-v' || $argv[$i] === '--version') {
|
||||
exit(Version::VERSION . "\n");
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--continue-on-error') {
|
||||
$ignoreErrors = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--debug-info') {
|
||||
$debugInfo = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--iso8859-1') {
|
||||
$encoding = 'iso8859-1';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--line-numbers' || $argv[$i] === '--line-comments') {
|
||||
$lineNumbers = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '--sourcemap') {
|
||||
$sourceMap = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($argv[$i] === '-T') {
|
||||
$dumpTree = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = parseArgument($i, array('-f', '--style'));
|
||||
|
||||
if (isset($value)) {
|
||||
$style = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = parseArgument($i, array('-i', '--load_paths'));
|
||||
|
||||
if (isset($value)) {
|
||||
$loadPaths = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = parseArgument($i, array('-p', '--precision'));
|
||||
|
||||
if (isset($value)) {
|
||||
$precision = $value;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_exists($argv[$i])) {
|
||||
$inputFile = $argv[$i];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($inputFile) {
|
||||
$data = file_get_contents($inputFile);
|
||||
|
||||
$newWorkingDir = dirname(realpath($inputFile));
|
||||
$oldWorkingDir = getcwd();
|
||||
|
||||
if ($oldWorkingDir !== $newWorkingDir) {
|
||||
$changeDir = chdir($newWorkingDir);
|
||||
$inputFile = basename($inputFile);
|
||||
}
|
||||
} else {
|
||||
$data = '';
|
||||
|
||||
while (! feof(STDIN)) {
|
||||
$data .= fread(STDIN, 8192);
|
||||
}
|
||||
}
|
||||
|
||||
if ($dumpTree) {
|
||||
$parser = new Parser($inputFile);
|
||||
|
||||
print_r(json_decode(json_encode($parser->parse($data)), true));
|
||||
|
||||
exit();
|
||||
}
|
||||
|
||||
$scss = new Compiler();
|
||||
|
||||
if ($debugInfo) {
|
||||
$scss->setLineNumberStyle(Compiler::DEBUG_INFO);
|
||||
}
|
||||
|
||||
if ($lineNumbers) {
|
||||
$scss->setLineNumberStyle(Compiler::LINE_COMMENTS);
|
||||
}
|
||||
|
||||
if ($ignoreErrors) {
|
||||
$scss->setIgnoreErrors($ignoreErrors);
|
||||
}
|
||||
|
||||
if ($loadPaths) {
|
||||
$scss->setImportPaths(explode(PATH_SEPARATOR, $loadPaths));
|
||||
}
|
||||
|
||||
if ($precision) {
|
||||
$scss->setNumberPrecision($precision);
|
||||
}
|
||||
|
||||
if ($style) {
|
||||
$scss->setFormatter('Leafo\\ScssPhp\\Formatter\\' . ucfirst($style));
|
||||
}
|
||||
|
||||
if ($sourceMap) {
|
||||
$scss->setSourceMap(Compiler::SOURCE_MAP_FILE);
|
||||
}
|
||||
|
||||
if ($encoding) {
|
||||
$scss->setEncoding($encoding);
|
||||
}
|
||||
|
||||
echo $scss->compile($data, $inputFile);
|
||||
|
||||
if ($changeDir) {
|
||||
chdir($oldWorkingDir);
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "leafo/scssphp",
|
||||
"type": "library",
|
||||
"description": "scssphp is a compiler for SCSS written in PHP.",
|
||||
"keywords": ["css", "stylesheet", "scss", "sass", "less"],
|
||||
"homepage": "http://leafo.github.io/scssphp/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Leaf Corcoran",
|
||||
"email": "leafot@gmail.com",
|
||||
"homepage": "http://leafo.net"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": { "Leafo\\ScssPhp\\": "src/" }
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "Leafo\\ScssPhp\\Test\\": "tests/" }
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"squizlabs/php_codesniffer": "~2.5",
|
||||
"phpunit/phpunit": "~4.6"
|
||||
},
|
||||
"bin": ["bin/pscss"],
|
||||
"archive": {
|
||||
"exclude": [
|
||||
"/Makefile",
|
||||
"/.gitattributes",
|
||||
"/.gitignore",
|
||||
"/.travis.yml",
|
||||
"/phpunit.xml.dist",
|
||||
"/tests"
|
||||
]
|
||||
}
|
||||
}
|
||||
+517
@@ -0,0 +1,517 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2017 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
use Leafo\ScssPhp\Compiler;
|
||||
use Leafo\ScssPhp\Exception\ServerException;
|
||||
use Leafo\ScssPhp\Version;
|
||||
|
||||
/**
|
||||
* Server
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
class Server
|
||||
{
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
private $showErrorsAsCSS;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $dir;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $cacheDir;
|
||||
|
||||
/**
|
||||
* @var \Leafo\ScssPhp\Compiler
|
||||
*/
|
||||
private $scss;
|
||||
|
||||
/**
|
||||
* Join path components
|
||||
*
|
||||
* @param string $left Path component, left of the directory separator
|
||||
* @param string $right Path component, right of the directory separator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function join($left, $right)
|
||||
{
|
||||
return rtrim($left, '/\\') . DIRECTORY_SEPARATOR . ltrim($right, '/\\');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name of requested .scss file
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function inputName()
|
||||
{
|
||||
switch (true) {
|
||||
case isset($_GET['p']):
|
||||
return $_GET['p'];
|
||||
case isset($_SERVER['PATH_INFO']):
|
||||
return $_SERVER['PATH_INFO'];
|
||||
case isset($_SERVER['DOCUMENT_URI']):
|
||||
return substr($_SERVER['DOCUMENT_URI'], strlen($_SERVER['SCRIPT_NAME']));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to requested .scss file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function findInput()
|
||||
{
|
||||
if (($input = $this->inputName())
|
||||
&& strpos($input, '..') === false
|
||||
&& substr($input, -5) === '.scss'
|
||||
) {
|
||||
$name = $this->join($this->dir, $input);
|
||||
|
||||
if (is_file($name) && is_readable($name)) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to cached .css file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function cacheName($fname)
|
||||
{
|
||||
return $this->join($this->cacheDir, md5($fname) . '.css');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to meta data
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function metadataName($out)
|
||||
{
|
||||
return $out . '.meta';
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether .scss file needs to be re-compiled.
|
||||
*
|
||||
* @param string $out Output path
|
||||
* @param string $etag ETag
|
||||
*
|
||||
* @return boolean True if compile required.
|
||||
*/
|
||||
protected function needsCompile($out, &$etag)
|
||||
{
|
||||
if (! is_file($out)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$mtime = filemtime($out);
|
||||
|
||||
$metadataName = $this->metadataName($out);
|
||||
|
||||
if (is_readable($metadataName)) {
|
||||
$metadata = unserialize(file_get_contents($metadataName));
|
||||
|
||||
foreach ($metadata['imports'] as $import => $originalMtime) {
|
||||
$currentMtime = filemtime($import);
|
||||
|
||||
if ($currentMtime !== $originalMtime || $currentMtime > $mtime) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$metaVars = crc32(serialize($this->scss->getVariables()));
|
||||
|
||||
if ($metaVars !== $metadata['vars']) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$etag = $metadata['etag'];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get If-Modified-Since header from client request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getIfModifiedSinceHeader()
|
||||
{
|
||||
$modifiedSince = null;
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
||||
$modifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
|
||||
|
||||
if (false !== ($semicolonPos = strpos($modifiedSince, ';'))) {
|
||||
$modifiedSince = substr($modifiedSince, 0, $semicolonPos);
|
||||
}
|
||||
}
|
||||
|
||||
return $modifiedSince;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get If-None-Match header from client request
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getIfNoneMatchHeader()
|
||||
{
|
||||
$noneMatch = null;
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
|
||||
$noneMatch = $_SERVER['HTTP_IF_NONE_MATCH'];
|
||||
}
|
||||
|
||||
return $noneMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile .scss file
|
||||
*
|
||||
* @param string $in Input path (.scss)
|
||||
* @param string $out Output path (.css)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function compile($in, $out)
|
||||
{
|
||||
$start = microtime(true);
|
||||
$css = $this->scss->compile(file_get_contents($in), $in);
|
||||
$elapsed = round((microtime(true) - $start), 4);
|
||||
|
||||
$v = Version::VERSION;
|
||||
$t = date('r');
|
||||
$css = "/* compiled by scssphp $v on $t (${elapsed}s) */\n\n" . $css;
|
||||
$etag = md5($css);
|
||||
|
||||
file_put_contents($out, $css);
|
||||
file_put_contents(
|
||||
$this->metadataName($out),
|
||||
serialize([
|
||||
'etag' => $etag,
|
||||
'imports' => $this->scss->getParsedFiles(),
|
||||
'vars' => crc32(serialize($this->scss->getVariables())),
|
||||
])
|
||||
);
|
||||
|
||||
return [$css, $etag];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format error as a pseudo-element in CSS
|
||||
*
|
||||
* @param \Exception $error
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function createErrorCSS(\Exception $error)
|
||||
{
|
||||
$message = str_replace(
|
||||
["'", "\n"],
|
||||
["\\'", "\\A"],
|
||||
$error->getfile() . ":\n\n" . $error->getMessage()
|
||||
);
|
||||
|
||||
return "body { display: none !important; }
|
||||
html:after {
|
||||
background: white;
|
||||
color: black;
|
||||
content: '$message';
|
||||
display: block !important;
|
||||
font-family: mono;
|
||||
padding: 1em;
|
||||
white-space: pre;
|
||||
}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render errors as a pseudo-element within valid CSS, displaying the errors on any
|
||||
* page that includes this CSS.
|
||||
*
|
||||
* @param boolean $show
|
||||
*/
|
||||
public function showErrorsAsCSS($show = true)
|
||||
{
|
||||
$this->showErrorsAsCSS = $show;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile .scss file
|
||||
*
|
||||
* @param string $in Input file (.scss)
|
||||
* @param string $out Output file (.css) optional
|
||||
*
|
||||
* @return string|bool
|
||||
*
|
||||
* @throws \Leafo\ScssPhp\Exception\ServerException
|
||||
*/
|
||||
public function compileFile($in, $out = null)
|
||||
{
|
||||
if (! is_readable($in)) {
|
||||
throw new ServerException('load error: failed to find ' . $in);
|
||||
}
|
||||
|
||||
$pi = pathinfo($in);
|
||||
|
||||
$this->scss->addImportPath($pi['dirname'] . '/');
|
||||
|
||||
$compiled = $this->scss->compile(file_get_contents($in), $in);
|
||||
|
||||
if ($out !== null) {
|
||||
return file_put_contents($out, $compiled);
|
||||
}
|
||||
|
||||
return $compiled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file need compiling
|
||||
*
|
||||
* @param string $in Input file (.scss)
|
||||
* @param string $out Output file (.css)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkedCompile($in, $out)
|
||||
{
|
||||
if (! is_file($out) || filemtime($in) > filemtime($out)) {
|
||||
$this->compileFile($in, $out);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile requested scss and serve css. Outputs HTTP response.
|
||||
*
|
||||
* @param string $salt Prefix a string to the filename for creating the cache name hash
|
||||
*/
|
||||
public function serve($salt = '')
|
||||
{
|
||||
$protocol = isset($_SERVER['SERVER_PROTOCOL'])
|
||||
? $_SERVER['SERVER_PROTOCOL']
|
||||
: 'HTTP/1.0';
|
||||
|
||||
if ($input = $this->findInput()) {
|
||||
$output = $this->cacheName($salt . $input);
|
||||
$etag = $noneMatch = trim($this->getIfNoneMatchHeader(), '"');
|
||||
|
||||
if ($this->needsCompile($output, $etag)) {
|
||||
try {
|
||||
list($css, $etag) = $this->compile($input, $output);
|
||||
|
||||
$lastModified = gmdate('D, d M Y H:i:s', filemtime($output)) . ' GMT';
|
||||
|
||||
header('Last-Modified: ' . $lastModified);
|
||||
header('Content-type: text/css');
|
||||
header('ETag: "' . $etag . '"');
|
||||
|
||||
echo $css;
|
||||
} catch (\Exception $e) {
|
||||
if ($this->showErrorsAsCSS) {
|
||||
header('Content-type: text/css');
|
||||
|
||||
echo $this->createErrorCSS($e);
|
||||
} else {
|
||||
header($protocol . ' 500 Internal Server Error');
|
||||
header('Content-type: text/plain');
|
||||
|
||||
echo 'Parse error: ' . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
header('X-SCSS-Cache: true');
|
||||
header('Content-type: text/css');
|
||||
header('ETag: "' . $etag . '"');
|
||||
|
||||
if ($etag === $noneMatch) {
|
||||
header($protocol . ' 304 Not Modified');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$modifiedSince = $this->getIfModifiedSinceHeader();
|
||||
$mtime = filemtime($output);
|
||||
|
||||
if (strtotime($modifiedSince) === $mtime) {
|
||||
header($protocol . ' 304 Not Modified');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$lastModified = gmdate('D, d M Y H:i:s', $mtime) . ' GMT';
|
||||
header('Last-Modified: ' . $lastModified);
|
||||
|
||||
echo file_get_contents($output);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
header($protocol . ' 404 Not Found');
|
||||
header('Content-type: text/plain');
|
||||
|
||||
$v = Version::VERSION;
|
||||
echo "/* INPUT NOT FOUND scss $v */\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on explicit input/output files does a full change check on cache before compiling.
|
||||
*
|
||||
* @param string $in
|
||||
* @param string $out
|
||||
* @param boolean $force
|
||||
*
|
||||
* @return string Compiled CSS results
|
||||
*
|
||||
* @throws \Leafo\ScssPhp\Exception\ServerException
|
||||
*/
|
||||
public function checkedCachedCompile($in, $out, $force = false)
|
||||
{
|
||||
if (! is_file($in) || ! is_readable($in)) {
|
||||
throw new ServerException('Invalid or unreadable input file specified.');
|
||||
}
|
||||
|
||||
if (is_dir($out) || ! is_writable(file_exists($out) ? $out : dirname($out))) {
|
||||
throw new ServerException('Invalid or unwritable output file specified.');
|
||||
}
|
||||
|
||||
if ($force || $this->needsCompile($out, $etag)) {
|
||||
list($css, $etag) = $this->compile($in, $out);
|
||||
} else {
|
||||
$css = file_get_contents($out);
|
||||
}
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute scssphp on a .scss file or a scssphp cache structure
|
||||
*
|
||||
* The scssphp cache structure contains information about a specific
|
||||
* scss file having been parsed. It can be used as a hint for future
|
||||
* calls to determine whether or not a rebuild is required.
|
||||
*
|
||||
* The cache structure contains two important keys that may be used
|
||||
* externally:
|
||||
*
|
||||
* compiled: The final compiled CSS
|
||||
* updated: The time (in seconds) the CSS was last compiled
|
||||
*
|
||||
* The cache structure is a plain-ol' PHP associative array and can
|
||||
* be serialized and unserialized without a hitch.
|
||||
*
|
||||
* @param mixed $in Input
|
||||
* @param boolean $force Force rebuild?
|
||||
*
|
||||
* @return array scssphp cache structure
|
||||
*/
|
||||
public function cachedCompile($in, $force = false)
|
||||
{
|
||||
// assume no root
|
||||
$root = null;
|
||||
|
||||
if (is_string($in)) {
|
||||
$root = $in;
|
||||
} elseif (is_array($in) and isset($in['root'])) {
|
||||
if ($force or ! isset($in['files'])) {
|
||||
// If we are forcing a recompile or if for some reason the
|
||||
// structure does not contain any file information we should
|
||||
// specify the root to trigger a rebuild.
|
||||
$root = $in['root'];
|
||||
} elseif (isset($in['files']) and is_array($in['files'])) {
|
||||
foreach ($in['files'] as $fname => $ftime) {
|
||||
if (! file_exists($fname) or filemtime($fname) > $ftime) {
|
||||
// One of the files we knew about previously has changed
|
||||
// so we should look at our incoming root again.
|
||||
$root = $in['root'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO: Throw an exception? We got neither a string nor something
|
||||
// that looks like a compatible lessphp cache structure.
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($root !== null) {
|
||||
// If we have a root value which means we should rebuild.
|
||||
$out = array();
|
||||
$out['root'] = $root;
|
||||
$out['compiled'] = $this->compileFile($root);
|
||||
$out['files'] = $this->scss->getParsedFiles();
|
||||
$out['updated'] = time();
|
||||
return $out;
|
||||
} else {
|
||||
// No changes, pass back the structure
|
||||
// we were given initially.
|
||||
return $in;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $dir Root directory to .scss files
|
||||
* @param string $cacheDir Cache directory
|
||||
* @param \Leafo\ScssPhp\Compiler|null $scss SCSS compiler instance
|
||||
*/
|
||||
public function __construct($dir, $cacheDir = null, $scss = null)
|
||||
{
|
||||
$this->dir = $dir;
|
||||
|
||||
if (! isset($cacheDir)) {
|
||||
$cacheDir = $this->join($dir, 'scss_cache');
|
||||
}
|
||||
|
||||
$this->cacheDir = $cacheDir;
|
||||
|
||||
if (! is_dir($this->cacheDir)) {
|
||||
throw new ServerException('Cache directory doesn\'t exist: ' . $cacheDir);
|
||||
}
|
||||
|
||||
if (! isset($scss)) {
|
||||
$scss = new Compiler();
|
||||
$scss->setImportPaths($this->dir);
|
||||
}
|
||||
|
||||
$this->scss = $scss;
|
||||
$this->showErrorsAsCSS = false;
|
||||
|
||||
if (! ini_get('date.timezone')) {
|
||||
throw new ServerException('Default date.timezone not set');
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
if (version_compare(PHP_VERSION, '5.4') < 0) {
|
||||
throw new \Exception('scssphp requires PHP 5.4 or above');
|
||||
}
|
||||
|
||||
if (! class_exists('Leafo\ScssPhp\Version', false)) {
|
||||
include_once __DIR__ . '/src/Base/Range.php';
|
||||
include_once __DIR__ . '/src/Block.php';
|
||||
include_once __DIR__ . '/src/Colors.php';
|
||||
include_once __DIR__ . '/src/Compiler.php';
|
||||
include_once __DIR__ . '/src/Compiler/Environment.php';
|
||||
include_once __DIR__ . '/src/Exception/CompilerException.php';
|
||||
include_once __DIR__ . '/src/Exception/ParserException.php';
|
||||
include_once __DIR__ . '/src/Exception/RangeException.php';
|
||||
include_once __DIR__ . '/src/Exception/ServerException.php';
|
||||
include_once __DIR__ . '/src/Formatter.php';
|
||||
include_once __DIR__ . '/src/Formatter/Compact.php';
|
||||
include_once __DIR__ . '/src/Formatter/Compressed.php';
|
||||
include_once __DIR__ . '/src/Formatter/Crunched.php';
|
||||
include_once __DIR__ . '/src/Formatter/Debug.php';
|
||||
include_once __DIR__ . '/src/Formatter/Expanded.php';
|
||||
include_once __DIR__ . '/src/Formatter/Nested.php';
|
||||
include_once __DIR__ . '/src/Formatter/OutputBlock.php';
|
||||
include_once __DIR__ . '/src/Node.php';
|
||||
include_once __DIR__ . '/src/Node/Number.php';
|
||||
include_once __DIR__ . '/src/Parser.php';
|
||||
include_once __DIR__ . '/src/SourceMap/Base64VLQEncoder.php';
|
||||
include_once __DIR__ . '/src/SourceMap/SourceMapGenerator.php';
|
||||
include_once __DIR__ . '/src/Type.php';
|
||||
include_once __DIR__ . '/src/Util.php';
|
||||
include_once __DIR__ . '/src/Version.php';
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2015-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Base;
|
||||
|
||||
/**
|
||||
* Range
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class Range
|
||||
{
|
||||
public $first;
|
||||
public $last;
|
||||
|
||||
/**
|
||||
* Initialize range
|
||||
*
|
||||
* @param integer|float $first
|
||||
* @param integer|float $last
|
||||
*/
|
||||
public function __construct($first, $last)
|
||||
{
|
||||
$this->first = $first;
|
||||
$this->last = $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for inclusion in range
|
||||
*
|
||||
* @param integer|float $value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function includes($value)
|
||||
{
|
||||
return $value >= $this->first && $value <= $this->last;
|
||||
}
|
||||
}
|
||||
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
/**
|
||||
* Block
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class Block
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var \Leafo\ScssPhp\Block
|
||||
*/
|
||||
public $parent;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sourceName;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $sourceIndex;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $sourceLine;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $sourceColumn;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $selectors;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $comments;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $children;
|
||||
}
|
||||
Vendored
+179
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
/**
|
||||
* CSS Colors
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
class Colors
|
||||
{
|
||||
/**
|
||||
* CSS Colors
|
||||
*
|
||||
* @see http://www.w3.org/TR/css3-color
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $cssColors = [
|
||||
'aliceblue' => '240,248,255',
|
||||
'antiquewhite' => '250,235,215',
|
||||
'aqua' => '0,255,255',
|
||||
'aquamarine' => '127,255,212',
|
||||
'azure' => '240,255,255',
|
||||
'beige' => '245,245,220',
|
||||
'bisque' => '255,228,196',
|
||||
'black' => '0,0,0',
|
||||
'blanchedalmond' => '255,235,205',
|
||||
'blue' => '0,0,255',
|
||||
'blueviolet' => '138,43,226',
|
||||
'brown' => '165,42,42',
|
||||
'burlywood' => '222,184,135',
|
||||
'cadetblue' => '95,158,160',
|
||||
'chartreuse' => '127,255,0',
|
||||
'chocolate' => '210,105,30',
|
||||
'coral' => '255,127,80',
|
||||
'cornflowerblue' => '100,149,237',
|
||||
'cornsilk' => '255,248,220',
|
||||
'crimson' => '220,20,60',
|
||||
'cyan' => '0,255,255',
|
||||
'darkblue' => '0,0,139',
|
||||
'darkcyan' => '0,139,139',
|
||||
'darkgoldenrod' => '184,134,11',
|
||||
'darkgray' => '169,169,169',
|
||||
'darkgreen' => '0,100,0',
|
||||
'darkgrey' => '169,169,169',
|
||||
'darkkhaki' => '189,183,107',
|
||||
'darkmagenta' => '139,0,139',
|
||||
'darkolivegreen' => '85,107,47',
|
||||
'darkorange' => '255,140,0',
|
||||
'darkorchid' => '153,50,204',
|
||||
'darkred' => '139,0,0',
|
||||
'darksalmon' => '233,150,122',
|
||||
'darkseagreen' => '143,188,143',
|
||||
'darkslateblue' => '72,61,139',
|
||||
'darkslategray' => '47,79,79',
|
||||
'darkslategrey' => '47,79,79',
|
||||
'darkturquoise' => '0,206,209',
|
||||
'darkviolet' => '148,0,211',
|
||||
'deeppink' => '255,20,147',
|
||||
'deepskyblue' => '0,191,255',
|
||||
'dimgray' => '105,105,105',
|
||||
'dimgrey' => '105,105,105',
|
||||
'dodgerblue' => '30,144,255',
|
||||
'firebrick' => '178,34,34',
|
||||
'floralwhite' => '255,250,240',
|
||||
'forestgreen' => '34,139,34',
|
||||
'fuchsia' => '255,0,255',
|
||||
'gainsboro' => '220,220,220',
|
||||
'ghostwhite' => '248,248,255',
|
||||
'gold' => '255,215,0',
|
||||
'goldenrod' => '218,165,32',
|
||||
'gray' => '128,128,128',
|
||||
'green' => '0,128,0',
|
||||
'greenyellow' => '173,255,47',
|
||||
'grey' => '128,128,128',
|
||||
'honeydew' => '240,255,240',
|
||||
'hotpink' => '255,105,180',
|
||||
'indianred' => '205,92,92',
|
||||
'indigo' => '75,0,130',
|
||||
'ivory' => '255,255,240',
|
||||
'khaki' => '240,230,140',
|
||||
'lavender' => '230,230,250',
|
||||
'lavenderblush' => '255,240,245',
|
||||
'lawngreen' => '124,252,0',
|
||||
'lemonchiffon' => '255,250,205',
|
||||
'lightblue' => '173,216,230',
|
||||
'lightcoral' => '240,128,128',
|
||||
'lightcyan' => '224,255,255',
|
||||
'lightgoldenrodyellow' => '250,250,210',
|
||||
'lightgray' => '211,211,211',
|
||||
'lightgreen' => '144,238,144',
|
||||
'lightgrey' => '211,211,211',
|
||||
'lightpink' => '255,182,193',
|
||||
'lightsalmon' => '255,160,122',
|
||||
'lightseagreen' => '32,178,170',
|
||||
'lightskyblue' => '135,206,250',
|
||||
'lightslategray' => '119,136,153',
|
||||
'lightslategrey' => '119,136,153',
|
||||
'lightsteelblue' => '176,196,222',
|
||||
'lightyellow' => '255,255,224',
|
||||
'lime' => '0,255,0',
|
||||
'limegreen' => '50,205,50',
|
||||
'linen' => '250,240,230',
|
||||
'magenta' => '255,0,255',
|
||||
'maroon' => '128,0,0',
|
||||
'mediumaquamarine' => '102,205,170',
|
||||
'mediumblue' => '0,0,205',
|
||||
'mediumorchid' => '186,85,211',
|
||||
'mediumpurple' => '147,112,219',
|
||||
'mediumseagreen' => '60,179,113',
|
||||
'mediumslateblue' => '123,104,238',
|
||||
'mediumspringgreen' => '0,250,154',
|
||||
'mediumturquoise' => '72,209,204',
|
||||
'mediumvioletred' => '199,21,133',
|
||||
'midnightblue' => '25,25,112',
|
||||
'mintcream' => '245,255,250',
|
||||
'mistyrose' => '255,228,225',
|
||||
'moccasin' => '255,228,181',
|
||||
'navajowhite' => '255,222,173',
|
||||
'navy' => '0,0,128',
|
||||
'oldlace' => '253,245,230',
|
||||
'olive' => '128,128,0',
|
||||
'olivedrab' => '107,142,35',
|
||||
'orange' => '255,165,0',
|
||||
'orangered' => '255,69,0',
|
||||
'orchid' => '218,112,214',
|
||||
'palegoldenrod' => '238,232,170',
|
||||
'palegreen' => '152,251,152',
|
||||
'paleturquoise' => '175,238,238',
|
||||
'palevioletred' => '219,112,147',
|
||||
'papayawhip' => '255,239,213',
|
||||
'peachpuff' => '255,218,185',
|
||||
'peru' => '205,133,63',
|
||||
'pink' => '255,192,203',
|
||||
'plum' => '221,160,221',
|
||||
'powderblue' => '176,224,230',
|
||||
'purple' => '128,0,128',
|
||||
'rebeccapurple' => '102,51,153',
|
||||
'red' => '255,0,0',
|
||||
'rosybrown' => '188,143,143',
|
||||
'royalblue' => '65,105,225',
|
||||
'saddlebrown' => '139,69,19',
|
||||
'salmon' => '250,128,114',
|
||||
'sandybrown' => '244,164,96',
|
||||
'seagreen' => '46,139,87',
|
||||
'seashell' => '255,245,238',
|
||||
'sienna' => '160,82,45',
|
||||
'silver' => '192,192,192',
|
||||
'skyblue' => '135,206,235',
|
||||
'slateblue' => '106,90,205',
|
||||
'slategray' => '112,128,144',
|
||||
'slategrey' => '112,128,144',
|
||||
'snow' => '255,250,250',
|
||||
'springgreen' => '0,255,127',
|
||||
'steelblue' => '70,130,180',
|
||||
'tan' => '210,180,140',
|
||||
'teal' => '0,128,128',
|
||||
'thistle' => '216,191,216',
|
||||
'tomato' => '255,99,71',
|
||||
'transparent' => '0,0,0,0',
|
||||
'turquoise' => '64,224,208',
|
||||
'violet' => '238,130,238',
|
||||
'wheat' => '245,222,179',
|
||||
'white' => '255,255,255',
|
||||
'whitesmoke' => '245,245,245',
|
||||
'yellow' => '255,255,0',
|
||||
'yellowgreen' => '154,205,50',
|
||||
];
|
||||
}
|
||||
+5336
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Compiler;
|
||||
|
||||
/**
|
||||
* Compiler environment
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class Environment
|
||||
{
|
||||
/**
|
||||
* @var \Leafo\ScssPhp\Block
|
||||
*/
|
||||
public $block;
|
||||
|
||||
/**
|
||||
* @var \Leafo\ScssPhp\Compiler\Environment
|
||||
*/
|
||||
public $parent;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $store;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $depth;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Exception;
|
||||
|
||||
/**
|
||||
* Compiler exception
|
||||
*
|
||||
* @author Oleksandr Savchenko <traveltino@gmail.com>
|
||||
*/
|
||||
class CompilerException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Exception;
|
||||
|
||||
/**
|
||||
* Parser Exception
|
||||
*
|
||||
* @author Oleksandr Savchenko <traveltino@gmail.com>
|
||||
*/
|
||||
class ParserException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Exception;
|
||||
|
||||
/**
|
||||
* Range exception
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class RangeException extends \Exception
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Exception;
|
||||
|
||||
/**
|
||||
* Server Exception
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class ServerException extends \Exception
|
||||
{
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
use Leafo\ScssPhp\Formatter\OutputBlock;
|
||||
use Leafo\ScssPhp\SourceMap\SourceMapGenerator;
|
||||
|
||||
/**
|
||||
* Base formatter
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
abstract class Formatter
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $indentLevel;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $indentChar;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $break;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $open;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $close;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $tagSeparator;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $assignSeparator;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
public $keepSemicolons;
|
||||
|
||||
/**
|
||||
* @var \Leafo\ScssPhp\Formatter\OutputBlock
|
||||
*/
|
||||
protected $currentBlock;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $currentLine;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $currentColumn;
|
||||
|
||||
/**
|
||||
* @var \Leafo\ScssPhp\SourceMap\SourceMapGenerator
|
||||
*/
|
||||
protected $sourceMapGenerator;
|
||||
|
||||
/**
|
||||
* Initialize formatter
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
abstract public function __construct();
|
||||
|
||||
/**
|
||||
* Return indentation (whitespace)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function indentStr()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return property assignment
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function property($name, $value)
|
||||
{
|
||||
return rtrim($name) . $this->assignSeparator . $value . ';';
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip semi-colon appended by property(); it's a separator, not a terminator
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param array $lines
|
||||
*/
|
||||
public function stripSemicolon(&$lines)
|
||||
{
|
||||
if ($this->keepSemicolons) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (($count = count($lines))
|
||||
&& substr($lines[$count - 1], -1) === ';'
|
||||
) {
|
||||
$lines[$count - 1] = substr($lines[$count - 1], 0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output lines inside a block
|
||||
*
|
||||
* @param \Leafo\ScssPhp\Formatter\OutputBlock $block
|
||||
*/
|
||||
protected function blockLines(OutputBlock $block)
|
||||
{
|
||||
$inner = $this->indentStr();
|
||||
|
||||
$glue = $this->break . $inner;
|
||||
|
||||
$this->write($inner . implode($glue, $block->lines));
|
||||
|
||||
if (! empty($block->children)) {
|
||||
$this->write($this->break);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output block selectors
|
||||
*
|
||||
* @param \Leafo\ScssPhp\Formatter\OutputBlock $block
|
||||
*/
|
||||
protected function blockSelectors(OutputBlock $block)
|
||||
{
|
||||
$inner = $this->indentStr();
|
||||
|
||||
$this->write($inner
|
||||
. implode($this->tagSeparator, $block->selectors)
|
||||
. $this->open . $this->break);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output block children
|
||||
*
|
||||
* @param \Leafo\ScssPhp\Formatter\OutputBlock $block
|
||||
*/
|
||||
protected function blockChildren(OutputBlock $block)
|
||||
{
|
||||
foreach ($block->children as $child) {
|
||||
$this->block($child);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output non-empty block
|
||||
*
|
||||
* @param \Leafo\ScssPhp\Formatter\OutputBlock $block
|
||||
*/
|
||||
protected function block(OutputBlock $block)
|
||||
{
|
||||
if (empty($block->lines) && empty($block->children)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->currentBlock = $block;
|
||||
|
||||
$pre = $this->indentStr();
|
||||
|
||||
if (! empty($block->selectors)) {
|
||||
$this->blockSelectors($block);
|
||||
|
||||
$this->indentLevel++;
|
||||
}
|
||||
|
||||
if (! empty($block->lines)) {
|
||||
$this->blockLines($block);
|
||||
}
|
||||
|
||||
if (! empty($block->children)) {
|
||||
$this->blockChildren($block);
|
||||
}
|
||||
|
||||
if (! empty($block->selectors)) {
|
||||
$this->indentLevel--;
|
||||
|
||||
if (empty($block->children)) {
|
||||
$this->write($this->break);
|
||||
}
|
||||
|
||||
$this->write($pre . $this->close . $this->break);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point to formatting a block
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param \Leafo\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree
|
||||
* @param \Leafo\ScssPhp\SourceMap\SourceMapGenerator|null $sourceMapGenerator Optional source map generator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format(OutputBlock $block, SourceMapGenerator $sourceMapGenerator = null)
|
||||
{
|
||||
$this->sourceMapGenerator = null;
|
||||
|
||||
if ($sourceMapGenerator) {
|
||||
$this->currentLine = 1;
|
||||
$this->currentColumn = 0;
|
||||
$this->sourceMapGenerator = $sourceMapGenerator;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
$this->block($block);
|
||||
|
||||
$out = ob_get_clean();
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
*/
|
||||
protected function write($str)
|
||||
{
|
||||
if ($this->sourceMapGenerator) {
|
||||
$this->sourceMapGenerator->addMapping(
|
||||
$this->currentLine,
|
||||
$this->currentColumn,
|
||||
$this->currentBlock->sourceLine,
|
||||
$this->currentBlock->sourceColumn - 1, //columns from parser are off by one
|
||||
$this->currentBlock->sourceName
|
||||
);
|
||||
|
||||
$lines = explode("\n", $str);
|
||||
$lineCount = count($lines);
|
||||
$this->currentLine += $lineCount-1;
|
||||
|
||||
$lastLine = array_pop($lines);
|
||||
|
||||
$this->currentColumn = ($lineCount === 1 ? $this->currentColumn : 0) + strlen($lastLine);
|
||||
}
|
||||
|
||||
echo $str;
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Formatter;
|
||||
|
||||
use Leafo\ScssPhp\Formatter;
|
||||
|
||||
/**
|
||||
* Compact formatter
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
class Compact extends Formatter
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->indentLevel = 0;
|
||||
$this->indentChar = '';
|
||||
$this->break = '';
|
||||
$this->open = ' {';
|
||||
$this->close = "}\n\n";
|
||||
$this->tagSeparator = ',';
|
||||
$this->assignSeparator = ':';
|
||||
$this->keepSemicolons = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function indentStr()
|
||||
{
|
||||
return ' ';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Formatter;
|
||||
|
||||
use Leafo\ScssPhp\Formatter;
|
||||
use Leafo\ScssPhp\Formatter\OutputBlock;
|
||||
|
||||
/**
|
||||
* Compressed formatter
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
class Compressed extends Formatter
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->indentLevel = 0;
|
||||
$this->indentChar = ' ';
|
||||
$this->break = '';
|
||||
$this->open = '{';
|
||||
$this->close = '}';
|
||||
$this->tagSeparator = ',';
|
||||
$this->assignSeparator = ':';
|
||||
$this->keepSemicolons = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockLines(OutputBlock $block)
|
||||
{
|
||||
$inner = $this->indentStr();
|
||||
|
||||
$glue = $this->break . $inner;
|
||||
|
||||
foreach ($block->lines as $index => $line) {
|
||||
if (substr($line, 0, 2) === '/*' && substr($line, 2, 1) !== '!') {
|
||||
unset($block->lines[$index]);
|
||||
} elseif (substr($line, 0, 3) === '/*!') {
|
||||
$block->lines[$index] = '/*' . substr($line, 3);
|
||||
}
|
||||
}
|
||||
|
||||
$this->write($inner . implode($glue, $block->lines));
|
||||
|
||||
if (! empty($block->children)) {
|
||||
$this->write($this->break);
|
||||
}
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Formatter;
|
||||
|
||||
use Leafo\ScssPhp\Formatter;
|
||||
use Leafo\ScssPhp\Formatter\OutputBlock;
|
||||
|
||||
/**
|
||||
* Crunched formatter
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class Crunched extends Formatter
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->indentLevel = 0;
|
||||
$this->indentChar = ' ';
|
||||
$this->break = '';
|
||||
$this->open = '{';
|
||||
$this->close = '}';
|
||||
$this->tagSeparator = ',';
|
||||
$this->assignSeparator = ':';
|
||||
$this->keepSemicolons = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockLines(OutputBlock $block)
|
||||
{
|
||||
$inner = $this->indentStr();
|
||||
|
||||
$glue = $this->break . $inner;
|
||||
|
||||
foreach ($block->lines as $index => $line) {
|
||||
if (substr($line, 0, 2) === '/*') {
|
||||
unset($block->lines[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->write($inner . implode($glue, $block->lines));
|
||||
|
||||
if (! empty($block->children)) {
|
||||
$this->write($this->break);
|
||||
}
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Formatter;
|
||||
|
||||
use Leafo\ScssPhp\Formatter;
|
||||
use Leafo\ScssPhp\Formatter\OutputBlock;
|
||||
|
||||
/**
|
||||
* Debug formatter
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class Debug extends Formatter
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->indentLevel = 0;
|
||||
$this->indentChar = '';
|
||||
$this->break = "\n";
|
||||
$this->open = ' {';
|
||||
$this->close = ' }';
|
||||
$this->tagSeparator = ', ';
|
||||
$this->assignSeparator = ': ';
|
||||
$this->keepSemicolons = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function indentStr()
|
||||
{
|
||||
return str_repeat(' ', $this->indentLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockLines(OutputBlock $block)
|
||||
{
|
||||
$indent = $this->indentStr();
|
||||
|
||||
if (empty($block->lines)) {
|
||||
$this->write("{$indent}block->lines: []\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($block->lines as $index => $line) {
|
||||
$this->write("{$indent}block->lines[{$index}]: $line\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockSelectors(OutputBlock $block)
|
||||
{
|
||||
$indent = $this->indentStr();
|
||||
|
||||
if (empty($block->selectors)) {
|
||||
$this->write("{$indent}block->selectors: []\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($block->selectors as $index => $selector) {
|
||||
$this->write("{$indent}block->selectors[{$index}]: $selector\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockChildren(OutputBlock $block)
|
||||
{
|
||||
$indent = $this->indentStr();
|
||||
|
||||
if (empty($block->children)) {
|
||||
$this->write("{$indent}block->children: []\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->indentLevel++;
|
||||
|
||||
foreach ($block->children as $i => $child) {
|
||||
$this->block($child);
|
||||
}
|
||||
|
||||
$this->indentLevel--;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function block(OutputBlock $block)
|
||||
{
|
||||
$indent = $this->indentStr();
|
||||
|
||||
$this->write("{$indent}block->type: {$block->type}\n" .
|
||||
"{$indent}block->depth: {$block->depth}\n");
|
||||
|
||||
$this->currentBlock = $block;
|
||||
|
||||
$this->blockSelectors($block);
|
||||
$this->blockLines($block);
|
||||
$this->blockChildren($block);
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Formatter;
|
||||
|
||||
use Leafo\ScssPhp\Formatter;
|
||||
use Leafo\ScssPhp\Formatter\OutputBlock;
|
||||
|
||||
/**
|
||||
* Expanded formatter
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
class Expanded extends Formatter
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->indentLevel = 0;
|
||||
$this->indentChar = ' ';
|
||||
$this->break = "\n";
|
||||
$this->open = ' {';
|
||||
$this->close = '}';
|
||||
$this->tagSeparator = ', ';
|
||||
$this->assignSeparator = ': ';
|
||||
$this->keepSemicolons = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function indentStr()
|
||||
{
|
||||
return str_repeat($this->indentChar, $this->indentLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockLines(OutputBlock $block)
|
||||
{
|
||||
$inner = $this->indentStr();
|
||||
|
||||
$glue = $this->break . $inner;
|
||||
|
||||
foreach ($block->lines as $index => $line) {
|
||||
if (substr($line, 0, 2) === '/*') {
|
||||
$block->lines[$index] = preg_replace('/(\r|\n)+/', $glue, $line);
|
||||
}
|
||||
}
|
||||
|
||||
$this->write($inner . implode($glue, $block->lines));
|
||||
|
||||
if (empty($block->selectors) || ! empty($block->children)) {
|
||||
$this->write($this->break);
|
||||
}
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Formatter;
|
||||
|
||||
use Leafo\ScssPhp\Formatter;
|
||||
use Leafo\ScssPhp\Formatter\OutputBlock;
|
||||
|
||||
/**
|
||||
* Nested formatter
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
class Nested extends Formatter
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $depth;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->indentLevel = 0;
|
||||
$this->indentChar = ' ';
|
||||
$this->break = "\n";
|
||||
$this->open = ' {';
|
||||
$this->close = ' }';
|
||||
$this->tagSeparator = ', ';
|
||||
$this->assignSeparator = ': ';
|
||||
$this->keepSemicolons = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function indentStr()
|
||||
{
|
||||
$n = $this->depth - 1;
|
||||
|
||||
return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockLines(OutputBlock $block)
|
||||
{
|
||||
$inner = $this->indentStr();
|
||||
|
||||
$glue = $this->break . $inner;
|
||||
|
||||
foreach ($block->lines as $index => $line) {
|
||||
if (substr($line, 0, 2) === '/*') {
|
||||
$block->lines[$index] = preg_replace('/(\r|\n)+/', $glue, $line);
|
||||
}
|
||||
}
|
||||
|
||||
$this->write($inner . implode($glue, $block->lines));
|
||||
|
||||
if (! empty($block->children)) {
|
||||
$this->write($this->break);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockSelectors(OutputBlock $block)
|
||||
{
|
||||
$inner = $this->indentStr();
|
||||
|
||||
$this->write($inner
|
||||
. implode($this->tagSeparator, $block->selectors)
|
||||
. $this->open . $this->break);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockChildren(OutputBlock $block)
|
||||
{
|
||||
foreach ($block->children as $i => $child) {
|
||||
$this->block($child);
|
||||
|
||||
if ($i < count($block->children) - 1) {
|
||||
$this->write($this->break);
|
||||
|
||||
if (isset($block->children[$i + 1])) {
|
||||
$next = $block->children[$i + 1];
|
||||
|
||||
if ($next->depth === max($block->depth, 1) && $child->depth >= $next->depth) {
|
||||
$this->write($this->break);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function block(OutputBlock $block)
|
||||
{
|
||||
if ($block->type === 'root') {
|
||||
$this->adjustAllChildren($block);
|
||||
}
|
||||
|
||||
if (empty($block->lines) && empty($block->children)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->currentBlock = $block;
|
||||
|
||||
|
||||
$this->depth = $block->depth;
|
||||
|
||||
if (! empty($block->selectors)) {
|
||||
$this->blockSelectors($block);
|
||||
|
||||
$this->indentLevel++;
|
||||
}
|
||||
|
||||
if (! empty($block->lines)) {
|
||||
$this->blockLines($block);
|
||||
}
|
||||
|
||||
if (! empty($block->children)) {
|
||||
$this->blockChildren($block);
|
||||
}
|
||||
|
||||
if (! empty($block->selectors)) {
|
||||
$this->indentLevel--;
|
||||
|
||||
$this->write($this->close);
|
||||
}
|
||||
|
||||
if ($block->type === 'root') {
|
||||
$this->write($this->break);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the depths of all children, depth first
|
||||
*
|
||||
* @param \Leafo\ScssPhp\Formatter\OutputBlock $block
|
||||
*/
|
||||
private function adjustAllChildren(OutputBlock $block)
|
||||
{
|
||||
// flatten empty nested blocks
|
||||
$children = [];
|
||||
|
||||
foreach ($block->children as $i => $child) {
|
||||
if (empty($child->lines) && empty($child->children)) {
|
||||
if (isset($block->children[$i + 1])) {
|
||||
$block->children[$i + 1]->depth = $child->depth;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$children[] = $child;
|
||||
}
|
||||
|
||||
$count = count($children);
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$depth = $children[$i]->depth;
|
||||
$j = $i + 1;
|
||||
|
||||
if (isset($children[$j]) && $depth < $children[$j]->depth) {
|
||||
$childDepth = $children[$j]->depth;
|
||||
|
||||
for (; $j < $count; $j++) {
|
||||
if ($depth < $children[$j]->depth && $childDepth >= $children[$j]->depth) {
|
||||
$children[$j]->depth = $depth + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$block->children = $children;
|
||||
|
||||
// make relative to parent
|
||||
foreach ($block->children as $child) {
|
||||
$this->adjustAllChildren($child);
|
||||
|
||||
$child->depth = $child->depth - $block->depth;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Formatter;
|
||||
|
||||
/**
|
||||
* Output block
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class OutputBlock
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $depth;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $selectors;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $lines;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $children;
|
||||
|
||||
/**
|
||||
* @var \Leafo\ScssPhp\Formatter\OutputBlock
|
||||
*/
|
||||
public $parent;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $sourceName;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $sourceLine;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $sourceColumn;
|
||||
}
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
/**
|
||||
* Base node
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
abstract class Node
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $sourceIndex;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $sourceLine;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
public $sourceColumn;
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\Node;
|
||||
|
||||
use Leafo\ScssPhp\Compiler;
|
||||
use Leafo\ScssPhp\Node;
|
||||
use Leafo\ScssPhp\Type;
|
||||
|
||||
/**
|
||||
* Dimension + optional units
|
||||
*
|
||||
* {@internal
|
||||
* This is a work-in-progress.
|
||||
*
|
||||
* The \ArrayAccess interface is temporary until the migration is complete.
|
||||
* }}
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class Number extends Node implements \ArrayAccess
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
static public $precision = 10;
|
||||
|
||||
/**
|
||||
* @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static protected $unitTable = [
|
||||
'in' => [
|
||||
'in' => 1,
|
||||
'pc' => 6,
|
||||
'pt' => 72,
|
||||
'px' => 96,
|
||||
'cm' => 2.54,
|
||||
'mm' => 25.4,
|
||||
'q' => 101.6,
|
||||
],
|
||||
'turn' => [
|
||||
'deg' => 360,
|
||||
'grad' => 400,
|
||||
'rad' => 6.28318530717958647692528676, // 2 * M_PI
|
||||
'turn' => 1,
|
||||
],
|
||||
's' => [
|
||||
's' => 1,
|
||||
'ms' => 1000,
|
||||
],
|
||||
'Hz' => [
|
||||
'Hz' => 1,
|
||||
'kHz' => 0.001,
|
||||
],
|
||||
'dpi' => [
|
||||
'dpi' => 1,
|
||||
'dpcm' => 2.54,
|
||||
'dppx' => 96,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var integer|float
|
||||
*/
|
||||
public $dimension;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $units;
|
||||
|
||||
/**
|
||||
* Initialize number
|
||||
*
|
||||
* @param mixed $dimension
|
||||
* @param mixed $initialUnit
|
||||
*/
|
||||
public function __construct($dimension, $initialUnit)
|
||||
{
|
||||
$this->type = Type::T_NUMBER;
|
||||
$this->dimension = $dimension;
|
||||
$this->units = is_array($initialUnit)
|
||||
? $initialUnit
|
||||
: ($initialUnit ? [$initialUnit => 1]
|
||||
: []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce number to target units
|
||||
*
|
||||
* @param array $units
|
||||
*
|
||||
* @return \Leafo\ScssPhp\Node\Number
|
||||
*/
|
||||
public function coerce($units)
|
||||
{
|
||||
if ($this->unitless()) {
|
||||
return new Number($this->dimension, $units);
|
||||
}
|
||||
|
||||
$dimension = $this->dimension;
|
||||
|
||||
foreach (static::$unitTable['in'] as $unit => $conv) {
|
||||
$from = isset($this->units[$unit]) ? $this->units[$unit] : 0;
|
||||
$to = isset($units[$unit]) ? $units[$unit] : 0;
|
||||
$factor = pow($conv, $from - $to);
|
||||
$dimension /= $factor;
|
||||
}
|
||||
|
||||
return new Number($dimension, $units);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize number
|
||||
*
|
||||
* @return \Leafo\ScssPhp\Node\Number
|
||||
*/
|
||||
public function normalize()
|
||||
{
|
||||
$dimension = $this->dimension;
|
||||
$units = [];
|
||||
|
||||
$this->normalizeUnits($dimension, $units, 'in');
|
||||
|
||||
return new Number($dimension, $units);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
if ($offset === -3) {
|
||||
return $this->sourceColumn !== null;
|
||||
}
|
||||
|
||||
if ($offset === -2) {
|
||||
return $this->sourceLine !== null;
|
||||
}
|
||||
|
||||
if ($offset === -1
|
||||
|| $offset === 0
|
||||
|| $offset === 1
|
||||
|| $offset === 2
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
switch ($offset) {
|
||||
case -3:
|
||||
return $this->sourceColumn;
|
||||
|
||||
case -2:
|
||||
return $this->sourceLine;
|
||||
|
||||
case -1:
|
||||
return $this->sourceIndex;
|
||||
|
||||
case 0:
|
||||
return $this->type;
|
||||
|
||||
case 1:
|
||||
return $this->dimension;
|
||||
|
||||
case 2:
|
||||
return $this->units;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if ($offset === 1) {
|
||||
$this->dimension = $value;
|
||||
} elseif ($offset === 2) {
|
||||
$this->units = $value;
|
||||
} elseif ($offset == -1) {
|
||||
$this->sourceIndex = $value;
|
||||
} elseif ($offset == -2) {
|
||||
$this->sourceLine = $value;
|
||||
} elseif ($offset == -3) {
|
||||
$this->sourceColumn = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
if ($offset === 1) {
|
||||
$this->dimension = null;
|
||||
} elseif ($offset === 2) {
|
||||
$this->units = null;
|
||||
} elseif ($offset === -1) {
|
||||
$this->sourceIndex = null;
|
||||
} elseif ($offset === -2) {
|
||||
$this->sourceLine = null;
|
||||
} elseif ($offset === -3) {
|
||||
$this->sourceColumn = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the number is unitless
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function unitless()
|
||||
{
|
||||
return ! array_sum($this->units);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unit(s) as the product of numerator units divided by the product of denominator units
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function unitStr()
|
||||
{
|
||||
$numerators = [];
|
||||
$denominators = [];
|
||||
|
||||
foreach ($this->units as $unit => $unitSize) {
|
||||
if ($unitSize > 0) {
|
||||
$numerators = array_pad($numerators, count($numerators) + $unitSize, $unit);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($unitSize < 0) {
|
||||
$denominators = array_pad($denominators, count($denominators) + $unitSize, $unit);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return implode('*', $numerators) . (count($denominators) ? '/' . implode('*', $denominators) : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Output number
|
||||
*
|
||||
* @param \Leafo\ScssPhp\Compiler $compiler
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function output(Compiler $compiler = null)
|
||||
{
|
||||
$dimension = round($this->dimension, static::$precision);
|
||||
|
||||
$units = array_filter($this->units, function ($unitSize) {
|
||||
return $unitSize;
|
||||
});
|
||||
|
||||
if (count($units) > 1 && array_sum($units) === 0) {
|
||||
$dimension = $this->dimension;
|
||||
$units = [];
|
||||
|
||||
$this->normalizeUnits($dimension, $units, 'in');
|
||||
|
||||
$dimension = round($dimension, static::$precision);
|
||||
$units = array_filter($units, function ($unitSize) {
|
||||
return $unitSize;
|
||||
});
|
||||
}
|
||||
|
||||
$unitSize = array_sum($units);
|
||||
|
||||
if ($compiler && ($unitSize > 1 || $unitSize < 0 || count($units) > 1)) {
|
||||
$compiler->throwError((string) $dimension . $this->unitStr() . " isn't a valid CSS value.");
|
||||
}
|
||||
|
||||
reset($units);
|
||||
$unit = key($units);
|
||||
$dimension = number_format($dimension, static::$precision, '.', '');
|
||||
|
||||
return (static::$precision ? rtrim(rtrim($dimension, '0'), '.') : $dimension) . $unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return $this->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize units
|
||||
*
|
||||
* @param integer|float $dimension
|
||||
* @param array $units
|
||||
* @param string $baseUnit
|
||||
*/
|
||||
private function normalizeUnits(&$dimension, &$units, $baseUnit = 'in')
|
||||
{
|
||||
$dimension = $this->dimension;
|
||||
$units = [];
|
||||
|
||||
foreach ($this->units as $unit => $exp) {
|
||||
if (isset(static::$unitTable[$baseUnit][$unit])) {
|
||||
$factor = pow(static::$unitTable[$baseUnit][$unit], $exp);
|
||||
|
||||
$unit = $baseUnit;
|
||||
$dimension /= $factor;
|
||||
}
|
||||
|
||||
$units[$unit] = $exp + (isset($units[$unit]) ? $units[$unit] : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+2494
@@ -0,0 +1,2494 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
use Leafo\ScssPhp\Block;
|
||||
use Leafo\ScssPhp\Compiler;
|
||||
use Leafo\ScssPhp\Exception\ParserException;
|
||||
use Leafo\ScssPhp\Node;
|
||||
use Leafo\ScssPhp\Type;
|
||||
|
||||
/**
|
||||
* Parser
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
class Parser
|
||||
{
|
||||
const SOURCE_INDEX = -1;
|
||||
const SOURCE_LINE = -2;
|
||||
const SOURCE_COLUMN = -3;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $precedence = [
|
||||
'=' => 0,
|
||||
'or' => 1,
|
||||
'and' => 2,
|
||||
'==' => 3,
|
||||
'!=' => 3,
|
||||
'<=>' => 3,
|
||||
'<=' => 4,
|
||||
'>=' => 4,
|
||||
'<' => 4,
|
||||
'>' => 4,
|
||||
'+' => 5,
|
||||
'-' => 5,
|
||||
'*' => 6,
|
||||
'/' => 6,
|
||||
'%' => 6,
|
||||
];
|
||||
|
||||
protected static $commentPattern;
|
||||
protected static $operatorPattern;
|
||||
protected static $whitePattern;
|
||||
|
||||
private $sourceName;
|
||||
private $sourceIndex;
|
||||
private $sourcePositions;
|
||||
private $charset;
|
||||
private $count;
|
||||
private $env;
|
||||
private $inParens;
|
||||
private $eatWhiteDefault;
|
||||
private $buffer;
|
||||
private $utf8;
|
||||
private $encoding;
|
||||
private $patternModifiers;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $sourceName
|
||||
* @param integer $sourceIndex
|
||||
* @param string $encoding
|
||||
*/
|
||||
public function __construct($sourceName, $sourceIndex = 0, $encoding = 'utf-8')
|
||||
{
|
||||
$this->sourceName = $sourceName ?: '(stdin)';
|
||||
$this->sourceIndex = $sourceIndex;
|
||||
$this->charset = null;
|
||||
$this->utf8 = ! $encoding || strtolower($encoding) === 'utf-8';
|
||||
$this->patternModifiers = $this->utf8 ? 'Aisu' : 'Ais';
|
||||
|
||||
if (empty(static::$operatorPattern)) {
|
||||
static::$operatorPattern = '([*\/%+-]|[!=]\=|\>\=?|\<\=\>|\<\=?|and|or)';
|
||||
|
||||
$commentSingle = '\/\/';
|
||||
$commentMultiLeft = '\/\*';
|
||||
$commentMultiRight = '\*\/';
|
||||
|
||||
static::$commentPattern = $commentMultiLeft . '.*?' . $commentMultiRight;
|
||||
static::$whitePattern = $this->utf8
|
||||
? '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisuS'
|
||||
: '/' . $commentSingle . '[^\n]*\s*|(' . static::$commentPattern . ')\s*|\s+/AisS';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get source file name
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSourceName()
|
||||
{
|
||||
return $this->sourceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw parser error
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $msg
|
||||
*
|
||||
* @throws \Leafo\ScssPhp\Exception\ParserException
|
||||
*/
|
||||
public function throwParseError($msg = 'parse error')
|
||||
{
|
||||
list($line, /* $column */) = $this->getSourcePosition($this->count);
|
||||
|
||||
$loc = empty($this->sourceName) ? "line: $line" : "$this->sourceName on line $line";
|
||||
|
||||
if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
|
||||
throw new ParserException("$msg: failed at `$m[1]` $loc");
|
||||
}
|
||||
|
||||
throw new ParserException("$msg: $loc");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser buffer
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $buffer
|
||||
*
|
||||
* @return \Leafo\ScssPhp\Block
|
||||
*/
|
||||
public function parse($buffer)
|
||||
{
|
||||
// strip BOM (byte order marker)
|
||||
if (substr($buffer, 0, 3) === "\xef\xbb\xbf") {
|
||||
$buffer = substr($buffer, 3);
|
||||
}
|
||||
|
||||
$this->buffer = rtrim($buffer, "\x00..\x1f");
|
||||
$this->count = 0;
|
||||
$this->env = null;
|
||||
$this->inParens = false;
|
||||
$this->eatWhiteDefault = true;
|
||||
|
||||
$this->saveEncoding();
|
||||
$this->extractLineNumbers($buffer);
|
||||
|
||||
$this->pushBlock(null); // root block
|
||||
$this->whitespace();
|
||||
$this->pushBlock(null);
|
||||
$this->popBlock();
|
||||
|
||||
while ($this->parseChunk()) {
|
||||
;
|
||||
}
|
||||
|
||||
if ($this->count !== strlen($this->buffer)) {
|
||||
$this->throwParseError();
|
||||
}
|
||||
|
||||
if (! empty($this->env->parent)) {
|
||||
$this->throwParseError('unclosed block');
|
||||
}
|
||||
|
||||
if ($this->charset) {
|
||||
array_unshift($this->env->children, $this->charset);
|
||||
}
|
||||
|
||||
$this->env->isRoot = true;
|
||||
|
||||
$this->restoreEncoding();
|
||||
|
||||
return $this->env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a value or value list
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param string $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function parseValue($buffer, &$out)
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->env = null;
|
||||
$this->inParens = false;
|
||||
$this->eatWhiteDefault = true;
|
||||
$this->buffer = (string) $buffer;
|
||||
|
||||
$this->saveEncoding();
|
||||
|
||||
$list = $this->valueList($out);
|
||||
|
||||
$this->restoreEncoding();
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a selector or selector list
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @param string $buffer
|
||||
* @param string $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function parseSelector($buffer, &$out)
|
||||
{
|
||||
$this->count = 0;
|
||||
$this->env = null;
|
||||
$this->inParens = false;
|
||||
$this->eatWhiteDefault = true;
|
||||
$this->buffer = (string) $buffer;
|
||||
|
||||
$this->saveEncoding();
|
||||
|
||||
$selector = $this->selectors($out);
|
||||
|
||||
$this->restoreEncoding();
|
||||
|
||||
return $selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single chunk off the head of the buffer and append it to the
|
||||
* current parse environment.
|
||||
*
|
||||
* Returns false when the buffer is empty, or when there is an error.
|
||||
*
|
||||
* This function is called repeatedly until the entire document is
|
||||
* parsed.
|
||||
*
|
||||
* This parser is most similar to a recursive descent parser. Single
|
||||
* functions represent discrete grammatical rules for the language, and
|
||||
* they are able to capture the text that represents those rules.
|
||||
*
|
||||
* Consider the function Compiler::keyword(). (All parse functions are
|
||||
* structured the same.)
|
||||
*
|
||||
* The function takes a single reference argument. When calling the
|
||||
* function it will attempt to match a keyword on the head of the buffer.
|
||||
* If it is successful, it will place the keyword in the referenced
|
||||
* argument, advance the position in the buffer, and return true. If it
|
||||
* fails then it won't advance the buffer and it will return false.
|
||||
*
|
||||
* All of these parse functions are powered by Compiler::match(), which behaves
|
||||
* the same way, but takes a literal regular expression. Sometimes it is
|
||||
* more convenient to use match instead of creating a new function.
|
||||
*
|
||||
* Because of the format of the functions, to parse an entire string of
|
||||
* grammatical rules, you can chain them together using &&.
|
||||
*
|
||||
* But, if some of the rules in the chain succeed before one fails, then
|
||||
* the buffer position will be left at an invalid state. In order to
|
||||
* avoid this, Compiler::seek() is used to remember and set buffer positions.
|
||||
*
|
||||
* Before parsing a chain, use $s = $this->seek() to remember the current
|
||||
* position into $s. Then if a chain fails, use $this->seek($s) to
|
||||
* go back where we started.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function parseChunk()
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
// the directives
|
||||
if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] === '@') {
|
||||
if ($this->literal('@at-root') &&
|
||||
($this->selectors($selector) || true) &&
|
||||
($this->map($with) || true) &&
|
||||
$this->literal('{')
|
||||
) {
|
||||
$atRoot = $this->pushSpecialBlock(Type::T_AT_ROOT, $s);
|
||||
$atRoot->selector = $selector;
|
||||
$atRoot->with = $with;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@media') && $this->mediaQueryList($mediaQueryList) && $this->literal('{')) {
|
||||
$media = $this->pushSpecialBlock(Type::T_MEDIA, $s);
|
||||
$media->queryList = $mediaQueryList[2];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@mixin') &&
|
||||
$this->keyword($mixinName) &&
|
||||
($this->argumentDef($args) || true) &&
|
||||
$this->literal('{')
|
||||
) {
|
||||
$mixin = $this->pushSpecialBlock(Type::T_MIXIN, $s);
|
||||
$mixin->name = $mixinName;
|
||||
$mixin->args = $args;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@include') &&
|
||||
$this->keyword($mixinName) &&
|
||||
($this->literal('(') &&
|
||||
($this->argValues($argValues) || true) &&
|
||||
$this->literal(')') || true) &&
|
||||
($this->end() ||
|
||||
$this->literal('{') && $hasBlock = true)
|
||||
) {
|
||||
$child = [Type::T_INCLUDE, $mixinName, isset($argValues) ? $argValues : null, null];
|
||||
|
||||
if (! empty($hasBlock)) {
|
||||
$include = $this->pushSpecialBlock(Type::T_INCLUDE, $s);
|
||||
$include->child = $child;
|
||||
} else {
|
||||
$this->append($child, $s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@scssphp-import-once') &&
|
||||
$this->valueList($importPath) &&
|
||||
$this->end()
|
||||
) {
|
||||
$this->append([Type::T_SCSSPHP_IMPORT_ONCE, $importPath], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@import') &&
|
||||
$this->valueList($importPath) &&
|
||||
$this->end()
|
||||
) {
|
||||
$this->append([Type::T_IMPORT, $importPath], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@import') &&
|
||||
$this->url($importPath) &&
|
||||
$this->end()
|
||||
) {
|
||||
$this->append([Type::T_IMPORT, $importPath], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@extend') &&
|
||||
$this->selectors($selectors) &&
|
||||
$this->end()
|
||||
) {
|
||||
// check for '!flag'
|
||||
$optional = $this->stripOptionalFlag($selectors);
|
||||
$this->append([Type::T_EXTEND, $selectors, $optional], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@function') &&
|
||||
$this->keyword($fnName) &&
|
||||
$this->argumentDef($args) &&
|
||||
$this->literal('{')
|
||||
) {
|
||||
$func = $this->pushSpecialBlock(Type::T_FUNCTION, $s);
|
||||
$func->name = $fnName;
|
||||
$func->args = $args;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@break') && $this->end()) {
|
||||
$this->append([Type::T_BREAK], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@continue') && $this->end()) {
|
||||
$this->append([Type::T_CONTINUE], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
|
||||
if ($this->literal('@return') && ($this->valueList($retVal) || true) && $this->end()) {
|
||||
$this->append([Type::T_RETURN, isset($retVal) ? $retVal : [Type::T_NULL]], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@each') &&
|
||||
$this->genericList($varNames, 'variable', ',', false) &&
|
||||
$this->literal('in') &&
|
||||
$this->valueList($list) &&
|
||||
$this->literal('{')
|
||||
) {
|
||||
$each = $this->pushSpecialBlock(Type::T_EACH, $s);
|
||||
|
||||
foreach ($varNames[2] as $varName) {
|
||||
$each->vars[] = $varName[1];
|
||||
}
|
||||
|
||||
$each->list = $list;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@while') &&
|
||||
$this->expression($cond) &&
|
||||
$this->literal('{')
|
||||
) {
|
||||
$while = $this->pushSpecialBlock(Type::T_WHILE, $s);
|
||||
$while->cond = $cond;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@for') &&
|
||||
$this->variable($varName) &&
|
||||
$this->literal('from') &&
|
||||
$this->expression($start) &&
|
||||
($this->literal('through') ||
|
||||
($forUntil = true && $this->literal('to'))) &&
|
||||
$this->expression($end) &&
|
||||
$this->literal('{')
|
||||
) {
|
||||
$for = $this->pushSpecialBlock(Type::T_FOR, $s);
|
||||
$for->var = $varName[1];
|
||||
$for->start = $start;
|
||||
$for->end = $end;
|
||||
$for->until = isset($forUntil);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@if') && $this->valueList($cond) && $this->literal('{')) {
|
||||
$if = $this->pushSpecialBlock(Type::T_IF, $s);
|
||||
$if->cond = $cond;
|
||||
$if->cases = [];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@debug') &&
|
||||
$this->valueList($value) &&
|
||||
$this->end()
|
||||
) {
|
||||
$this->append([Type::T_DEBUG, $value], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@warn') &&
|
||||
$this->valueList($value) &&
|
||||
$this->end()
|
||||
) {
|
||||
$this->append([Type::T_WARN, $value], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@error') &&
|
||||
$this->valueList($value) &&
|
||||
$this->end()
|
||||
) {
|
||||
$this->append([Type::T_ERROR, $value], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('@content') && $this->end()) {
|
||||
$this->append([Type::T_MIXIN_CONTENT], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
$last = $this->last();
|
||||
|
||||
if (isset($last) && $last[0] === Type::T_IF) {
|
||||
list(, $if) = $last;
|
||||
|
||||
if ($this->literal('@else')) {
|
||||
if ($this->literal('{')) {
|
||||
$else = $this->pushSpecialBlock(Type::T_ELSE, $s);
|
||||
} elseif ($this->literal('if') && $this->valueList($cond) && $this->literal('{')) {
|
||||
$else = $this->pushSpecialBlock(Type::T_ELSEIF, $s);
|
||||
$else->cond = $cond;
|
||||
}
|
||||
|
||||
if (isset($else)) {
|
||||
$else->dontAppend = true;
|
||||
$if->cases[] = $else;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
}
|
||||
|
||||
// only retain the first @charset directive encountered
|
||||
if ($this->literal('@charset') &&
|
||||
$this->valueList($charset) &&
|
||||
$this->end()
|
||||
) {
|
||||
if (! isset($this->charset)) {
|
||||
$statement = [Type::T_CHARSET, $charset];
|
||||
|
||||
list($line, $column) = $this->getSourcePosition($s);
|
||||
|
||||
$statement[static::SOURCE_LINE] = $line;
|
||||
$statement[static::SOURCE_COLUMN] = $column;
|
||||
$statement[static::SOURCE_INDEX] = $this->sourceIndex;
|
||||
|
||||
$this->charset = $statement;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
// doesn't match built in directive, do generic one
|
||||
if ($this->literal('@', false) &&
|
||||
$this->keyword($dirName) &&
|
||||
($this->variable($dirValue) || $this->openString('{', $dirValue) || true) &&
|
||||
$this->literal('{')
|
||||
) {
|
||||
if ($dirName === 'media') {
|
||||
$directive = $this->pushSpecialBlock(Type::T_MEDIA, $s);
|
||||
} else {
|
||||
$directive = $this->pushSpecialBlock(Type::T_DIRECTIVE, $s);
|
||||
$directive->name = $dirName;
|
||||
}
|
||||
|
||||
if (isset($dirValue)) {
|
||||
$directive->value = $dirValue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// property shortcut
|
||||
// captures most properties before having to parse a selector
|
||||
if ($this->keyword($name, false) &&
|
||||
$this->literal(': ') &&
|
||||
$this->valueList($value) &&
|
||||
$this->end()
|
||||
) {
|
||||
$name = [Type::T_STRING, '', [$name]];
|
||||
$this->append([Type::T_ASSIGN, $name, $value], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
// variable assigns
|
||||
if ($this->variable($name) &&
|
||||
$this->literal(':') &&
|
||||
$this->valueList($value) &&
|
||||
$this->end()
|
||||
) {
|
||||
// check for '!flag'
|
||||
$assignmentFlags = $this->stripAssignmentFlags($value);
|
||||
$this->append([Type::T_ASSIGN, $name, $value, $assignmentFlags], $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
// misc
|
||||
if ($this->literal('-->')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// opening css block
|
||||
if ($this->selectors($selectors) && $this->literal('{')) {
|
||||
$this->pushBlock($selectors, $s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
// property assign, or nested assign
|
||||
if ($this->propertyName($name) && $this->literal(':')) {
|
||||
$foundSomething = false;
|
||||
|
||||
if ($this->valueList($value)) {
|
||||
$this->append([Type::T_ASSIGN, $name, $value], $s);
|
||||
$foundSomething = true;
|
||||
}
|
||||
|
||||
if ($this->literal('{')) {
|
||||
$propBlock = $this->pushSpecialBlock(Type::T_NESTED_PROPERTY, $s);
|
||||
$propBlock->prefix = $name;
|
||||
$foundSomething = true;
|
||||
} elseif ($foundSomething) {
|
||||
$foundSomething = $this->end();
|
||||
}
|
||||
|
||||
if ($foundSomething) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
// closing a block
|
||||
if ($this->literal('}')) {
|
||||
$block = $this->popBlock();
|
||||
|
||||
if (isset($block->type) && $block->type === Type::T_INCLUDE) {
|
||||
$include = $block->child;
|
||||
unset($block->child);
|
||||
$include[3] = $block;
|
||||
$this->append($include, $s);
|
||||
} elseif (empty($block->dontAppend)) {
|
||||
$type = isset($block->type) ? $block->type : Type::T_BLOCK;
|
||||
$this->append([$type, $block], $s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// extra stuff
|
||||
if ($this->literal(';') ||
|
||||
$this->literal('<!--')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push block onto parse tree
|
||||
*
|
||||
* @param array $selectors
|
||||
* @param integer $pos
|
||||
*
|
||||
* @return \Leafo\ScssPhp\Block
|
||||
*/
|
||||
protected function pushBlock($selectors, $pos = 0)
|
||||
{
|
||||
list($line, $column) = $this->getSourcePosition($pos);
|
||||
|
||||
$b = new Block;
|
||||
$b->sourceName = $this->sourceName;
|
||||
$b->sourceLine = $line;
|
||||
$b->sourceColumn = $column;
|
||||
$b->sourceIndex = $this->sourceIndex;
|
||||
$b->selectors = $selectors;
|
||||
$b->comments = [];
|
||||
$b->parent = $this->env;
|
||||
|
||||
if (! $this->env) {
|
||||
$b->children = [];
|
||||
} elseif (empty($this->env->children)) {
|
||||
$this->env->children = $this->env->comments;
|
||||
$b->children = [];
|
||||
$this->env->comments = [];
|
||||
} else {
|
||||
$b->children = $this->env->comments;
|
||||
$this->env->comments = [];
|
||||
}
|
||||
|
||||
$this->env = $b;
|
||||
|
||||
return $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push special (named) block onto parse tree
|
||||
*
|
||||
* @param string $type
|
||||
* @param integer $pos
|
||||
*
|
||||
* @return \Leafo\ScssPhp\Block
|
||||
*/
|
||||
protected function pushSpecialBlock($type, $pos)
|
||||
{
|
||||
$block = $this->pushBlock(null, $pos);
|
||||
$block->type = $type;
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pop scope and return last block
|
||||
*
|
||||
* @return \Leafo\ScssPhp\Block
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function popBlock()
|
||||
{
|
||||
$block = $this->env;
|
||||
|
||||
if (empty($block->parent)) {
|
||||
$this->throwParseError('unexpected }');
|
||||
}
|
||||
|
||||
$this->env = $block->parent;
|
||||
unset($block->parent);
|
||||
|
||||
$comments = $block->comments;
|
||||
if (count($comments)) {
|
||||
$this->env->comments = $comments;
|
||||
unset($block->comments);
|
||||
}
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek input stream
|
||||
*
|
||||
* @param string $regex
|
||||
* @param array $out
|
||||
* @param integer $from
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function peek($regex, &$out, $from = null)
|
||||
{
|
||||
if (! isset($from)) {
|
||||
$from = $this->count;
|
||||
}
|
||||
|
||||
$r = '/' . $regex . '/' . $this->patternModifiers;
|
||||
$result = preg_match($r, $this->buffer, $out, null, $from);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to position in input stream (or return current position in input stream)
|
||||
*
|
||||
* @param integer $where
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function seek($where = null)
|
||||
{
|
||||
if ($where === null) {
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
$this->count = $where;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match string looking for either ending delim, escape, or string interpolation
|
||||
*
|
||||
* {@internal This is a workaround for preg_match's 250K string match limit. }}
|
||||
*
|
||||
* @param array $m Matches (passed by reference)
|
||||
* @param string $delim Delimeter
|
||||
*
|
||||
* @return boolean True if match; false otherwise
|
||||
*/
|
||||
protected function matchString(&$m, $delim)
|
||||
{
|
||||
$token = null;
|
||||
|
||||
$end = strlen($this->buffer);
|
||||
|
||||
// look for either ending delim, escape, or string interpolation
|
||||
foreach (['#{', '\\', $delim] as $lookahead) {
|
||||
$pos = strpos($this->buffer, $lookahead, $this->count);
|
||||
|
||||
if ($pos !== false && $pos < $end) {
|
||||
$end = $pos;
|
||||
$token = $lookahead;
|
||||
}
|
||||
}
|
||||
|
||||
if (! isset($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$match = substr($this->buffer, $this->count, $end - $this->count);
|
||||
$m = [
|
||||
$match . $token,
|
||||
$match,
|
||||
$token
|
||||
];
|
||||
$this->count = $end + strlen($token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to match something on head of buffer
|
||||
*
|
||||
* @param string $regex
|
||||
* @param array $out
|
||||
* @param boolean $eatWhitespace
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function match($regex, &$out, $eatWhitespace = null)
|
||||
{
|
||||
if (! isset($eatWhitespace)) {
|
||||
$eatWhitespace = $this->eatWhiteDefault;
|
||||
}
|
||||
|
||||
$r = '/' . $regex . '/' . $this->patternModifiers;
|
||||
|
||||
if (preg_match($r, $this->buffer, $out, null, $this->count)) {
|
||||
$this->count += strlen($out[0]);
|
||||
|
||||
if ($eatWhitespace) {
|
||||
$this->whitespace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match literal string
|
||||
*
|
||||
* @param string $what
|
||||
* @param boolean $eatWhitespace
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function literal($what, $eatWhitespace = null)
|
||||
{
|
||||
if (! isset($eatWhitespace)) {
|
||||
$eatWhitespace = $this->eatWhiteDefault;
|
||||
}
|
||||
|
||||
$len = strlen($what);
|
||||
|
||||
if (strcasecmp(substr($this->buffer, $this->count, $len), $what) === 0) {
|
||||
$this->count += $len;
|
||||
|
||||
if ($eatWhitespace) {
|
||||
$this->whitespace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match some whitespace
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function whitespace()
|
||||
{
|
||||
$gotWhite = false;
|
||||
|
||||
while (preg_match(static::$whitePattern, $this->buffer, $m, null, $this->count)) {
|
||||
if (isset($m[1]) && empty($this->commentsSeen[$this->count])) {
|
||||
$this->appendComment([Type::T_COMMENT, $m[1]]);
|
||||
|
||||
$this->commentsSeen[$this->count] = true;
|
||||
}
|
||||
|
||||
$this->count += strlen($m[0]);
|
||||
$gotWhite = true;
|
||||
}
|
||||
|
||||
return $gotWhite;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append comment to current block
|
||||
*
|
||||
* @param array $comment
|
||||
*/
|
||||
protected function appendComment($comment)
|
||||
{
|
||||
$comment[1] = substr(preg_replace(['/^\s+/m', '/^(.)/m'], ['', ' \1'], $comment[1]), 1);
|
||||
|
||||
$this->env->comments[] = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append statement to current block
|
||||
*
|
||||
* @param array $statement
|
||||
* @param integer $pos
|
||||
*/
|
||||
protected function append($statement, $pos = null)
|
||||
{
|
||||
if ($pos !== null) {
|
||||
list($line, $column) = $this->getSourcePosition($pos);
|
||||
|
||||
$statement[static::SOURCE_LINE] = $line;
|
||||
$statement[static::SOURCE_COLUMN] = $column;
|
||||
$statement[static::SOURCE_INDEX] = $this->sourceIndex;
|
||||
}
|
||||
|
||||
$this->env->children[] = $statement;
|
||||
|
||||
$comments = $this->env->comments;
|
||||
|
||||
if (count($comments)) {
|
||||
$this->env->children = array_merge($this->env->children, $comments);
|
||||
$this->env->comments = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last child was appended
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function last()
|
||||
{
|
||||
$i = count($this->env->children) - 1;
|
||||
|
||||
if (isset($this->env->children[$i])) {
|
||||
return $this->env->children[$i];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse media query list
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function mediaQueryList(&$out)
|
||||
{
|
||||
return $this->genericList($out, 'mediaQuery', ',', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse media query
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function mediaQuery(&$out)
|
||||
{
|
||||
$expressions = null;
|
||||
$parts = [];
|
||||
|
||||
if (($this->literal('only') && ($only = true) || $this->literal('not') && ($not = true) || true) &&
|
||||
$this->mixedKeyword($mediaType)
|
||||
) {
|
||||
$prop = [Type::T_MEDIA_TYPE];
|
||||
|
||||
if (isset($only)) {
|
||||
$prop[] = [Type::T_KEYWORD, 'only'];
|
||||
}
|
||||
|
||||
if (isset($not)) {
|
||||
$prop[] = [Type::T_KEYWORD, 'not'];
|
||||
}
|
||||
|
||||
$media = [Type::T_LIST, '', []];
|
||||
|
||||
foreach ((array) $mediaType as $type) {
|
||||
if (is_array($type)) {
|
||||
$media[2][] = $type;
|
||||
} else {
|
||||
$media[2][] = [Type::T_KEYWORD, $type];
|
||||
}
|
||||
}
|
||||
|
||||
$prop[] = $media;
|
||||
$parts[] = $prop;
|
||||
}
|
||||
|
||||
if (empty($parts) || $this->literal('and')) {
|
||||
$this->genericList($expressions, 'mediaExpression', 'and', false);
|
||||
|
||||
if (is_array($expressions)) {
|
||||
$parts = array_merge($parts, $expressions[2]);
|
||||
}
|
||||
}
|
||||
|
||||
$out = $parts;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse media expression
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function mediaExpression(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
$value = null;
|
||||
|
||||
if ($this->literal('(') &&
|
||||
$this->expression($feature) &&
|
||||
($this->literal(':') && $this->expression($value) || true) &&
|
||||
$this->literal(')')
|
||||
) {
|
||||
$out = [Type::T_MEDIA_EXPRESSION, $feature];
|
||||
|
||||
if ($value) {
|
||||
$out[] = $value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse argument values
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function argValues(&$out)
|
||||
{
|
||||
if ($this->genericList($list, 'argValue', ',', false)) {
|
||||
$out = $list[2];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse argument value
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function argValue(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
$keyword = null;
|
||||
|
||||
if (! $this->variable($keyword) || ! $this->literal(':')) {
|
||||
$this->seek($s);
|
||||
$keyword = null;
|
||||
}
|
||||
|
||||
if ($this->genericList($value, 'expression')) {
|
||||
$out = [$keyword, $value, false];
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->literal('...')) {
|
||||
$out[2] = true;
|
||||
} else {
|
||||
$this->seek($s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse comma separated value list
|
||||
*
|
||||
* @param string $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function valueList(&$out)
|
||||
{
|
||||
return $this->genericList($out, 'spaceList', ',');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse space separated value list
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function spaceList(&$out)
|
||||
{
|
||||
return $this->genericList($out, 'expression');
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse generic list
|
||||
*
|
||||
* @param array $out
|
||||
* @param callable $parseItem
|
||||
* @param string $delim
|
||||
* @param boolean $flatten
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function genericList(&$out, $parseItem, $delim = '', $flatten = true)
|
||||
{
|
||||
$s = $this->seek();
|
||||
$items = [];
|
||||
|
||||
while ($this->$parseItem($value)) {
|
||||
$items[] = $value;
|
||||
|
||||
if ($delim) {
|
||||
if (! $this->literal($delim)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($items) === 0) {
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($flatten && count($items) === 1) {
|
||||
$out = $items[0];
|
||||
} else {
|
||||
$out = [Type::T_LIST, $delim, $items];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse expression
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function expression(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->literal('(')) {
|
||||
if ($this->literal(')')) {
|
||||
$out = [Type::T_LIST, '', []];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->valueList($out) && $this->literal(')') && $out[0] === Type::T_LIST) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->map($out)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
}
|
||||
|
||||
if ($this->value($lhs)) {
|
||||
$out = $this->expHelper($lhs, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse left-hand side of subexpression
|
||||
*
|
||||
* @param array $lhs
|
||||
* @param integer $minP
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function expHelper($lhs, $minP)
|
||||
{
|
||||
$operators = static::$operatorPattern;
|
||||
|
||||
$ss = $this->seek();
|
||||
$whiteBefore = isset($this->buffer[$this->count - 1]) &&
|
||||
ctype_space($this->buffer[$this->count - 1]);
|
||||
|
||||
while ($this->match($operators, $m, false) && static::$precedence[$m[1]] >= $minP) {
|
||||
$whiteAfter = isset($this->buffer[$this->count]) &&
|
||||
ctype_space($this->buffer[$this->count]);
|
||||
$varAfter = isset($this->buffer[$this->count]) &&
|
||||
$this->buffer[$this->count] === '$';
|
||||
|
||||
$this->whitespace();
|
||||
|
||||
$op = $m[1];
|
||||
|
||||
// don't turn negative numbers into expressions
|
||||
if ($op === '-' && $whiteBefore && ! $whiteAfter && ! $varAfter) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (! $this->value($rhs)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// peek and see if rhs belongs to next operator
|
||||
if ($this->peek($operators, $next) && static::$precedence[$next[1]] > static::$precedence[$op]) {
|
||||
$rhs = $this->expHelper($rhs, static::$precedence[$next[1]]);
|
||||
}
|
||||
|
||||
$lhs = [Type::T_EXPRESSION, $op, $lhs, $rhs, $this->inParens, $whiteBefore, $whiteAfter];
|
||||
$ss = $this->seek();
|
||||
$whiteBefore = isset($this->buffer[$this->count - 1]) &&
|
||||
ctype_space($this->buffer[$this->count - 1]);
|
||||
}
|
||||
|
||||
$this->seek($ss);
|
||||
|
||||
return $lhs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse value
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function value(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->literal('not', false) && $this->whitespace() && $this->value($inner)) {
|
||||
$out = [Type::T_UNARY, 'not', $inner, $this->inParens];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('not', false) && $this->parenValue($inner)) {
|
||||
$out = [Type::T_UNARY, 'not', $inner, $this->inParens];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->literal('+') && $this->value($inner)) {
|
||||
$out = [Type::T_UNARY, '+', $inner, $this->inParens];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
// negation
|
||||
if ($this->literal('-', false) &&
|
||||
($this->variable($inner) ||
|
||||
$this->unit($inner) ||
|
||||
$this->parenValue($inner))
|
||||
) {
|
||||
$out = [Type::T_UNARY, '-', $inner, $this->inParens];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
if ($this->parenValue($out) ||
|
||||
$this->interpolation($out) ||
|
||||
$this->variable($out) ||
|
||||
$this->color($out) ||
|
||||
$this->unit($out) ||
|
||||
$this->string($out) ||
|
||||
$this->func($out) ||
|
||||
$this->progid($out)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->keyword($keyword)) {
|
||||
if ($keyword === 'null') {
|
||||
$out = [Type::T_NULL];
|
||||
} else {
|
||||
$out = [Type::T_KEYWORD, $keyword];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse parenthesized value
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function parenValue(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
$inParens = $this->inParens;
|
||||
|
||||
if ($this->literal('(')) {
|
||||
if ($this->literal(')')) {
|
||||
$out = [Type::T_LIST, '', []];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->inParens = true;
|
||||
|
||||
if ($this->expression($exp) && $this->literal(')')) {
|
||||
$out = $exp;
|
||||
$this->inParens = $inParens;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->inParens = $inParens;
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse "progid:"
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function progid(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->literal('progid:', false) &&
|
||||
$this->openString('(', $fn) &&
|
||||
$this->literal('(')
|
||||
) {
|
||||
$this->openString(')', $args, '(');
|
||||
|
||||
if ($this->literal(')')) {
|
||||
$out = [Type::T_STRING, '', [
|
||||
'progid:', $fn, '(', $args, ')'
|
||||
]];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse function call
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function func(&$func)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->keyword($name, false) &&
|
||||
$this->literal('(')
|
||||
) {
|
||||
if ($name === 'alpha' && $this->argumentList($args)) {
|
||||
$func = [Type::T_FUNCTION, $name, [Type::T_STRING, '', $args]];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($name !== 'expression' && ! preg_match('/^(-[a-z]+-)?calc$/', $name)) {
|
||||
$ss = $this->seek();
|
||||
|
||||
if ($this->argValues($args) && $this->literal(')')) {
|
||||
$func = [Type::T_FUNCTION_CALL, $name, $args];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($ss);
|
||||
}
|
||||
|
||||
if (($this->openString(')', $str, '(') || true) &&
|
||||
$this->literal(')')
|
||||
) {
|
||||
$args = [];
|
||||
|
||||
if (! empty($str)) {
|
||||
$args[] = [null, [Type::T_STRING, '', [$str]]];
|
||||
}
|
||||
|
||||
$func = [Type::T_FUNCTION_CALL, $name, $args];
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse function call argument list
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function argumentList(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
$this->literal('(');
|
||||
|
||||
$args = [];
|
||||
|
||||
while ($this->keyword($var)) {
|
||||
if ($this->literal('=') && $this->expression($exp)) {
|
||||
$args[] = [Type::T_STRING, '', [$var . '=']];
|
||||
$arg = $exp;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
$args[] = $arg;
|
||||
|
||||
if (! $this->literal(',')) {
|
||||
break;
|
||||
}
|
||||
|
||||
$args[] = [Type::T_STRING, '', [', ']];
|
||||
}
|
||||
|
||||
if (! $this->literal(')') || ! count($args)) {
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $args;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse mixin/function definition argument list
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function argumentDef(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
$this->literal('(');
|
||||
|
||||
$args = [];
|
||||
|
||||
while ($this->variable($var)) {
|
||||
$arg = [$var[1], null, false];
|
||||
|
||||
$ss = $this->seek();
|
||||
|
||||
if ($this->literal(':') && $this->genericList($defaultVal, 'expression')) {
|
||||
$arg[1] = $defaultVal;
|
||||
} else {
|
||||
$this->seek($ss);
|
||||
}
|
||||
|
||||
$ss = $this->seek();
|
||||
|
||||
if ($this->literal('...')) {
|
||||
$sss = $this->seek();
|
||||
|
||||
if (! $this->literal(')')) {
|
||||
$this->throwParseError('... has to be after the final argument');
|
||||
}
|
||||
|
||||
$arg[2] = true;
|
||||
$this->seek($sss);
|
||||
} else {
|
||||
$this->seek($ss);
|
||||
}
|
||||
|
||||
$args[] = $arg;
|
||||
|
||||
if (! $this->literal(',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $this->literal(')')) {
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $args;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse map
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function map(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
if (! $this->literal('(')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$keys = [];
|
||||
$values = [];
|
||||
|
||||
while ($this->genericList($key, 'expression') && $this->literal(':') &&
|
||||
$this->genericList($value, 'expression')
|
||||
) {
|
||||
$keys[] = $key;
|
||||
$values[] = $value;
|
||||
|
||||
if (! $this->literal(',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! count($keys) || ! $this->literal(')')) {
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = [Type::T_MAP, $keys, $values];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse color
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function color(&$out)
|
||||
{
|
||||
$color = [Type::T_COLOR];
|
||||
|
||||
if ($this->match('(#([0-9a-f]{6})|#([0-9a-f]{3}))', $m)) {
|
||||
if (isset($m[3])) {
|
||||
$num = hexdec($m[3]);
|
||||
|
||||
foreach ([3, 2, 1] as $i) {
|
||||
$t = $num & 0xf;
|
||||
$color[$i] = $t << 4 | $t;
|
||||
$num >>= 4;
|
||||
}
|
||||
} else {
|
||||
$num = hexdec($m[2]);
|
||||
|
||||
foreach ([3, 2, 1] as $i) {
|
||||
$color[$i] = $num & 0xff;
|
||||
$num >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
$out = $color;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse number with unit
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function unit(&$unit)
|
||||
{
|
||||
if ($this->match('([0-9]*(\.)?[0-9]+)([%a-zA-Z]+)?', $m)) {
|
||||
$unit = new Node\Number($m[1], empty($m[3]) ? '' : $m[3]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse string
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function string(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->literal('"', false)) {
|
||||
$delim = '"';
|
||||
} elseif ($this->literal("'", false)) {
|
||||
$delim = "'";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
$content = [];
|
||||
$oldWhite = $this->eatWhiteDefault;
|
||||
$this->eatWhiteDefault = false;
|
||||
$hasInterpolation = false;
|
||||
|
||||
while ($this->matchString($m, $delim)) {
|
||||
if ($m[1] !== '') {
|
||||
$content[] = $m[1];
|
||||
}
|
||||
|
||||
if ($m[2] === '#{') {
|
||||
$this->count -= strlen($m[2]);
|
||||
|
||||
if ($this->interpolation($inter, false)) {
|
||||
$content[] = $inter;
|
||||
$hasInterpolation = true;
|
||||
} else {
|
||||
$this->count += strlen($m[2]);
|
||||
$content[] = '#{'; // ignore it
|
||||
}
|
||||
} elseif ($m[2] === '\\') {
|
||||
if ($this->literal('"', false)) {
|
||||
$content[] = $m[2] . '"';
|
||||
} elseif ($this->literal("'", false)) {
|
||||
$content[] = $m[2] . "'";
|
||||
} else {
|
||||
$content[] = $m[2];
|
||||
}
|
||||
} else {
|
||||
$this->count -= strlen($delim);
|
||||
break; // delim
|
||||
}
|
||||
}
|
||||
|
||||
$this->eatWhiteDefault = $oldWhite;
|
||||
|
||||
if ($this->literal($delim)) {
|
||||
if ($hasInterpolation) {
|
||||
$delim = '"';
|
||||
|
||||
foreach ($content as &$string) {
|
||||
if ($string === "\\'") {
|
||||
$string = "'";
|
||||
} elseif ($string === '\\"') {
|
||||
$string = '"';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$out = [Type::T_STRING, $delim, $content];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse keyword or interpolation
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function mixedKeyword(&$out)
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
$oldWhite = $this->eatWhiteDefault;
|
||||
$this->eatWhiteDefault = false;
|
||||
|
||||
for (;;) {
|
||||
if ($this->keyword($key)) {
|
||||
$parts[] = $key;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->interpolation($inter)) {
|
||||
$parts[] = $inter;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$this->eatWhiteDefault = $oldWhite;
|
||||
|
||||
if (count($parts) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->eatWhiteDefault) {
|
||||
$this->whitespace();
|
||||
}
|
||||
|
||||
$out = $parts;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an unbounded string stopped by $end
|
||||
*
|
||||
* @param string $end
|
||||
* @param array $out
|
||||
* @param string $nestingOpen
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function openString($end, &$out, $nestingOpen = null)
|
||||
{
|
||||
$oldWhite = $this->eatWhiteDefault;
|
||||
$this->eatWhiteDefault = false;
|
||||
|
||||
$patt = '(.*?)([\'"]|#\{|' . $this->pregQuote($end) . '|' . static::$commentPattern . ')';
|
||||
|
||||
$nestingLevel = 0;
|
||||
|
||||
$content = [];
|
||||
|
||||
while ($this->match($patt, $m, false)) {
|
||||
if (isset($m[1]) && $m[1] !== '') {
|
||||
$content[] = $m[1];
|
||||
|
||||
if ($nestingOpen) {
|
||||
$nestingLevel += substr_count($m[1], $nestingOpen);
|
||||
}
|
||||
}
|
||||
|
||||
$tok = $m[2];
|
||||
|
||||
$this->count-= strlen($tok);
|
||||
|
||||
if ($tok === $end && ! $nestingLevel--) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (($tok === "'" || $tok === '"') && $this->string($str)) {
|
||||
$content[] = $str;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($tok === '#{' && $this->interpolation($inter)) {
|
||||
$content[] = $inter;
|
||||
continue;
|
||||
}
|
||||
|
||||
$content[] = $tok;
|
||||
$this->count+= strlen($tok);
|
||||
}
|
||||
|
||||
$this->eatWhiteDefault = $oldWhite;
|
||||
|
||||
if (count($content) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// trim the end
|
||||
if (is_string(end($content))) {
|
||||
$content[count($content) - 1] = rtrim(end($content));
|
||||
}
|
||||
|
||||
$out = [Type::T_STRING, '', $content];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parser interpolation
|
||||
*
|
||||
* @param array $out
|
||||
* @param boolean $lookWhite save information about whitespace before and after
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function interpolation(&$out, $lookWhite = true)
|
||||
{
|
||||
$oldWhite = $this->eatWhiteDefault;
|
||||
$this->eatWhiteDefault = true;
|
||||
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->literal('#{') && $this->valueList($value) && $this->literal('}', false)) {
|
||||
if ($lookWhite) {
|
||||
$left = preg_match('/\s/', $this->buffer[$s - 1]) ? ' ' : '';
|
||||
$right = preg_match('/\s/', $this->buffer[$this->count]) ? ' ': '';
|
||||
} else {
|
||||
$left = $right = false;
|
||||
}
|
||||
|
||||
$out = [Type::T_INTERPOLATE, $value, $left, $right];
|
||||
$this->eatWhiteDefault = $oldWhite;
|
||||
|
||||
if ($this->eatWhiteDefault) {
|
||||
$this->whitespace();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
$this->eatWhiteDefault = $oldWhite;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse property name (as an array of parts or a string)
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function propertyName(&$out)
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
$oldWhite = $this->eatWhiteDefault;
|
||||
$this->eatWhiteDefault = false;
|
||||
|
||||
for (;;) {
|
||||
if ($this->interpolation($inter)) {
|
||||
$parts[] = $inter;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->keyword($text)) {
|
||||
$parts[] = $text;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($parts) === 0 && $this->match('[:.#]', $m, false)) {
|
||||
// css hacks
|
||||
$parts[] = $m[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$this->eatWhiteDefault = $oldWhite;
|
||||
|
||||
if (count($parts) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// match comment hack
|
||||
if (preg_match(
|
||||
static::$whitePattern,
|
||||
$this->buffer,
|
||||
$m,
|
||||
null,
|
||||
$this->count
|
||||
)) {
|
||||
if (! empty($m[0])) {
|
||||
$parts[] = $m[0];
|
||||
$this->count += strlen($m[0]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->whitespace(); // get any extra whitespace
|
||||
|
||||
$out = [Type::T_STRING, '', $parts];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse comma separated selector list
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function selectors(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
$selectors = [];
|
||||
|
||||
while ($this->selector($sel)) {
|
||||
$selectors[] = $sel;
|
||||
|
||||
if (! $this->literal(',')) {
|
||||
break;
|
||||
}
|
||||
|
||||
while ($this->literal(',')) {
|
||||
; // ignore extra
|
||||
}
|
||||
}
|
||||
|
||||
if (count($selectors) === 0) {
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $selectors;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse whitespace separated selector list
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function selector(&$out)
|
||||
{
|
||||
$selector = [];
|
||||
|
||||
for (;;) {
|
||||
if ($this->match('[>+~]+', $m)) {
|
||||
$selector[] = [$m[0]];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->selectorSingle($part)) {
|
||||
$selector[] = $part;
|
||||
$this->match('\s+', $m);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->match('\/[^\/]+\/', $m)) {
|
||||
$selector[] = [$m[0]];
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (count($selector) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $selector;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the parts that make up a selector
|
||||
*
|
||||
* {@internal
|
||||
* div[yes=no]#something.hello.world:nth-child(-2n+1)%placeholder
|
||||
* }}
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function selectorSingle(&$out)
|
||||
{
|
||||
$oldWhite = $this->eatWhiteDefault;
|
||||
$this->eatWhiteDefault = false;
|
||||
|
||||
$parts = [];
|
||||
|
||||
if ($this->literal('*', false)) {
|
||||
$parts[] = '*';
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
// see if we can stop early
|
||||
if ($this->match('\s*[{,]', $m)) {
|
||||
$this->count--;
|
||||
break;
|
||||
}
|
||||
|
||||
$s = $this->seek();
|
||||
|
||||
// self
|
||||
if ($this->literal('&', false)) {
|
||||
$parts[] = Compiler::$selfSelector;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->literal('.', false)) {
|
||||
$parts[] = '.';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->literal('|', false)) {
|
||||
$parts[] = '|';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->match('\\\\\S', $m)) {
|
||||
$parts[] = $m[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
// for keyframes
|
||||
if ($this->unit($unit)) {
|
||||
$parts[] = $unit;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->keyword($name)) {
|
||||
$parts[] = $name;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->interpolation($inter)) {
|
||||
$parts[] = $inter;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->literal('%', false) && $this->placeholder($placeholder)) {
|
||||
$parts[] = '%';
|
||||
$parts[] = $placeholder;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->literal('#', false)) {
|
||||
$parts[] = '#';
|
||||
continue;
|
||||
}
|
||||
|
||||
// a pseudo selector
|
||||
if ($this->match('::?', $m) && $this->mixedKeyword($nameParts)) {
|
||||
$parts[] = $m[0];
|
||||
|
||||
foreach ($nameParts as $sub) {
|
||||
$parts[] = $sub;
|
||||
}
|
||||
|
||||
$ss = $this->seek();
|
||||
|
||||
if ($this->literal('(') &&
|
||||
($this->openString(')', $str, '(') || true) &&
|
||||
$this->literal(')')
|
||||
) {
|
||||
$parts[] = '(';
|
||||
|
||||
if (! empty($str)) {
|
||||
$parts[] = $str;
|
||||
}
|
||||
|
||||
$parts[] = ')';
|
||||
} else {
|
||||
$this->seek($ss);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
// attribute selector
|
||||
if ($this->literal('[') &&
|
||||
($this->openString(']', $str, '[') || true) &&
|
||||
$this->literal(']')
|
||||
) {
|
||||
$parts[] = '[';
|
||||
|
||||
if (! empty($str)) {
|
||||
$parts[] = $str;
|
||||
}
|
||||
|
||||
$parts[] = ']';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$this->eatWhiteDefault = $oldWhite;
|
||||
|
||||
if (count($parts) === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $parts;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a variable
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function variable(&$out)
|
||||
{
|
||||
$s = $this->seek();
|
||||
|
||||
if ($this->literal('$', false) && $this->keyword($name)) {
|
||||
$out = [Type::T_VARIABLE, $name];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->seek($s);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a keyword
|
||||
*
|
||||
* @param string $word
|
||||
* @param boolean $eatWhitespace
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function keyword(&$word, $eatWhitespace = null)
|
||||
{
|
||||
if ($this->match(
|
||||
$this->utf8
|
||||
? '(([\pL\w_\-\*!"\']|[\\\\].)([\pL\w\-_"\']|[\\\\].)*)'
|
||||
: '(([\w_\-\*!"\']|[\\\\].)([\w\-_"\']|[\\\\].)*)',
|
||||
$m,
|
||||
$eatWhitespace
|
||||
)) {
|
||||
$word = $m[1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a placeholder
|
||||
*
|
||||
* @param string $placeholder
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function placeholder(&$placeholder)
|
||||
{
|
||||
if ($this->match(
|
||||
$this->utf8
|
||||
? '([\pL\w\-_]+|#[{][$][\pL\w\-_]+[}])'
|
||||
: '([\w\-_]+|#[{][$][\w\-_]+[}])',
|
||||
$m
|
||||
)) {
|
||||
$placeholder = $m[1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a url
|
||||
*
|
||||
* @param array $out
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function url(&$out)
|
||||
{
|
||||
if ($this->match('(url\(\s*(["\']?)([^)]+)\2\s*\))', $m)) {
|
||||
$out = [Type::T_STRING, '', ['url(' . $m[2] . $m[3] . $m[2] . ')']];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume an end of statement delimiter
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function end()
|
||||
{
|
||||
if ($this->literal(';')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->count === strlen($this->buffer) || $this->buffer[$this->count] === '}') {
|
||||
// if there is end of file or a closing block next then we don't need a ;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip assignment flag from the list
|
||||
*
|
||||
* @param array $value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function stripAssignmentFlags(&$value)
|
||||
{
|
||||
$flags = [];
|
||||
|
||||
for ($token = &$value; $token[0] === Type::T_LIST && ($s = count($token[2])); $token = &$lastNode) {
|
||||
$lastNode = &$token[2][$s - 1];
|
||||
|
||||
while ($lastNode[0] === Type::T_KEYWORD && in_array($lastNode[1], ['!default', '!global'])) {
|
||||
array_pop($token[2]);
|
||||
|
||||
$node = end($token[2]);
|
||||
|
||||
$token = $this->flattenList($token);
|
||||
|
||||
$flags[] = $lastNode[1];
|
||||
|
||||
$lastNode = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return $flags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip optional flag from selector list
|
||||
*
|
||||
* @param array $selectors
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function stripOptionalFlag(&$selectors)
|
||||
{
|
||||
$optional = false;
|
||||
|
||||
$selector = end($selectors);
|
||||
$part = end($selector);
|
||||
|
||||
if ($part === ['!optional']) {
|
||||
array_pop($selectors[count($selectors) - 1]);
|
||||
|
||||
$optional = true;
|
||||
}
|
||||
|
||||
return $optional;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn list of length 1 into value type
|
||||
*
|
||||
* @param array $value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function flattenList($value)
|
||||
{
|
||||
if ($value[0] === Type::T_LIST && count($value[2]) === 1) {
|
||||
return $this->flattenList($value[2][0]);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* {@internal
|
||||
* advance counter to next occurrence of $what
|
||||
* $until - don't include $what in advance
|
||||
* $allowNewline, if string, will be used as valid char set
|
||||
* }}
|
||||
*/
|
||||
protected function to($what, &$out, $until = false, $allowNewline = false)
|
||||
{
|
||||
if (is_string($allowNewline)) {
|
||||
$validChars = $allowNewline;
|
||||
} else {
|
||||
$validChars = $allowNewline ? '.' : "[^\n]";
|
||||
}
|
||||
|
||||
if (! $this->match('(' . $validChars . '*?)' . $this->pregQuote($what), $m, ! $until)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($until) {
|
||||
$this->count -= strlen($what); // give back $what
|
||||
}
|
||||
|
||||
$out = $m[1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
protected function show()
|
||||
{
|
||||
if ($this->peek("(.*?)(\n|$)", $m, $this->count)) {
|
||||
return $m[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Quote regular expression
|
||||
*
|
||||
* @param string $what
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function pregQuote($what)
|
||||
{
|
||||
return preg_quote($what, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract line numbers from buffer
|
||||
*
|
||||
* @param string $buffer
|
||||
*/
|
||||
private function extractLineNumbers($buffer)
|
||||
{
|
||||
$this->sourcePositions = [0 => 0];
|
||||
$prev = 0;
|
||||
|
||||
while (($pos = strpos($buffer, "\n", $prev)) !== false) {
|
||||
$this->sourcePositions[] = $pos;
|
||||
$prev = $pos + 1;
|
||||
}
|
||||
|
||||
$this->sourcePositions[] = strlen($buffer);
|
||||
|
||||
if (substr($buffer, -1) !== "\n") {
|
||||
$this->sourcePositions[] = strlen($buffer) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get source line number and column (given character position in the buffer)
|
||||
*
|
||||
* @param integer $pos
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
private function getSourcePosition($pos)
|
||||
{
|
||||
$low = 0;
|
||||
$high = count($this->sourcePositions);
|
||||
|
||||
while ($low < $high) {
|
||||
$mid = (int) (($high + $low) / 2);
|
||||
|
||||
if ($pos < $this->sourcePositions[$mid]) {
|
||||
$high = $mid - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($pos >= $this->sourcePositions[$mid + 1]) {
|
||||
$low = $mid + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
return [$mid + 1, $pos - $this->sourcePositions[$mid]];
|
||||
}
|
||||
|
||||
return [$low + 1, $pos - $this->sourcePositions[$low]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save internal encoding
|
||||
*/
|
||||
private function saveEncoding()
|
||||
{
|
||||
if (version_compare(PHP_VERSION, '7.2.0') >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$iniDirective = 'mbstring' . '.func_overload'; // deprecated in PHP 7.2
|
||||
|
||||
if (ini_get($iniDirective) & 2) {
|
||||
$this->encoding = mb_internal_encoding();
|
||||
|
||||
mb_internal_encoding('iso-8859-1');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore internal encoding
|
||||
*/
|
||||
private function restoreEncoding()
|
||||
{
|
||||
if ($this->encoding) {
|
||||
mb_internal_encoding($this->encoding);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\SourceMap;
|
||||
|
||||
/**
|
||||
* Base64 VLQ Encoder
|
||||
*
|
||||
* {@internal Derivative of oyejorge/less.php's lib/SourceMap/Base64VLQ.php, relicensed with permission. }}
|
||||
*
|
||||
* @author Josh Schmidt <oyejorge@gmail.com>
|
||||
* @author Nicolas FRANÇOIS <nicolas.francois@frog-labs.com>
|
||||
*/
|
||||
class Base64VLQEncoder
|
||||
{
|
||||
/**
|
||||
* Shift
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $shift = 5;
|
||||
|
||||
/**
|
||||
* Mask
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $mask = 0x1F; // == (1 << shift) == 0b00011111
|
||||
|
||||
/**
|
||||
* Continuation bit
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
|
||||
|
||||
/**
|
||||
* Char to integer map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $charToIntMap = array(
|
||||
'A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, 'H' => 7,
|
||||
'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, 'O' => 14, 'P' => 15,
|
||||
'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23,
|
||||
'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27, 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31,
|
||||
'g' => 32, 'h' => 33, 'i' => 34, 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39,
|
||||
'o' => 40, 'p' => 41, 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47,
|
||||
'w' => 48, 'x' => 49, 'y' => 50, 'z' => 51, 0 => 52, 1 => 53, 2 => 54, 3 => 55,
|
||||
4 => 56, 5 => 57, 6 => 58, 7 => 59, 8 => 60, 9 => 61, '+' => 62, '/' => 63,
|
||||
);
|
||||
|
||||
/**
|
||||
* Integer to char map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $intToCharMap = array(
|
||||
0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', 7 => 'H',
|
||||
8 => 'I', 9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N', 14 => 'O', 15 => 'P',
|
||||
16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U', 21 => 'V', 22 => 'W', 23 => 'X',
|
||||
24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b', 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f',
|
||||
32 => 'g', 33 => 'h', 34 => 'i', 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n',
|
||||
40 => 'o', 41 => 'p', 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v',
|
||||
48 => 'w', 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
|
||||
56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+', 63 => '/',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// I leave it here for future reference
|
||||
// foreach (str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
|
||||
// {
|
||||
// $this->charToIntMap[$char] = $i;
|
||||
// $this->intToCharMap[$i] = $char;
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from a two-complement value to a value where the sign bit is
|
||||
* is placed in the least significant bit. For example, as decimals:
|
||||
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
|
||||
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
|
||||
* We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
|
||||
* even on a 64 bit machine.
|
||||
*
|
||||
* @param string $aValue
|
||||
*/
|
||||
public function toVLQSigned($aValue)
|
||||
{
|
||||
return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert to a two-complement value from a value where the sign bit is
|
||||
* is placed in the least significant bit. For example, as decimals:
|
||||
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
|
||||
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
|
||||
* We assume that the value was generated with a 32 bit machine in mind.
|
||||
* Hence
|
||||
* 1 becomes -2147483648
|
||||
* even on a 64 bit machine.
|
||||
*
|
||||
* @param integer $aValue
|
||||
*/
|
||||
public function fromVLQSigned($aValue)
|
||||
{
|
||||
return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base 64 VLQ encoded value.
|
||||
*
|
||||
* @param string $aValue The value to encode
|
||||
*
|
||||
* @return string The encoded value
|
||||
*/
|
||||
public function encode($aValue)
|
||||
{
|
||||
$encoded = '';
|
||||
$vlq = $this->toVLQSigned($aValue);
|
||||
|
||||
do {
|
||||
$digit = $vlq & $this->mask;
|
||||
$vlq = $this->zeroFill($vlq, $this->shift);
|
||||
|
||||
if ($vlq > 0) {
|
||||
$digit |= $this->continuationBit;
|
||||
}
|
||||
|
||||
$encoded .= $this->base64Encode($digit);
|
||||
} while ($vlq > 0);
|
||||
|
||||
return $encoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value decoded from base 64 VLQ.
|
||||
*
|
||||
* @param string $encoded The encoded value to decode
|
||||
*
|
||||
* @return integer The decoded value
|
||||
*/
|
||||
public function decode($encoded)
|
||||
{
|
||||
$vlq = 0;
|
||||
$i = 0;
|
||||
|
||||
do {
|
||||
$digit = $this->base64Decode($encoded[$i]);
|
||||
$vlq |= ($digit & $this->mask) << ($i * $this->shift);
|
||||
$i++;
|
||||
} while ($digit & $this->continuationBit);
|
||||
|
||||
return $this->fromVLQSigned($vlq);
|
||||
}
|
||||
|
||||
/**
|
||||
* Right shift with zero fill.
|
||||
*
|
||||
* @param integer $a number to shift
|
||||
* @param integer $b number of bits to shift
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function zeroFill($a, $b)
|
||||
{
|
||||
return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode single 6-bit digit as base64.
|
||||
*
|
||||
* @param integer $number
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \Exception If the number is invalid
|
||||
*/
|
||||
public function base64Encode($number)
|
||||
{
|
||||
if ($number < 0 || $number > 63) {
|
||||
throw new \Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number));
|
||||
}
|
||||
|
||||
return $this->intToCharMap[$number];
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode single 6-bit digit from base64
|
||||
*
|
||||
* @param string $char
|
||||
*
|
||||
* @return integer
|
||||
*
|
||||
* @throws \Exception If the number is invalid
|
||||
*/
|
||||
public function base64Decode($char)
|
||||
{
|
||||
if (! array_key_exists($char, $this->charToIntMap)) {
|
||||
throw new \Exception(sprintf('Invalid base 64 digit "%s" given.', $char));
|
||||
}
|
||||
|
||||
return $this->charToIntMap[$char];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp\SourceMap;
|
||||
|
||||
use Leafo\ScssPhp\Exception\CompilerException;
|
||||
|
||||
/**
|
||||
* Source Map Generator
|
||||
*
|
||||
* {@internal Derivative of oyejorge/less.php's lib/SourceMap/Generator.php, relicensed with permission. }}
|
||||
*
|
||||
* @author Josh Schmidt <oyejorge@gmail.com>
|
||||
* @author Nicolas FRANÇOIS <nicolas.francois@frog-labs.com>
|
||||
*/
|
||||
class SourceMapGenerator
|
||||
{
|
||||
/**
|
||||
* What version of source map does the generator generate?
|
||||
*/
|
||||
const VERSION = 3;
|
||||
|
||||
/**
|
||||
* Array of default options
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultOptions = array(
|
||||
// an optional source root, useful for relocating source files
|
||||
// on a server or removing repeated values in the 'sources' entry.
|
||||
// This value is prepended to the individual entries in the 'source' field.
|
||||
'sourceRoot' => '',
|
||||
|
||||
// an optional name of the generated code that this source map is associated with.
|
||||
'sourceMapFilename' => null,
|
||||
|
||||
// url of the map
|
||||
'sourceMapURL' => null,
|
||||
|
||||
// absolute path to a file to write the map to
|
||||
'sourceMapWriteTo' => null,
|
||||
|
||||
// output source contents?
|
||||
'outputSourceFiles' => false,
|
||||
|
||||
// base path for filename normalization
|
||||
'sourceMapRootpath' => '',
|
||||
|
||||
// base path for filename normalization
|
||||
'sourceMapBasepath' => ''
|
||||
);
|
||||
|
||||
/**
|
||||
* The base64 VLQ encoder
|
||||
*
|
||||
* @var \Leafo\ScssPhp\SourceMap\Base64VLQEncoder
|
||||
*/
|
||||
protected $encoder;
|
||||
|
||||
/**
|
||||
* Array of mappings
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $mappings = array();
|
||||
|
||||
/**
|
||||
* Array of contents map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contentsMap = array();
|
||||
|
||||
/**
|
||||
* File to content map
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sources = array();
|
||||
protected $source_keys = array();
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $options;
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->options = array_merge($this->defaultOptions, $options);
|
||||
$this->encoder = new Base64VLQEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mapping
|
||||
*
|
||||
* @param integer $generatedLine The line number in generated file
|
||||
* @param integer $generatedColumn The column number in generated file
|
||||
* @param integer $originalLine The line number in original file
|
||||
* @param integer $originalColumn The column number in original file
|
||||
* @param string $sourceFile The original source file
|
||||
*/
|
||||
public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile)
|
||||
{
|
||||
$this->mappings[] = array(
|
||||
'generated_line' => $generatedLine,
|
||||
'generated_column' => $generatedColumn,
|
||||
'original_line' => $originalLine,
|
||||
'original_column' => $originalColumn,
|
||||
'source_file' => $sourceFile
|
||||
);
|
||||
|
||||
$this->sources[$sourceFile] = $sourceFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the source map to a file
|
||||
*
|
||||
* @param string $file The absolute path to a file
|
||||
* @param string $content The content to write
|
||||
*
|
||||
* @throws \Leafo\ScssPhp\Exception\CompilerException If the file could not be saved
|
||||
*/
|
||||
public function saveMap($content)
|
||||
{
|
||||
$file = $this->options['sourceMapWriteTo'];
|
||||
$dir = dirname($file);
|
||||
|
||||
// directory does not exist
|
||||
if (! is_dir($dir)) {
|
||||
// FIXME: create the dir automatically?
|
||||
throw new CompilerException(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir));
|
||||
}
|
||||
|
||||
// FIXME: proper saving, with dir write check!
|
||||
if (file_put_contents($file, $content) === false) {
|
||||
throw new CompilerException(sprintf('Cannot save the source map to "%s"', $file));
|
||||
}
|
||||
|
||||
return $this->options['sourceMapURL'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the JSON source map
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
|
||||
*/
|
||||
public function generateJson()
|
||||
{
|
||||
$sourceMap = array();
|
||||
$mappings = $this->generateMappings();
|
||||
|
||||
// File version (always the first entry in the object) and must be a positive integer.
|
||||
$sourceMap['version'] = self::VERSION;
|
||||
|
||||
// An optional name of the generated code that this source map is associated with.
|
||||
$file = $this->options['sourceMapFilename'];
|
||||
|
||||
if ($file) {
|
||||
$sourceMap['file'] = $file;
|
||||
}
|
||||
|
||||
// An optional source root, useful for relocating source files on a server or removing repeated values in the
|
||||
// 'sources' entry. This value is prepended to the individual entries in the 'source' field.
|
||||
$root = $this->options['sourceRoot'];
|
||||
|
||||
if ($root) {
|
||||
$sourceMap['sourceRoot'] = $root;
|
||||
}
|
||||
|
||||
// A list of original sources used by the 'mappings' entry.
|
||||
$sourceMap['sources'] = array();
|
||||
|
||||
foreach ($this->sources as $source_uri => $source_filename) {
|
||||
$sourceMap['sources'][] = $this->normalizeFilename($source_filename);
|
||||
}
|
||||
|
||||
// A list of symbol names used by the 'mappings' entry.
|
||||
$sourceMap['names'] = array();
|
||||
|
||||
// A string with the encoded mapping data.
|
||||
$sourceMap['mappings'] = $mappings;
|
||||
|
||||
if ($this->options['outputSourceFiles']) {
|
||||
// An optional list of source content, useful when the 'source' can't be hosted.
|
||||
// The contents are listed in the same order as the sources above.
|
||||
// 'null' may be used if some original sources should be retrieved by name.
|
||||
$sourceMap['sourcesContent'] = $this->getSourcesContent();
|
||||
}
|
||||
|
||||
// less.js compat fixes
|
||||
if (count($sourceMap['sources']) && empty($sourceMap['sourceRoot'])) {
|
||||
unset($sourceMap['sourceRoot']);
|
||||
}
|
||||
|
||||
return json_encode($sourceMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sources contents
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function getSourcesContent()
|
||||
{
|
||||
if (empty($this->sources)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = array();
|
||||
|
||||
foreach ($this->sources as $sourceFile) {
|
||||
$content[] = file_get_contents($sourceFile);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the mappings string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateMappings()
|
||||
{
|
||||
if (! count($this->mappings)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->source_keys = array_flip(array_keys($this->sources));
|
||||
|
||||
// group mappings by generated line number.
|
||||
$groupedMap = $groupedMapEncoded = array();
|
||||
|
||||
foreach ($this->mappings as $m) {
|
||||
$groupedMap[$m['generated_line']][] = $m;
|
||||
}
|
||||
|
||||
ksort($groupedMap);
|
||||
$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
|
||||
|
||||
foreach ($groupedMap as $lineNumber => $line_map) {
|
||||
while (++$lastGeneratedLine < $lineNumber) {
|
||||
$groupedMapEncoded[] = ';';
|
||||
}
|
||||
|
||||
$lineMapEncoded = array();
|
||||
$lastGeneratedColumn = 0;
|
||||
|
||||
foreach ($line_map as $m) {
|
||||
$mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn);
|
||||
$lastGeneratedColumn = $m['generated_column'];
|
||||
|
||||
// find the index
|
||||
if ($m['source_file']) {
|
||||
$index = $this->findFileIndex($m['source_file']);
|
||||
|
||||
if ($index !== false) {
|
||||
$mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex);
|
||||
$lastOriginalIndex = $index;
|
||||
// lines are stored 0-based in SourceMap spec version 3
|
||||
$mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine);
|
||||
$lastOriginalLine = $m['original_line'] - 1;
|
||||
$mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn);
|
||||
$lastOriginalColumn = $m['original_column'];
|
||||
}
|
||||
}
|
||||
|
||||
$lineMapEncoded[] = $mapEncoded;
|
||||
}
|
||||
|
||||
$groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';';
|
||||
}
|
||||
|
||||
return rtrim(implode($groupedMapEncoded), ';');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index for the filename
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return integer|false
|
||||
*/
|
||||
protected function findFileIndex($filename)
|
||||
{
|
||||
return $this->source_keys[$filename];
|
||||
}
|
||||
|
||||
protected function normalizeFilename($filename)
|
||||
{
|
||||
$filename = $this->fixWindowsPath($filename);
|
||||
$rootpath = $this->options['sourceMapRootpath'];
|
||||
$basePath = $this->options['sourceMapBasepath'];
|
||||
|
||||
// "Trim" the 'sourceMapBasepath' from the output filename.
|
||||
if (strpos($filename, $basePath) === 0) {
|
||||
$filename = substr($filename, strlen($basePath));
|
||||
}
|
||||
|
||||
// Remove extra leading path separators.
|
||||
if (strpos($filename, '\\') === 0 || strpos($filename, '/') === 0) {
|
||||
$filename = substr($filename, 1);
|
||||
}
|
||||
|
||||
return $rootpath . $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix windows paths
|
||||
*
|
||||
* @param string $path
|
||||
* @param boolean $addEndSlash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fixWindowsPath($path, $addEndSlash = false)
|
||||
{
|
||||
$slash = ($addEndSlash) ? '/' : '';
|
||||
|
||||
if (! empty($path)) {
|
||||
$path = str_replace('\\', '/', $path);
|
||||
$path = rtrim($path, '/') . $slash;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
/**
|
||||
* Block/node types
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class Type
|
||||
{
|
||||
const T_ASSIGN = 'assign';
|
||||
const T_AT_ROOT = 'at-root';
|
||||
const T_BLOCK = 'block';
|
||||
const T_BREAK = 'break';
|
||||
const T_CHARSET = 'charset';
|
||||
const T_COLOR = 'color';
|
||||
const T_COMMENT = 'comment';
|
||||
const T_CONTINUE = 'continue';
|
||||
const T_CONTROL = 'control';
|
||||
const T_DEBUG = 'debug';
|
||||
const T_DIRECTIVE = 'directive';
|
||||
const T_EACH = 'each';
|
||||
const T_ELSE = 'else';
|
||||
const T_ELSEIF = 'elseif';
|
||||
const T_ERROR = 'error';
|
||||
const T_EXPRESSION = 'exp';
|
||||
const T_EXTEND = 'extend';
|
||||
const T_FOR = 'for';
|
||||
const T_FUNCTION = 'function';
|
||||
const T_FUNCTION_CALL = 'fncall';
|
||||
const T_HSL = 'hsl';
|
||||
const T_IF = 'if';
|
||||
const T_IMPORT = 'import';
|
||||
const T_INCLUDE = 'include';
|
||||
const T_INTERPOLATE = 'interpolate';
|
||||
const T_INTERPOLATED = 'interpolated';
|
||||
const T_KEYWORD = 'keyword';
|
||||
const T_LIST = 'list';
|
||||
const T_MAP = 'map';
|
||||
const T_MEDIA = 'media';
|
||||
const T_MEDIA_EXPRESSION = 'mediaExp';
|
||||
const T_MEDIA_TYPE = 'mediaType';
|
||||
const T_MEDIA_VALUE = 'mediaValue';
|
||||
const T_MIXIN = 'mixin';
|
||||
const T_MIXIN_CONTENT = 'mixin_content';
|
||||
const T_NESTED_PROPERTY = 'nestedprop';
|
||||
const T_NOT = 'not';
|
||||
const T_NULL = 'null';
|
||||
const T_NUMBER = 'number';
|
||||
const T_RETURN = 'return';
|
||||
const T_ROOT = 'root';
|
||||
const T_SCSSPHP_IMPORT_ONCE = 'scssphp-import-once';
|
||||
const T_SELF = 'self';
|
||||
const T_STRING = 'string';
|
||||
const T_UNARY = 'unary';
|
||||
const T_VARIABLE = 'var';
|
||||
const T_WARN = 'warn';
|
||||
const T_WHILE = 'while';
|
||||
}
|
||||
Vendored
+70
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
use Leafo\ScssPhp\Base\Range;
|
||||
use Leafo\ScssPhp\Exception\RangeException;
|
||||
|
||||
/**
|
||||
* Utilty functions
|
||||
*
|
||||
* @author Anthon Pang <anthon.pang@gmail.com>
|
||||
*/
|
||||
class Util
|
||||
{
|
||||
/**
|
||||
* Asserts that `value` falls within `range` (inclusive), leaving
|
||||
* room for slight floating-point errors.
|
||||
*
|
||||
* @param string $name The name of the value. Used in the error message.
|
||||
* @param \Leafo\ScssPhp\Base\Range $range Range of values.
|
||||
* @param array $value The value to check.
|
||||
* @param string $unit The unit of the value. Used in error reporting.
|
||||
*
|
||||
* @return mixed `value` adjusted to fall within range, if it was outside by a floating-point margin.
|
||||
*
|
||||
* @throws \Leafo\ScssPhp\Exception\RangeException
|
||||
*/
|
||||
public static function checkRange($name, Range $range, $value, $unit = '')
|
||||
{
|
||||
$val = $value[1];
|
||||
$grace = new Range(-0.00001, 0.00001);
|
||||
|
||||
if ($range->includes($val)) {
|
||||
return $val;
|
||||
}
|
||||
|
||||
if ($grace->includes($val - $range->first)) {
|
||||
return $range->first;
|
||||
}
|
||||
|
||||
if ($grace->includes($val - $range->last)) {
|
||||
return $range->last;
|
||||
}
|
||||
|
||||
throw new RangeException("$name {$val} must be between {$range->first} and {$range->last}$unit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode URI component
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function encodeURIComponent($string)
|
||||
{
|
||||
$revert = array('%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')');
|
||||
|
||||
return strtr(rawurlencode($string), $revert);
|
||||
}
|
||||
}
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* SCSSPHP
|
||||
*
|
||||
* @copyright 2012-2018 Leaf Corcoran
|
||||
*
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*
|
||||
* @link http://leafo.github.io/scssphp
|
||||
*/
|
||||
|
||||
namespace Leafo\ScssPhp;
|
||||
|
||||
/**
|
||||
* SCSSPHP version
|
||||
*
|
||||
* @author Leaf Corcoran <leafot@gmail.com>
|
||||
*/
|
||||
class Version
|
||||
{
|
||||
const VERSION = 'v0.7.5';
|
||||
}
|
||||
Reference in New Issue
Block a user