N°2435.2 Manage SCSSPHP lib using composer

This commit is contained in:
Molkobain
2019-08-13 10:50:54 +02:00
parent 5960dc6245
commit bb4c8ea52d
43 changed files with 762 additions and 2 deletions

View File

@@ -0,0 +1,20 @@
Copyright (c) 2015 Leaf Corcoran, http://scssphp.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.

View File

@@ -0,0 +1,47 @@
# scssphp
### <http://scssphp.github.io/scssphp>
[![Build](https://travis-ci.org/scssphp/scssphp.svg?branch=master)](http://travis-ci.org/scssphp/scssphp)
[![License](https://poser.pugx.org/scssphp/scssphp/license.svg)](https://packagist.org/packages/scssphp/scssphp)
`scssphp` is a compiler for SCSS written in PHP.
Checkout the homepage, <http://scssphp.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

View File

@@ -0,0 +1,215 @@
#!/usr/bin/env php
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.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 ScssPhp\ScssPhp\Compiler;
use ScssPhp\ScssPhp\Parser;
use ScssPhp\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('ScssPhp\\ScssPhp\\Formatter\\' . ucfirst($style));
}
if ($sourceMap) {
$scss->setSourceMap(Compiler::SOURCE_MAP_INLINE);
}
if ($encoding) {
$scss->setEncoding($encoding);
}
echo $scss->compile($data, $inputFile);
if ($changeDir) {
chdir($oldWorkingDir);
}

View File

@@ -0,0 +1,48 @@
{
"name": "scssphp/scssphp",
"type": "library",
"description": "scssphp is a compiler for SCSS written in PHP.",
"keywords": ["css", "stylesheet", "scss", "sass", "less"],
"homepage": "http://scssphp.github.io/scssphp/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anthon Pang",
"email": "apang@softwaredevelopment.ca",
"homepage": "https://github.com/robocoder"
},
{
"name": "Cédric Morin",
"email": "cedric@yterium.com",
"homepage": "https://github.com/Cerdic"
}
],
"autoload": {
"psr-4": { "ScssPhp\\ScssPhp\\": "src/" }
},
"autoload-dev": {
"psr-4": { "ScssPhp\\ScssPhp\\Test\\": "tests/" }
},
"require": {
"php": "^5.6.0 || ^7"
},
"require-dev": {
"squizlabs/php_codesniffer": "~2.5",
"phpunit/phpunit": "~4.6",
"twbs/bootstrap": "~4.3",
"zurb/foundation": "~6.5"
},
"bin": ["bin/pscss"],
"archive": {
"exclude": [
"/Makefile",
"/.gitattributes",
"/.gitignore",
"/.travis.yml",
"/phpunit.xml.dist",
"/tests"
]
}
}

View File

@@ -0,0 +1,515 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp;
use ScssPhp\ScssPhp\Compiler;
use ScssPhp\ScssPhp\Exception\ServerException;
use ScssPhp\ScssPhp\Version;
/**
* Server
*
* @author Leaf Corcoran <leafot@gmail.com>
*/
class Server
{
/**
* @var boolean
*/
private $showErrorsAsCSS;
/**
* @var string
*/
private $dir;
/**
* @var string
*/
private $cacheDir;
/**
* @var \ScssPhp\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 = gmdate('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 \ScssPhp\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('r', filemtime($output));
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('r', $mtime);
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 \ScssPhp\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 = [];
$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 \ScssPhp\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;
date_default_timezone_set('UTC');
}
}

View File

@@ -0,0 +1,34 @@
<?php
if (version_compare(PHP_VERSION, '5.6') < 0) {
throw new \Exception('scssphp requires PHP 5.6 or above');
}
if (! class_exists('ScssPhp\ScssPhp\Version', false)) {
include_once __DIR__ . '/src/Base/Range.php';
include_once __DIR__ . '/src/Block.php';
include_once __DIR__ . '/src/Cache.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/Base64.php';
include_once __DIR__ . '/src/SourceMap/Base64VLQ.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';
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* SCSSPHP
*
* @copyright 2015-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\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;
}
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp;
/**
* Block
*
* @author Anthon Pang <anthon.pang@gmail.com>
*/
class Block
{
/**
* @var string
*/
public $type;
/**
* @var \ScssPhp\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;
/**
* @var \ScssPhp\ScssPhp\Block
*/
public $selfParent;
}

View File

@@ -0,0 +1,239 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp;
use Exception;
/**
* The scss cache manager.
*
* In short:
*
* allow to put in cache/get from cache a generic result from a known operation on a generic dataset,
* taking in account options that affects the result
*
* The cache manager is agnostic about data format and only the operation is expected to be described by string
*
*/
/**
* SCSS cache
*
* @author Cedric Morin
*/
class Cache
{
const CACHE_VERSION = 0;
// directory used for storing data
public static $cacheDir = false;
// prefix for the storing data
public static $prefix = 'scssphp_';
// force a refresh : 'once' for refreshing the first hit on a cache only, true to never use the cache in this hit
public static $forceFefresh = false;
// specifies the number of seconds after which data cached will be seen as 'garbage' and potentially cleaned up
public static $gcLifetime = 604800;
// array of already refreshed cache if $forceFefresh==='once'
protected static $refreshed = [];
/**
* Constructor
*
* @param array $options
*/
public function __construct($options)
{
// check $cacheDir
if (isset($options['cache_dir'])) {
self::$cacheDir = $options['cache_dir'];
}
if (empty(self::$cacheDir)) {
throw new Exception('cache_dir not set');
}
if (isset($options['prefix'])) {
self::$prefix = $options['prefix'];
}
if (empty(self::$prefix)) {
throw new Exception('prefix not set');
}
if (isset($options['forceRefresh'])) {
self::$forceFefresh = $options['force_refresh'];
}
self::checkCacheDir();
}
/**
* Get the cached result of $operation on $what,
* which is known as dependant from the content of $options
*
* @param string $operation parse, compile...
* @param mixed $what content key (e.g., filename to be treated)
* @param array $options any option that affect the operation result on the content
* @param integer $lastModified last modified timestamp
*
* @return mixed
*
* @throws \Exception
*/
public function getCache($operation, $what, $options = [], $lastModified = null)
{
$fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
if ((! self::$forceRefresh || (self::$forceRefresh === 'once' && isset(self::$refreshed[$fileCache])))
&& file_exists($fileCache)
) {
$cacheTime = filemtime($fileCache);
if ((is_null($lastModified) || $cacheTime > $lastModified)
&& $cacheTime + self::$gcLifetime > time()
) {
$c = file_get_contents($fileCache);
$c = unserialize($c);
if (is_array($c) && isset($c['value'])) {
return $c['value'];
}
}
}
return null;
}
/**
* Put in cache the result of $operation on $what,
* which is known as dependant from the content of $options
*
* @param string $operation
* @param mixed $what
* @param mixed $value
* @param array $options
*/
public function setCache($operation, $what, $value, $options = [])
{
$fileCache = self::$cacheDir . self::cacheName($operation, $what, $options);
$c = ['value' => $value];
$c = serialize($c);
file_put_contents($fileCache, $c);
if (self::$forceRefresh === 'once') {
self::$refreshed[$fileCache] = true;
}
}
/**
* Get the cache name for the caching of $operation on $what,
* which is known as dependant from the content of $options
*
* @param string $operation
* @param mixed $what
* @param array $options
*
* @return string
*/
private static function cacheName($operation, $what, $options = [])
{
$t = [
'version' => self::CACHE_VERSION,
'operation' => $operation,
'what' => $what,
'options' => $options
];
$t = self::$prefix
. sha1(json_encode($t))
. ".$operation"
. ".scsscache";
return $t;
}
/**
* Check that the cache dir exists and is writeable
*
* @throws \Exception
*/
public static function checkCacheDir()
{
self::$cacheDir = str_replace('\\', '/', self::$cacheDir);
self::$cacheDir = rtrim(self::$cacheDir, '/') . '/';
if (! file_exists(self::$cacheDir)) {
if (! mkdir(self::$cacheDir)) {
throw new Exception('Cache directory couldn\'t be created: ' . self::$cacheDir);
}
} elseif (! is_dir(self::$cacheDir)) {
throw new Exception('Cache directory doesn\'t exist: ' . self::$cacheDir);
} elseif (! is_writable(self::$cacheDir)) {
throw new Exception('Cache directory isn\'t writable: ' . self::$cacheDir);
}
}
/**
* Delete unused cached files
*/
public static function cleanCache()
{
static $clean = false;
if ($clean || empty(self::$cacheDir)) {
return;
}
$clean = true;
// only remove files with extensions created by SCSSPHP Cache
// css files removed based on the list files
$removeTypes = ['scsscache' => 1];
$files = scandir(self::$cacheDir);
if (! $files) {
return;
}
$checkTime = time() - self::$gcLifetime;
foreach ($files as $file) {
// don't delete if the file wasn't created with SCSSPHP Cache
if (strpos($file, self::$prefix) !== 0) {
continue;
}
$parts = explode('.', $file);
$type = array_pop($parts);
if (! isset($removeTypes[$type])) {
continue;
}
$fullPath = self::$cacheDir . $file;
$mtime = filemtime($fullPath);
// don't delete if it's a relatively new file
if ($mtime > $checkTime) {
continue;
}
unlink($fullPath);
}
}
}

View File

@@ -0,0 +1,179 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\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',
];
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,45 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Compiler;
/**
* Compiler environment
*
* @author Anthon Pang <anthon.pang@gmail.com>
*/
class Environment
{
/**
* @var \ScssPhp\ScssPhp\Block
*/
public $block;
/**
* @var \ScssPhp\ScssPhp\Compiler\Environment
*/
public $parent;
/**
* @var array
*/
public $store;
/**
* @var array
*/
public $storeUnreduced;
/**
* @var integer
*/
public $depth;
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Exception;
/**
* Compiler exception
*
* @author Oleksandr Savchenko <traveltino@gmail.com>
*/
class CompilerException extends \Exception
{
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Exception;
/**
* Parser Exception
*
* @author Oleksandr Savchenko <traveltino@gmail.com>
*/
class ParserException extends \Exception
{
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Exception;
/**
* Range exception
*
* @author Anthon Pang <anthon.pang@gmail.com>
*/
class RangeException extends \Exception
{
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Exception;
/**
* Server Exception
*
* @author Anthon Pang <anthon.pang@gmail.com>
*/
class ServerException extends \Exception
{
}

View File

@@ -0,0 +1,275 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp;
use ScssPhp\ScssPhp\Formatter\OutputBlock;
use ScssPhp\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 \ScssPhp\ScssPhp\Formatter\OutputBlock
*/
protected $currentBlock;
/**
* @var integer
*/
protected $currentLine;
/**
* @var integer
*/
protected $currentColumn;
/**
* @var \ScssPhp\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 \ScssPhp\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 \ScssPhp\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 \ScssPhp\ScssPhp\Formatter\OutputBlock $block
*/
protected function blockChildren(OutputBlock $block)
{
foreach ($block->children as $child) {
$this->block($child);
}
}
/**
* Output non-empty block
*
* @param \ScssPhp\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 \ScssPhp\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree
* @param \ScssPhp\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,
//columns from parser are off by one
$this->currentBlock->sourceColumn > 0 ? $this->currentBlock->sourceColumn - 1 : 0,
$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;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Formatter;
use ScssPhp\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 ' ';
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Formatter;
use ScssPhp\ScssPhp\Formatter;
use ScssPhp\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);
}
}
/**
* Output block selectors
*
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
*/
protected function blockSelectors(OutputBlock $block)
{
$inner = $this->indentStr();
$this->write(
$inner
. implode(
$this->tagSeparator,
str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors)
)
. $this->open . $this->break
);
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Formatter;
use ScssPhp\ScssPhp\Formatter;
use ScssPhp\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);
}
}
/**
* Output block selectors
*
* @param \ScssPhp\ScssPhp\Formatter\OutputBlock $block
*/
protected function blockSelectors(OutputBlock $block)
{
$inner = $this->indentStr();
$this->write(
$inner
. implode(
$this->tagSeparator,
str_replace([' > ', ' + ', ' ~ '], ['>', '+', '~'], $block->selectors)
)
. $this->open . $this->break
);
}
}

View File

@@ -0,0 +1,121 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Formatter;
use ScssPhp\ScssPhp\Formatter;
use ScssPhp\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);
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Formatter;
use ScssPhp\ScssPhp\Formatter;
use ScssPhp\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);
}
}
}

