summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2013-08-08 18:55:02 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2013-08-08 18:56:06 -0700
commit028b73079c221f97f7ff1a9537a88279b8c0b098 (patch)
tree6436c2464f77a9a52d141f93a0526b40e64b9eaa
parent2879d05f396aa78cd252cf7dc0474fd3e55606d9 (diff)
downloadpyscss-028b73079c221f97f7ff1a9537a88279b8c0b098.tar.gz
Clean up ListValue, rename to List.
Tried very hard to clean up all uses in the compass library; hope I got them all!
-rw-r--r--scss/__init__.py7
-rw-r--r--scss/functions/compass/gradients.py51
-rw-r--r--scss/functions/compass/helpers.py153
-rw-r--r--scss/functions/compass/images.py18
-rw-r--r--scss/functions/compass/sprites.py36
-rw-r--r--scss/functions/core.py72
-rw-r--r--scss/functions/extra.py59
-rw-r--r--scss/tests/functions/test_core.py3
-rw-r--r--scss/types.py99
9 files changed, 243 insertions, 255 deletions
diff --git a/scss/__init__.py b/scss/__init__.py
index b9764b1..85ca173 100644
--- a/scss/__init__.py
+++ b/scss/__init__.py
@@ -1001,19 +1001,16 @@ class Scss(object):
if not name:
return
- name = ListValue(name)
+ iterable = ListValue(name)
var = var.strip()
var = calculator.do_glob_math(var)
var = normalize_var(var)
- for n, v in name.items():
- v = to_str(v)
+ for v in iterable:
inner_rule = rule.copy()
inner_rule.namespace = inner_rule.namespace.derive()
inner_rule.unparsed_contents = block.unparsed_contents
inner_rule.namespace.set_variable(var, v)
- if not isinstance(n, int):
- inner_rule.namespace.set_variable(n, v)
self.manage_children(inner_rule, p_children, scope)
# @print_timing(10)
diff --git a/scss/functions/compass/gradients.py b/scss/functions/compass/gradients.py
index 6698283..73dd46b 100644
--- a/scss/functions/compass/gradients.py
+++ b/scss/functions/compass/gradients.py
@@ -11,7 +11,7 @@ import six
from scss.functions.library import FunctionLibrary
from scss.functions.compass.helpers import opposite_position, position
-from scss.types import ColorValue, ListValue, NumberValue, StringValue
+from scss.types import ColorValue, List, NumberValue, StringValue
from scss.util import escape, split_params, to_float, to_str
log = logging.getLogger(__name__)
@@ -23,8 +23,8 @@ register = COMPASS_GRADIENTS_LIBRARY.register
def __color_stops(percentages, *args):
if len(args) == 1:
- if isinstance(args[0], (list, tuple, ListValue)):
- return ListValue(args[0]).values()
+ if isinstance(args[0], (list, tuple, List)):
+ list(args[0])
elif isinstance(args[0], (StringValue, six.string_types)):
color_stops = []
colors = split_params(args[0].value)
@@ -43,12 +43,7 @@ def __color_stops(percentages, *args):
stops = []
prev_color = False
for c in args:
- if isinstance(c, ListValue):
- cs = c.values()
- else:
- cs = [c]
-
- for c in cs:
+ for c in List.from_maybe(c):
if isinstance(c, ColorValue):
if prev_color:
stops.append(None)
@@ -98,8 +93,7 @@ def __color_stops(percentages, *args):
@register('grad-color-stops')
def grad_color_stops(*args):
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
+ args = List.from_maybe_starargs(args)
color_stops = __color_stops(True, *args)
ret = ', '.join(['color-stop(%s, %s)' % (to_str(s), c) for s, c in color_stops])
return StringValue(ret)
@@ -123,7 +117,7 @@ def grad_point(*p):
vrt = NumberValue(0, '%')
elif 'bottom' in pos:
vrt = NumberValue(1, '%')
- return ListValue([v for v in (hrz, vrt) if v is not None])
+ return List([v for v in (hrz, vrt) if v is not None])
def __grad_position(index, default, radial, color_stops):
@@ -144,8 +138,7 @@ def grad_end_position(*color_stops):
@register('color-stops')
def color_stops(*args):
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
+ args = List.from_maybe_starargs(args)
color_stops = __color_stops(False, *args)
ret = ', '.join(['%s %s' % (c, to_str(s)) for s, c in color_stops])
return StringValue(ret)
@@ -153,8 +146,7 @@ def color_stops(*args):
@register('color-stops-in-percentages')
def color_stops_in_percentages(*args):
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
+ args = List.from_maybe_starargs(args)
color_stops = __color_stops(True, *args)
ret = ', '.join(['%s %s' % (c, to_str(s)) for s, c in color_stops])
return StringValue(ret)
@@ -164,7 +156,7 @@ def _get_gradient_position_and_angle(args):
for arg in args:
if isinstance(arg, (StringValue, NumberValue, six.string_types)):
_arg = [arg]
- elif isinstance(arg, (list, tuple, ListValue)):
+ elif isinstance(arg, (list, tuple, List)):
_arg = arg
else:
continue
@@ -194,7 +186,7 @@ def _get_gradient_shape_and_size(args):
for arg in args:
if isinstance(arg, (StringValue, NumberValue, six.string_types)):
_arg = [arg]
- elif isinstance(arg, (list, tuple, ListValue)):
+ elif isinstance(arg, (list, tuple, List)):
_arg = arg
else:
continue
@@ -212,20 +204,16 @@ def _get_gradient_shape_and_size(args):
def _get_gradient_color_stops(args):
color_stops = []
for arg in args:
- if isinstance(arg, ColorValue):
- color_stops.append(arg)
- elif isinstance(arg, (list, tuple, ListValue)):
- for a in arg:
- if isinstance(a, ColorValue):
- color_stops.append(arg)
- break
+ for a in List.from_maybe(arg):
+ if isinstance(a, ColorValue):
+ color_stops.append(arg)
+ break
return color_stops or None
@register('radial-gradient')
def radial_gradient(*args):
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
+ args = List.from_maybe_starargs(args)
position_and_angle = _get_gradient_position_and_angle(args)
shape_and_size = _get_gradient_shape_and_size(args)
@@ -282,8 +270,7 @@ def radial_gradient(*args):
@register('linear-gradient')
def linear_gradient(*args):
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
+ args = List.from_maybe_starargs(args)
position_and_angle = _get_gradient_position_and_angle(args)
color_stops = _get_gradient_color_stops(args)
@@ -343,8 +330,7 @@ def linear_gradient(*args):
@register('radial-svg-gradient')
def radial_svg_gradient(*args):
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
+ args = List.from_maybe_starargs(args)
color_stops = args
center = None
if isinstance(args[-1], (StringValue, NumberValue, six.string_types)):
@@ -361,8 +347,7 @@ def radial_svg_gradient(*args):
@register('linear-svg-gradient')
def linear_svg_gradient(*args):
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
+ args = List.from_maybe_starargs(args)
color_stops = args
start = None
if isinstance(args[-1], (StringValue, NumberValue, six.string_types)):
diff --git a/scss/functions/compass/helpers.py b/scss/functions/compass/helpers.py
index 62bdfbb..a12efb5 100644
--- a/scss/functions/compass/helpers.py
+++ b/scss/functions/compass/helpers.py
@@ -17,7 +17,7 @@ import warnings
from scss import config
from scss.functions.library import FunctionLibrary
-from scss.types import BooleanValue, ListValue, Null, NumberValue, QuotedStringValue, StringValue
+from scss.types import BooleanValue, List, Null, NumberValue, QuotedStringValue, StringValue
from scss.util import escape, to_str
log = logging.getLogger(__name__)
@@ -51,7 +51,7 @@ def blank(*objs):
is_blank = not o
elif isinstance(o, QuotedStringValue):
is_blank = not len(o.value.strip())
- elif isinstance(o, ListValue):
+ elif isinstance(o, List):
is_blank = all(blank(el) for el in o)
else:
is_blank = False
@@ -65,37 +65,35 @@ def blank(*objs):
@register('compact')
def compact(*args):
"""Returns a new list after removing any non-true values"""
- sep = ','
- if len(args) == 1 and isinstance(args[0], ListValue):
- sep = args[0].value.get('_', '')
+ use_comma = True
+ if len(args) == 1 and isinstance(args[0], List):
+ use_comma = args[0].use_comma
args = args[0]
- return ListValue(
+ return List(
[arg for arg in args if arg],
- separator=sep,
+ use_comma=use_comma,
)
@register('reject')
def reject(lst, *values):
"""Removes the given values from the list"""
- ret = {}
- if not isinstance(lst, ListValue):
- lst = ListValue(lst)
- lst = lst.value
+ if not isinstance(lst, List):
+ lst = List(lst)
+
if len(values) == 1:
values = values[0]
- if isinstance(values, ListValue):
- values = values.value.values()
- elif not isinstance(values, (list, tuple)):
- values = ListValue([values])
- for i, item in lst.items():
+ if isinstance(values, (list, tuple, List)):
+ values = frozenset(values)
+ else:
+ values = frozenset([values])
+
+ ret = []
+ for item in lst:
if item not in values:
- ret[i] = item
- separator = lst.get('_', None)
- if separator is not None:
- ret['_'] = separator
- return ListValue(ret)
+ ret.append(item)
+ return List(ret, use_comma=lst.use_comma)
@register('first-value-of')
@@ -103,7 +101,7 @@ def first_value_of(lst):
if isinstance(lst, QuotedStringValue):
first = lst.value.split()[0]
return type(lst)(first)
- elif isinstance(lst, ListValue):
+ elif isinstance(lst, List):
if len(lst):
return lst[0]
else:
@@ -114,15 +112,12 @@ def first_value_of(lst):
@register('-compass-list')
def dash_compass_list(*args):
- separator = None
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
- else:
- separator = ','
- ret = ListValue(args)
- if separator:
- ret.value['_'] = separator
- return ret
+ use_comma = False
+ if len(args) == 1 and isinstance(args[0], (list, tuple, List)):
+ args = args[0]
+ if isinstance(args, List):
+ use_comma = args.use_comma
+ return List(args, use_comma=use_comma)
@register('-compass-space-list')
@@ -141,14 +136,12 @@ def dash_compass_slice(lst, start_index, end_index=None):
start_index = NumberValue(start_index).value
end_index = NumberValue(end_index).value if end_index is not None else None
ret = {}
- lst = ListValue(lst).value
- for i, item in lst.items():
- if not isinstance(i, int):
- if i == '_':
- ret[i] = item
- elif i > start_index and end_index is None or i <= end_index:
- ret[i] = item
- return ListValue(ret)
+ lst = List(lst)
+ if end_index:
+ # This function has an inclusive end, but Python slicing is exclusive
+ end_index += 1
+ ret = lst.value[start_index:end_index]
+ return List(ret, use_comma=lst.use_comma)
# ------------------------------------------------------------------------------
@@ -158,8 +151,8 @@ def dash_compass_slice(lst, start_index, end_index=None):
def prefixed(prefix, *args):
to_fnct_str = 'to_' + to_str(prefix).replace('-', '_')
for arg in args:
- if isinstance(arg, ListValue):
- for k, iarg in arg.value.items():
+ if isinstance(arg, List):
+ for iarg in arg:
if hasattr(iarg, to_fnct_str):
return BooleanValue(True)
else:
@@ -173,22 +166,21 @@ def prefix(prefix, *args):
to_fnct_str = 'to_' + to_str(prefix).replace('-', '_')
args = list(args)
for i, arg in enumerate(args):
- if isinstance(arg, ListValue):
- _value = {}
- for k, iarg in arg.value.items():
+ if isinstance(arg, List):
+ _value = []
+ for iarg in arg:
to_fnct = getattr(iarg, to_fnct_str, None)
if to_fnct:
- _value[k] = to_fnct()
+ _value.append(to_fnct())
else:
- _value[k] = iarg
- args[i] = ListValue(_value)
+ _value.append(iarg)
+ args[i] = List(_value)
else:
to_fnct = getattr(arg, to_fnct_str, None)
if to_fnct:
args[i] = to_fnct()
- if len(args) == 1:
- return args[0]
- return ListValue(args, ',')
+
+ return List.maybe_new(args, use_comma=True)
@register('-moz')
@@ -233,8 +225,8 @@ def dash_o(*args):
@register('append-selector', 2)
def append_selector(selector, to_append):
- if isinstance(selector, ListValue):
- lst = selector.values()
+ if isinstance(selector, List):
+ lst = selector.value
else:
lst = StringValue(selector).value.split(',')
to_append = StringValue(to_append).value.strip()
@@ -257,18 +249,18 @@ _elements_of_type_html5_block = 'article, aside, details, figcaption, figure, fo
_elements_of_type_html5_inline = 'audio, canvas, command, datalist, embed, keygen, mark, meter, output, progress, rp, rt, ruby, time, video, wbr'
_elements_of_type_html5 = 'article, aside, audio, canvas, command, datalist, details, embed, figcaption, figure, footer, header, hgroup, keygen, mark, menu, meter, nav, output, progress, rp, rt, ruby, section, summary, time, video, wbr'
_elements_of_type = {
- 'block': dict(enumerate(sorted(_elements_of_type_block.replace(' ', '').split(',')))),
- 'inline': dict(enumerate(sorted(_elements_of_type_inline.replace(' ', '').split(',')))),
- 'table': dict(enumerate(sorted(_elements_of_type_table.replace(' ', '').split(',')))),
- 'list-item': dict(enumerate(sorted(_elements_of_type_list_item.replace(' ', '').split(',')))),
- 'table-row-group': dict(enumerate(sorted(_elements_of_type_table_row_group.replace(' ', '').split(',')))),
- 'table-header-group': dict(enumerate(sorted(_elements_of_type_table_header_group.replace(' ', '').split(',')))),
- 'table-footer-group': dict(enumerate(sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')))),
- 'table-row': dict(enumerate(sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')))),
- 'table-cell': dict(enumerate(sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')))),
- 'html5-block': dict(enumerate(sorted(_elements_of_type_html5_block.replace(' ', '').split(',')))),
- 'html5-inline': dict(enumerate(sorted(_elements_of_type_html5_inline.replace(' ', '').split(',')))),
- 'html5': dict(enumerate(sorted(_elements_of_type_html5.replace(' ', '').split(',')))),
+ 'block': sorted(_elements_of_type_block.replace(' ', '').split(',')),
+ 'inline': sorted(_elements_of_type_inline.replace(' ', '').split(',')),
+ 'table': sorted(_elements_of_type_table.replace(' ', '').split(',')),
+ 'list-item': sorted(_elements_of_type_list_item.replace(' ', '').split(',')),
+ 'table-row-group': sorted(_elements_of_type_table_row_group.replace(' ', '').split(',')),
+ 'table-header-group': sorted(_elements_of_type_table_header_group.replace(' ', '').split(',')),
+ 'table-footer-group': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')),
+ 'table-row': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')),
+ 'table-cell': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')),
+ 'html5-block': sorted(_elements_of_type_html5_block.replace(' ', '').split(',')),
+ 'html5-inline': sorted(_elements_of_type_html5_inline.replace(' ', '').split(',')),
+ 'html5': sorted(_elements_of_type_html5.replace(' ', '').split(',')),
}
@register('elements-of-type', 1)
@@ -277,8 +269,7 @@ def elements_of_type(display):
ret = _elements_of_type.get(d.value, None)
if ret is None:
raise Exception("Elements of type '%s' not found!" % d.value)
- ret['_'] = ','
- return ListValue(ret)
+ return List(ret, use_comma=True)
@register('enumerate', 3)
@@ -306,7 +297,7 @@ def enumerate_(prefix, frm, through, separator='-'):
else:
ret.append(NumberValue(i))
- return ListValue(ret, separator=',')
+ return List(ret, use_comma=True)
@register('headers', 0)
@@ -343,14 +334,14 @@ def headers(frm=None, to=None):
@register('nest')
def nest(*arguments):
- if isinstance(arguments[0], ListValue):
- lst = arguments[0].values()
+ if isinstance(arguments[0], List):
+ lst = arguments[0].value
else:
lst = StringValue(arguments[0]).value.split(',')
ret = [unicode(s).strip() for s in lst if unicode(s).strip()]
for arg in arguments[1:]:
- if isinstance(arg, ListValue):
- lst = arg.values()
+ if isinstance(arg, List):
+ lst = arg.value
else:
lst = StringValue(arg).value.split(',')
new_ret = []
@@ -366,10 +357,8 @@ def nest(*arguments):
else:
new_ret.append(r + ' ' + s)
ret = new_ret
- ret = sorted(set(ret))
- ret = dict(enumerate(ret))
- ret['_'] = ','
- return ret
+
+ return List(sorted(set(ret)), use_comma=True)
# This isn't actually from Compass, but it's just a shortcut for enumerate().
@@ -393,8 +382,8 @@ OPPOSITE_POSITIONS = dict(
)
def _position(opposite, positions):
- if isinstance(positions, ListValue):
- positions = positions.values()
+ if isinstance(positions, List):
+ positions = positions.value
else:
positions = [positions]
@@ -431,7 +420,7 @@ def _position(opposite, positions):
if len(ret) == 1:
return ret[0]
else:
- return ListValue(ret, separator='')
+ return List(ret, use_comma=False)
@register('position')
@@ -495,12 +484,12 @@ def _font_url(path, only_path=False, cache_buster=True, inline=False):
def _font_files(args, inline):
- if len(args) == 1 and isinstance(args[0], (list, tuple, ListValue)):
- args = ListValue(args[0]).values()
+ if len(args) == 1 and isinstance(args[0], (list, tuple, List)):
+ args = list(args[0])
n = 0
params = [[], []]
for arg in args:
- if isinstance(arg, ListValue):
+ if isinstance(arg, List):
if len(arg) == 2:
if n % 2 == 1:
params[1].append(None)
@@ -527,7 +516,7 @@ def _font_files(args, inline):
fonts.append('%s format("%s")' % (_font_url(font, inline=inline), StringValue(format).value))
else:
fonts.append(_font_url(font, inline=inline))
- return ListValue(fonts)
+ return List(fonts)
@register('font-url', 1)
diff --git a/scss/functions/compass/images.py b/scss/functions/compass/images.py
index d332684..f8ebb6f 100644
--- a/scss/functions/compass/images.py
+++ b/scss/functions/compass/images.py
@@ -17,7 +17,7 @@ from scss import config
from scss.functions.compass import _image_size_cache
from scss.functions.compass.helpers import add_cache_buster
from scss.functions.library import FunctionLibrary
-from scss.types import ColorValue, ListValue, NumberValue, StringValue
+from scss.types import ColorValue, List, NumberValue, StringValue
from scss.util import escape
try:
@@ -68,26 +68,16 @@ def _image_url(path, only_path=False, cache_buster=True, dst_color=None, src_col
filetime = 'NA'
BASE_URL = config.STATIC_URL
if path:
- dst_colors = dst_color
- if isinstance(dst_colors, ListValue):
- dst_colors = [list(ColorValue(v).value[:3]) for n, v in dst_colors.items() if v]
- else:
- dst_colors = [list(ColorValue(dst_colors).value[:3])] if dst_colors else []
+ dst_colors = [list(ColorValue(v).value[:3]) for v in List.from_maybe(dst_color) if v]
src_colors = src_color
- if isinstance(src_colors, ListValue):
- src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for n, v in src_colors.items()]
- else:
- src_colors = [tuple(ColorValue(src_colors).value[:3]) if src_colors else (0, 0, 0)]
+ src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for v in List.from_maybe(src_color)]
len_colors = max(len(dst_colors), len(src_colors))
dst_colors = (dst_colors * len_colors)[:len_colors]
src_colors = (src_colors * len_colors)[:len_colors]
- if isinstance(spacing, ListValue):
- spacing = [int(NumberValue(v).value) for n, v in spacing.items()]
- else:
- spacing = [int(NumberValue(spacing).value)]
+ spacing = [int(NumberValue(v).value) for v in List.from_maybe(spacing)]
spacing = (spacing * 4)[:4]
file_name, file_ext = os.path.splitext(os.path.normpath(filepath).replace('\\', '_').replace('/', '_'))
diff --git a/scss/functions/compass/sprites.py b/scss/functions/compass/sprites.py
index a441e20..5dd7fcf 100644
--- a/scss/functions/compass/sprites.py
+++ b/scss/functions/compass/sprites.py
@@ -33,7 +33,7 @@ from scss import config
from scss.functions.compass import _image_size_cache
from scss.functions.compass.layouts import PackedSpritesLayout, HorizontalSpritesLayout, VerticalSpritesLayout, DiagonalSpritesLayout
from scss.functions.library import FunctionLibrary
-from scss.types import ColorValue, ListValue, NumberValue, QuotedStringValue, StringValue
+from scss.types import ColorValue, List, NumberValue, QuotedStringValue, StringValue
from scss.util import escape
log = logging.getLogger(__name__)
@@ -172,7 +172,7 @@ def sprite_map(g, **kwargs):
direction = kwargs.get('direction', config.SPRTE_MAP_DIRECTION)
repeat = StringValue(kwargs.get('repeat', 'no-repeat'))
collapse = kwargs.get('collapse') or 0
- if isinstance(collapse, ListValue):
+ if isinstance(collapse, List):
collapse_x = int(NumberValue(collapse[0]).value)
collapse_y = int(NumberValue(collapse[-1]).value)
else:
@@ -194,22 +194,13 @@ def sprite_map(g, **kwargs):
position = 1.0
padding = kwargs.get('padding', kwargs.get('spacing', 0))
- if isinstance(padding, ListValue):
- padding = [int(NumberValue(v).value) for n, v in padding.items()]
- else:
- padding = [int(NumberValue(padding).value)]
+ padding = [int(NumberValue(v).value) for v in List.from_maybe(padding)]
padding = (padding * 4)[:4]
dst_colors = kwargs.get('dst_color')
- if isinstance(dst_colors, ListValue):
- dst_colors = [list(ColorValue(v).value[:3]) for n, v in dst_colors.items() if v]
- else:
- dst_colors = [list(ColorValue(dst_colors).value[:3])] if dst_colors else []
+ dst_colors = [list(ColorValue(v).value[:3]) for v in List.from_maybe(dst_colors) if v]
src_colors = kwargs.get('src_color')
- if isinstance(src_colors, ListValue):
- src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for n, v in src_colors.items()]
- else:
- src_colors = [tuple(ColorValue(src_colors).value[:3]) if src_colors else (0, 0, 0)]
+ src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for v in List.from_maybe(src_colors)]
len_colors = max(len(dst_colors), len(src_colors))
dst_colors = (dst_colors * len_colors)[:len_colors]
src_colors = (src_colors * len_colors)[:len_colors]
@@ -253,10 +244,7 @@ def sprite_map(g, **kwargs):
if _padding is None:
_padding = padding
else:
- if isinstance(_padding, ListValue):
- _padding = [int(NumberValue(v).value) for n, v in _padding.items()]
- else:
- _padding = [int(NumberValue(_padding).value)]
+ _padding = [int(NumberValue(v).value) for v in List.from_maybe(_padding)]
_padding = (_padding * 4)[:4]
all_paddings.append(_padding)
@@ -267,18 +255,12 @@ def sprite_map(g, **kwargs):
has_dst_colors = True
else:
has_dst_colors = True
- if isinstance(_dst_colors, ListValue):
- _dst_colors = [list(ColorValue(v).value[:3]) for n, v in _dst_colors.items() if v]
- else:
- _dst_colors = [list(ColorValue(_dst_colors).value[:3])] if _dst_colors else []
+ _dst_colors = [list(ColorValue(v).value[:3]) for v in List.from_maybe(_dst_colors) if v]
_src_colors = kwargs.get(name + '_src_color')
if _src_colors is None:
_src_colors = src_colors
else:
- if isinstance(_src_colors, ListValue):
- _src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for n, v in _src_colors.items()]
- else:
- _src_colors = [tuple(ColorValue(_src_colors).value[:3]) if _src_colors else (0, 0, 0)]
+ _src_colors = [tuple(ColorValue(v).value[:3]) if v else (0, 0, 0) for v in List.from_maybe(_src_colors)]
_len_colors = max(len(_dst_colors), len(_src_colors))
_dst_colors = (_dst_colors * _len_colors)[:_len_colors]
_src_colors = (_src_colors * _len_colors)[:_len_colors]
@@ -419,7 +401,7 @@ def sprite_file(map, sprite):
def sprites(map):
map = StringValue(map).value
sprite_map = sprite_maps.get(map, {})
- return ListValue(sorted(s for s in sprite_map if not s.startswith('*')))
+ return List(list(sorted(s for s in sprite_map if not s.startswith('*'))))
@register('sprite', 2)
diff --git a/scss/functions/core.py b/scss/functions/core.py
index dd4b00e..2d49a0d 100644
--- a/scss/functions/core.py
+++ b/scss/functions/core.py
@@ -14,7 +14,7 @@ from six.moves import xrange
from scss.cssdefs import _variable_re
from scss.functions.library import FunctionLibrary
-from scss.types import BooleanValue, ColorValue, ListValue, NumberValue, QuotedStringValue, StringValue, String
+from scss.types import BooleanValue, ColorValue, List, NumberValue, QuotedStringValue, StringValue, String
log = logging.getLogger(__name__)
@@ -428,16 +428,21 @@ CORE_LIBRARY.add(NumberValue.wrap_python_function(math.floor), 'floor', 1)
# ------------------------------------------------------------------------------
# List functions
-def __parse_separator(separator):
+def __parse_separator(separator, default_from=None):
if separator is None:
return None
separator = StringValue(separator).value
if separator == 'comma':
- return ','
+ return True
elif separator == 'space':
- return ' '
+ return False
elif separator == 'auto':
- return None
+ if not default_from:
+ return True
+ elif len(default_from) < 2:
+ return True
+ else:
+ return default_from.use_comma
else:
raise ValueError('Separator must be auto, comma, or space')
@@ -446,9 +451,8 @@ def __parse_separator(separator):
@register('-compass-list-size')
@register('length')
def _length(*lst):
- if len(lst) == 1 and isinstance(lst[0], (list, tuple, ListValue)):
- lst = ListValue(lst[0]).values()
- lst = ListValue(lst)
+ if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)):
+ lst = lst[0]
return NumberValue(len(lst))
@@ -460,7 +464,7 @@ def nth(lst, n):
Return the Nth item in the string
"""
n = NumberValue(n).value
- lst = ListValue(lst).value
+ lst = List(lst).value
try:
n = int(float(n)) - 1
n = n % len(lst)
@@ -483,39 +487,37 @@ def nth(lst, n):
@register('join', 2)
@register('join', 3)
def join(lst1, lst2, separator=None):
- ret = ListValue(lst1)
- lst2 = ListValue(lst2).value
- lst_len = len(ret.value)
- ret.value.update((k + lst_len if isinstance(k, int) else k, v) for k, v in lst2.items())
- separator = __parse_separator(separator)
- if separator is not None:
- ret.value['_'] = separator
- return ret
+ ret = []
+ ret.extend(List.from_maybe(lst1))
+ ret.extend(List.from_maybe(lst2))
+
+ use_comma = __parse_separator(separator, default_from=lst1)
+ return List(ret, use_comma=use_comma)
@register('min')
def min_(*lst):
- if len(lst) == 1 and isinstance(lst[0], (list, tuple, ListValue)):
- lst = ListValue(lst[0]).values()
- lst = ListValue(lst).value
- return min(lst.values())
+ if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)):
+ lst = lst[0]
+ return min(lst)
@register('max')
def max_(*lst):
- if len(lst) == 1 and isinstance(lst[0], (list, tuple, ListValue)):
- lst = ListValue(lst[0]).values()
- lst = ListValue(lst).value
- return max(lst.values())
+ if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)):
+ lst = lst[0]
+ return max(lst)
@register('append', 2)
@register('append', 3)
def append(lst, val, separator=None):
- separator = __parse_separator(separator)
- ret = ListValue(lst, separator)
- ret.value[len(ret)] = val
- return ret
+ ret = []
+ ret.extend(List.from_maybe(lst))
+ ret.append(val)
+
+ use_comma = __parse_separator(separator, default_from=lst)
+ return List(ret, use_comma=use_comma)
@register('index', 2)
@@ -535,9 +537,15 @@ def _type_of(obj): # -> bool, number, string, color, list
@register('unit', 1)
-def _unit(number): # -> px, em, cm, etc.
- unit = NumberValue(number).unit
- return StringValue(unit)
+def unit(number): # -> px, em, cm, etc.
+ numer = '*'.join(number.unit_numer)
+ denom = '*'.join(number.unit_denom)
+
+ if denom:
+ ret = numer + '/' + denom
+ else:
+ ret = numer
+ return StringValue(ret)
@register('unitless', 1)
diff --git a/scss/functions/extra.py b/scss/functions/extra.py
index 66f05e6..70ad480 100644
--- a/scss/functions/extra.py
+++ b/scss/functions/extra.py
@@ -13,7 +13,7 @@ from six.moves import xrange
from scss import config
from scss.functions.library import FunctionLibrary
-from scss.types import ColorValue, NumberValue, StringValue, ListValue
+from scss.types import ColorValue, NumberValue, StringValue, List
from scss.util import escape
try:
@@ -273,25 +273,10 @@ def background_noise(density=None, opacity=None, size=None, monochrome=False, in
if not Image:
raise Exception("Images manipulation require PIL")
- if isinstance(density, ListValue):
- density = [NumberValue(v).value for n, v in density.items()]
- else:
- density = [NumberValue(density).value]
-
- if isinstance(intensity, ListValue):
- intensity = [NumberValue(v).value for n, v in intensity.items()]
- else:
- intensity = [NumberValue(intensity).value]
-
- if isinstance(color, ListValue):
- color = [ColorValue(v).value for n, v in color.items() if v]
- else:
- color = [ColorValue(color).value] if color else []
-
- if isinstance(opacity, ListValue):
- opacity = [NumberValue(v).value for n, v in opacity.items()]
- else:
- opacity = [NumberValue(opacity).value]
+ density = [NumberValue(v).value for v in List.from_maybe(density)]
+ intensity = [NumberValue(v).value for v in List.from_maybe(intensity)]
+ color = [ColorValue(v).value for v in List.from_maybe(color) if v]
+ opacity = [NumberValue(v).value for v in List.from_maybe(opacity)]
size = int(NumberValue(size).value) if size else 0
if size < 1 or size > 512:
@@ -347,25 +332,10 @@ def background_brushed(density=None, intensity=None, color=None, opacity=None, s
if not Image:
raise Exception("Images manipulation require PIL")
- if isinstance(density, ListValue):
- density = [NumberValue(v).value for n, v in density.items()]
- else:
- density = [NumberValue(density).value]
-
- if isinstance(intensity, ListValue):
- intensity = [NumberValue(v).value for n, v in intensity.items()]
- else:
- intensity = [NumberValue(intensity).value]
-
- if isinstance(color, ListValue):
- color = [ColorValue(v).value for n, v in color.items() if v]
- else:
- color = [ColorValue(color).value] if color else []
-
- if isinstance(opacity, ListValue):
- opacity = [NumberValue(v).value for n, v in opacity.items()]
- else:
- opacity = [NumberValue(opacity).value]
+ density = [NumberValue(v).value for v in List.from_maybe(density)]
+ intensity = [NumberValue(v).value for v in List.from_maybe(intensity)]
+ color = [ColorValue(v).value for v in List.from_maybe(color) if v]
+ opacity = [NumberValue(v).value for v in List.from_maybe(opacity)]
size = int(NumberValue(size).value) if size else -1
if size < 0 or size > 512:
@@ -373,15 +343,8 @@ def background_brushed(density=None, intensity=None, color=None, opacity=None, s
monochrome = bool(monochrome)
- if isinstance(direction, ListValue):
- direction = [NumberValue(v).value for n, v in direction.items()]
- else:
- direction = [NumberValue(direction).value]
-
- if isinstance(spread, ListValue):
- spread = [NumberValue(v).value for n, v in spread.items()]
- else:
- spread = [NumberValue(spread).value]
+ direction = [NumberValue(v).value for v in List.from_maybe(direction)]
+ spread = [NumberValue(v).value for v in List.from_maybe(spread)]
background = ColorValue(background).value if background else None
diff --git a/scss/tests/functions/test_core.py b/scss/tests/functions/test_core.py
index 53b03b0..d02a479 100644
--- a/scss/tests/functions/test_core.py
+++ b/scss/tests/functions/test_core.py
@@ -260,7 +260,6 @@ def test_nth(calc):
assert calc('nth(10px 20px 30px, 1)') == calc('10px')
assert calc('nth((Helvetica, Arial, sans-serif), 3)') == calc('sans-serif')
-@xfail(reason="second example broken, unclear why")
def test_join(calc):
# Examples from the Ruby docs
assert calc('join(10px 20px, 30px 40px)') == calc('10px 20px 30px 40px')
@@ -269,7 +268,6 @@ def test_join(calc):
assert calc('join(10px, 20px, comma)') == calc('10px, 20px')
assert calc('join((blue, red), (#abc, #def), space)') == calc('blue red #abc #def')
-@xfail(reason="possibly a problem with list equality?")
def test_append(calc):
# Examples from the Ruby docs
assert calc('append(10px 20px, 30px)') == calc('10px 20px 30px')
@@ -301,7 +299,6 @@ def test_type_of(calc):
assert calc('type-of(#fff)') == calc('color')
assert calc('type-of(blue)') == calc('color')
-@xfail(reason="numeric type does not yet understand multiple units")
def test_unit(calc):
# Examples from the Ruby docs
assert calc('unit(100)') == calc('""')
diff --git a/scss/types.py b/scss/types.py
index 47d4f56..89aa226 100644
--- a/scss/types.py
+++ b/scss/types.py
@@ -391,10 +391,32 @@ class NumberValue(Value):
return val + unit
-class ListValue(Value):
+class List(Value):
+ """A list of other values. May be delimited by commas or spaces.
+
+ Lists of one item don't make much sense in CSS, but can exist in Sass. Use ......
+
+ Lists may also contain zero items, but these are forbidden from appearing
+ in CSS output.
+ """
+
sass_type_name = u'list'
- def __init__(self, tokens, separator=None):
+ def __init__(self, tokens, separator=None, use_comma=True):
+ if isinstance(tokens, ListValue):
+ tokens = tokens.value
+
+ if not isinstance(tokens, (list, tuple)):
+ raise TypeError("Expected list, got %r" % (tokens,))
+
+ self.value = list(tokens)
+ # TODO...
+ self.use_comma = separator == ","
+ return
+
+
+
+
self.tokens = tokens
if tokens is None:
self.value = {}
@@ -423,10 +445,65 @@ class ListValue(Value):
if separator:
self.value['_'] = separator
+ @classmethod
+ def maybe_new(cls, values, use_comma=True):
+ """If `values` contains only one item, return that item. Otherwise,
+ return a List as normal.
+ """
+ if len(values) == 1:
+ return values[0]
+ else:
+ return cls(values, use_comma=use_comma)
+
+ def maybe(self):
+ """If this List contains only one item, return it. Otherwise, return
+ the List.
+ """
+ if len(self.value) == 1:
+ return self.value[0]
+ else:
+ return self
+
+ @classmethod
+ def from_maybe(cls, values, use_comma=True):
+ """If `values` appears to not be a list, return a list containing it.
+ Otherwise, return a List as normal.
+ """
+ if not isinstance(values, (list, tuple, List)):
+ values = [values]
+
+ return cls(values, use_comma=use_comma)
+
+ @classmethod
+ def from_maybe_starargs(cls, args):
+ """If `args` has one element which appears to be a list, return it.
+ Otherwise, return a list as normal.
+
+ Mainly used by Sass function implementations that predate `...`
+ support, so they can accept both a list of arguments and a single list
+ stored in a variable.
+ """
+ if len(args) == 1:
+ if isinstance(args[0], cls):
+ return args[0]
+ elif isinstance(args[0], (list, tuple)):
+ return cls(args[0])
+
+ return cls(args)
+
@property
def separator(self):
return self.value.get('_', '')
+ def delimiter(self, compress=False):
+ if self.use_comma:
+ if compress:
+ return ','
+ else:
+ return ', '
+ else:
+ return ' '
+
@classmethod
def _do_op(cls, first, second, op):
if isinstance(first, ListValue) and isinstance(second, ListValue):
@@ -453,34 +530,32 @@ class ListValue(Value):
return dict((i if isinstance(k, int) else k, v) for i, (k, v) in enumerate(sorted(lst.items())))
def __len__(self):
- return len(self.value) - (1 if '_' in self.value else 0)
+ return len(self.value)
def __str__(self):
return self.render()
def __iter__(self):
- return iter(self.values())
+ return iter(self.value)
def values(self):
- return list(zip(*self.items()))[1]
+ return self.value
def keys(self):
- return list(zip(*self.items()))[0]
+ return range(len(self.value))
def items(self):
- return sorted((k, v) for k, v in self.value.items() if k != '_')
+ return enumerate(self.value)
def __getitem__(self, key):
return self.value[key]
def render(self, compress=False, short_colors=False):
- delim = self.separator
- if not compress or not delim:
- delim += ' '
+ delim = self.delimiter(compress)
return delim.join(
item.render(compress=compress, short_colors=short_colors)
- for item in self.values()
+ for item in self.value
)
@@ -759,5 +834,7 @@ class String(Value):
return self.__str__()
+# Backwards-compatibility.
+ListValue = List
QuotedStringValue = String
StringValue = String