/**
* SCSSPHP
*
- * @copyright 2017 Leaf Corcoran
+ * @copyright 2015-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
public $parent;
+ /**
+ * @var string
+ */
+ public $sourceName;
+
/**
* @var integer
*/
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
use Leafo\ScssPhp\Exception\CompilerException;
use Leafo\ScssPhp\Formatter\OutputBlock;
use Leafo\ScssPhp\Node;
+use Leafo\ScssPhp\SourceMap\SourceMapGenerator;
use Leafo\ScssPhp\Type;
use Leafo\ScssPhp\Parser;
use Leafo\ScssPhp\Util;
const WITH_SUPPORTS = 4;
const WITH_ALL = 7;
+ const SOURCE_MAP_NONE = 0;
+ const SOURCE_MAP_INLINE = 1;
+ const SOURCE_MAP_FILE = 2;
+
/**
* @var array
*/
protected $encoding = null;
protected $lineNumberStyle = null;
+ protected $sourceMap = self::SOURCE_MAP_NONE;
+ protected $sourceMapOptions = [];
+
+ /**
+ * @var string|\Leafo\ScssPhp\Formatter
+ */
protected $formatter = 'Leafo\ScssPhp\Formatter\Nested';
protected $rootEnv;
protected $rootBlock;
+ /**
+ * @var \Leafo\ScssPhp\Compiler\Environment
+ */
protected $env;
protected $scope;
protected $storeEnv;
*/
public function compile($code, $path = null)
{
- $locale = setlocale(LC_NUMERIC, 0);
- setlocale(LC_NUMERIC, 'C');
-
$this->indentLevel = -1;
$this->commentsSeen = [];
$this->extends = [];
$this->compileRoot($tree);
$this->popEnv();
- $out = $this->formatter->format($this->scope);
+ $sourceMapGenerator = null;
+
+ if ($this->sourceMap) {
+ if (is_object($this->sourceMap) && $this->sourceMap instanceof SourceMapGenerator) {
+ $sourceMapGenerator = $this->sourceMap;
+ $this->sourceMap = self::SOURCE_MAP_FILE;
+ } elseif ($this->sourceMap !== self::SOURCE_MAP_NONE) {
+ $sourceMapGenerator = new SourceMapGenerator($this->sourceMapOptions);
+ }
+ }
+
+ $out = $this->formatter->format($this->scope, $sourceMapGenerator);
+
+ if (! empty($out) && $this->sourceMap && $this->sourceMap !== self::SOURCE_MAP_NONE) {
+ $sourceMap = $sourceMapGenerator->generateJson();
+ $sourceMapUrl = null;
+
+ switch ($this->sourceMap) {
+ case self::SOURCE_MAP_INLINE:
+ $sourceMapUrl = sprintf('data:application/json,%s', Util::encodeURIComponent($sourceMap));
+ break;
+
+ case self::SOURCE_MAP_FILE:
+ $sourceMapUrl = $sourceMapGenerator->saveMap($sourceMap);
+ break;
+ }
- setlocale(LC_NUMERIC, $locale);
+ $out .= sprintf('/*# sourceMappingURL=%s */', $sourceMapUrl);
+ }
return $out;
}
protected function makeOutputBlock($type, $selectors = null)
{
$out = new OutputBlock;
- $out->type = $type;
- $out->lines = [];
- $out->children = [];
- $out->parent = $this->scope;
- $out->selectors = $selectors;
- $out->depth = $this->env->depth;
+ $out->type = $type;
+ $out->lines = [];
+ $out->children = [];
+ $out->parent = $this->scope;
+ $out->selectors = $selectors;
+ $out->depth = $this->env->depth;
+ $out->sourceName = $this->env->block->sourceName;
+ $out->sourceLine = $this->env->block->sourceLine;
+ $out->sourceColumn = $this->env->block->sourceColumn;
return $out;
}
if ($needsWrap) {
$wrapped = new Block;
+ $wrapped->sourceName = $media->sourceName;
$wrapped->sourceIndex = $media->sourceIndex;
$wrapped->sourceLine = $media->sourceLine;
$wrapped->sourceColumn = $media->sourceColumn;
// wrap inline selector
if ($block->selector) {
$wrapped = new Block;
+ $wrapped->sourceName = $block->sourceName;
$wrapped->sourceIndex = $block->sourceIndex;
$wrapped->sourceLine = $block->sourceLine;
$wrapped->sourceColumn = $block->sourceColumn;
}
$b = new Block;
+ $b->sourceName = $e->block->sourceName;
$b->sourceIndex = $e->block->sourceIndex;
$b->sourceLine = $e->block->sourceLine;
$b->sourceColumn = $e->block->sourceColumn;
/**
* Compile selector to string; self(&) should have been replaced by now
*
- * @param array $selector
+ * @param string|array $selector
*
* @return string
*/
/**
* Compile selector part
*
- * @param arary $piece
+ * @param array $piece
*
* @return string
*/
list(, $for) = $child;
$start = $this->reduce($for->start, true);
+ $end = $this->reduce($for->end, true);
+
+ if (! ($start[2] == $end[2] || $end->unitless())) {
+ $this->throwError('Incompatible units: "%s" and "%s".', $start->unitStr(), $end->unitStr());
+
+ break;
+ }
+
+ $unit = $start[2];
$start = $start[1];
- $end = $this->reduce($for->end, true);
- $end = $end[1];
+ $end = $end[1];
+
$d = $start < $end ? 1 : -1;
for (;;) {
break;
}
- $this->set($for->var, new Node\Number($start, ''));
+ $this->set($for->var, new Node\Number($start, $unit));
$start += $d;
$ret = $this->compileChildren($for->children, $out);
*
* @param string $value
*
- * @return bool
+ * @return boolean
*/
protected function isImmediateRelationshipCombinator($value)
{
* @param array $value
* @param boolean $inExp
*
- * @return array
+ * @return array|\Leafo\ScssPhp\Node\Number
*/
protected function reduce($value, $inExp = false)
{
* @param array $left
* @param array $right
*
- * @return array
+ * @return \Leafo\ScssPhp\Node\Number
*/
protected function opAddNumberNumber($left, $right)
{
* @param array $left
* @param array $right
*
- * @return array
+ * @return \Leafo\ScssPhp\Node\Number
*/
protected function opMulNumberNumber($left, $right)
{
* @param array $left
* @param array $right
*
- * @return array
+ * @return \Leafo\ScssPhp\Node\Number
*/
protected function opSubNumberNumber($left, $right)
{
* @param array $left
* @param array $right
*
- * @return array
+ * @return array|\Leafo\ScssPhp\Node\Number
*/
protected function opDivNumberNumber($left, $right)
{
* @param array $left
* @param array $right
*
- * @return array
+ * @return \Leafo\ScssPhp\Node\Number
*/
protected function opModNumberNumber($left, $right)
{
* @param array $left
* @param array $right
*
- * @return array
+ * @return \Leafo\ScssPhp\Node\Number
*/
protected function opCmpNumberNumber($left, $right)
{
$this->lineNumberStyle = $lineNumberStyle;
}
+ /**
+ * Enable/disable source maps
+ *
+ * @api
+ *
+ * @param integer $sourceMap
+ */
+ public function setSourceMap($sourceMap)
+ {
+ $this->sourceMap = $sourceMap;
+ }
+
+ /**
+ * Set source map options
+ *
+ * @api
+ *
+ * @param array $sourceMapOptions
+ */
+ public function setSourceMapOptions($sourceMapOptions)
+ {
+ $this->sourceMapOptions = $sourceMapOptions;
+ }
+
/**
* Register function
*
* Call SCSS @function
*
* @param string $name
- * @param array $args
+ * @param array $argValues
* @param array $returnValue
*
* @return boolean Returns true if returnValue is set; otherwise, false
*
* @param mixed $value
*
- * @return array
+ * @return array|\Leafo\ScssPhp\Node\Number
*/
private function coerceValue($value)
{
/**
* Coerce something to list
*
- * @param array $item
+ * @param array $item
+ * @param string $delim
*
* @return array
*/
protected function libRound($args)
{
$num = $args[0];
- $num[1] = round($num[1]);
- return $num;
+ return new Node\Number(round($num[1]), $num[2]);
}
protected static $libFloor = ['value'];
protected function libFloor($args)
{
$num = $args[0];
- $num[1] = floor($num[1]);
- return $num;
+ return new Node\Number(floor($num[1]), $num[2]);
}
protected static $libCeil = ['value'];
protected function libCeil($args)
{
$num = $args[0];
- $num[1] = ceil($num[1]);
- return $num;
+ return new Node\Number(ceil($num[1]), $num[2]);
}
protected static $libAbs = ['value'];
protected function libAbs($args)
{
$num = $args[0];
- $num[1] = abs($num[1]);
- return $num;
+ return new Node\Number(abs($num[1]), $num[2]);
}
protected function libMin($args)
$string = $this->coerceString($args[0]);
$stringContent = $this->compileStringContent($string);
- $string[2] = [mb_strtolower($stringContent)];
+ $string[2] = [function_exists('mb_strtolower') ? mb_strtolower($stringContent) : strtolower($stringContent)];
return $string;
}
$string = $this->coerceString($args[0]);
$stringContent = $this->compileStringContent($string);
- $string[2] = [mb_strtoupper($stringContent)];
+ $string[2] = [function_exists('mb_strtoupper') ? mb_strtoupper($stringContent) : strtoupper($stringContent)];
return $string;
}
* Workaround IE7's content counter bug.
*
* @param array $args
+ *
+ * @return array
*/
protected function libCounter($args)
{
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
namespace Leafo\ScssPhp;
use Leafo\ScssPhp\Formatter\OutputBlock;
+use Leafo\ScssPhp\SourceMap\SourceMapGenerator;
/**
* Base formatter
public $assignSeparator;
/**
- * @var boolea
+ * @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
*
$glue = $this->break . $inner;
- echo $inner . implode($glue, $block->lines);
+ $this->write($inner . implode($glue, $block->lines));
if (! empty($block->children)) {
- echo $this->break;
+ $this->write($this->break);
}
}
{
$inner = $this->indentStr();
- echo $inner
+ $this->write($inner
. implode($this->tagSeparator, $block->selectors)
- . $this->open . $this->break;
+ . $this->open . $this->break);
}
/**
return;
}
+ $this->currentBlock = $block;
+
$pre = $this->indentStr();
if (! empty($block->selectors)) {
$this->indentLevel--;
if (empty($block->children)) {
- echo $this->break;
+ $this->write($this->break);
}
- echo $pre . $this->close . $this->break;
+ $this->write($pre . $this->close . $this->break);
}
}
*
* @api
*
- * @param \Leafo\ScssPhp\Formatter\OutputBlock $block An abstract syntax tree
+ * @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)
+ 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);
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;
+ }
}
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
}
}
- echo $inner . implode($glue, $block->lines);
+ $this->write($inner . implode($glue, $block->lines));
if (! empty($block->children)) {
- echo $this->break;
+ $this->write($this->break);
}
}
}
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
}
}
- echo $inner . implode($glue, $block->lines);
+ $this->write($inner . implode($glue, $block->lines));
if (! empty($block->children)) {
- echo $this->break;
+ $this->write($this->break);
}
}
}
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
$indent = $this->indentStr();
if (empty($block->lines)) {
- echo "{$indent}block->lines: []\n";
+ $this->write("{$indent}block->lines: []\n");
return;
}
foreach ($block->lines as $index => $line) {
- echo "{$indent}block->lines[{$index}]: $line\n";
+ $this->write("{$indent}block->lines[{$index}]: $line\n");
}
}
$indent = $this->indentStr();
if (empty($block->selectors)) {
- echo "{$indent}block->selectors: []\n";
+ $this->write("{$indent}block->selectors: []\n");
return;
}
foreach ($block->selectors as $index => $selector) {
- echo "{$indent}block->selectors[{$index}]: $selector\n";
+ $this->write("{$indent}block->selectors[{$index}]: $selector\n");
}
}
$indent = $this->indentStr();
if (empty($block->children)) {
- echo "{$indent}block->children: []\n";
+ $this->write("{$indent}block->children: []\n");
return;
}
{
$indent = $this->indentStr();
- echo "{$indent}block->type: {$block->type}\n" .
- "{$indent}block->depth: {$block->depth}\n";
+ $this->write("{$indent}block->type: {$block->type}\n" .
+ "{$indent}block->depth: {$block->depth}\n");
+
+ $this->currentBlock = $block;
$this->blockSelectors($block);
$this->blockLines($block);
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
}
}
- echo $inner . implode($glue, $block->lines);
+ $this->write($inner . implode($glue, $block->lines));
if (empty($block->selectors) || ! empty($block->children)) {
- echo $this->break;
+ $this->write($this->break);
}
}
}
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
}
}
- echo $inner . implode($glue, $block->lines);
+ $this->write($inner . implode($glue, $block->lines));
if (! empty($block->children)) {
- echo $this->break;
+ $this->write($this->break);
}
}
{
$inner = $this->indentStr();
- echo $inner
+ $this->write($inner
. implode($this->tagSeparator, $block->selectors)
- . $this->open . $this->break;
+ . $this->open . $this->break);
}
/**
$this->block($child);
if ($i < count($block->children) - 1) {
- echo $this->break;
+ $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) {
- echo $this->break;
+ $this->write($this->break);
}
}
}
return;
}
+ $this->currentBlock = $block;
+
+
$this->depth = $block->depth;
if (! empty($block->selectors)) {
if (! empty($block->selectors)) {
$this->indentLevel--;
- echo $this->close;
+ $this->write($this->close);
}
if ($block->type === 'root') {
- echo $this->break;
+ $this->write($this->break);
}
}
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @var \Leafo\ScssPhp\Formatter\OutputBlock
*/
public $parent;
+
+ /**
+ * @var string
+ */
+ public $sourceName;
+
+ /**
+ * @var integer
+ */
+ public $sourceLine;
+
+ /**
+ * @var integer
+ */
+ public $sourceColumn;
}
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* @var integer
*/
- static public $precision = 5;
+ static public $precision = 10;
/**
* @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
reset($units);
$unit = key($units);
+ $dimension = number_format($dimension, static::$precision, '.', '');
- return (string) $dimension . $unit;
+ return (static::$precision ? rtrim(rtrim($dimension, '0'), '.') : $dimension) . $unit;
}
/**
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
list($line, $column) = $this->getSourcePosition($pos);
$b = new Block;
+ $b->sourceName = $this->sourceName;
$b->sourceLine = $line;
$b->sourceColumn = $column;
$b->sourceIndex = $this->sourceIndex;
*/
private function saveEncoding()
{
- if (ini_get('mbstring.func_overload') & 2) {
+ 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');
+++ /dev/null
-<?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;
- }
-
- /**
- * 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)) {
- mkdir($this->cacheDir, 0755, true);
- }
-
- if (! isset($scss)) {
- $scss = new Compiler();
- $scss->setImportPaths($this->dir);
- }
-
- $this->scss = $scss;
- $this->showErrorsAsCSS = false;
-
- if (! ini_get('date.timezone')) {
- date_default_timezone_set('UTC');
- }
- }
-
- /**
- * Helper method to serve compiled scss
- *
- * @param string $path Root path
- */
- public static function serveFrom($path)
- {
- $server = new static($path);
- $server->serve();
- }
-}
--- /dev/null
+<?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];
+ }
+}
--- /dev/null
+<?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;
+ }
+}
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
use Leafo\ScssPhp\Exception\RangeException;
/**
- * Utilties
+ * Utilty functions
*
* @author Anthon Pang <anthon.pang@gmail.com>
*/
* 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 Range $range Range of values.
- * @param array $value The value to check.
- * @param string $unit The unit of the value. Used in error reporting.
+ * @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.
*
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);
+ }
}
/**
* SCSSPHP
*
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
*/
class Version
{
- const VERSION = 'v0.6.7';
+ const VERSION = 'v0.7.5';
}
- Copy all the files from the folder 'src' this directory.
- Copy the license file from the project root.
-- Apply path for https://github.com/leafo/scssphp/issues/532 if it is not merged yet
Licensed under MIT, Copyright (c) 2015 Leaf Corcoran.
<location>scssphp</location>
<name>scssphp</name>
<license>MIT</license>
- <version>0.6.7</version>
+ <version>0.7.5</version>
</library>
<library>
<location>spout</location>