From 3aba299080d6cdf046f69bcc29f988f3f73f4ff6 Mon Sep 17 00:00:00 2001 From: Daniel Corn Date: Wed, 30 Jan 2013 16:51:01 +0100 Subject: [PATCH 1/5] Added more verbose Exception class Added support for media queries without '@media screen and ...' --- .gitignore | 4 +- Exception/ScssException.php | 83 ++++++++++++++++++++++++++++ scss.inc.php | 105 +++++++++++++++++++++++++++--------- 3 files changed, 166 insertions(+), 26 deletions(-) create mode 100644 Exception/ScssException.php diff --git a/.gitignore b/.gitignore index c8e6336e..3e658f54 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ /*.php .sass-cache /sass/ -/compass/ \ No newline at end of file +/compass/ + +sftp-config.json diff --git a/Exception/ScssException.php b/Exception/ScssException.php new file mode 100644 index 00000000..42793ed5 --- /dev/null +++ b/Exception/ScssException.php @@ -0,0 +1,83 @@ +, iresults + * Daniel Corn + * All rights reserved + * + * This script is part of the TYPO3 project. The TYPO3 project is + * free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * The GNU General Public License can be found at + * http://www.gnu.org/copyleft/gpl.html. + * + * This script is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * This copyright notice MUST APPEAR in all copies of the script! + */ + +/** + * The iresults error class extends a PHP exception with an additional user info + * property to allow more information to be transported with the error. + * + * @author Daniel Corn + * @package Iresults + * @subpackage Iresults + */ +class Exception_ScssException extends Exception { + /** + * The additional information transported with this error. + * + * @var array + */ + protected $userInfo = array(); + + /** + * Gets the user info dictionary. + * @return array + */ + public function getUserInfo() { + return $this->userInfo; + } + + /** + * Setter for userInfo + * + * @param array $newValue The new value to set + * @return void + * @internal + */ + public function _setUserInfo($newValue) { + $this->userInfo = $newValue; + } + + + /* MWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWM */ + /* FACTORY METHODS MWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWM */ + /* MWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWM */ + /** + * Factory method: Returns a new error with the given message, code and + * user information. + * + * @return Tx_Iresults_Error + */ + static public function errorWithMessageCodeAndUserInfo($message, $code = 0, $userInfo = array()) { + $error = NULL; + if (IR_MODERN_PHP) { + $calledClass = get_called_class(); + $error = new $calledClass($message, $code); + } else { + $error = new self($message, $code); + } + $error->_setUserInfo($userInfo); + return $error; + } +} +?> \ No newline at end of file diff --git a/scss.inc.php b/scss.inc.php index f2422391..af17b276 100644 --- a/scss.inc.php +++ b/scss.inc.php @@ -1,4 +1,5 @@ pushEnv($media); $parentScope = $this->mediaParent($this->scope); - $this->scope = $this->makeOutputBlock("media", array( $this->compileMediaQuery($this->multiplyMedia($this->env))) ); @@ -575,7 +575,11 @@ protected function compileChild($child, $out) { list(,$name, $argValues, $content) = $child; $mixin = $this->get(self::$namespaces["mixin"] . $name, false); if (!$mixin) { - throw new Exception(sprintf('Undefined mixin "%s"', $name)); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + sprintf('Undefined mixin "%s"', $name), + 1358520635, + array('child' => $child) + ); } $callingScope = $this->env; @@ -605,7 +609,11 @@ protected function compileChild($child, $out) { case "mixin_content": $content = $this->get(self::$namespaces["special"] . "content"); if (is_null($content)) { - throw new Exception("Unexpected @content inside of mixin"); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + "Unexpected @content inside of mixin", + 1358520684, + array('child' => $child) + ); } $this->storeEnv = $content->scope; @@ -623,7 +631,11 @@ protected function compileChild($child, $out) { fwrite(STDERR, "Line $line DEBUG: $value\n"); break; default: - throw new Exception("unknown child type: $child[0]"); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + "unknown child type: $child[0]", + 1358520695, + array('child' => $child) + ); } } @@ -694,7 +706,7 @@ protected function reduce($value, $inExp = false) { $left[0] == "number" && $right[0] == "number") { if ($opName == "mod" && $right[2] != "") { - throw new Exception(sprintf('Cannot modulo by a number with units: %s%s.', $right[1], $right[2])); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo(sprintf('Cannot modulo by a number with units: %s%s.', $right[1], $right[2]), 1358517317, array('right' => $right, 'value' => $value)); } $unitChange = true; @@ -940,12 +952,12 @@ protected function op_color_color($op, $left, $right) { break; case '/': if ($rval == 0) { - throw new Exception("color: Can't divide by zero"); + throw new Exception_ScssException("color: Can't divide by zero"); } $out[] = $lval / $rval; break; default: - throw new Exception("color: unknow op $op"); + throw new Exception_ScssException("color: unknow op $op"); } } @@ -1071,7 +1083,7 @@ protected function compileValue($value) { return $this->compileValue($reduced); default: - throw new Exception("unknown value type: $type"); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo("unknown value type: $type", 1358517391, array('value' => $value)); } } @@ -1207,13 +1219,25 @@ protected function applyArguments($argDef, $argValues) { foreach ((array) $argValues as $arg) { if (!empty($arg[0])) { if (!isset($args[$arg[0][1]])) { - throw new Exception(sprintf('Mixin or function doesn\'t have an argument named $%s.', $arg[0][1])); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + sprintf('Mixin or function doesn\'t have an argument named $%s.', $arg[0][1]), + 1358517429, + array('argument' => $arg) + ); } elseif ($args[$arg[0][1]][0] < count($remaining)) { - throw new Exception(sprintf('The argument $%s was passed both by position and by name.', $arg[0][1])); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + sprintf('The argument $%s was passed both by position and by name.', $arg[0][1]), + 1358517461, + array('argument' => $arg) + ); } $keywordArgs[$arg[0][1]] = $arg[1]; } elseif (count($keywordArgs)) { - throw new Exception('Positional arguments must come before keyword arguments.'); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + 'Positional arguments must come before keyword arguments.', + 1358517486, + array('argument' => $arg) + ); } elseif ($arg[2] == true) { $val = $this->reduce($arg[1], true); if ($val[0] == "list") { @@ -1242,7 +1266,11 @@ protected function applyArguments($argDef, $argValues) { } elseif (!empty($default)) { $val = $default; } else { - throw new Exception(sprintf('There is missing argument $%s', $name)); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + sprintf('There is missing argument $%s', $name), + 1358517511, + array('argument' => $arg) + ); } $this->set($name, $this->reduce($val, true), true); @@ -1516,18 +1544,31 @@ protected function coerceString($value) { protected function assertList($value) { if ($value[0] != "list") - throw new exception("expecting list"); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + "expecting list", + 1358517536, + array('value' => $value) + ); return $value; } protected function assertColor($value) { if ($color = $this->coerceColor($value)) return $color; - throw new Exception("expecting color"); + + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + "expecting color", + 1358517536, + array('value' => $value) + ); } protected function assertNumber($value) { if ($value[0] != "number") - throw new Exception("expecting number"); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + "expecting number", + 1358517536, + array('value' => $value) + ); return $value[1]; } @@ -2038,14 +2079,22 @@ protected function getNormalizedNumbers($args) { $numbers = array(); foreach ($args as $key => $item) { if ('number' != $item[0]) { - throw new Exception(sprintf('%s is not a number', $item[0])); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + sprintf('%s is not a number', $item[0]), + 1358517631, + array('key' => $key, 'item' => $item) + ); } $number = $this->normalizeNumber($item); if (null === $unit) { $unit = $number[2]; } elseif ($unit !== $number[2]) { - throw new Exception(sprintf('Incompatible units: "%s" and "%s".', $originalUnit, $item[2])); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + sprintf('Incompatible units: "%s" and "%s".', $originalUnit, $item[2]), + 1358517663, + array('originalUnit' => $originalUnit, 'item' => $item) + ); } $originalUnit = $item[2]; @@ -2158,7 +2207,7 @@ protected function lib_unitless($args) { protected function lib_comparable($args) { list($number1, $number2) = $args; if (!isset($number1[0]) || $number1[0] != "number" || !isset($number2[0]) || $number2[0] != "number") { - throw new Exception('Invalid argument(s) for "comparable"'); + throw new Exception_ScssException('Invalid argument(s) for "comparable"'); } $number1 = $this->normalizeNumber($number1); @@ -2788,10 +2837,12 @@ protected function mediaQuery(&$out) { } if ($this->literal("and")) { - $this->genericList($expressions, "mediaExpression", "and", false); - if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]); + // Yes there is an and } + $this->genericList($expressions, "mediaExpression", "and", false); + if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]); + $out = $parts; return true; } @@ -3066,12 +3117,16 @@ protected function argumentDef(&$out) { } else { $this->seek($ss); } - + $ss = $this->seek(); if ($this->literal("...")) { $sss = $this->seek(); if (!$this->literal(")")) { - throw new Exception('... has to be after the final argument'); + throw Exception_ScssException::errorWithMessageCodeAndUserInfo( + '... has to be after the final argument', + 1358517719, + array('var' => $var, 'argument' => $arg) + ); } $arg[2] = true; $this->seek($sss); @@ -3599,9 +3654,9 @@ protected function throwParseError($msg = "parse error", $count = null) { } if ($this->peek("(.*?)(\n|$)", $m, $count)) { - throw new Exception("$msg: failed at `$m[1]` $loc"); + throw new Exception_ScssException("$msg: failed at `$m[1]` $loc"); } else { - throw new Exception("$msg: $loc"); + throw new Exception_ScssException("$msg: $loc"); } } @@ -3896,7 +3951,7 @@ public function serve() { if ($this->needsCompile($input, $output)) { try { echo $this->compile($input, $output); - } catch (exception $e) { + } catch (Exception $e) { header('HTTP/1.1 500 Internal Server Error'); echo "Parse error: " . $e->getMessage() . "\n"; } From 9e135dd2d66fc460dad23257e9265e47fe44d36a Mon Sep 17 00:00:00 2001 From: Daniel Corn Date: Sat, 2 Feb 2013 11:21:24 +0100 Subject: [PATCH 2/5] Renamed package to iresults/scssphp --- composer.json | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 85947555..c9655dbc 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,8 @@ { - "name": "leafo/scssphp", + "name": "iresults/scssphp", "type": "library", "description": "scssphp is a compiler for SCSS written in PHP.", - "homepage": "http://leafo.net/scssphp/", + "homepage": "https://github.com/iresults/scssphp", "license": [ "MIT", "GPL-3.0" @@ -12,6 +12,11 @@ "name": "Leaf Corcoran", "email": "leafot@gmail.com", "homepage": "http://leafo.net" + }, + { + "name": "Daniel Corn", + "email": "cod@iresults.li", + "homepage": "http://iresults.li" } ], "autoload": { @@ -23,4 +28,4 @@ "require-dev": { "php": ">=5.3.0" } -} +} \ No newline at end of file From 75bf2af109cdd44f41dbefde301d7ac659059ac9 Mon Sep 17 00:00:00 2001 From: Daniel Corn Date: Thu, 7 Mar 2013 12:52:03 +0100 Subject: [PATCH 3/5] Fix for #54 --- scss.inc.php | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/scss.inc.php b/scss.inc.php index 8b4c9cbb..541add30 100644 --- a/scss.inc.php +++ b/scss.inc.php @@ -515,6 +515,7 @@ protected function compileChild($child, $out) { break; case "assign": list(,$name, $value) = $child; + if ($name[0] == "var") { $isDefault = !empty($child[3]); if (!$isDefault || $this->get($name[1], true) === true) { @@ -533,6 +534,7 @@ protected function compileChild($child, $out) { } $compiledValue = $this->compileValue($value); + $out->lines[] = $this->formatter->property( $this->compileValue($name), $compiledValue); @@ -1100,6 +1102,10 @@ protected function compileValue($value) { case "number": return round($value[1], self::$numberPrecision) . $value[2]; case "string": + // Check if the string contains a dollar + if (is_array($value[2]) && $value[2][0] && strpos($value[2][0], '$') !== FALSE) { + $value[2][0] = $this->replaceVariableInString($value[2][0]); + } return $value[1] . $this->compileStringContent($value) . $value[1]; case "function": $args = !empty($value[2]) ? $this->compileValue($value[2]) : ""; @@ -1110,6 +1116,13 @@ protected function compileValue($value) { list(, $delim, $items) = $value; + if (is_array($items[0]) + && $items[0][0] === 'string' + && is_array($items[0][0][2]) + && $items[0][0][2][0][2] == 'opacity=$opacity') { + die; + } + $filtered = array(); foreach ($items as $item) { if ($item[0] == "null") continue; @@ -1151,6 +1164,50 @@ protected function compileValue($value) { } } + /** + * Find and replace variables in the given input string + * @param string $input + * @return string Returns the input string with the variable replaced + */ + protected function replaceVariableInString($input) { + if (!is_string($input) || strpos($input, '$') === FALSE) { + return $input; + } + $matches = array(); + if (!preg_match_all('!\$[a-zA-Z0-9]+!', $input, $matches)) { + return $input; + } + + $output = $input; + $matches = $matches[0]; + $matches = array_unique($matches); + foreach ($matches as $variable) { + $variable = trim($variable); + if (!$variable || $variable === '$') { + continue; + } + // Strip the leading $ + $variableIdentifier = substr($variable, 1); + + $value = $this->get($variableIdentifier); + if (is_array($value)) { + // Output numbers correctly + if ('number' === $value[0]) { + $value = $this->normalizeNumber($value); + } + $value = $value[1]; + } + #$this->pd($value, '!\\' . $variable . '\\b!'); + + // Stricter + // $output = preg_replace('!\\' . $variable . '\\b!', $value, $output); + + // Faster + $output = str_replace($variable, $value, $output); + } + return $output; + } + protected function compileStringContent($string) { $parts = array(); foreach ($string[2] as $part) { @@ -2287,6 +2344,17 @@ protected function lib_comparable($args) { return $number1[2] == $number2[2] || $number1[2] == "" || $number2[2] == ""; } + /** + * Dumps a given variable (or the given variables) wrapped into a 'pre' tag. + * @var mixed $var1 + */ + public function pd($var1 = '__iresults_pd_noValue') { + $args = func_get_args(); + if (class_exists('Tx_Iresults')) { + call_user_func_array(array('Tx_Iresults', 'pd'), $args); + } + } + static protected $cssColors = array( 'aliceblue' => '240,248,255', 'antiquewhite' => '250,235,215', @@ -3808,6 +3876,17 @@ protected function flattenList($value) { } return $value; } + + /** + * Dumps a given variable (or the given variables) wrapped into a 'pre' tag. + * @var mixed $var1 + */ + public function pd($var1 = '__iresults_pd_noValue') { + $args = func_get_args(); + if (class_exists('Tx_Iresults')) { + call_user_func_array(array('Tx_Iresults', 'pd'), $args); + } + } } /** From 3276059a082c3b9510ea970b58d202936188dc83 Mon Sep 17 00:00:00 2001 From: Daniel Corn Date: Fri, 8 Mar 2013 09:05:08 +0100 Subject: [PATCH 4/5] Removed a debugging statement --- scss.inc.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/scss.inc.php b/scss.inc.php index 541add30..68a2b8d8 100644 --- a/scss.inc.php +++ b/scss.inc.php @@ -1116,13 +1116,6 @@ protected function compileValue($value) { list(, $delim, $items) = $value; - if (is_array($items[0]) - && $items[0][0] === 'string' - && is_array($items[0][0][2]) - && $items[0][0][2][0][2] == 'opacity=$opacity') { - die; - } - $filtered = array(); foreach ($items as $item) { if ($item[0] == "null") continue; From 541cb2d3346518933b0e86321c70e8227dbc9e81 Mon Sep 17 00:00:00 2001 From: Daniel Corn Date: Fri, 8 Mar 2013 09:11:48 +0100 Subject: [PATCH 5/5] Fixed a bug where a variable with a wrong type was given to strpos --- scss.inc.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scss.inc.php b/scss.inc.php index 68a2b8d8..66a8cf3a 100644 --- a/scss.inc.php +++ b/scss.inc.php @@ -1103,7 +1103,7 @@ protected function compileValue($value) { return round($value[1], self::$numberPrecision) . $value[2]; case "string": // Check if the string contains a dollar - if (is_array($value[2]) && $value[2][0] && strpos($value[2][0], '$') !== FALSE) { + if (is_array($value[2]) && is_string($value[2][0]) && strpos($value[2][0], '$') !== FALSE) { $value[2][0] = $this->replaceVariableInString($value[2][0]); } return $value[1] . $this->compileStringContent($value) . $value[1];