1
from __future__ import absolute_import
2
from __future__ import division
3
from __future__ import print_function
10
from scss.cssdefs import COLOR_LOOKUP, COLOR_NAMES, ZEROABLE_UNITS, convert_units_to_base_units, cancel_base_units, count_base_units
11
from scss.util import escape
14
################################################################################
19
sass_type_name = u'unknown'
22
return '<%s(%s)>' % (self.__class__.__name__, repr(self.value))
24
# Sass values are all true, except for booleans and nulls
28
def __nonzero__(self):
29
# Py 2's name for __bool__
30
return self.__bool__()
32
# All Sass scalars also act like one-element spaced lists
41
def __getitem__(self, key):
42
if key not in (-1, 0):
47
def __contains__(self, item):
50
### NOTE: From here on down, the operators are exposed to Sass code and
51
### thus should ONLY return Sass types
53
# Reasonable default for equality
54
def __eq__(self, other):
56
type(self) == type(other) and self.value == other.value)
58
def __ne__(self, other):
59
return Boolean(not self.__eq__(other))
61
# Only numbers support ordering
62
def __lt__(self, other):
63
raise TypeError("Can't compare %r with %r" % (self, other))
65
def __le__(self, other):
66
raise TypeError("Can't compare %r with %r" % (self, other))
68
def __gt__(self, other):
69
raise TypeError("Can't compare %r with %r" % (self, other))
71
def __ge__(self, other):
72
raise TypeError("Can't compare %r with %r" % (self, other))
75
def __add__(self, other):
76
# Default behavior is to treat both sides like strings
77
if isinstance(other, String):
78
return String(self.render() + other.value, quotes=other.quotes)
79
return String(self.render() + other.render())
81
def __sub__(self, other):
82
# Default behavior is to treat the whole expression like one string
83
return String(self.render() + "-" + other.render())
85
def __div__(self, other):
86
return String(self.render() + "/" + other.render())
88
# Sass types have no notion of floor vs true division
89
def __truediv__(self, other):
90
return self.__div__(other)
92
def __floordiv__(self, other):
93
return self.__div__(other)
95
def __mul__(self, other):
99
return String("+" + self.render())
102
return String("-" + self.render())
105
"""Return the Python dict equivalent of this map.
107
If this type can't be expressed as a map, raise.
109
return dict(self.to_pairs())
112
"""Return the Python list-of-tuples equivalent of this map. Note that
113
this is different from ``self.to_dict().items()``, because Sass maps
116
If this type can't be expressed as a map, raise.
118
raise ValueError("Not a map: {0!r}".format(self))
120
def render(self, compress=False):
121
return self.__str__()
126
sass_type_name = u'null'
128
def __init__(self, value=None):
132
return self.sass_type_name
135
return "<%s()>" % (self.__class__.__name__,)
143
def __eq__(self, other):
144
return Boolean(isinstance(other, Null))
146
def __ne__(self, other):
147
return Boolean(not self.__eq__(other))
149
def render(self, compress=False):
150
return self.sass_type_name
153
class Undefined(Null):
154
sass_type_name = u'undefined'
156
def __init__(self, value=None):
159
def __add__(self, other):
162
def __radd__(self, other):
165
def __sub__(self, other):
168
def __rsub__(self, other):
171
def __div__(self, other):
174
def __rdiv__(self, other):
177
def __truediv__(self, other):
180
def __rtruediv__(self, other):
183
def __floordiv__(self, other):
186
def __rfloordiv__(self, other):
189
def __mul__(self, other):
192
def __rmul__(self, other):
202
class Boolean(Value):
203
sass_type_name = u'bool'
205
def __init__(self, value):
206
self.value = bool(value)
209
return 'true' if self.value else 'false'
212
return hash(self.value)
217
def render(self, compress=False):
225
sass_type_name = u'number'
227
def __init__(self, amount, unit=None, unit_numer=(), unit_denom=()):
228
if isinstance(amount, Number):
229
assert not unit and not unit_numer and not unit_denom
230
self.value = amount.value
231
self.unit_numer = amount.unit_numer
232
self.unit_denom = amount.unit_denom
235
if not isinstance(amount, (int, float)):
236
raise TypeError("Expected number, got %r" % (amount,))
239
unit_numer = unit_numer + (unit.lower(),)
241
# Cancel out any convertable units on the top and bottom
242
numerator_base_units = count_base_units(unit_numer)
243
denominator_base_units = count_base_units(unit_denom)
245
# Count which base units appear both on top and bottom
246
cancelable_base_units = {}
247
for unit, count in numerator_base_units.items():
248
cancelable_base_units[unit] = min(
249
count, denominator_base_units.get(unit, 0))
251
# Actually remove the units
252
numer_factor, unit_numer = cancel_base_units(unit_numer, cancelable_base_units)
253
denom_factor, unit_denom = cancel_base_units(unit_denom, cancelable_base_units)
256
self.unit_numer = tuple(unit_numer)
257
self.unit_denom = tuple(unit_denom)
258
self.value = amount * (numer_factor / denom_factor)
261
full_unit = ' * '.join(self.unit_numer)
264
full_unit += ' * '.join(self.unit_denom)
267
full_unit = ' ' + full_unit
269
return '<%s(%r%s)>' % (self.__class__.__name__, self.value, full_unit)
272
return hash((self.value, self.unit_numer, self.unit_denom))
275
return int(self.value)
278
return float(self.value)
284
return self * Number(-1)
289
def __eq__(self, other):
290
if not isinstance(other, Number):
291
return Boolean(False)
292
return self._compare(other, operator.__eq__, soft_fail=True)
294
def __lt__(self, other):
295
return self._compare(other, operator.__lt__)
297
def __le__(self, other):
298
return self._compare(other, operator.__le__)
300
def __gt__(self, other):
301
return self._compare(other, operator.__gt__)
303
def __ge__(self, other):
304
return self._compare(other, operator.__ge__)
306
def _compare(self, other, op, soft_fail=False):
307
if not isinstance(other, Number):
308
raise TypeError("Can't compare %r and %r" % (self, other))
310
# A unitless operand is treated as though it had the other operand's
311
# units, and zero values can cast to anything, so in both cases the
312
# units can be ignored
313
if (self.is_unitless or other.is_unitless or
314
self.value == 0 or other.value == 0):
318
left = self.to_base_units()
319
right = other.to_base_units()
321
if left.unit_numer != right.unit_numer or left.unit_denom != right.unit_denom:
323
# Used for equality only, where == should never fail
324
return Boolean(False)
326
raise ValueError("Can't reconcile units: %r and %r" % (self, other))
328
return Boolean(op(round(left.value, 5), round(right.value, 5)))
330
def __pow__(self, exp):
331
if not isinstance(exp, Number):
332
raise TypeError("Can't raise %r to power %r" % (self, exp))
333
if not exp.is_unitless:
334
raise TypeError("Exponent %r cannot have units" % (exp,))
337
return Number(self.value ** exp.value)
339
# Units can only be exponentiated to integral powers -- what's the
340
# square root of 'px'? (Well, it's sqrt(px), but supporting that is
341
# a bit out of scope.)
342
if exp.value != int(exp.value):
343
raise ValueError("Can't raise units of %r to non-integral power %r" % (self, exp))
346
self.value ** int(exp.value),
347
unit_numer=self.unit_numer * int(exp.value),
348
unit_denom=self.unit_denom * int(exp.value),
351
def __mul__(self, other):
352
if not isinstance(other, Number):
353
return NotImplemented
355
amount = self.value * other.value
356
numer = self.unit_numer + other.unit_numer
357
denom = self.unit_denom + other.unit_denom
359
return Number(amount, unit_numer=numer, unit_denom=denom)
361
def __div__(self, other):
362
if not isinstance(other, Number):
363
return NotImplemented
365
amount = self.value / other.value
366
numer = self.unit_numer + other.unit_denom
367
denom = self.unit_denom + other.unit_numer
369
return Number(amount, unit_numer=numer, unit_denom=denom)
371
def __add__(self, other):
372
# Numbers auto-cast to strings when added to other strings
373
if isinstance(other, String):
374
return String(self.render(), quotes=None) + other
376
return self._add_sub(other, operator.add)
378
def __sub__(self, other):
379
return self._add_sub(other, operator.sub)
381
def _add_sub(self, other, op):
382
"""Implements both addition and subtraction."""
383
if not isinstance(other, Number):
384
return NotImplemented
386
# If either side is unitless, inherit the other side's units. Skip all
387
# the rest of the conversion math, too.
388
if self.is_unitless or other.is_unitless:
390
op(self.value, other.value),
391
unit_numer=self.unit_numer or other.unit_numer,
392
unit_denom=self.unit_denom or other.unit_denom,
395
# Likewise, if either side is zero, it can auto-cast to any units
398
op(self.value, other.value),
399
unit_numer=other.unit_numer,
400
unit_denom=other.unit_denom,
402
elif other.value == 0:
404
op(self.value, other.value),
405
unit_numer=self.unit_numer,
406
unit_denom=self.unit_denom,
409
# Reduce both operands to the same units
410
left = self.to_base_units()
411
right = other.to_base_units()
413
if left.unit_numer != right.unit_numer or left.unit_denom != right.unit_denom:
414
raise ValueError("Can't reconcile units: %r and %r" % (self, other))
416
new_amount = op(left.value, right.value)
418
# Convert back to the left side's units
420
new_amount = new_amount * self.value / left.value
422
return Number(new_amount, unit_numer=self.unit_numer, unit_denom=self.unit_denom)
424
### Helper methods, mostly used internally
426
def to_base_units(self):
427
"""Convert to a fixed set of "base" units. The particular units are
428
arbitrary; what's important is that they're consistent.
430
Used for addition and comparisons.
432
# Convert to "standard" units, as defined by the conversions dict above
435
numer_factor, numer_units = convert_units_to_base_units(self.unit_numer)
436
denom_factor, denom_units = convert_units_to_base_units(self.unit_denom)
439
amount * numer_factor / denom_factor,
440
unit_numer=numer_units,
441
unit_denom=denom_units,
444
### Utilities for public consumption
447
def wrap_python_function(cls, fn):
448
"""Wraps an unary Python math function, translating the argument from
449
Sass to Python on the way in, and vice versa for the return value.
451
Used to wrap simple Python functions like `ceil`, `floor`, etc.
453
def wrapped(sass_arg):
454
# TODO enforce no units for trig?
455
python_arg = sass_arg.value
456
python_ret = fn(python_arg)
459
unit_numer=sass_arg.unit_numer,
460
unit_denom=sass_arg.unit_denom)
465
def to_python_index(self, length, check_bounds=True, circular=False):
466
"""Return a plain Python integer appropriate for indexing a sequence of
467
the given length. Raise if this is impossible for any reason
470
if not self.is_unitless:
471
raise ValueError("Index cannot have units: {0!r}".format(self))
473
ret = int(self.value)
474
if ret != self.value:
475
raise ValueError("Index must be an integer: {0!r}".format(ret))
478
raise ValueError("Index cannot be zero")
480
if check_bounds and not circular and abs(ret) > length:
481
raise ValueError("Index {0!r} out of bounds for length {1}".format(ret, length))
492
def has_simple_unit(self):
493
"""Returns True iff the unit is expressible in CSS, i.e., has no
494
denominator and at most one unit in the numerator.
496
return len(self.unit_numer) <= 1 and not self.unit_denom
498
def is_simple_unit(self, unit):
499
"""Return True iff the unit is simple (as above) and matches the given
502
if self.unit_denom or len(self.unit_numer) > 1:
505
if not self.unit_numer:
506
# Empty string historically means no unit
509
return self.unit_numer[0] == unit
512
def is_unitless(self):
513
return not self.unit_numer and not self.unit_denom
515
def render(self, compress=False):
516
if not self.has_simple_unit:
517
raise ValueError("Can't express compound units in CSS: %r" % (self,))
520
unit = self.unit_numer[0]
525
if compress and unit in ZEROABLE_UNITS and value == 0:
528
if value == 0: # -0.0 is plain 0
531
val = "%0.05f" % round(value, 5)
532
val = val.rstrip('0').rstrip('.')
534
if compress and val.startswith('0.'):
535
# Strip off leading zero when compressing
542
"""A list of other values. May be delimited by commas or spaces.
544
Lists of one item don't make much sense in CSS, but can exist in Sass. Use ......
546
Lists may also contain zero items, but these are forbidden from appearing
550
sass_type_name = u'list'
552
def __init__(self, iterable, separator=None, use_comma=None, is_literal=False):
553
if isinstance(iterable, List):
554
iterable = iterable.value
556
if not isinstance(iterable, (list, tuple)):
557
raise TypeError("Expected list, got %r" % (iterable,))
559
self.value = list(iterable)
561
for item in self.value:
562
if not isinstance(item, Value):
563
raise TypeError("Expected a Sass type, got %r" % (item,))
565
# TODO remove separator argument entirely
566
if use_comma is None:
567
self.use_comma = separator == ","
569
self.use_comma = use_comma
571
self.is_literal = is_literal
574
def maybe_new(cls, values, use_comma=True):
575
"""If `values` contains only one item, return that item. Otherwise,
576
return a List as normal.
581
return cls(values, use_comma=use_comma)
584
"""If this List contains only one item, return it. Otherwise, return
587
if len(self.value) == 1:
593
def from_maybe(cls, values, use_comma=True):
594
"""If `values` appears to not be a list, return a list containing it.
595
Otherwise, return a List as normal.
602
def from_maybe_starargs(cls, args, use_comma=True):
603
"""If `args` has one element which appears to be a list, return it.
604
Otherwise, return a list as normal.
606
Mainly used by Sass function implementations that predate `...`
607
support, so they can accept both a list of arguments and a single list
608
stored in a variable.
611
if isinstance(args[0], cls):
613
elif isinstance(args[0], (list, tuple)):
614
return cls(args[0], use_comma=use_comma)
616
return cls(args, use_comma=use_comma)
619
return "<List(%r, %r)>" % (
621
self.delimiter(compress=True),
625
return hash((tuple(self.value), self.use_comma))
627
def delimiter(self, compress=False):
637
return len(self.value)
643
return iter(self.value)
645
def __contains__(self, item):
646
return item in self.value
648
def __getitem__(self, key):
649
return self.value[key]
655
return super(List, self).to_pairs()
657
pairs.append(tuple(item))
661
def render(self, compress=False):
663
raise ValueError("Can't render empty list as CSS")
665
delim = self.delimiter(compress)
670
# Non-literal lists have nulls stripped
671
value = [item for item in self.value if not item.is_null]
672
# Non-empty lists containing only nulls become nothing, just like
678
item.render(compress=compress)
682
# DEVIATION: binary ops on lists and scalars act element-wise
683
def __add__(self, other):
684
if isinstance(other, List):
685
max_list, min_list = (self, other) if len(self) > len(other) else (other, self)
686
return List([item + max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma)
688
elif isinstance(other, String):
689
# UN-DEVIATION: adding a string should fall back to canonical
690
# behavior of string addition
691
return super(List, self).__add__(other)
694
return List([item + other for item in self], use_comma=self.use_comma)
696
def __sub__(self, other):
697
if isinstance(other, List):
698
max_list, min_list = (self, other) if len(self) > len(other) else (other, self)
699
return List([item - max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma)
701
return List([item - other for item in self], use_comma=self.use_comma)
703
def __mul__(self, other):
704
if isinstance(other, List):
705
max_list, min_list = (self, other) if len(self) > len(other) else (other, self)
706
max_list, min_list = (self, other) if len(self) > len(other) else (other, self)
707
return List([item * max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma)
709
return List([item * other for item in self], use_comma=self.use_comma)
711
def __div__(self, other):
712
if isinstance(other, List):
713
max_list, min_list = (self, other) if len(self) > len(other) else (other, self)
714
return List([item / max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma)
716
return List([item / other for item in self], use_comma=self.use_comma)
722
return List([-item for item in self], use_comma=self.use_comma)
725
def _constrain(value, lb=0, ub=1):
726
"""Helper for Color constructors. Constrains a value to a range."""
736
sass_type_name = u'color'
737
original_literal = None
739
def __init__(self, tokens):
741
self.value = (0, 0, 0, 1)
743
self.value = (0, 0, 0, 1)
744
elif isinstance(tokens, Color):
745
self.value = tokens.value
747
raise TypeError("Can't make Color from %r" % (tokens,))
749
### Alternate constructors
752
def from_rgb(cls, red, green, blue, alpha=1.0, original_literal=None):
753
red = _constrain(red)
754
green = _constrain(green)
755
blue = _constrain(blue)
756
alpha = _constrain(alpha)
758
self = cls.__new__(cls) # TODO
760
# TODO really should store these things internally as 0-1, but can't
761
# until stuff stops examining .value directly
762
self.value = (red * 255.0, green * 255.0, blue * 255.0, alpha)
764
if original_literal is not None:
765
self.original_literal = original_literal
770
def from_hsl(cls, hue, saturation, lightness, alpha=1.0):
771
hue = _constrain(hue)
772
saturation = _constrain(saturation)
773
lightness = _constrain(lightness)
774
alpha = _constrain(alpha)
776
r, g, b = colorsys.hls_to_rgb(hue, lightness, saturation)
777
return cls.from_rgb(r, g, b, alpha)
780
def from_hex(cls, hex_string, literal=False):
781
if not hex_string.startswith('#'):
782
raise ValueError("Expected #abcdef, got %r" % (hex_string,))
785
original_literal = hex_string
787
original_literal = None
789
hex_string = hex_string[1:]
791
# Always include the alpha channel
792
if len(hex_string) == 3:
794
elif len(hex_string) == 6:
797
# Now there should be only two possibilities. Normalize to a list of
799
if len(hex_string) == 4:
800
chunks = [ch * 2 for ch in hex_string]
801
elif len(hex_string) == 8:
803
hex_string[0:2], hex_string[2:4], hex_string[4:6], hex_string[6:8]
806
rgba = [int(ch, 16) / 255 for ch in chunks]
807
return cls.from_rgb(*rgba, original_literal=original_literal)
810
def from_name(cls, name):
811
"""Build a Color from a CSS color name."""
812
self = cls.__new__(cls) # TODO
813
self.original_literal = name
815
r, g, b, a = COLOR_NAMES[name]
817
self.value = r, g, b, a
824
# TODO: deprecate, relies on internals
825
return tuple(self.value[:3])
839
h, l, s = colorsys.rgb_to_hls(*rgba[:3])
849
int(self.value[0] * 1 + 0.5),
850
int(self.value[1] * 1 + 0.5),
851
int(self.value[2] * 1 + 0.5),
852
int(self.value[3] * 255 + 0.5),
856
return '<%s(%s)>' % (self.__class__.__name__, repr(self.value))
859
return hash(self.value)
861
def __eq__(self, other):
862
if not isinstance(other, Color):
863
return Boolean(False)
865
# Scale channels to 255 and round to integers; this allows only 8-bit
866
# color, but Ruby sass makes the same assumption, and otherwise it's
867
# easy to get lots of float errors for HSL colors.
868
left = tuple(round(n) for n in self.rgba255)
869
right = tuple(round(n) for n in other.rgba255)
870
return Boolean(left == right)
872
def __add__(self, other):
873
if isinstance(other, (Color, Number)):
874
return self._operate(other, operator.add)
876
return super(Color, self).__add__(other)
878
def __sub__(self, other):
879
if isinstance(other, (Color, Number)):
880
return self._operate(other, operator.sub)
882
return super(Color, self).__sub__(other)
884
def __mul__(self, other):
885
if isinstance(other, (Color, Number)):
886
return self._operate(other, operator.mul)
888
return super(Color, self).__mul__(other)
890
def __div__(self, other):
891
if isinstance(other, (Color, Number)):
892
return self._operate(other, operator.div)
894
return super(Color, self).__div__(other)
896
def _operate(self, other, op):
897
if isinstance(other, Number):
898
if not other.is_unitless:
899
raise ValueError("Expected unitless Number, got %r" % (other,))
901
other_rgb = (other.value,) * 3
902
elif isinstance(other, Color):
903
if self.alpha != other.alpha:
904
raise ValueError("Alpha channels must match between %r and %r"
907
other_rgb = other.rgb
909
raise TypeError("Expected Color or Number, got %r" % (other,))
912
min(255., max(0., op(left, right)))
915
for (left, right) in zip(self.rgb, other_rgb)
918
return Color.from_rgb(*new_rgb, alpha=self.alpha)
920
def render(self, compress=False):
921
"""Return a rendered representation of the color. If `compress` is
922
true, the shortest possible representation is used; otherwise, named
923
colors are rendered as names and all others are rendered as hex (or
924
with the rgba function).
927
if not compress and self.original_literal:
928
return self.original_literal
932
# TODO this assumes CSS resolution is 8-bit per channel, but so does
934
r, g, b, a = self.value
935
r, g, b = int(round(r)), int(round(g)), int(round(b))
937
# Build a candidate list in order of preference. If `compress` is
938
# True, the shortest candidate is used; otherwise, the first candidate
943
if key in COLOR_LOOKUP:
944
candidates.append(COLOR_LOOKUP[key])
947
# Hex is always shorter than function notation
948
if all(ch % 17 == 0 for ch in (r, g, b)):
949
candidates.append("#%1x%1x%1x" % (r // 17, g // 17, b // 17))
951
candidates.append("#%02x%02x%02x" % (r, g, b))
953
# Can't use hex notation for RGBA
958
candidates.append("rgba(%d,%s%d,%s%d,%s%.2g)" % (r, sp, g, sp, b, sp, a))
961
return min(candidates, key=len)
966
# TODO be unicode-clean and delete this nonsense
967
DEFAULT_STRING_ENCODING = "utf8"
971
"""Represents both CSS quoted string values and CSS identifiers (such as
974
Makes no distinction between single and double quotes, except that the same
975
quotes are preserved on string literals that pass through unmodified.
976
Otherwise, double quotes are used.
979
sass_type_name = u'string'
981
def __init__(self, value, quotes='"'):
982
if isinstance(value, String):
983
# TODO unclear if this should be here, but many functions rely on
986
elif isinstance(value, Number):
987
# TODO this may only be necessary in the case of __radd__ and
991
if isinstance(value, six.binary_type):
992
value = value.decode(DEFAULT_STRING_ENCODING)
994
if not isinstance(value, six.text_type):
995
raise TypeError("Expected string, got {0!r}".format(value))
997
# TODO probably disallow creating an unquoted string outside a
998
# set of chars like [-a-zA-Z0-9]+
1003
# TODO well, at least 3 uses unicode everywhere
1004
self.value = value.encode(DEFAULT_STRING_ENCODING)
1005
self.quotes = quotes
1008
def unquoted(cls, value):
1009
"""Helper to create a string with no quotes."""
1010
return cls(value, quotes=None)
1013
return hash(self.value)
1017
return self.quotes + escape(self.value) + self.quotes
1022
if self.quotes != '"':
1023
quotes = ', quotes=%r' % self.quotes
1026
return '<%s(%s%s)>' % (self.__class__.__name__, repr(self.value), quotes)
1028
def __eq__(self, other):
1029
return Boolean(isinstance(other, String) and self.value == other.value)
1031
def __add__(self, other):
1032
if isinstance(other, String):
1033
other_value = other.value
1035
other_value = other.render()
1038
self.value + other_value,
1039
quotes='"' if self.quotes else None)
1041
def __mul__(self, other):
1042
# DEVIATION: Ruby Sass doesn't do this, because Ruby doesn't. But
1043
# Python does, and in Ruby Sass it's just fatal anyway.
1044
if not isinstance(other, Number):
1045
return super(String, self).__mul__(other)
1047
if not other.is_unitless:
1048
raise TypeError("Can only multiply strings by unitless numbers")
1052
raise ValueError("Can only multiply strings by integers")
1054
return String(self.value * int(other.value), quotes=self.quotes)
1056
def render(self, compress=False):
1057
return self.__str__()
1061
sass_type_name = u'map'
1063
def __init__(self, pairs, index=None):
1064
self.pairs = tuple(pairs)
1068
for key, value in pairs:
1069
self.index[key] = value
1074
return "<Map: (%s)>" % (", ".join("%s: %s" % pair for pair in self.pairs),)
1077
return hash(self.pairs)
1080
return len(self.pairs)
1083
return iter(self.pairs)
1085
def __getitem__(self, index):
1086
return List(self.pairs[index], use_comma=True)
1088
def __eq__(self, other):
1090
return self.pairs == other.to_pairs()
1092
return NotImplemented
1100
def render(self, compress=False):
1101
raise TypeError("Cannot render map %r as CSS" % (self,))
1104
def expect_type(value, types, unit=any):
1105
if not isinstance(value, types):
1106
if isinstance(types, type):
1108
sass_type_names = list(set(t.sass_type_name for t in types))
1109
sass_type_names.sort()
1111
# Join with commas in English fashion
1112
if len(sass_type_names) == 1:
1113
sass_type = sass_type_names[0]
1114
elif len(sass_type_names) == 2:
1115
sass_type = u' or '.join(sass_type_names)
1117
sass_type = u', '.join(sass_type_names[:-1])
1118
sass_type += u', or ' + sass_type_names[-1]
1120
raise TypeError("Expected %s, got %r" % (sass_type, value))
1122
if unit is not any and isinstance(value, Number):
1123
if unit is None and not value.is_unitless:
1124
raise ValueError("Expected unitless number, got %r" % (value,))
1126
elif unit == '%' and not (
1127
value.is_unitless or value.is_simple_unit('%')):
1128
raise ValueError("Expected unitless number or percentage, got %r" % (value,))