diff options
author | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-08-12 16:35:09 -0700 |
---|---|---|
committer | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-08-13 13:26:11 -0700 |
commit | 6b278fa0870072b8100241ed5b3a5ae36fb7c67f (patch) | |
tree | 19f01031b160b7289b0bed4d6b32d197f13c8a06 | |
parent | 6eceabdaecf0631b55c0b51812fe43b8958e19bc (diff) | |
download | pyscss-6b278fa0870072b8100241ed5b3a5ae36fb7c67f.tar.gz |
Fix test_expression to use real types. Minor type fixes.
-rw-r--r-- | scss/expression.py | 10 | ||||
-rw-r--r-- | scss/tests/test_expression.py | 107 | ||||
-rw-r--r-- | scss/types.py | 41 |
3 files changed, 94 insertions, 64 deletions
diff --git a/scss/expression.py b/scss/expression.py index 5ab779e..2c5ff73 100644 --- a/scss/expression.py +++ b/scss/expression.py @@ -9,7 +9,8 @@ import six import scss.config as config from scss.cssdefs import COLOR_NAMES, is_builtin_css_function, _expr_glob_re, _interpolate_re, _variable_re -from scss.types import BooleanValue, ColorValue, ListValue, Null, Undefined, NumberValue, ParserValue, String +from scss.rule import Namespace +from scss.types import BooleanValue, ColorValue, ListValue, Null, NumberValue, ParserValue, String, Undefined from scss.util import dequote, normalize_var, to_str ################################################################################ @@ -28,8 +29,11 @@ ast_cache = {} class Calculator(object): """Expression evaluator.""" - def __init__(self, namespace): - self.namespace = namespace + def __init__(self, namespace=None): + if namespace is None: + self.namespace = Namespace() + else: + self.namespace = namespace def _calculate_expr(self, result): _group0 = result.group(1) diff --git a/scss/tests/test_expression.py b/scss/tests/test_expression.py index 1784180..75a4dee 100644 --- a/scss/tests/test_expression.py +++ b/scss/tests/test_expression.py @@ -1,10 +1,13 @@ from scss.expression import Calculator +from scss.functions.core import CORE_LIBRARY from scss.rule import Namespace -from scss.types import NumberValue -from scss.util import to_str +from scss.types import Color, Null, Number, String import pytest +@pytest.fixture +def calc(): + return Calculator().evaluate_expression def test_reference_operations(): """Test the example expressions in the reference document: @@ -12,36 +15,41 @@ def test_reference_operations(): http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#operations """ # TODO: break this into its own file and add the entire reference guide - ns = Namespace() - calc = lambda expr: Calculator(ns).evaluate_expression(expr).render() + + # Need to build the calculator manually to get at its namespace, and need + # to use calculate() instead of evaluate_expression() so interpolation + # works + ns = Namespace(functions=CORE_LIBRARY) + calc = Calculator(ns).calculate # Simple example - assert calc('1in + 8pt') == '1.11111in' + assert calc('1in + 8pt') == Number(1.11111111, "in") # Division - ns.set_variable('$width', '1000px') - ns.set_variable('$font-size', '12px') - ns.set_variable('$line-height', '30px') - assert calc('10px/8px') == '10px / 8px' # plain CSS; no division - assert calc('$width/2') == '500px' # uses a variable; does division - assert calc('(500px/2)') == '250px' # uses parens; does division - assert calc('5px + 8px/2px') == '9px' # uses +; does division - assert calc('#{$font-size}/#{$line-height}') == '12px/30px' + ns.set_variable('$width', Number(1000, "px")) + ns.set_variable('$font-size', Number(12, "px")) + ns.set_variable('$line-height', Number(30, "px")) + assert calc('10px/8px') == String('10px / 8px') # plain CSS; no division + assert calc('$width/2') == Number(500, "px") # uses a variable; does division + assert calc('(500px/2)') == Number(250, "px") # uses parens; does division + assert calc('5px + 8px/2px') == Number(9, "px") # uses +; does division + # TODO: Ruby doesn't include these spaces + assert calc('#{$font-size}/#{$line-height}') == String('12px / 30px') # uses #{}; does no division # Color operations - ns.set_variable('$translucent-red', 'rgba(255, 0, 0, 0.5)') - ns.set_variable('$green', '#00ff00') - assert calc('#010203 + #040506') == '#050709' - assert calc('#010203 * 2') == '#020406' - assert calc('rgba(255, 0, 0, 0.75) + rgba(0, 255, 0, 0.75)') == 'rgba(255, 255, 0, 0.75)' - assert calc('opacify($translucent-red, 0.3)') == 'rgba(255, 0, 0, 0.9)' - assert calc('transparentize($translucent-red, 0.25)') == 'rgba(255, 0, 0, 0.25)' + ns.set_variable('$translucent-red', Color.from_rgb(1, 0, 0, 0.5)) + ns.set_variable('$green', Color.from_name('lime')) + assert calc('#010203 + #040506') == Color.from_hex('#050709') + assert calc('#010203 * 2') == Color.from_hex('#020406') + assert calc('rgba(255, 0, 0, 0.75) + rgba(0, 255, 0, 0.75)') == Color.from_rgb(1, 1, 0, 0.75) + assert calc('opacify($translucent-red, 0.3)') == Color.from_rgb(1, 0, 0, 0.9) + assert calc('transparentize($translucent-red, 0.25)') == Color.from_rgb(1, 0, 0, 0.25) assert calc("progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr='#{ie-hex-str($green)}', endColorstr='#{ie-hex-str($translucent-red)}')" ) == "progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr=#FF00FF00, endColorstr=#80FF0000)" # String operations - ns.set_variable('$value', 'null') + ns.set_variable('$value', Null()) assert calc('e + -resize') == 'e-resize' assert calc('"Foo " + Bar') == '"Foo Bar"' assert calc('sans- + "serif"') == 'sans-serif' @@ -55,41 +63,38 @@ def test_reference_operations(): # Types: numbers, colors, strings, booleans, lists # Test them all! -def test_addition(): - calc = lambda expr: to_str(Calculator(Namespace()).calculate(expr)) - - assert calc('123 + 456') == '579' +def test_addition(calc): + assert calc('123 + 456') == Number(579) - assert calc('1px + 2px') == '3px' + assert calc('1px + 2px') == Number(3, "px") - assert calc('123 + abc') == '123abc' - assert calc('abc + 123') == 'abc123' + assert calc('123 + abc') == String('123abc') + assert calc('abc + 123') == String('abc123') - assert calc('abc + def') == 'abcdef' - assert calc('abc + "def"') == 'abcdef' - assert calc('"abc" + def') == '"abcdef"' - assert calc('"abc" + "def"') == '"abcdef"' + assert calc('abc + def') == String('abcdef') + assert calc('abc + "def"') == String('abcdef') + ret = calc('"abc" + def') + assert ret == String('abcdef') + assert ret.quotes == '"' + ret = calc('"abc" + "def"') + assert ret == String('abcdef') + assert ret.quotes == '"' - assert calc('#010305 + #050301') == '#060606' - assert calc('#ffffff + #ffffff') == 'white' + assert calc('#010305 + #050301') == Color.from_hex('#060606') + assert calc('#ffffff + #ffffff') == Color.from_name('white') -def test_subtraction(): - calc = lambda expr: to_str(Calculator(Namespace()).calculate(expr)) - - assert calc('123 - 456') == '-333' - assert calc('456 - 123') == '333' +def test_subtraction(calc): + assert calc('123 - 456') == Number(-333) + assert calc('456 - 123') == Number(333) # TODO test that subtracting e.g. strings doesn't work - assert calc('#0f0f0f - #050505') == '#0a0a0a' - -def test_division(): - calc = Calculator(Namespace()).calculate + assert calc('#0f0f0f - #050505') == Color.from_hex('#0a0a0a') - assert calc('(5px / 5px)') == NumberValue(1) - -def test_comparison_numeric(): - calc = Calculator(Namespace()).calculate +def test_division(calc): + assert calc('(5px / 5px)') == Number(1) + assert calc('(5px / 5px)') == Number(1) +def test_comparison_numeric(calc): assert calc('123 < 456') assert calc('123 <= 456') assert calc('123 <= 123') @@ -107,9 +112,7 @@ def test_comparison_numeric(): assert not calc('123 != 123') assert not calc('123 == 456') -def test_comparison_stringerific(): - calc = Calculator(Namespace()).calculate - +def test_comparison_stringerific(calc): assert calc('"abc" == "abc"') assert calc('"abc" != "xyz"') @@ -133,9 +136,7 @@ def test_comparison_stringerific(): with pytest.raises(TypeError): calc(expression) -def test_comparison_null(): - calc = Calculator(Namespace()).calculate - +def test_comparison_null(calc): assert calc('null == null') assert calc('null != 0') diff --git a/scss/types.py b/scss/types.py index 1e314eb..f3e3b9d 100644 --- a/scss/types.py +++ b/scss/types.py @@ -146,7 +146,7 @@ class BooleanValue(Value): class Number(Value): sass_type_name = u'number' - def __init__(self, amount, unit_numer=(), unit_denom=(), unit=None): + def __init__(self, amount, unit=None, unit_numer=(), unit_denom=()): if isinstance(amount, NumberValue): assert not unit and not unit_numer and not unit_denom self.value = amount.value @@ -222,7 +222,7 @@ class Number(Value): if left.unit_numer != right.unit_numer or left.unit_denom != right.unit_denom: raise ValueError("Can't reconcile units: %r and %r" % (self, other)) - return op(left.value, right.value) + return op(round(left.value, 5), round(right.value, 5)) def __mul__(self, other): if not isinstance(other, NumberValue): @@ -235,7 +235,7 @@ class Number(Value): return NumberValue(amount, unit_numer=numer, unit_denom=denom) - def __truediv__(self, other): + def __div__(self, other): if not isinstance(other, NumberValue): return NotImplemented @@ -274,8 +274,8 @@ class Number(Value): if self.is_unitless or other.is_unitless: return NumberValue( op(self.value, other.value), - self.unit_numer or other.unit_numer, - self.unit_denom or other.unit_denom, + unit_numer=self.unit_numer or other.unit_numer, + unit_denom=self.unit_denom or other.unit_denom, ) # Reduce both operands to the same units @@ -291,7 +291,7 @@ class Number(Value): if left.value != 0: new_amount = new_amount * self.value / left.value - return NumberValue(new_amount, self.unit_numer, self.unit_denom) + return NumberValue(new_amount, unit_numer=self.unit_numer, unit_denom=self.unit_denom) ### Helper methods, mostly used internally @@ -310,8 +310,8 @@ class Number(Value): return NumberValue( amount * numer_factor / denom_factor, - numer_units, - denom_units, + unit_numer=numer_units, + unit_denom=denom_units, ) @@ -520,6 +520,31 @@ class Color(Value): return self @classmethod + def from_hex(cls, hex_string): + if not hex_string.startswith('#'): + raise ValueError("Expected #abcdef, got %r" % (hex_string,)) + + hex_string = hex_string[1:] + + # Always include the alpha channel + if len(hex_string) == 3: + hex_string += 'f' + elif len(hex_string) == 6: + hex_string += 'ff' + + # Now there should be only two possibilities. Normalize to a list of + # two hex digits + if len(hex_string) == 4: + chunks = [ch * 2 for ch in hex_string] + elif len(hex_string) == 8: + chunks = [ + hex_string[0:2], hex_string[2:4], hex_string[4:6], hex_string[6:8] + ] + + rgba = [int(ch, 16) / 255. for ch in chunks] + return cls.from_rgb(*rgba) + + @classmethod def from_name(cls, name): """Build a Color from a CSS color name.""" self = cls.__new__(cls) # TODO |