MDL-61529 core: update lib scssphp to version 0.7.5
authorBas Brands <bas@moodle.com>
Fri, 16 Mar 2018 09:36:40 +0000 (10:36 +0100)
committerBas Brands <bas@moodle.com>
Fri, 16 Mar 2018 09:36:40 +0000 (10:36 +0100)
28 files changed:
lib/scssphp/Base/Range.php
lib/scssphp/Block.php
lib/scssphp/Colors.php
lib/scssphp/Compiler.php
lib/scssphp/Compiler/Environment.php
lib/scssphp/Exception/CompilerException.php
lib/scssphp/Exception/ParserException.php
lib/scssphp/Exception/RangeException.php
lib/scssphp/Exception/ServerException.php
lib/scssphp/Formatter.php
lib/scssphp/Formatter/Compact.php
lib/scssphp/Formatter/Compressed.php
lib/scssphp/Formatter/Crunched.php
lib/scssphp/Formatter/Debug.php
lib/scssphp/Formatter/Expanded.php
lib/scssphp/Formatter/Nested.php
lib/scssphp/Formatter/OutputBlock.php
lib/scssphp/Node.php
lib/scssphp/Node/Number.php
lib/scssphp/Parser.php
lib/scssphp/Server.php [deleted file]
lib/scssphp/SourceMap/Base64VLQEncoder.php [new file with mode: 0644]
lib/scssphp/SourceMap/SourceMapGenerator.php [new file with mode: 0644]
lib/scssphp/Type.php
lib/scssphp/Util.php
lib/scssphp/Version.php
lib/scssphp/moodle_readme.txt
lib/thirdpartylibs.xml

index 3f3a067..8bcc6ec 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2017 Leaf Corcoran
+ * @copyright 2015-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index a6b5f3f..a6ef8e0 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -28,6 +28,11 @@ class Block
      */
     public $parent;
 
+    /**
+     * @var string
+     */
+    public $sourceName;
+
     /**
      * @var integer
      */
index 61a71ab..2314ea5 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index 68003b9..292e960 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -18,6 +18,7 @@ use Leafo\ScssPhp\Compiler\Environment;
 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;
@@ -64,6 +65,10 @@ class Compiler
     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
      */
@@ -120,11 +125,20 @@ class Compiler
     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;
@@ -165,9 +179,6 @@ class Compiler
      */
     public function compile($code, $path = null)
     {
-        $locale = setlocale(LC_NUMERIC, 0);
-        setlocale(LC_NUMERIC, 'C');
-
         $this->indentLevel    = -1;
         $this->commentsSeen   = [];
         $this->extends        = [];
@@ -194,9 +205,35 @@ class Compiler
         $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;
     }
@@ -273,12 +310,15 @@ class Compiler
     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;
     }
@@ -661,6 +701,7 @@ class Compiler
 
             if ($needsWrap) {
                 $wrapped = new Block;
+                $wrapped->sourceName   = $media->sourceName;
                 $wrapped->sourceIndex  = $media->sourceIndex;
                 $wrapped->sourceLine   = $media->sourceLine;
                 $wrapped->sourceColumn = $media->sourceColumn;
@@ -734,6 +775,7 @@ class Compiler
         // 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;
@@ -790,6 +832,7 @@ class Compiler
             }
 
             $b = new Block;
+            $b->sourceName   = $e->block->sourceName;
             $b->sourceIndex  = $e->block->sourceIndex;
             $b->sourceLine   = $e->block->sourceLine;
             $b->sourceColumn = $e->block->sourceColumn;
@@ -1181,7 +1224,7 @@ class Compiler
     /**
      * Compile selector to string; self(&) should have been replaced by now
      *
-     * @param array $selector
+     * @param string|array $selector
      *
      * @return string
      */
@@ -1203,7 +1246,7 @@ class Compiler
     /**
      * Compile selector part
      *
-     * @param arary $piece
+     * @param array $piece
      *
      * @return string
      */
