diff options
author | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2014-09-01 21:20:27 -0700 |
---|---|---|
committer | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2014-09-01 21:20:27 -0700 |
commit | 3de66863bbca766c05b4b97a9621b854412ae3b3 (patch) | |
tree | fb709ce97d27ab6d2f504b965ccc684768c551ef | |
parent | 66caf45e0b152c14bcec5a24e5a31ca75fe4ea73 (diff) | |
download | pyscss-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.py | 22 | ||||
-rw-r--r-- | scss/expression.py | 14 | ||||
-rw-r--r-- | scss/grammar/expression.g | 13 | ||||
-rw-r--r-- | scss/grammar/expression.py | 6 | ||||
-rw-r--r-- | scss/grammar/scanner.py | 3 | ||||
-rw-r--r-- | 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: "(?<!\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 + ")" |