View File

@@ -0,0 +1,201 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Formatter;
use ScssPhp\ScssPhp\Formatter;
use ScssPhp\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 \ScssPhp\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;
}
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\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 \ScssPhp\ScssPhp\Formatter\OutputBlock
*/
public $parent;
/**
* @var string
*/
public $sourceName;
/**
* @var integer
*/
public $sourceLine;
/**
* @var integer
*/
public $sourceColumn;
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\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;
}

View File

@@ -0,0 +1,330 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Node;
use ScssPhp\ScssPhp\Compiler;
use ScssPhp\ScssPhp\Node;
use ScssPhp\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 \ScssPhp\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 \ScssPhp\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 \ScssPhp\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);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\SourceMap;
/**
* Base 64 Encode/Decode
*
* @author Anthon Pang <anthon.pang@gmail.com>
*/
class Base64
{
/**
* @var array
*/
private static $encodingMap = [
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 => '/',
];
/**
* @var array
*/
private static $decodingMap = [
'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,
];
/**
* Convert to base64
*
* @param integer $value
*
* @return string
*/
public static function encode($value)
{
return self::$encodingMap[$value];
}
/**
* Convert from base64
*
* @param string $value
*
* @return integer
*/
public static function decode($value)
{
return self::$decodingMap[$value];
}
}