@@ -1729,9 +1772,18 @@ class Compiler
                 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 (;;) {
@@ -1741,7 +1793,7 @@ class Compiler
                         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);
@@ -1926,7 +1978,7 @@ class Compiler
      *
      * @param string $value
      *
-     * @return bool
+     * @return boolean
      */
     protected function isImmediateRelationshipCombinator($value)
     {
@@ -1963,7 +2015,7 @@ class Compiler
      * @param array   $value
      * @param boolean $inExp
      *
-     * @return array
+     * @return array|\Leafo\ScssPhp\Node\Number
      */
     protected function reduce($value, $inExp = false)
     {
@@ -2238,7 +2290,7 @@ class Compiler
      * @param array $left
      * @param array $right
      *
-     * @return array
+     * @return \Leafo\ScssPhp\Node\Number
      */
     protected function opAddNumberNumber($left, $right)
     {
@@ -2251,7 +2303,7 @@ class Compiler
      * @param array $left
      * @param array $right
      *
-     * @return array
+     * @return \Leafo\ScssPhp\Node\Number
      */
     protected function opMulNumberNumber($left, $right)
     {
@@ -2264,7 +2316,7 @@ class Compiler
      * @param array $left
      * @param array $right
      *
-     * @return array
+     * @return \Leafo\ScssPhp\Node\Number
      */
     protected function opSubNumberNumber($left, $right)
     {
@@ -2277,7 +2329,7 @@ class Compiler
      * @param array $left
      * @param array $right
      *
-     * @return array
+     * @return array|\Leafo\ScssPhp\Node\Number
      */
     protected function opDivNumberNumber($left, $right)
     {
@@ -2294,7 +2346,7 @@ class Compiler
      * @param array $left
      * @param array $right
      *
-     * @return array
+     * @return \Leafo\ScssPhp\Node\Number
      */
     protected function opModNumberNumber($left, $right)
     {
@@ -2580,7 +2632,7 @@ class Compiler
      * @param array $left
      * @param array $right
      *
-     * @return array
+     * @return \Leafo\ScssPhp\Node\Number
      */
     protected function opCmpNumberNumber($left, $right)
     {
@@ -3304,6 +3356,30 @@ class Compiler
         $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
      *
@@ -3505,7 +3581,7 @@ class Compiler
      * 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
@@ -3777,7 +3853,7 @@ class Compiler
      *
      * @param mixed $value
      *
-     * @return array
+     * @return array|\Leafo\ScssPhp\Node\Number
      */
     private function coerceValue($value)
     {
@@ -3850,7 +3926,8 @@ class Compiler
     /**
      * Coerce something to list
      *
-     * @param array $item
+     * @param array  $item
+     * @param string $delim
      *
      * @return array
      */
@@ -4672,36 +4749,32 @@ class Compiler
     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)
@@ -5129,7 +5202,7 @@ class Compiler
         $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;
     }
@@ -5140,7 +5213,7 @@ class Compiler
         $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;
     }
@@ -5210,6 +5283,8 @@ class Compiler
      * Workaround IE7's content counter bug.
      *
      * @param array $args
+     *
+     * @return array
      */
     protected function libCounter($args)
     {
index b4b86e3..fe309dd 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index 45fc167..7ca2e2b 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index c0ee002..6d64335 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index 47192ff..3ba6bf1 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index 68d3a29..d0ed084 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index 0d2635f..b4f90aa 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -12,6 +12,7 @@
 namespace Leafo\ScssPhp;
 
 use Leafo\ScssPhp\Formatter\OutputBlock;
+use Leafo\ScssPhp\SourceMap\SourceMapGenerator;
 
 /**
  * Base formatter
@@ -56,10 +57,30 @@ abstract class 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
      *
@@ -123,10 +144,10 @@ abstract class 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);
         }
     }
 
@@ -139,9 +160,9 @@ abstract class Formatter
     {
         $inner = $this->indentStr();
 
-        echo $inner
+        $this->write($inner
             . implode($this->tagSeparator, $block->selectors)
-            . $this->open . $this->break;
+            . $this->open . $this->break);
     }
 
     /**
@@ -167,6 +188,8 @@ abstract class Formatter
             return;
         }
 
+        $this->currentBlock = $block;
+
         $pre = $this->indentStr();
 
         if (! empty($block->selectors)) {
@@ -187,10 +210,10 @@ abstract class Formatter
             $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);
         }
     }
 
@@ -199,12 +222,21 @@ abstract class Formatter
      *
      * @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);
@@ -213,4 +245,30 @@ abstract class Formatter
 
         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;
+    }
 }
index aaf972b..4efa1a0 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index 17aca54..1faa7e1 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -53,10 +53,10 @@ class Compressed extends Formatter
             }
         }
 
-        echo $inner . implode($glue, $block->lines);
+        $this->write($inner . implode($glue, $block->lines));
 
         if (! empty($block->children)) {
-            echo $this->break;
+            $this->write($this->break);
         }
     }
 }
index 0a89bc4..42d77b5 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -51,10 +51,10 @@ class Crunched extends Formatter
             }
         }
 
-        echo $inner . implode($glue, $block->lines);
+        $this->write($inner . implode($glue, $block->lines));
 
         if (! empty($block->children)) {
-            echo $this->break;
+            $this->write($this->break);
         }
     }
 }
index 321c536..bfcbf41 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -52,13 +52,13 @@ class Debug extends Formatter
         $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");
         }
     }
 
@@ -70,13 +70,13 @@ class Debug extends Formatter
         $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");
         }
     }
 
@@ -88,7 +88,7 @@ class Debug extends Formatter
         $indent = $this->indentStr();
 
         if (empty($block->children)) {
-            echo "{$indent}block->children: []\n";
+            $this->write("{$indent}block->children: []\n");
 
             return;
         }
@@ -109,8 +109,10 @@ class Debug extends Formatter
     {
         $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);
index dff17e6..d8c1e88 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -59,10 +59,10 @@ class Expanded extends Formatter
             }
         }
 
-        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);
         }
     }
 }
index c4ff803..8f72206 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -66,10 +66,10 @@ class Nested extends Formatter
             }
         }
 
-        echo $inner . implode($glue, $block->lines);
+        $this->write($inner . implode($glue, $block->lines));
 
         if (! empty($block->children)) {
-            echo $this->break;
+            $this->write($this->break);
         }
     }
 
@@ -80,9 +80,9 @@ class Nested extends Formatter
     {
         $inner = $this->indentStr();
 
-        echo $inner
+        $this->write($inner
             . implode($this->tagSeparator, $block->selectors)
-            . $this->open . $this->break;
+            . $this->open . $this->break);
     }
 
     /**
@@ -94,13 +94,13 @@ class Nested extends Formatter
             $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);
                     }
                 }
             }
@@ -120,6 +120,9 @@ class Nested extends Formatter
             return;
         }
 
+        $this->currentBlock = $block;
+
+
         $this->depth = $block->depth;
 
         if (! empty($block->selectors)) {
@@ -139,11 +142,11 @@ class Nested extends Formatter
         if (! empty($block->selectors)) {
             $this->indentLevel--;
 
-            echo $this->close;
+            $this->write($this->close);
         }
 
         if ($block->type === 'root') {
-            echo $this->break;
+            $this->write($this->break);
         }
     }
 
index 88e8640..5eb589c 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -47,4 +47,19 @@ class OutputBlock
      * @var \Leafo\ScssPhp\Formatter\OutputBlock
      */
     public $parent;
+
+    /**
+     * @var string
+     */
+    public $sourceName;
+
+    /**
+     * @var integer
+     */
+    public $sourceLine;
+
+    /**
+     * @var integer
+     */
+    public $sourceColumn;
 }
index eb543c7..b9f7945 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index 442d57d..42c1680 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -31,7 +31,7 @@ class Number extends Node implements \ArrayAccess
     /**
      * @var integer
      */
-    static public $precision = 5;
+    static public $precision = 10;
 
     /**
      * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
@@ -291,8 +291,9 @@ class Number extends Node implements \ArrayAccess
 
         reset($units);
         $unit = key($units);
+        $dimension = number_format($dimension, static::$precision, '.', '');
 
-        return (string) $dimension . $unit;
+        return (static::$precision ? rtrim(rtrim($dimension, '0'), '.') : $dimension) . $unit;
     }
 
     /**
index 619b7ba..6fdea3e 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -708,6 +708,7 @@ class Parser
         list($line, $column) = $this->getSourcePosition($pos);
 
         $b = new Block;
+        $b->sourceName   = $this->sourceName;
         $b->sourceLine   = $line;
         $b->sourceColumn = $column;
         $b->sourceIndex  = $this->sourceIndex;
@@ -2468,7 +2469,13 @@ class Parser
      */
     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');
diff --git a/lib/scssphp/Server.php b/lib/scssphp/Server.php
deleted file mode 100644 (file)
index e4caddb..0000000
+++ /dev/null
@@ -1,463 +0,0 @@
-<?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();
-    }
-}
diff --git a/lib/scssphp/SourceMap/Base64VLQEncoder.php b/lib/scssphp/SourceMap/Base64VLQEncoder.php
new file mode 100644 (file)
index 0000000..1189ce0
--- /dev/null
@@ -0,0 +1,217 @@
+<?php
+/**
+ * SCSSPHP
+ *
+ * @copyright 2012-2018 Leaf Corcoran
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @link http://leafo.github.io/scssphp
+ */
+
+namespace Leafo\ScssPhp\SourceMap;
+
+/**
+ * Base64 VLQ Encoder
+ *
+ * {@internal Derivative of oyejorge/less.php's lib/SourceMap/Base64VLQ.php, relicensed with permission. }}
+ *
+ * @author Josh Schmidt <oyejorge@gmail.com>
+ * @author Nicolas FRANÇOIS <nicolas.francois@frog-labs.com>
+ */
+class Base64VLQEncoder
+{
+    /**
+     * Shift
+     *
+     * @var integer
+     */
+    private $shift = 5;
+
+    /**
+     * Mask
+     *
+     * @var integer
+     */
+    private $mask = 0x1F; // == (1 << shift) == 0b00011111
+
+    /**
+     * Continuation bit
+     *
+     * @var integer
+     */
+    private $continuationBit = 0x20; // == (mask - 1 ) == 0b00100000
+
+    /**
+     * Char to integer map
+     *
+     * @var array
+     */
+    private $charToIntMap = array(
+        'A' => 0,  'B' => 1,  'C' => 2,  'D' => 3,  'E' => 4,  'F' => 5,  'G' => 6,  'H' => 7,
+        'I' => 8,  'J' => 9,  'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, 'O' => 14, 'P' => 15,
+        'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23,
+        'Y' => 24, 'Z' => 25, 'a' => 26, 'b' => 27, 'c' => 28, 'd' => 29, 'e' => 30, 'f' => 31,
+        'g' => 32, 'h' => 33, 'i' => 34, 'j' => 35, 'k' => 36, 'l' => 37, 'm' => 38, 'n' => 39,
+        'o' => 40, 'p' => 41, 'q' => 42, 'r' => 43, 's' => 44, 't' => 45, 'u' => 46, 'v' => 47,
+        'w' => 48, 'x' => 49, 'y' => 50, 'z' => 51,   0 => 52,   1 => 53,   2 => 54,   3 => 55,
+          4 => 56,   5 => 57,   6 => 58,   7 => 59,   8 => 60,   9 => 61, '+' => 62, '/' => 63,
+    );
+
+    /**
+     * Integer to char map
+     *
+     * @var array
+     */
+    private $intToCharMap = array(
+         0 => 'A',  1 => 'B',  2 => 'C',  3 => 'D',  4 => 'E',  5 => 'F',  6 => 'G',  7 => 'H',
+         8 => 'I',  9 => 'J', 10 => 'K', 11 => 'L', 12 => 'M', 13 => 'N', 14 => 'O', 15 => 'P',
+        16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U', 21 => 'V', 22 => 'W', 23 => 'X',
+        24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b', 28 => 'c', 29 => 'd', 30 => 'e', 31 => 'f',
+        32 => 'g', 33 => 'h', 34 => 'i', 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n',
+        40 => 'o', 41 => 'p', 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v',
+        48 => 'w', 49 => 'x', 50 => 'y', 51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3',
+        56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8', 61 => '9', 62 => '+', 63 => '/',
+    );
+
+    /**
+     * Constructor
+     */
+    public function __construct()
+    {
+        // I leave it here for future reference
+        // foreach (str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') as $i => $char)
+        // {
+        //     $this->charToIntMap[$char] = $i;
+        //     $this->intToCharMap[$i] = $char;
+        // }
+    }
+
+    /**
+     * Convert from a two-complement value to a value where the sign bit is
+     * is placed in the least significant bit. For example, as decimals:
+     *   1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+     *   2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+     * We generate the value for 32 bit machines, hence -2147483648 becomes 1, not 4294967297,
+     * even on a 64 bit machine.
+     *
+     * @param string $aValue
+     */
+    public function toVLQSigned($aValue)
+    {
+        return 0xffffffff & ($aValue < 0 ? ((-$aValue) << 1) + 1 : ($aValue << 1) + 0);
+    }
+
+    /**
+     * Convert to a two-complement value from a value where the sign bit is
+     * is placed in the least significant bit. For example, as decimals:
+     *   2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+     *   4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+     * We assume that the value was generated with a 32 bit machine in mind.
+     * Hence
+     *   1 becomes -2147483648
+     * even on a 64 bit machine.
+     *
+     * @param integer $aValue
+     */
+    public function fromVLQSigned($aValue)
+    {
+        return $aValue & 1 ? $this->zeroFill(~$aValue + 2, 1) | (-1 - 0x7fffffff) : $this->zeroFill($aValue, 1);
+    }
+
+    /**
+     * Return the base 64 VLQ encoded value.
+     *
+     * @param string $aValue The value to encode
+     *
+     * @return string The encoded value
+     */
+    public function encode($aValue)
+    {
+        $encoded = '';
+        $vlq = $this->toVLQSigned($aValue);
+
+        do {
+            $digit = $vlq & $this->mask;
+            $vlq = $this->zeroFill($vlq, $this->shift);
+
+            if ($vlq > 0) {
+                $digit |= $this->continuationBit;
+            }
+
+            $encoded .= $this->base64Encode($digit);
+        } while ($vlq > 0);
+
+        return $encoded;
+    }
+
+    /**
+     * Return the value decoded from base 64 VLQ.
+     *
+     * @param string $encoded The encoded value to decode
+     *
+     * @return integer The decoded value
+     */
+    public function decode($encoded)
+    {
+        $vlq = 0;
+        $i = 0;
+
+        do {
+            $digit = $this->base64Decode($encoded[$i]);
+            $vlq |= ($digit & $this->mask) << ($i * $this->shift);
+            $i++;
+        } while ($digit & $this->continuationBit);
+
+        return $this->fromVLQSigned($vlq);
+    }
+
+    /**
+     * Right shift with zero fill.
+     *
+     * @param integer $a number to shift
+     * @param integer $b number of bits to shift
+     *
+     * @return integer
+     */
+    public function zeroFill($a, $b)
+    {
+        return ($a >= 0) ? ($a >> $b) : ($a >> $b) & (PHP_INT_MAX >> ($b - 1));
+    }
+
+    /**
+     * Encode single 6-bit digit as base64.
+     *
+     * @param integer $number
+     *
+     * @return string
+     *
+     * @throws \Exception If the number is invalid
+     */
+    public function base64Encode($number)
+    {
+        if ($number < 0 || $number > 63) {
+            throw new \Exception(sprintf('Invalid number "%s" given. Must be between 0 and 63.', $number));
+        }
+
+        return $this->intToCharMap[$number];
+    }
+
+    /**
+     * Decode single 6-bit digit from base64
+     *
+     * @param string $char
+     *
+     * @return integer
+     *
+     * @throws \Exception If the number is invalid
+     */
+    public function base64Decode($char)
+    {
+        if (! array_key_exists($char, $this->charToIntMap)) {
+            throw new \Exception(sprintf('Invalid base 64 digit "%s" given.', $char));
+        }
+
+        return $this->charToIntMap[$char];
+    }
+}
diff --git a/lib/scssphp/SourceMap/SourceMapGenerator.php b/lib/scssphp/SourceMap/SourceMapGenerator.php
new file mode 100644 (file)
index 0000000..70b47cd
--- /dev/null
@@ -0,0 +1,337 @@
+<?php
+/**
+ * SCSSPHP
+ *
+ * @copyright 2012-2018 Leaf Corcoran
+ *
+ * @license http://opensource.org/licenses/MIT MIT
+ *
+ * @link http://leafo.github.io/scssphp
+ */
+
+namespace Leafo\ScssPhp\SourceMap;
+
+use Leafo\ScssPhp\Exception\CompilerException;
+
+/**
+ * Source Map Generator
+ *
+ * {@internal Derivative of oyejorge/less.php's lib/SourceMap/Generator.php, relicensed with permission. }}
+ *
+ * @author Josh Schmidt <oyejorge@gmail.com>
+ * @author Nicolas FRANÇOIS <nicolas.francois@frog-labs.com>
+ */
+class SourceMapGenerator
+{
+    /**
+     * What version of source map does the generator generate?
+     */
+    const VERSION = 3;
+
+    /**
+     * Array of default options
+     *
+     * @var array
+     */
+    protected $defaultOptions = array(
+        // an optional source root, useful for relocating source files
+        // on a server or removing repeated values in the 'sources' entry.
+        // This value is prepended to the individual entries in the 'source' field.
+        'sourceRoot' => '',
+
+        // an optional name of the generated code that this source map is associated with.
+        'sourceMapFilename' => null,
+
+        // url of the map
+        'sourceMapURL' => null,
+
+        // absolute path to a file to write the map to
+        'sourceMapWriteTo' => null,
+
+        // output source contents?
+        'outputSourceFiles' => false,
+
+        // base path for filename normalization
+        'sourceMapRootpath' => '',
+
+        // base path for filename normalization
+        'sourceMapBasepath' => ''
+    );
+
+    /**
+     * The base64 VLQ encoder
+     *
+     * @var \Leafo\ScssPhp\SourceMap\Base64VLQEncoder
+     */
+    protected $encoder;
+
+    /**
+     * Array of mappings
+     *
+     * @var array
+     */
+    protected $mappings = array();
+
+    /**
+     * Array of contents map
+     *
+     * @var array
+     */
+    protected $contentsMap = array();
+
+    /**
+     * File to content map
+     *
+     * @var array
+     */
+    protected $sources = array();
+    protected $source_keys = array();
+
+    /**
+     * @var array
+     */
+    private $options;
+
+    public function __construct(array $options = [])
+    {
+        $this->options = array_merge($this->defaultOptions, $options);
+        $this->encoder = new Base64VLQEncoder();
+    }
+
+    /**
+     * Adds a mapping
+     *
+     * @param integer $generatedLine   The line number in generated file
+     * @param integer $generatedColumn The column number in generated file
+     * @param integer $originalLine    The line number in original file
+     * @param integer $originalColumn  The column number in original file
+     * @param string  $sourceFile      The original source file
+     */
+    public function addMapping($generatedLine, $generatedColumn, $originalLine, $originalColumn, $sourceFile)
+    {
+        $this->mappings[] = array(
+            'generated_line' => $generatedLine,
+            'generated_column' => $generatedColumn,
+            'original_line' => $originalLine,
+            'original_column' => $originalColumn,
+            'source_file' => $sourceFile
+        );
+
+        $this->sources[$sourceFile] = $sourceFile;
+    }
+
+    /**
+     * Saves the source map to a file
+     *
+     * @param string $file    The absolute path to a file
+     * @param string $content The content to write
+     *
+     * @throws \Leafo\ScssPhp\Exception\CompilerException If the file could not be saved
+     */
+    public function saveMap($content)
+    {
+        $file = $this->options['sourceMapWriteTo'];
+        $dir  = dirname($file);
+
+        // directory does not exist
+        if (! is_dir($dir)) {
+            // FIXME: create the dir automatically?
+            throw new CompilerException(sprintf('The directory "%s" does not exist. Cannot save the source map.', $dir));
+        }
+
+        // FIXME: proper saving, with dir write check!
+        if (file_put_contents($file, $content) === false) {
+            throw new CompilerException(sprintf('Cannot save the source map to "%s"', $file));
+        }
+
+        return $this->options['sourceMapURL'];
+    }
+
+    /**
+     * Generates the JSON source map
+     *
+     * @return string
+     *
+     * @see https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#
+     */
+    public function generateJson()
+    {
+        $sourceMap = array();
+        $mappings  = $this->generateMappings();
+
+        // File version (always the first entry in the object) and must be a positive integer.
+        $sourceMap['version'] = self::VERSION;
+
+        // An optional name of the generated code that this source map is associated with.
+        $file = $this->options['sourceMapFilename'];
+
+        if ($file) {
+            $sourceMap['file'] = $file;
+        }
+
+        // An optional source root, useful for relocating source files on a server or removing repeated values in the
+        // 'sources' entry. This value is prepended to the individual entries in the 'source' field.
+        $root = $this->options['sourceRoot'];
+
+        if ($root) {
+            $sourceMap['sourceRoot'] = $root;
+        }
+
+        // A list of original sources used by the 'mappings' entry.
+        $sourceMap['sources'] = array();
+
+        foreach ($this->sources as $source_uri => $source_filename) {
+            $sourceMap['sources'][] = $this->normalizeFilename($source_filename);
+        }
+
+        // A list of symbol names used by the 'mappings' entry.
+        $sourceMap['names'] = array();
+
+        // A string with the encoded mapping data.
+        $sourceMap['mappings'] = $mappings;
+
+        if ($this->options['outputSourceFiles']) {
+            // An optional list of source content, useful when the 'source' can't be hosted.
+            // The contents are listed in the same order as the sources above.
+            // 'null' may be used if some original sources should be retrieved by name.
+            $sourceMap['sourcesContent'] = $this->getSourcesContent();
+        }
+
+        // less.js compat fixes
+        if (count($sourceMap['sources']) && empty($sourceMap['sourceRoot'])) {
+            unset($sourceMap['sourceRoot']);
+        }
+
+        return json_encode($sourceMap);
+    }
+
+    /**
+     * Returns the sources contents
+     *
+     * @return array|null
+     */
+    protected function getSourcesContent()
+    {
+        if (empty($this->sources)) {
+            return null;
+        }
+
+        $content = array();
+
+        foreach ($this->sources as $sourceFile) {
+            $content[] = file_get_contents($sourceFile);
+        }
+
+        return $content;
+    }
+
+    /**
+     * Generates the mappings string
+     *
+     * @return string
+     */
+    public function generateMappings()
+    {
+        if (! count($this->mappings)) {
+            return '';
+        }
+
+        $this->source_keys = array_flip(array_keys($this->sources));
+
+        // group mappings by generated line number.
+        $groupedMap = $groupedMapEncoded = array();
+
+        foreach ($this->mappings as $m) {
+            $groupedMap[$m['generated_line']][] = $m;
+        }
+
+        ksort($groupedMap);
+        $lastGeneratedLine = $lastOriginalIndex = $lastOriginalLine = $lastOriginalColumn = 0;
+
+        foreach ($groupedMap as $lineNumber => $line_map) {
+            while (++$lastGeneratedLine < $lineNumber) {
+                $groupedMapEncoded[] = ';';
+            }
+
+            $lineMapEncoded = array();
+            $lastGeneratedColumn = 0;
+
+            foreach ($line_map as $m) {
+                $mapEncoded = $this->encoder->encode($m['generated_column'] - $lastGeneratedColumn);
+                $lastGeneratedColumn = $m['generated_column'];
+
+                // find the index
+                if ($m['source_file']) {
+                    $index = $this->findFileIndex($m['source_file']);
+
+                    if ($index !== false) {
+                        $mapEncoded .= $this->encoder->encode($index - $lastOriginalIndex);
+                        $lastOriginalIndex = $index;
+                        // lines are stored 0-based in SourceMap spec version 3
+                        $mapEncoded .= $this->encoder->encode($m['original_line'] - 1 - $lastOriginalLine);
+                        $lastOriginalLine = $m['original_line'] - 1;
+                        $mapEncoded .= $this->encoder->encode($m['original_column'] - $lastOriginalColumn);
+                        $lastOriginalColumn = $m['original_column'];
+                    }
+                }
+
+                $lineMapEncoded[] = $mapEncoded;
+            }
+
+            $groupedMapEncoded[] = implode(',', $lineMapEncoded) . ';';
+        }
+
+        return rtrim(implode($groupedMapEncoded), ';');
+    }
+
+    /**
+     * Finds the index for the filename
+     *
+     * @param string $filename
+     *
+     * @return integer|false
+     */
+    protected function findFileIndex($filename)
+    {
+        return $this->source_keys[$filename];
+    }
+
+    protected function normalizeFilename($filename)
+    {
+        $filename = $this->fixWindowsPath($filename);
+        $rootpath = $this->options['sourceMapRootpath'];
+        $basePath = $this->options['sourceMapBasepath'];
+
+        // "Trim" the 'sourceMapBasepath' from the output filename.
+        if (strpos($filename, $basePath) === 0) {
+            $filename = substr($filename, strlen($basePath));
+        }
+
+        // Remove extra leading path separators.
+        if (strpos($filename, '\\') === 0 || strpos($filename, '/') === 0) {
+            $filename = substr($filename, 1);
+        }
+
+        return $rootpath . $filename;
+    }
+
+    /**
+     * Fix windows paths
+     *
+     * @param string  $path
+     * @param boolean $addEndSlash
+     *
+     * @return string
+     */
+    public function fixWindowsPath($path, $addEndSlash = false)
+    {
+        $slash = ($addEndSlash) ? '/' : '';
+
+        if (! empty($path)) {
+            $path = str_replace('\\', '/', $path);
+            $path = rtrim($path, '/') . $slash;
+        }
+
+        return $path;
+    }
+}
index 2d659e4..e84c47e 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
index b2a05db..7526e02 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -15,7 +15,7 @@ use Leafo\ScssPhp\Base\Range;
 use Leafo\ScssPhp\Exception\RangeException;
 
 /**
- * Utilties
+ * Utilty functions
  *
  * @author Anthon Pang <anthon.pang@gmail.com>
  */
@@ -25,10 +25,10 @@ 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 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.
      *
@@ -53,4 +53,18 @@ class Util
 
         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);
+    }
 }
index 2d6eabc..3b8dcd0 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * SCSSPHP
  *
- * @copyright 2012-2017 Leaf Corcoran
+ * @copyright 2012-2018 Leaf Corcoran
  *
  * @license http://opensource.org/licenses/MIT MIT
  *
@@ -18,5 +18,5 @@ namespace Leafo\ScssPhp;
  */
 class Version
 {
-    const VERSION = 'v0.6.7';
+    const VERSION = 'v0.7.5';
 }
index 53ec986..44fd815 100644 (file)
@@ -7,6 +7,5 @@ Import procedure:
 
 - 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.
index b003465..0b670a1 100644 (file)
     <location>scssphp</location>
     <name>scssphp</name>
     <license>MIT</license>
-    <version>0.6.7</version>
+    <version>0.7.5</version>
   </library>
   <library>
     <location>spout</location>