summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2013-10-07 18:34:55 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2013-10-07 18:34:55 -0700
commite01ba8869b1fa3f1c178df151b67dbd0b0d929a0 (patch)
tree13c28d3c7438f9b308401bd0eb21b266084c94b0
parent09269cb684c37be6d2ac5641b228eb9b76beab8a (diff)
downloadpyscss-e01ba8869b1fa3f1c178df151b67dbd0b0d929a0.tar.gz
Map improvements, bringing us more or less to 3.3 parity.
Maps now act as lists, and lists of pairs act as maps; the map functions are tested; and a few bugfixes were made.
-rw-r--r--scss/functions/core.py68
-rw-r--r--scss/tests/files/general/maps-example-lists.scss2
-rw-r--r--scss/tests/functions/test_core.py32
-rw-r--r--scss/types.py59
4 files changed, 109 insertions, 52 deletions
diff --git a/scss/functions/core.py b/scss/functions/core.py
index 7a99b12..b1fb6a0 100644
--- a/scss/functions/core.py
+++ b/scss/functions/core.py
@@ -568,13 +568,21 @@ def _length(*lst):
return Number(len(lst))
+@register('set-nth', 3)
+def set_nth(list, n, value):
+ expect_type(n, Number, unit=None)
+
+ py_n = n.to_python_index(len(list))
+ return List(
+ tuple(list[:py_n]) + (value,) + tuple(list[py_n + 1:]),
+ use_comma=list.use_comma)
+
+
# TODO get the compass bit outta here
@register('-compass-nth', 2)
@register('nth', 2)
def nth(lst, n):
- """
- Return the Nth item in the string
- """
+ """Return the nth item in the list."""
expect_type(n, (String, Number), unit=None)
if isinstance(n, String):
@@ -585,7 +593,7 @@ def nth(lst, n):
else:
raise ValueError("Invalid index %r" % (n,))
else:
- i = (int(n.value) - 1) % len(lst)
+ i = n.to_python_index(len(lst))
return lst[i]
@@ -650,76 +658,60 @@ def list_separator(list):
return String.unquoted('space')
-@register('set-nth', 3)
-def set_nth(list, n, value):
- expect_type(n, Number, unit=None)
-
- py_n = n.to_python_index(len(list))
- return List(
- tuple(list[:py_n]) + (value,) + tuple(list[py_n + 1:]),
- use_comma=list.use_comma)
-
-
# ------------------------------------------------------------------------------
# Map functions
@register('map-get', 2)
def map_get(map, key):
- return map.get_by_key(key)
+ return map.to_dict().get(key, Null())
@register('map-merge', 2)
def map_merge(*maps):
- pairs = []
+ key_order = []
index = {}
for map in maps:
- for key, value in map.pairs:
- if key in index:
- continue
+ for key, value in map.to_pairs():
+ if key not in index:
+ key_order.append(key)
- pairs.append((key, value))
index[key] = value
- return Map(pairs)
+
+ pairs = [(key, index[key]) for key in key_order]
+ return Map(pairs, index=index)
@register('map-keys', 1)
def map_keys(map):
return List(
- [k for (k, v) in map.pairs],
- comma=True)
+ [k for (k, v) in map.to_pairs()],
+ use_comma=True)
@register('map-values', 1)
def map_values(map):
return List(
- [v for (k, v) in map.pairs],
- comma=True)
+ [v for (k, v) in map.to_pairs()],
+ use_comma=True)
@register('map-has-key', 2)
def map_has_key(map, key):
- return Boolean(key in map.index)
+ return Boolean(key in map.to_dict())
# DEVIATIONS: these do not exist in ruby sass
@register('map-get', 3)
def map_get3(map, key, default):
- return map.get_by_key(key, default)
+ return map.to_dict().get(key, default)
@register('map-get-nested', 2)
-def map_get_nested(map, keys):
- for key in keys:
- map = map.get_by_key(key)
-
- return map
-
-
@register('map-get-nested', 3)
-def map_get_nested3(map, keys, default):
+def map_get_nested3(map, keys, default=Null()):
for key in keys:
- map = map.get_by_key(key, None)
+ map = map.to_dict().get(key, None)
if map is None:
return default
@@ -731,11 +723,11 @@ def map_merge_deep(*maps):
pairs = []
keys = set()
for map in maps:
- for key, value in map.pairs:
+ for key, value in map.to_pairs():
keys.add(key)
for key in keys:
- values = [map.get_by_key(key, None) for map in maps]
+ values = [map.to_dict().get(key, None) for map in maps]
values = [v for v in values if v is not None]
if all(isinstance(v, Map) for v in values):
pairs.append((key, map_merge_deep(*values)))
diff --git a/scss/tests/files/general/maps-example-lists.scss b/scss/tests/files/general/maps-example-lists.scss
index 00cf9ba..f33d85f 100644
--- a/scss/tests/files/general/maps-example-lists.scss
+++ b/scss/tests/files/general/maps-example-lists.scss
@@ -11,7 +11,7 @@ $themes: (
header #F4FAC7,
text #C2454E,
border #FFB158
- ),
+ )
// ...
);
diff --git a/scss/tests/functions/test_core.py b/scss/tests/functions/test_core.py
index 4f886c9..ad95374 100644
--- a/scss/tests/functions/test_core.py
+++ b/scss/tests/functions/test_core.py
@@ -343,6 +343,9 @@ def test_nth(calc):
# Examples from the Ruby docs
assert calc('nth(10px 20px 30px, 1)') == calc('10px')
assert calc('nth((Helvetica, Arial, sans-serif), 3)') == calc('sans-serif')
+ assert calc('nth((width: 10px, length: 20px), 2)') == calc('length, 20px')
+
+ assert calc('nth(10px 20px 30px, -1)') == calc('30px')
def test_join(calc):
@@ -393,7 +396,34 @@ def test_set_nth(calc):
# Map functions
-# ...
+def test_map_get(calc):
+ # Examples from the Ruby docs
+ assert calc('map-get(("foo": 1, "bar": 2), "foo")') == calc('1')
+ assert calc('map-get(("foo": 1, "bar": 2), "bar")') == calc('2')
+ assert calc('map-get(("foo": 1, "bar": 2), "baz")') == calc('null')
+
+
+def test_map_merge(calc):
+ # Examples from the Ruby docs
+ assert calc('map-merge(("foo": 1), ("bar": 2))') == calc('("foo": 1, "bar": 2)')
+ assert calc('map-merge(("foo": 1, "bar": 2), ("bar": 3))') == calc('("foo": 1, "bar": 3)')
+
+
+def test_map_keys(calc):
+ # Examples from the Ruby docs
+ assert calc('map-keys(("foo": 1, "bar": 2))') == calc('"foo", "bar"')
+
+
+def test_map_values(calc):
+ # Examples from the Ruby docs
+ assert calc('map-values(("foo": 1, "bar": 2))') == calc('1, 2')
+ assert calc('map-values(("foo": 1, "bar": 2, "baz": 1))') == calc('1, 2, 1')
+
+
+def test_map_has_key(calc):
+ # Examples from the Ruby docs
+ assert calc('map-has-key(("foo": 1, "bar": 2), "foo")') == calc('true')
+ assert calc('map-has-key(("foo": 1, "bar": 2), "baz")') == calc('false')
# ------------------------------------------------------------------------------
diff --git a/scss/types.py b/scss/types.py
index f6e6c10..cd3910c 100644
--- a/scss/types.py
+++ b/scss/types.py
@@ -101,6 +101,22 @@ class Value(object):
def __neg__(self):
return String("-" + self.render())
+ def to_dict(self):
+ """Return the Python dict equivalent of this map.
+
+ If this type can't be expressed as a map, raise.
+ """
+ return dict(self.to_pairs())
+
+ def to_pairs(self):
+ """Return the Python list-of-tuples equivalent of this map. Note that
+ this is different from ``self.to_dict().items()``, because Sass maps
+ preserve order.
+
+ If this type can't be expressed as a map, raise.
+ """
+ raise ValueError("Not a map: {0!r}".format(self))
+
def render(self, compress=False):
return self.__str__()
@@ -628,6 +644,16 @@ class List(Value):
def __getitem__(self, key):
return self.value[key]
+ def to_pairs(self):
+ pairs = []
+ for item in self:
+ if len(item) != 2:
+ return super(List, self).to_pairs()
+
+ pairs.append(tuple(item))
+
+ return pairs
+
def render(self, compress=False):
if not self.value:
raise ValueError("Can't render empty list as CSS")
@@ -1011,16 +1037,19 @@ class String(Value):
return self.__str__()
-### XXX EXPERIMENTAL XXX
missing = object()
class Map(Value):
sass_type_name = u'map'
- def __init__(self, pairs):
+ def __init__(self, pairs, index=None):
self.pairs = tuple(pairs)
- self.index = {}
- for key, value in pairs:
- self.index[key] = value
+
+ if index is None:
+ self.index = {}
+ for key, value in pairs:
+ self.index[key] = value
+ else:
+ self.index = index
def __repr__(self):
return "<Map: (%s)>" % (", ".join("%s: %s" % pair for pair in self.pairs),)
@@ -1034,14 +1063,20 @@ class Map(Value):
def __iter__(self):
return iter(self.pairs)
- def get_by_key(self, key, default=missing):
- if default is missing:
- return self.index[key]
- else:
- return self.index.get(key, default)
+ def __getitem__(self, index):
+ return List(self.pairs[index], use_comma=True)
+
+ def __eq__(self, other):
+ try:
+ return self.pairs == other.to_pairs()
+ except ValueError:
+ return NotImplemented
+
+ def to_dict(self):
+ return self.index
- def get_by_pos(self, key):
- return self.pairs[key][1]
+ def to_pairs(self):
+ return self.pairs
def render(self, compress=False):
raise TypeError("Cannot render map %r as CSS" % (self,))