From 3de66863bbca766c05b4b97a9621b854412ae3b3 Mon Sep 17 00:00:00 2001 From: "Eevee (Alex Munroe)" Date: Mon, 1 Sep 2014 21:20:27 -0700 Subject: Fix a couple bugs with interpolation. - Sometimes whitespace could be lost after an interpolation. - Null wasn't being correctly interpolated as empty string. - Removing String.__str__ revealed a tiny bug in apply_vars. --- scss/ast.py | 22 ++-------------------- scss/expression.py | 14 ++++++++++++-- scss/grammar/expression.g | 13 ++++++++----- scss/grammar/expression.py | 6 ++---- scss/grammar/scanner.py | 3 +++ scss/types.py | 44 ++++++++++++++++++++++++++++++++++++++++---- 6 files changed, 67 insertions(+), 35 deletions(-) diff --git a/scss/ast.py b/scss/ast.py index 51cb91c..06de23a 100644 --- a/scss/ast.py +++ b/scss/ast.py @@ -240,25 +240,6 @@ class Interpolation(Expression): return Literal(type(parts[0], quotes=quotes, **kwargs)) - def _render_interpolated(self, value): - """Return the result of interpolating `value`, which is slightly - different than just rendering it, since it's an intermediate thing. - """ - # Strings are taken literally - if isinstance(value, String): - return value.value - - # Lists are joined recursively - if isinstance(value, List): - # TODO Ruby /immediately/ respects `compress` here -- need to - # inspect the compilation for whether to pass it in (probably in - # other places too) - return value.delimiter().join( - self._render_interpolated(item) for item in value) - else: - # TODO like here - return value.render() - def evaluate(self, calculator, divide=False): result = [] for i, part in enumerate(self.parts): @@ -268,7 +249,8 @@ class Interpolation(Expression): else: # Interspersed (even) parts are nodes value = part.evaluate(calculator, divide) - result.append(self._render_interpolated(value)) + # TODO need to know whether to pass `compress` here + result.append(value.render_interpolated()) return self.type(''.join(result), quotes=self.quotes, **self.kwargs) diff --git a/scss/expression.py b/scss/expression.py index 090e902..39eb623 100644 --- a/scss/expression.py +++ b/scss/expression.py @@ -14,6 +14,7 @@ from scss.errors import SassError, SassEvaluationError, SassParseError from scss.grammar.expression import SassExpression, SassExpressionScanner from scss.rule import Namespace from scss.types import String +from scss.types import Value from scss.util import dequote @@ -88,8 +89,13 @@ class Calculator(object): return n else: if v: - if not isinstance(v, six.string_types): - v = v.render() + if not isinstance(v, Value): + raise TypeError( + "Somehow got a variable {0!r} " + "with a non-Sass value: {1!r}" + .format(n, v) + ) + v = v.render() # TODO this used to test for _dequote if m.group(1): v = dequote(v) @@ -98,6 +104,10 @@ class Calculator(object): return v cont = _interpolate_re.sub(_av, cont) + + else: + # Variable succeeded, so we need to render it + cont = cont.render() # TODO this is surprising and shouldn't be here cont = self.do_glob_math(cont) return cont diff --git a/scss/grammar/expression.g b/scss/grammar/expression.g index 026f07a..df4d7e0 100644 --- a/scss/grammar/expression.g +++ b/scss/grammar/expression.g @@ -38,6 +38,12 @@ from scss.grammar import Scanner %% parser SassExpression: + # These need to go before the ignore, so they match first, and we don't + # lose spaces inside a string! + # Don't allow quotes or # unless they're escaped (or the # is alone) + token SINGLE_STRING_GUTS: '([^\'\\\\#]|[\\\\].|#(?![{]))*' + token DOUBLE_STRING_GUTS: "([^\"\\\\#]|[\\\\].|#(?![{]))*" + ignore: "[ \r\t\n]+" token LPAR: "\\(|\\[" token RPAR: "\\)|\\]" @@ -60,11 +66,7 @@ parser SassExpression: token DOTDOTDOT: '[.]{3}' token SINGLE_QUOTE: "'" token DOUBLE_QUOTE: '"' - # Don't allow quotes or # unless they're escaped (or the # is alone) - token SINGLE_STRING_GUTS: '([^\'\\\\#]|[\\\\].|#(?![{]))*' - token DOUBLE_STRING_GUTS: "([^\"\\\\#]|[\\\\].|#(?![{]))*" - token STR: "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'" - token QSTR: '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"' + token UNITS: "(?