diff options
author | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-10-07 18:34:55 -0700 |
---|---|---|
committer | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-10-07 18:34:55 -0700 |
commit | e01ba8869b1fa3f1c178df151b67dbd0b0d929a0 (patch) | |
tree | 13c28d3c7438f9b308401bd0eb21b266084c94b0 | |
parent | 09269cb684c37be6d2ac5641b228eb9b76beab8a (diff) | |
download | pyscss-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.py | 68 | ||||
-rw-r--r-- | scss/tests/files/general/maps-example-lists.scss | 2 | ||||
-rw-r--r-- | scss/tests/functions/test_core.py | 32 | ||||
-rw-r--r-- | scss/types.py | 59 |
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,)) |