View File

@@ -0,0 +1,137 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\SourceMap;
use ScssPhp\ScssPhp\SourceMap\Base64;
/**
* Base 64 VLQ
*
* Based on the Base 64 VLQ implementation in Closure Compiler:
* https://github.com/google/closure-compiler/blob/master/src/com/google/debugging/sourcemap/Base64VLQ.java
*
* Copyright 2011 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author John Lenz <johnlenz@google.com>
* @author Anthon Pang <anthon.pang@gmail.com>
*/
class Base64VLQ
{
// A Base64 VLQ digit can represent 5 bits, so it is base-32.
const VLQ_BASE_SHIFT = 5;
// A mask of bits for a VLQ digit (11111), 31 decimal.
const VLQ_BASE_MASK = 31;
// The continuation bit is the 6th bit.
const VLQ_CONTINUATION_BIT = 32;
/**
* Returns the VLQ encoded value.
*
* @param integer $value
*
* @return string
*/
public static function encode($value)
{
$encoded = '';
$vlq = self::toVLQSigned($value);
do {
$digit = $vlq & self::VLQ_BASE_MASK;
$vlq >>= self::VLQ_BASE_SHIFT;
if ($vlq > 0) {
$digit |= self::VLQ_CONTINUATION_BIT;
}
$encoded .= Base64::encode($digit);
} while ($vlq > 0);
return $encoded;
}
/**
* Decodes VLQValue.
*
* @param string $str
* @param integer $index
*
* @return integer
*/
public static function decode($str, &$index)
{
$result = 0;
$shift = 0;
do {
$c = $str[$index++];
$digit = Base64::decode($c);
$continuation = ($digit & self::VLQ_CONTINUATION_BIT) != 0;
$digit &= self::VLQ_BASE_MASK;
$result = $result + ($digit << $shift);
$shift = $shift + self::VLQ_BASE_SHIFT;
} while ($continuation);
return self::fromVLQSigned($result);
}
/**
* Converts 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)
*
* @param integer $value
*
* @return integer
*/
private static function toVLQSigned($value)
{
if ($value < 0) {
return ((-$value) << 1) + 1;
}
return ($value << 1) + 0;
}
/**
* Converts 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
*
* @param integer $value
*
* @return integer
*/
private static function fromVLQSigned($value)
{
$negate = ($value & 1) === 1;
$value = $value >> 1;
return $negate ? -$value : $value;
}
}

