summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2013-08-12 16:35:09 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2013-08-13 13:26:11 -0700
commit6b278fa0870072b8100241ed5b3a5ae36fb7c67f (patch)
tree19f01031b160b7289b0bed4d6b32d197f13c8a06
parent6eceabdaecf0631b55c0b51812fe43b8958e19bc (diff)
downloadpyscss-6b278fa0870072b8100241ed5b3a5ae36fb7c67f.tar.gz
Fix test_expression to use real types. Minor type fixes.
-rw-r--r--scss/expression.py10
-rw-r--r--scss/tests/test_expression.py107
-rw-r--r--scss/types.py41
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