From 028b73079c221f97f7ff1a9537a88279b8c0b098 Mon Sep 17 00:00:00 2001 From: "Eevee (Alex Munroe)" Date: Thu, 8 Aug 2013 18:55:02 -0700 Subject: Clean up ListValue, rename to List. Tried very hard to clean up all uses in the compass library; hope I got them all! --- scss/__init__.py | 7 +- scss/functions/compass/gradients.py | 51 +++++------- scss/functions/compass/helpers.py | 153 +++++++++++++++++------------------- scss/functions/compass/images.py | 18 +---- scss/functions/compass/sprites.py | 36 +++------ scss/functions/core.py | 72 +++++++++-------- scss/functions/extra.py | 59 +++----------- scss/tests/functions/test_core.py | 3 - scss/types.py | 99 ++++++++++++++++++++--- 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 -- cgit v1.2.1