View File

@@ -0,0 +1,217 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\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 = [
'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 = [
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];
}
}

View File

@@ -0,0 +1,347 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\SourceMap;
use ScssPhp\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 = [
// 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 \ScssPhp\ScssPhp\SourceMap\Base64VLQ
*/
protected $encoder;
/**
* Array of mappings
*
* @var array
*/
protected $mappings = [];
/**
* Array of contents map
*
* @var array
*/
protected $contentsMap = [];
/**
* File to content map
*
* @var array
*/
protected $sources = [];
protected $sourceKeys = [];
/**
* @var array
*/
private $options;
public function __construct(array $options = [])
{
$this->options = array_merge($this->defaultOptions, $options);
$this->encoder = new Base64VLQ();
}
/**
* 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[] = [
'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 $content The content to write
*
* @return string
*
* @throws \ScssPhp\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 = [];
$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'] = [];
foreach ($this->sources as $sourceUri => $sourceFilename) {
$sourceMap['sources'][] = $this->normalizeFilename($sourceFilename);
}
// A list of symbol names used by the 'mappings' entry.
$sourceMap['names'] = [];
// 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, JSON_UNESCAPED_SLASHES);
}
/**
* Returns the sources contents
*
* @return array|null
*/
protected function getSourcesContent()
{
if (empty($this->sources)) {
return null;
}
$content = [];
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->sourceKeys = array_flip(array_keys($this->sources));
// group mappings by generated line number.
$groupedMap = $groupedMapEncoded = [];
foreach ($this->mappings as $m) {
$groupedMap[$m['generated_line']][] = $m;
}
ksort($groupedMap);
$lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
foreach ($groupedMap as $lineNumber => $lineMap) {
while (++$lastGeneratedLine < $lineNumber) {
$groupedMapEncoded[] = ';';
}
$lineMapEncoded = [];
$lastGeneratedColumn = 0;
foreach ($lineMap 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->sourceKeys[$filename];
}
/**
* Normalize filename
*
* @param string $filename
*
* @return string
*/
protected function normalizeFilename($filename)
{
$filename = $this->fixWindowsPath($filename);
$rootpath = $this->options['sourceMapRootpath'];
$basePath = $this->options['sourceMapBasepath'];
// "Trim" the 'sourceMapBasepath' from the output filename.
if (strlen($basePath) && 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;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\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';
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp;
use ScssPhp\ScssPhp\Base\Range;
use ScssPhp\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 \ScssPhp\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 \ScssPhp\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 = ['%21' => '!', '%2A' => '*', '%27' => "'", '%28' => '(', '%29' => ')'];
return strtr(rawurlencode($string), $revert);
}
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* SCSSPHP
*
* @copyright 2012-2019 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp;
/**
* SCSSPHP version
*
* @author Leaf Corcoran <leafot@gmail.com>
*/
class Version
{
const VERSION = 'v1.0.0';
}