summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2014-09-01 21:20:27 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2014-09-01 21:20:27 -0700
commit3de66863bbca766c05b4b97a9621b854412ae3b3 (patch)
treefb709ce97d27ab6d2f504b965ccc684768c551ef
parent66caf45e0b152c14bcec5a24e5a31ca75fe4ea73 (diff)
downloadpyscss-3de66863bbca766c05b4b97a9621b854412ae3b3.tar.gz
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.
-rw-r--r--scss/ast.py22
-rw-r--r--scss/expression.py14
-rw-r--r--scss/grammar/expression.g13
-rw-r--r--scss/grammar/expression.py6
-rw-r--r--scss/grammar/scanner.py3
-rw-r--r--scss/types.py44
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: "(?<!\s)(?:[a-zA-Z]+|%)(?![-\w])"
token NUM: "(?:\d+(?:\.\d*)?|\.\d+)"
token COLOR: "#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?![a-fA-F0-9])"
@@ -92,6 +94,7 @@ parser SassExpression:
# FIXME: Also, URLs may not contain $ as it breaks urls with variables?
token BAREURL: "(?:[\\\\].|[^#$'\"()\\x00-\\x08\\x0b\\x0e-\\x1f\\x7f]|#(?![{]))*"
+ # -------------------------------------------------------------------------
# Goals:
rule goal: expr_lst END {{ return expr_lst }}
diff --git a/scss/grammar/expression.py b/scss/grammar/expression.py
index 397fa4c..cf3c9e7 100644
--- a/scss/grammar/expression.py
+++ b/scss/grammar/expression.py
@@ -42,6 +42,8 @@ class SassExpressionScanner(Scanner):
_patterns = [
('":"', ':'),
('","', ','),
+ ('SINGLE_STRING_GUTS', "([^'\\\\#]|[\\\\].|#(?![{]))*"),
+ ('DOUBLE_STRING_GUTS', '([^"\\\\#]|[\\\\].|#(?![{]))*'),
('[ \r\t\n]+', '[ \r\t\n]+'),
('LPAR', '\\(|\\['),
('RPAR', '\\)|\\]'),
@@ -64,10 +66,6 @@ class SassExpressionScanner(Scanner):
('DOTDOTDOT', '[.]{3}'),
('SINGLE_QUOTE', "'"),
('DOUBLE_QUOTE', '"'),
- ('SINGLE_STRING_GUTS', "([^'\\\\#]|[\\\\].|#(?![{]))*"),
- ('DOUBLE_STRING_GUTS', '([^"\\\\#]|[\\\\].|#(?![{]))*'),
- ('STR', "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"),
- ('QSTR', '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'),
('UNITS', '(?<!\\s)(?:[a-zA-Z]+|%)(?![-\\w])'),
('NUM', '(?:\\d+(?:\\.\\d*)?|\\.\\d+)'),
('COLOR', '#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?![a-fA-F0-9])'),
diff --git a/scss/grammar/scanner.py b/scss/grammar/scanner.py
index 8e48d2b..5aee227 100644
--- a/scss/grammar/scanner.py
+++ b/scss/grammar/scanner.py
@@ -206,6 +206,9 @@ except ImportError:
and add the restriction to self.restrictions
"""
# Keep looking for a token, ignoring any in self.ignore
+ if DEBUG:
+ print()
+ print("Being asked to match with restriction:", repr(restrict))
token = None
while True:
best_pat = None
diff --git a/scss/types.py b/scss/types.py
index 65406df..a1c07ab 100644
--- a/scss/types.py
+++ b/scss/types.py
@@ -125,7 +125,19 @@ class Value(object):
raise ValueError("Not a map: {0!r}".format(self))
def render(self, compress=False):
- return self.__str__()
+ """Return this value's CSS representation as a string (text, i.e.
+ unicode!).
+
+ If `compress` is true, try hard to shorten the string at the cost of
+ readability.
+ """
+ raise NotImplementedError
+
+ def render_interpolated(self, compress=False):
+ """Return this value's string representation as appropriate for
+ returning from an interpolation.
+ """
+ return self.render(compress)
class Null(Value):
@@ -156,6 +168,10 @@ class Null(Value):
def render(self, compress=False):
return self.sass_type_name
+ def render_interpolated(self, compress=False):
+ # Interpolating a null gives you nothing.
+ return ''
+
class Undefined(Null):
sass_type_name = 'undefined'
@@ -692,6 +708,10 @@ class List(Value):
for item in value
)
+ def render_interpolated(self, compress=False):
+ return self.delimiter(compress).join(
+ item.render_interpolated(compress) for item in self)
+
# DEVIATION: binary ops on lists and scalars act element-wise
def __add__(self, other):
if isinstance(other, List):
@@ -1107,6 +1127,10 @@ class String(Value):
else:
return self._render_quoted()
+ def render_interpolated(self, compress=False):
+ # Always render without quotes
+ return self.value
+
def _render_bareword(self):
# TODO this is currently unused, and only implemented due to an
# oversight, but would make for a much better implementation of
@@ -1173,6 +1197,12 @@ class Function(String):
super(Function, self).render(compress),
)
+ def render_interpolated(self, compress=False):
+ return "{0}({1})".format(
+ self.function_name,
+ super(Function, self).render_interpolated(compress),
+ )
+
class Url(Function):
# Bare URLs may not contain quotes, parentheses, or unprintables. Quoted
@@ -1185,11 +1215,17 @@ class Url(Function):
def render(self, compress=False):
if self.quotes is None:
- # Need to escape some stuff to keep this as valid CSS
- inside = self.bad_identifier_rx.sub(
- self._escape_character, self.value)
+ return self.render_interpolated(compress)
else:
inside = self._render_quoted()
+ return "url(" + inside + ")"
+
+ def render_interpolated(self, compress=False):
+ # Always render without quotes.
+ # When doing that, we need to escape some stuff to make sure the result
+ # is valid CSS.
+ inside = self.bad_identifier_rx.sub(
+ self._escape_character, self.value)
return "url(" + inside + ")"