Commits

Alessandro Molina  committed 4a5a92b

Move to libsass-python and make experimental release

  • Participants
  • Parent commits 859a02c
  • Tags 0.4.0

Comments (0)

Files changed (4)

 from setuptools import setup, find_packages
 import sys, os
 
-version = '0.3.0'
+version = '0.4.0'
 
 here = os.path.abspath(os.path.dirname(__file__))
 try:
       zip_safe=False,
       install_requires=[
         "TurboGears2 >= 2.0b7",
+        "libsass < 0.2.3"
       ],
       entry_points="""
       # -*- Entry points: -*-

File tgext/scss/compiler.py

-#!/usr/bin/env python
-#-*- coding: utf-8 -*-
-"""
-pyScss, a Scss compiler for Python
-
-@author     German M. Bravo (Kronuz) <german.mb@gmail.com>
-@version    1.0
-@see        https://github.com/Kronuz/pyScss
-@copyright  (c) 2011 German M. Bravo (Kronuz)
-@license    MIT License
-            http://www.opensource.org/licenses/mit-license.php
-
-pyScss compiles Scss, a superset of CSS that is more powerful, elegant and
-easier to maintain than plain-vanilla CSS. The library acts as a CSS source code
-preprocesor which allows you to use variables, nested rules, mixins, andhave
-inheritance of rules, all with a CSS-compatible syntax which the preprocessor
-then compiles to standard CSS.
-
-Scss, as an extension of CSS, helps keep large stylesheets well-organized. It
-borrows concepts and functionality from projects such as OOCSS and other similar
-frameworks like as Sass. It's build on top of the original PHP xCSS codebase
-structure but it's been completely rewritten, many bugs have been fixed and it
-has been extensively extended to support almost the full range of Sass' Scss
-syntax and functionality.
-
-Bits of code in pyScss come from various projects:
-Compass:
-    (c) 2009 Christopher M. Eppstein
-    http://compass-style.org/
-Sass:
-    (c) 2006-2009 Hampton Catlin and Nathan Weizenbaum
-    http://sass-lang.com/
-xCSS:
-    (c) 2010 Anton Pawlik
-    http://xcss.antpaw.org/docs/
-
-"""
-
-################################################################################
-# Configuration:
-import os
-VERBOSITY = 1
-DEBUG = 0
-################################################################################
-
-import logging
-logging.basicConfig()
-log = logging.getLogger(__name__)
-log.setLevel(logging.INFO)
-
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
-import re
-import sys
-import time
-import textwrap
-from collections import deque
-
-profiling = {}
-
-# units and conversions
-_units = ['em', 'ex', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'deg', 'rad'
-                                                                 'grad', 'ms', 's', 'hz', 'khz', '%']
-_zero_units = ['em', 'ex', 'px', 'cm', 'mm', 'in', 'pt', 'pc']  # units that can be zeroed
-_units_weights = {
-    'em': 10,
-    'mm': 10,
-    'ms': 10,
-    'hz': 10,
-    '%': 100,
-    }
-_conv = {
-    'size': {
-        'em': 13.0,
-        'px': 1.0
-    },
-    'length': {
-        'mm':  1.0,
-        'cm':  10.0,
-        'in':  25.4,
-        'pt':  25.4 / 72,
-        'pc':  25.4 / 6
-    },
-    'time': {
-        'ms':  1.0,
-        's':   1000.0
-    },
-    'freq': {
-        'hz':  1.0,
-        'khz': 1000.0
-    },
-    'any': {
-        '%': 1.0 / 100
-    }
-}
-_conv_type = {}
-_conv_factor = {}
-for t, m in _conv.items():
-    for k, f in m.items():
-        _conv_type[k] = t
-        _conv_factor[k] = f
-del t, m, k, f
-
-# color literals
-_colors = {
-    'aliceblue': '#f0f8ff',
-    'antiquewhite': '#faebd7',
-    'aqua': '#00ffff',
-    'aquamarine': '#7fffd4',
-    'azure': '#f0ffff',
-    'beige': '#f5f5dc',
-    'bisque': '#ffe4c4',
-    'black': '#000000',
-    'blanchedalmond': '#ffebcd',
-    'blue': '#0000ff',
-    'blueviolet': '#8a2be2',
-    'brown': '#a52a2a',
-    'burlywood': '#deb887',
-    'cadetblue': '#5f9ea0',
-    'chartreuse': '#7fff00',
-    'chocolate': '#d2691e',
-    'coral': '#ff7f50',
-    'cornflowerblue': '#6495ed',
-    'cornsilk': '#fff8dc',
-    'crimson': '#dc143c',
-    'cyan': '#00ffff',
-    'darkblue': '#00008b',
-    'darkcyan': '#008b8b',
-    'darkgoldenrod': '#b8860b',
-    'darkgray': '#a9a9a9',
-    'darkgreen': '#006400',
-    'darkkhaki': '#bdb76b',
-    'darkmagenta': '#8b008b',
-    'darkolivegreen': '#556b2f',
-    'darkorange': '#ff8c00',
-    'darkorchid': '#9932cc',
-    'darkred': '#8b0000',
-    'darksalmon': '#e9967a',
-    'darkseagreen': '#8fbc8f',
-    'darkslateblue': '#483d8b',
-    'darkslategray': '#2f4f4f',
-    'darkturquoise': '#00ced1',
-    'darkviolet': '#9400d3',
-    'deeppink': '#ff1493',
-    'deepskyblue': '#00bfff',
-    'dimgray': '#696969',
-    'dodgerblue': '#1e90ff',
-    'firebrick': '#b22222',
-    'floralwhite': '#fffaf0',
-    'forestgreen': '#228b22',
-    'fuchsia': '#ff00ff',
-    'gainsboro': '#dcdcdc',
-    'ghostwhite': '#f8f8ff',
-    'gold': '#ffd700',
-    'goldenrod': '#daa520',
-    'gray': '#808080',
-    'green': '#008000',
-    'greenyellow': '#adff2f',
-    'honeydew': '#f0fff0',
-    'hotpink': '#ff69b4',
-    'indianred': '#cd5c5c',
-    'indigo': '#4b0082',
-    'ivory': '#fffff0',
-    'khaki': '#f0e68c',
-    'lavender': '#e6e6fa',
-    'lavenderblush': '#fff0f5',
-    'lawngreen': '#7cfc00',
-    'lemonchiffon': '#fffacd',
-    'lightblue': '#add8e6',
-    'lightcoral': '#f08080',
-    'lightcyan': '#e0ffff',
-    'lightgoldenrodyellow': '#fafad2',
-    'lightgreen': '#90ee90',
-    'lightgrey': '#d3d3d3',
-    'lightpink': '#ffb6c1',
-    'lightsalmon': '#ffa07a',
-    'lightseagreen': '#20b2aa',
-    'lightskyblue': '#87cefa',
-    'lightslategray': '#778899',
-    'lightsteelblue': '#b0c4de',
-    'lightyellow': '#ffffe0',
-    'lime': '#00ff00',
-    'limegreen': '#32cd32',
-    'linen': '#faf0e6',
-    'magenta': '#ff00ff',
-    'maroon': '#800000',
-    'mediumaquamarine': '#66cdaa',
-    'mediumblue': '#0000cd',
-    'mediumorchid': '#ba55d3',
-    'mediumpurple': '#9370db',
-    'mediumseagreen': '#3cb371',
-    'mediumslateblue': '#7b68ee',
-    'mediumspringgreen': '#00fa9a',
-    'mediumturquoise': '#48d1cc',
-    'mediumvioletred': '#c71585',
-    'midnightblue': '#191970',
-    'mintcream': '#f5fffa',
-    'mistyrose': '#ffe4e1',
-    'moccasin': '#ffe4b5',
-    'navajowhite': '#ffdead',
-    'navy': '#000080',
-    'oldlace': '#fdf5e6',
-    'olive': '#808000',
-    'olivedrab': '#6b8e23',
-    'orange': '#ffa500',
-    'orangered': '#ff4500',
-    'orchid': '#da70d6',
-    'palegoldenrod': '#eee8aa',
-    'palegreen': '#98fb98',
-    'paleturquoise': '#afeeee',
-    'palevioletred': '#db7093',
-    'papayawhip': '#ffefd5',
-    'peachpuff': '#ffdab9',
-    'peru': '#cd853f',
-    'pink': '#ffc0cb',
-    'plum': '#dda0dd',
-    'powderblue': '#b0e0e6',
-    'purple': '#800080',
-    'red': '#ff0000',
-    'rosybrown': '#bc8f8f',
-    'royalblue': '#4169e1',
-    'saddlebrown': '#8b4513',
-    'salmon': '#fa8072',
-    'sandybrown': '#f4a460',
-    'seagreen': '#2e8b57',
-    'seashell': '#fff5ee',
-    'sienna': '#a0522d',
-    'silver': '#c0c0c0',
-    'skyblue': '#87ceeb',
-    'slateblue': '#6a5acd',
-    'slategray': '#708090',
-    'snow': '#fffafa',
-    'springgreen': '#00ff7f',
-    'steelblue': '#4682b4',
-    'tan': '#d2b48c',
-    'teal': '#008080',
-    'thistle': '#d8bfd8',
-    'tomato': '#ff6347',
-    'turquoise': '#40e0d0',
-    'violet': '#ee82ee',
-    'wheat': '#f5deb3',
-    'white': '#ffffff',
-    'whitesmoke': '#f5f5f5',
-    'yellow': '#ffff00',
-    'yellowgreen': '#9acd32'
-}
-
-_safe_strings = {
-    '^doubleslash^': '//',
-    '^bigcopen^': '/*',
-    '^bigcclose^': '*/',
-    '^doubledot^': ':',
-    '^semicolon^': ';',
-    '^curlybracketopen^': '{',
-    '^curlybracketclosed^': '}',
-    }
-_reverse_safe_strings = dict((v, k) for k, v in _safe_strings.items())
-_safe_strings_re = re.compile('|'.join(map(re.escape, _safe_strings)))
-_reverse_safe_strings_re = re.compile('|'.join(map(re.escape, _reverse_safe_strings)))
-
-_default_scss_files = {}  # Files to be compiled ({file: content, ...})
-
-_default_scss_index = {0: '<unknown>:0'}
-
-_default_scss_vars = {
-    # unsafe chars will be hidden as vars
-    '$__doubleslash': '//',
-    '$__bigcopen': '/*',
-    '$__bigcclose': '*/',
-    '$__doubledot': ':',
-    '$__semicolon': ';',
-    '$__curlybracketopen': '{',
-    '$__curlybracketclosed': '}',
-
-    # shortcuts (it's "a hidden feature" for now)
-    'bg:': 'background:',
-    'bgc:': 'background-color:',
-    }
-
-_default_scss_opts = {
-    'verbosity': VERBOSITY,
-    'compress': 1,
-    'compress_short_colors': 1,  # Converts things like #RRGGBB to #RGB
-    'compress_reverse_colors': 1,  # Gets the shortest name of all for colors
-    'short_colors': 0,  # Converts things like #RRGGBB to #RGB
-    'reverse_colors': 0,  # Gets the shortest name of all for colors
-}
-
-SEPARATOR = '\x00'
-_nl_re = re.compile(r'\n', re.MULTILINE)
-_nl_num_re = re.compile(r'\n.+' + SEPARATOR, re.MULTILINE)
-_nl_num_nl_re = re.compile(r'\n.+' + SEPARATOR + r'\s*\n', re.MULTILINE)
-
-_short_color_re = re.compile(r'(?<!\w)#([a-f0-9])\1([a-f0-9])\2([a-f0-9])\3\b', re.IGNORECASE)
-_long_color_re = re.compile(r'(?<!\w)#([a-f0-9]){2}([a-f0-9]){2}([a-f0-9]){2}\b', re.IGNORECASE)
-_reverse_colors = dict((v, k) for k, v in _colors.items())
-for long_k, v in _colors.items():
-    # Calculate the different possible representations of a color:
-    short_k = _short_color_re.sub(r'#\1\2\3', v).lower()
-    rgb_k = _long_color_re.sub(lambda m: 'rgb(%d, %d, %d)' % (int(m.group(1), 16), int(m.group(2), 16), int(m.group(3), 16)), v)
-    rgba_k = _long_color_re.sub(lambda m: 'rgba(%d, %d, %d, 1)' % (int(m.group(1), 16), int(m.group(2), 16), int(m.group(3), 16)), v)
-    # get the shortest of all to use it:
-    k = min([short_k, long_k, rgb_k, rgba_k], key=len)
-    _reverse_colors[long_k] = k
-    _reverse_colors[short_k] = k
-    _reverse_colors[rgb_k] = k
-    _reverse_colors[rgba_k] = k
-_reverse_colors_re = re.compile(r'(?<![-\w.#$])(' + '|'.join(map(re.escape, _reverse_colors)) + r')(?![-\w])', re.IGNORECASE)
-_colors_re = re.compile(r'(?<![-\w.#$])(' + '|'.join(map(re.escape, _colors)) + r')(?![-\w])', re.IGNORECASE)
-
-_expr_glob_re = re.compile(r'''
-    \#\{(.*?)\}                   # Global Interpolation only
-''', re.VERBOSE)
-
-_ml_comment_re = re.compile(r'\/\*(.*?)\*\/', re.DOTALL)
-_sl_comment_re = re.compile(r'(?<!\w{2}:)\/\/.*')
-_zero_units_re = re.compile(r'\b0(' + '|'.join(map(re.escape, _zero_units)) + r')(?!\w)', re.IGNORECASE)
-_zero_re = re.compile(r'\b0\.(?=\d)')
-
-_escape_chars_re = re.compile(r'([^-a-zA-Z0-9_])')
-_variable_re = re.compile('^\\$[-a-zA-Z0-9_]+$')
-_interpolate_re = re.compile(r'(#\{\s*)?(\$[-\w]+)(?(1)\s*\})')
-_spaces_re = re.compile(r'\s+')
-_expand_rules_space_re = re.compile(r'\s*{')
-_collapse_properties_space_re = re.compile(r'([:#])\s*{')
-
-_strings_re = re.compile(r'([\'"]).*?\1')
-_blocks_re = re.compile(r'[{},;()\'"\n]')
-
-_prop_split_re = re.compile(r'[:=]')
-_skip_word_re = re.compile(r'-?[\w\s#.,:%]*$|[\w\-#.,:%]*$', re.MULTILINE)
-_skip_re = re.compile(r'''
-    (?:url|alpha)\([^)]*\)$
-|
-    [\w\-#.,:%]+(?:\s+[\w\-#.,:%]+)*$
-''', re.MULTILINE | re.IGNORECASE | re.VERBOSE)
-_has_code_re = re.compile('''
-    (?:^|(?<=[{;}]))            # the character just before it should be a '{', a ';' or a '}'
-    \s*                         # ...followed by any number of spaces
-    (?:
-        (?:
-            \+
-        |
-            @include
-        |
-            @warn
-        |
-            @mixin
-        |
-            @function
-        |
-            @if
-        |
-            @else
-        |
-            @for
-        |
-            @each
-        )
-        (?![^(:;}]*['"])
-    |
-        @import
-    )
-''', re.VERBOSE)
-
-_css_function_re = re.compile(r'^(from|to|mask|rotate|format|local|url|attr|counter|counters|color-stop|rect|-webkit-.*|-webkit-.*|-moz-.*|-pie-.*|-ms-.*|-o-.*)$')
-
-FILEID = 0
-POSITION = 1
-CODESTR = 2
-DEPS = 3
-CONTEXT = 4
-OPTIONS = 5
-SELECTORS = 6
-PROPERTIES = 7
-PATH = 8
-INDEX = 9
-LINENO = 10
-FINAL = 11
-MEDIA = 12
-RULE_VARS = {
-    'FILEID': FILEID,
-    'POSITION': POSITION,
-    'CODESTR': CODESTR,
-    'DEPS': DEPS,
-    'CONTEXT': CONTEXT,
-    'OPTIONS': OPTIONS,
-    'SELECTORS': SELECTORS,
-    'PROPERTIES': PROPERTIES,
-    'PATH': PATH,
-    'INDEX': INDEX,
-    'LINENO': LINENO,
-    'FINAL': FINAL,
-    'MEDIA': MEDIA,
-    }
-
-
-def spawn_rule(rule=None, **kwargs):
-    if rule is None:
-        rule = [None] * len(RULE_VARS)
-        rule[DEPS] = set()
-        rule[SELECTORS] = ''
-        rule[PROPERTIES] = []
-        rule[PATH] = './'
-        rule[INDEX] = {}
-        rule[LINENO] = 0
-        rule[FINAL] = False
-    else:
-        rule = list(rule)
-    for k, v in kwargs.items():
-        rule[RULE_VARS[k.upper()]] = v
-    return rule
-
-
-def print_timing(level=0):
-    def _print_timing(func):
-        if VERBOSITY:
-            def wrapper(*arg):
-                if VERBOSITY >= level:
-                    t1 = time.time()
-                    res = func(*arg)
-                    t2 = time.time()
-                    profiling.setdefault(func.func_name, 0)
-                    profiling[func.func_name] += (t2 - t1)
-                    return res
-                else:
-                    return func(*arg)
-            return wrapper
-        else:
-            return func
-    return _print_timing
-
-
-def split_params(params):
-    params = params.split(',') or []
-    if params:
-        final_params = []
-        param = params.pop(0)
-        try:
-            while True:
-                while param.count('(') != param.count(')'):
-                    try:
-                        param = param + ',' + params.pop(0)
-                    except IndexError:
-                        break
-                final_params.append(param)
-                param = params.pop(0)
-        except IndexError:
-            pass
-        params = final_params
-    return params
-
-
-def dequote(s):
-    if s and s[0] in ('"', "'") and s[-1] == s[0]:
-        s = s[1:-1]
-        s = unescape(s)
-    return s
-
-
-def depar(s):
-    while s and s[0] == '(' and s[-1] == ')':
-        s = s[1:-1]
-    return s
-
-
-class Scss(object):
-    import_re = re.compile(r'^\s*@import +url\(\s*["\']?([^\)\'\"]+)["\']?\s*\)\s*;?\s*$', re.MULTILINE)
-
-    # configuration:
-    construct = 'self'
-
-    def __init__(self, scss_vars=None, scss_opts=None):
-        if scss_vars is None:
-            self.scss_vars = None
-        else:
-            self.scss_vars = _default_scss_vars.copy()
-            self.scss_vars.update(scss_vars)
-        if scss_opts is None:
-            self.scss_opts = None
-        else:
-            self.scss_opts = _default_scss_opts.copy()
-            self.scss_opts.update(scss_opts)
-        self.scss_files = None
-        self.scss_index = None
-        self.reset()
-
-    def clean(self):
-        self.children = deque()
-        self.rules = []
-        self._rules = {}
-        self.parts = {}
-
-    def reset(self, input_scss=None):
-        # Initialize
-        self.css_files = []
-        self._scss_vars = self.scss_vars.copy() if self.scss_vars is not None else _default_scss_vars.copy()
-        self._scss_opts = self.scss_opts.copy() if self.scss_opts is not None else _default_scss_opts.copy()
-        self._scss_files = self.scss_files.copy() if self.scss_files is not None else _default_scss_files.copy()
-        self._scss_index = self.scss_index.copy() if self.scss_index is not None else _default_scss_index.copy()
-
-        self._contexts = {}
-        self._replaces = {}
-
-        self.clean()
-
-    def longest_common_prefix(self, seq1, seq2):
-        start = 0
-        common = 0
-        length = min(len(seq1), len(seq2))
-        while start < length:
-            if seq1[start] != seq2[start]:
-                break
-            if seq1[start] == ' ':
-                common = start + 1
-            elif seq1[start] in ('#', ':', '.'):
-                common = start
-            start += 1
-        return common
-
-    def longest_common_suffix(self, seq1, seq2):
-        seq1, seq2 = seq1[::-1], seq2[::-1]
-        start = 0
-        common = 0
-        length = min(len(seq1), len(seq2))
-        while start < length:
-            if seq1[start] != seq2[start]:
-                break
-            if seq1[start] == ' ':
-                common = start + 1
-            elif seq1[start] in ('#', ':', '.'):
-                common = start + 1
-            start += 1
-        return common
-
-    def locate_blocks(self, codestr):
-        """
-        Returns all code blocks between `{` and `}` and a proper key
-        that can be multilined as long as it's joined by `,` or enclosed in
-        `(` and `)`.
-        Returns the "lose" code that's not part of the block as a third item.
-        """
-        def _strip_selprop(selprop, lineno):
-            _lineno, _sep, selprop = selprop.partition(SEPARATOR)
-            if _sep == SEPARATOR:
-                lineno = _lineno.strip()
-                lineno = int(lineno) if lineno else 0
-            else:
-                selprop = _lineno
-            selprop = _nl_num_re.sub('\n', selprop)
-            selprop = selprop.strip()
-            return selprop, lineno
-
-        def _strip(selprop):
-            selprop, _ = _strip_selprop(selprop, 0)
-            return selprop
-
-        lineno = 0
-
-        par = 0
-        instr = None
-        depth = 0
-        skip = False
-        thin = None
-        i = init = safe = lose = 0
-        start = end = None
-
-        for m in _blocks_re.finditer(codestr):
-            _s = m.start(0)
-            _e = m.end(0)
-            i = _e - 1
-            if _s == _e:
-                break
-            if instr is not None:
-                if codestr[i] == instr:
-                    instr = None
-            elif codestr[i] in ('"', "'"):
-                instr = codestr[i]
-            elif codestr[i] == '(':
-                par += 1
-                thin = None
-                safe = i + 1
-            elif codestr[i] == ')':
-                par -= 1
-            elif not par and not instr:
-                if codestr[i] == '{':
-                    if depth == 0:
-                        if i > 0 and codestr[i - 1] == '#':
-                            skip = True
-                        else:
-                            start = i
-                            if thin is not None and _strip(codestr[thin:i - 1]):
-                                init = thin
-                            if lose < init:
-                                losestr = codestr[lose:init]
-                                for _property in losestr.split(';'):
-                                    _property, lineno = _strip_selprop(_property, lineno)
-                                    if _property:
-                                        yield lineno, _property, None
-                                lose = init
-                            thin = None
-                    depth += 1
-                elif codestr[i] == '}':
-                    if depth > 0:
-                        depth -= 1
-                        if depth == 0:
-                            if not skip:
-                                end = i
-                                _selectors, lineno = _strip_selprop(codestr[init:start], lineno)
-                                _codestr = codestr[start + 1:end].strip()
-                                if _selectors:
-                                    yield lineno, _selectors, _codestr
-                                init = safe = lose = end + 1
-                                thin = None
-                            skip = False
-                elif depth == 0:
-                    if codestr[i] == ';':
-                        init = safe = i + 1
-                        thin = None
-                    elif codestr[i] == ',':
-                        if thin is not None and _strip(codestr[thin:i - 1]):
-                            init = thin
-                        thin = None
-                        safe = i + 1
-                    elif codestr[i] == '\n':
-                        if thin is None and _strip(codestr[safe:i - 1]):
-                            thin = i + 1
-                        elif thin is not None and _strip(codestr[thin:i - 1]):
-                            init = i + 1
-                            thin = None
-        if depth > 0:
-            if not skip:
-                _selectors, lineno = _strip_selprop(codestr[init:start], lineno)
-                _codestr = codestr[start + 1:].strip()
-                if _selectors:
-                    yield lineno, _selectors, _codestr
-                if par:
-                    raise Exception("Missing closing parenthesis somewhere in block: '%s'" % _selectors)
-                elif instr:
-                    raise Exception("Missing closing string somewhere in block: '%s'" % _selectors)
-                else:
-                    raise Exception("Block never closed: '%s'" % _selectors)
-        losestr = codestr[lose:]
-        for _property in losestr.split(';'):
-            _property, lineno = _strip_selprop(_property, lineno)
-            if _property:
-                yield lineno, _property, None
-
-    def normalize_selectors(self, _selectors, extra_selectors=None, extra_parents=None):
-        """
-        Normalizes or extends selectors in a string.
-        An optional extra parameter that can be a list of extra selectors to be
-        added to the final normalized selectors string.
-        """
-        # Fixe tabs and spaces in selectors
-        _selectors = _spaces_re.sub(' ', _selectors)
-
-        if isinstance(extra_selectors, basestring):
-            extra_selectors = extra_selectors.split(',')
-
-        if isinstance(extra_parents, basestring):
-            extra_parents = extra_parents.split('&')
-
-        parents = set()
-        if ' extends ' in _selectors:
-            selectors = set()
-            for key in _selectors.split(','):
-                child, _, parent = key.partition(' extends ')
-                child = child.strip()
-                parent = parent.strip()
-                selectors.add(child)
-                parents.update(s.strip() for s in parent.split('&') if s.strip())
-        else:
-            selectors = set(s.strip() for s in _selectors.split(',') if s.strip())
-        if extra_selectors:
-            selectors.update(s.strip() for s in extra_selectors if s.strip())
-        selectors.discard('')
-        if not selectors:
-            return ''
-        if extra_parents:
-            parents.update(s.strip() for s in extra_parents if s.strip())
-        parents.discard('')
-        if parents:
-            return ','.join(sorted(selectors)) + ' extends ' + '&'.join(sorted(parents))
-        return ','.join(sorted(selectors))
-
-    def apply_vars(self, cont, context, options=None, rule=None, _dequote=False):
-        if isinstance(cont, basestring) and '$' in cont:
-            if cont in context:
-                # Optimization: the full cont is a variable in the context,
-                # flatten the interpolation and use it:
-                while isinstance(cont, basestring) and cont in context:
-                    _cont = context[cont]
-                    if _cont == cont:
-                        break
-                    cont = _cont
-            else:
-                # Flatten the context (no variables mapping to variables)
-                flat_context = {}
-                for k, v in context.items():
-                    while isinstance(v, basestring) and v in context:
-                        _v = context[v]
-                        if _v == v:
-                            break
-                        v = _v
-                    flat_context[k] = v
-
-                # Interpolate variables:
-                def _av(m):
-                    v = flat_context.get(m.group(2))
-                    if v:
-                        v = to_str(v)
-                        if _dequote and m.group(1):
-                            v = dequote(v)
-                    elif v is not None:
-                        v = to_str(v)
-                    else:
-                        v = m.group(0)
-                    return v
-
-                cont = _interpolate_re.sub(_av, cont)
-        if options is not None:
-            # ...apply math:
-            cont = self.do_glob_math(cont, context, options, rule, _dequote)
-        return cont
-
-    @print_timing(2)
-    def Compilation(self, input_scss=None):
-        self.reset()
-
-        if input_scss is not None:
-            self._scss_files = {'<string>': input_scss}
-
-        # Compile
-        for fileid, codestr in self._scss_files.iteritems():
-            codestr = self.load_string(codestr, fileid)
-            self._scss_files[fileid] = codestr
-            rule = spawn_rule(fileid=fileid, codestr=codestr, context=self._scss_vars, options=self._scss_opts, index=self._scss_index)
-            self.children.append(rule)
-
-        # this will manage rule: child objects inside of a node
-        self.parse_children()
-
-        # this will manage rule: ' extends '
-        self.parse_extends()
-
-        # this will manage the order of the rules
-        self.manage_order()
-
-        self.parse_properties()
-
-        final_cont = ''
-        for fileid in self.css_files:
-            if fileid != '<string>':
-                final_cont += '/* Generated from: ' + fileid + ' */\n'
-            fcont = self.create_css(fileid)
-            final_cont += fcont
-
-        final_cont = self.post_process(final_cont)
-
-        return final_cont
-    compile = Compilation
-
-    def load_string(self, codestr, filename=None):
-        if filename is not None:
-            codestr += '\n'
-
-            idx = {
-                'next_id': len(self._scss_index),
-                'line': 1,
-                }
-
-            def _cnt(m):
-                idx['line'] += 1
-                lineno = '%s:%d' % (filename, idx['line'])
-                next_id = idx['next_id']
-                self._scss_index[next_id] = lineno
-                idx['next_id'] += 1
-                return '\n' + str(next_id) + SEPARATOR
-            lineno = '%s:%d' % (filename, idx['line'])
-            next_id = idx['next_id']
-            self._scss_index[next_id] = lineno
-            codestr = str(next_id) + SEPARATOR + _nl_re.sub(_cnt, codestr)
-
-        # remove empty lines
-        codestr = _nl_num_nl_re.sub('\n', codestr)
-
-        # protects codestr: "..." strings
-        codestr = _strings_re.sub(lambda m: _reverse_safe_strings_re.sub(lambda n: _reverse_safe_strings[n.group(0)], m.group(0)), codestr)
-
-        # removes multiple line comments
-        codestr = _ml_comment_re.sub('', codestr)
-
-        # removes inline comments, but not :// (protocol)
-        codestr = _sl_comment_re.sub('', codestr)
-
-        codestr = _safe_strings_re.sub(lambda m: _safe_strings[m.group(0)], codestr)
-
-        # expand the space in rules
-        codestr = _expand_rules_space_re.sub(' {', codestr)
-
-        # collapse the space in properties blocks
-        codestr = _collapse_properties_space_re.sub(r'\1{', codestr)
-
-        # to do math operations, we need to get the color's hex values (for color names):
-        def _pp(m):
-            v = m.group(0)
-            return _colors.get(v, v)
-        codestr = _colors_re.sub(_pp, codestr)
-
-        return codestr
-
-    @print_timing(3)
-    def parse_children(self):
-        pos = 0
-        while True:
-            try:
-                rule = self.children.popleft()
-            except:
-                break
-                # Check if the block has nested blocks and work it out:
-            _selectors, _, _parents = rule[SELECTORS].partition(' extends ')
-            _selectors = _selectors.split(',')
-            _parents = set(_parents.split('&'))
-            _parents.discard('')
-
-            # manage children or expand children:
-            _children = deque()
-            self.manage_children(rule, _selectors, _parents, _children, None, rule[MEDIA])
-            self.children.extendleft(_children)
-
-            # prepare maps:
-            if _parents:
-                rule[SELECTORS] = ','.join(_selectors) + ' extends ' + '&'.join(_parents)
-            rule[POSITION] = pos
-            selectors = rule[SELECTORS]
-            self.parts.setdefault(selectors, [])
-            self.parts[selectors].append(rule)
-            self.rules.append(rule)
-            pos += 1
-
-            #print >>sys.stderr, '='*80
-            #for r in [rule]+list(self.children)[:5]: print >>sys.stderr, repr(r[POSITION]), repr(r[SELECTORS]), repr(r[CODESTR][:80]+('...' if len(r[CODESTR])>80 else ''))
-            #for r in [rule]+list(self.children)[:5]: print >>sys.stderr, repr(r[POSITION]), repr(r[SELECTORS]), repr(r[CODESTR][:80]+('...' if len(r[CODESTR])>80 else '')), dict((k, v) for k, v in r[CONTEXT].items() if k.startswith('$') and not k.startswith('$__')), dict(r[PROPERTIES]).keys()
-
-    @print_timing(4)
-    def manage_children(self, rule, p_selectors, p_parents, p_children, scope, media):
-        for c_lineno, c_property, c_codestr in self.locate_blocks(rule[CODESTR]):
-            if '@return' in rule[OPTIONS]:
-                return
-                # Rules preprocessing...
-            if c_property.startswith('+'):  # expands a '+' at the beginning of a rule as @include
-                c_property = '@include ' + c_property[1:]
-                try:
-                    if '(' not in c_property or c_property.index(':') < c_property.index('('):
-                        c_property = c_property.replace(':', '(', 1)
-                        if '(' in c_property:
-                            c_property += ')'
-                except ValueError:
-                    pass
-            elif c_property.startswith('='):  # expands a '=' at the beginning of a rule as @mixin
-                c_property = '@mixin' + c_property[1:]
-            elif c_property == '@prototype ':  # Remove '@prototype '
-                c_property = c_property[11:]
-                ####################################################################
-
-            if c_property.startswith('@'):
-                code, name = (c_property.split(None, 1) + [''])[:2]
-                code = code.lower()
-                if code == '@warn':
-                    name = self.calculate(name, rule[CONTEXT], rule[OPTIONS], rule)
-                    log.warn(dequote(to_str(name)))
-                elif code == '@print':
-                    name = self.calculate(name, rule[CONTEXT], rule[OPTIONS], rule)
-                    log.info(dequote(to_str(name)))
-                elif code == '@raw':
-                    name = self.calculate(name, rule[CONTEXT], rule[OPTIONS], rule)
-                    log.info(repr(name))
-                elif code == '@debug':
-                    global DEBUG
-                    name = name.strip()
-                    if name.lower() in ('1', 'true', 't', 'yes', 'y', 'on'):
-                        name = 1
-                    elif name.lower() in ('0', 'false', 'f', 'no', 'n', 'off'):
-                        name = 0
-                    DEBUG = name
-                    log.info("Debug mode is %s", 'On' if DEBUG else 'Off')
-                elif code == '@option':
-                    self._settle_options(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                elif code == '@import':
-                    self._do_import(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                elif code == '@extend':
-                    name = self.apply_vars(name, rule[CONTEXT], rule[OPTIONS], rule)
-                    p_parents.update(p.strip() for p in name.replace(',', '&').split('&'))
-                    p_parents.discard('')
-                elif c_codestr is not None and code in ('@mixin', '@function'):
-                    self._do_functions(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                elif code == '@return':
-                    ret = self.calculate(name, rule[CONTEXT], rule[OPTIONS], rule)
-                    rule[OPTIONS]['@return'] = ret
-                elif code == '@include':
-                    self._do_include(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                elif c_codestr is not None and (code == '@if' or c_property.startswith('@else if ')):
-                    self._do_if(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                elif c_codestr is not None and code == '@else':
-                    self._do_else(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                elif c_codestr is not None and code == '@for':
-                    self._do_for(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                elif c_codestr is not None and code == '@each':
-                    self._do_each(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                # elif c_codestr is not None and code == '@while':
-                #     self._do_while(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name)
-                elif c_codestr is not None and code in ('@variables', '@vars'):
-                    self._get_variables(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr)
-                elif c_codestr is not None and code == '@media':
-                    _media = (media or []) + [name]
-                    rule[CODESTR] = self.construct + ' {' + c_codestr + '}'
-                    self.manage_children(rule, p_selectors, p_parents, p_children, scope, _media)
-                elif c_codestr is None:
-                    rule[PROPERTIES].append((c_lineno, c_property, None))
-                elif scope is None:  # needs to have no scope to crawl down the nested rules
-                    self._nest_rules(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr)
-            ####################################################################
-            # Properties
-            elif c_codestr is None:
-                self._get_properties(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr)
-            # Nested properties
-            elif c_property.endswith(':'):
-                rule[CODESTR] = c_codestr
-                self.manage_children(rule, p_selectors, p_parents, p_children, (scope or '') + c_property[:-1] + '-', media)
-            ####################################################################
-            # Nested rules
-            elif scope is None:  # needs to have no scope to crawl down the nested rules
-                self._nest_rules(rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr)
-
-    @print_timing(10)
-    def _settle_options(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-        for option in name.split(','):
-            option, value = (option.split(':', 1) + [''])[:2]
-            option = option.strip().lower()
-            value = value.strip()
-            if option:
-                if value.lower() in ('1', 'true', 't', 'yes', 'y', 'on'):
-                    value = 1
-                elif value.lower() in ('0', 'false', 'f', 'no', 'n', 'off'):
-                    value = 0
-                rule[OPTIONS][option] = value
-
-    @print_timing(10)
-    def _do_functions(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-        """
-        Implements @mixin and @function
-        """
-        if name:
-            funct, params, _ = name.partition('(')
-            funct = funct.strip()
-            params = split_params(depar(params + _))
-            defaults = {}
-            new_params = []
-            for param in params:
-                param, _, default = param.partition(':')
-                param = param.strip()
-                default = default.strip()
-                if param:
-                    new_params.append(param)
-                    if default:
-                        default = self.apply_vars(default, rule[CONTEXT], None, rule)
-                        defaults[param] = default
-            context = rule[CONTEXT].copy()
-            for p in new_params:
-                context.pop(p, None)
-            mixin = [list(new_params), defaults, self.apply_vars(c_codestr, context, None, rule)]
-            if code == '@function':
-                def _call(mixin):
-                    def __call(R, *args, **kwargs):
-                        m_params = mixin[0]
-                        m_vars = rule[CONTEXT].copy()
-                        m_vars.update(mixin[1])
-                        m_codestr = mixin[2]
-                        for i, a in enumerate(args):
-                            m_vars[m_params[i]] = a
-                        m_vars.update(kwargs)
-                        _options = rule[OPTIONS].copy()
-                        _rule = spawn_rule(R, codestr=m_codestr, context=m_vars, options=_options, deps=set(), properties=[], final=False, lineno=c_lineno)
-                        self.manage_children(_rule, p_selectors, p_parents, p_children, (scope or '') + '', R[MEDIA])
-                        ret = _rule[OPTIONS].pop('@return', '')
-                        return ret
-                    return __call
-                _mixin = _call(mixin)
-                _mixin.mixin = mixin
-                mixin = _mixin
-                # Insert as many @mixin options as the default parameters:
-            while len(new_params):
-                rule[OPTIONS]['%s %s:%d' % (code, funct, len(new_params))] = mixin
-                param = new_params.pop()
-                if param not in defaults:
-                    break
-            if not new_params:
-                rule[OPTIONS][code + ' ' + funct + ':0'] = mixin
-
-    @print_timing(10)
-    def _do_include(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-        """
-        Implements @include, for @mixins
-        """
-        funct, params, _ = name.partition('(')
-        funct = funct.strip()
-        funct = self.do_glob_math(funct, rule[CONTEXT], rule[OPTIONS], rule, True)
-        params = split_params(depar(params + _))
-        new_params = {}
-        num_args = 0
-        for param in params:
-            varname, _, param = param.partition(':')
-            if param:
-                param = param.strip()
-                varname = varname.strip()
-            else:
-                param = varname.strip()
-                varname = num_args
-                if param:
-                    num_args += 1
-            if param:
-                new_params[varname] = param
-        mixin = rule[OPTIONS].get('@mixin %s:%s' % (funct, num_args))
-        if not mixin:
-            # Fallback to single parmeter:
-            mixin = rule[OPTIONS].get('@mixin %s:1' % (funct,))
-            if mixin and all(map(lambda o: isinstance(o, int), new_params.keys())):
-                new_params = {0: ', '.join(new_params.values())}
-        if mixin:
-            m_params = mixin[0]
-            m_vars = mixin[1].copy()
-            m_codestr = mixin[2]
-            for varname, value in new_params.items():
-                try:
-                    m_param = m_params[varname]
-                except:
-                    m_param = varname
-                value = self.calculate(value, rule[CONTEXT], rule[OPTIONS], rule)
-                m_vars[m_param] = value
-            for p in m_vars:
-                if p not in new_params:
-                    if isinstance(m_vars[p], basestring):
-                        value = self.calculate(m_vars[p], m_vars, rule[OPTIONS], rule)
-                        m_vars[p] = value
-            _context = rule[CONTEXT].copy()
-            _context.update(m_vars)
-            _rule = spawn_rule(rule, codestr=m_codestr, context=_context, lineno=c_lineno)
-            self.manage_children(_rule, p_selectors, p_parents, p_children, scope, media)
-        else:
-            log.error("Required mixin not found: %s:%d (%s)", funct, num_args, rule[INDEX][rule[LINENO]])
-
-    def _do_import(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-        """
-        Implements @import
-        Load and import mixins and functions and rules
-        """
-        i_codestr = None
-
-        #This is here for backward compatibility with tgext.scss version 2.
-        if 'url("/' in name or "url('/" in name:
-            match = self.import_re.match('@import ' + name)
-            if match:
-                name = match.group(1)
-
-        # Protect against going to prohibited places...
-        if '..' not in name and '://' not in name and 'url(' not in name:
-            names = name.split(',')
-            for name in names:
-                name = dequote(name.strip())
-
-                #Avoid making possible to import files from root
-                if name[0] == '/':
-                    name = name[1:]
-
-                if '@import ' + name not in rule[OPTIONS]:  # If already imported in this scope, skip...
-                    filename = os.path.basename(name)
-                    dirname = os.path.dirname(name)
-                    load_paths = []
-                    i_codestr = None
-                    for path in ['./'] + self._scss_opts['root_path'].split(','):
-                        for basepath in ['./', os.path.dirname(rule[PATH])]:
-                            i_codestr = None
-                            full_path = os.path.realpath(os.path.join(path, basepath, dirname))
-                            if full_path not in load_paths:
-                                try:
-                                    full_filename = os.path.join(full_path, '_' + filename + '.scss')
-                                    i_codestr = open(full_filename).read()
-                                except:
-                                    try:
-                                        full_filename = os.path.join(full_path, filename + '.scss')
-                                        i_codestr = open(full_filename).read()
-                                    except:
-                                        try:
-                                            full_filename = os.path.join(full_path, '_' + filename)
-                                            i_codestr = open(full_filename).read()
-                                        except:
-                                            try:
-                                                full_filename = os.path.join(full_path, filename)
-                                                i_codestr = open(full_filename).read()
-                                            except:
-                                                pass
-                                if i_codestr is not None:
-                                    break
-                                else:
-                                    load_paths.append(full_path)
-                        if i_codestr is not None:
-                            break
-                    i_codestr = i_codestr and self.load_string(i_codestr, full_filename)
-                    if i_codestr is None:
-                        log.warn("File to import not found or unreadable: '%s'\nLoad paths:\n\t%s", filename, "\n\t".join(load_paths))
-                    else:
-                        _rule = spawn_rule(rule, codestr=i_codestr, path=full_filename, lineno=c_lineno)
-                        self.manage_children(_rule, p_selectors, p_parents, p_children, scope, media)
-                        rule[OPTIONS]['@import ' + name] = True
-        else:
-            rule[PROPERTIES].append((c_lineno, c_property, None))
-
-    @print_timing(10)
-    def _do_if(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-        """
-        Implements @if and @else if
-        """
-        if code != '@if':
-            if '@if' not in rule[OPTIONS]:
-                log.error("@else with no @if (%s)", rule[INDEX][rule[LINENO]])
-            val = not rule[OPTIONS].get('@if', True)
-            name = c_property[9:].strip()
-        else:
-            val = True
-        if val:
-            val = self.calculate(name, rule[CONTEXT], rule[OPTIONS], rule)
-            val = bool(False if not val or val in('0', 'false',) or (isinstance(val, basestring) and _variable_re.match(val)) else val)
-            if val:
-                rule[CODESTR] = c_codestr
-                self.manage_children(rule, p_selectors, p_parents, p_children, scope, media)
-            rule[OPTIONS]['@if'] = val
-
-    @print_timing(10)
-    def _do_else(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-        """
-        Implements @else
-        """
-        if '@if' not in rule[OPTIONS]:
-            log.error("@else with no @if (%s", rule[INDEX][rule[LINENO]])
-        val = rule[OPTIONS].pop('@if', True)
-        if not val:
-            rule[CODESTR] = c_codestr
-            self.manage_children(rule, p_selectors, p_parents, p_children, scope, media)
-
-    @print_timing(10)
-    def _do_for(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-        """
-        Implements @for
-        """
-        var, _, name = name.partition('from')
-        frm, _, through = name.partition('through')
-        if not through:
-            frm, _, through = frm.partition('to')
-        frm = self.calculate(frm, rule[CONTEXT], rule[OPTIONS], rule)
-        through = self.calculate(through, rule[CONTEXT], rule[OPTIONS], rule)
-        try:
-            frm = int(float(frm))
-            through = int(float(through))
-        except ValueError:
-            pass
-        else:
-            if frm > through:
-                frm, through = through, frm
-                rev = reversed
-            else:
-                rev = lambda x: x
-            var = var.strip()
-            var = self.do_glob_math(var, rule[CONTEXT], rule[OPTIONS], rule, True)
-
-            for i in rev(range(frm, through + 1)):
-                rule[CODESTR] = c_codestr
-                rule[CONTEXT][var] = str(i)
-                self.manage_children(rule, p_selectors, p_parents, p_children, scope, media)
-
-    @print_timing(10)
-    def _do_each(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-        """
-        Implements @each
-        """
-        var, _, name = name.partition('in')
-        name = self.calculate(name, rule[CONTEXT], rule[OPTIONS], rule)
-        if name:
-            name = ListValue(name)
-            var = var.strip()
-            var = self.do_glob_math(var, rule[CONTEXT], rule[OPTIONS], rule, True)
-
-            for n, v in name.items():
-                v = to_str(v)
-                rule[CODESTR] = c_codestr
-                rule[CONTEXT][var] = v
-                if not isinstance(n, int):
-                    rule[CONTEXT][n] = v
-                self.manage_children(rule, p_selectors, p_parents, p_children, scope, media)
-
-    # @print_timing(10)
-    # def _do_while(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr, code, name):
-    #     THIS DOES NOT WORK AS MODIFICATION OF INNER VARIABLES ARE NOT KNOWN AT THIS POINT!!
-    #     """
-    #     Implements @while
-    #     """
-    #     first_val = None
-    #     while True:
-    #         val = self.calculate(name, rule[CONTEXT], rule[OPTIONS], rule)
-    #         val = bool(False if not val or val in('0', 'false',) or (isinstance(val, basestring) and _variable_re.match(val)) else val)
-    #         if first_val is None:
-    #             first_val = val
-    #         if not val:
-    #             break
-    #         rule[CODESTR] = c_codestr
-    #         self.manage_children(rule, p_selectors, p_parents, p_children, scope, media)
-    #     rule[OPTIONS]['@if'] = first_val
-
-    @print_timing(10)
-    def _get_variables(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr):
-        """
-        Implements @variables and @vars
-        """
-        _rule = list(rule)
-        _rule[CODESTR] = c_codestr
-        _rule[PROPERTIES] = rule[CONTEXT]
-        self.manage_children(_rule, p_selectors, p_parents, p_children, scope, media)
-
-    @print_timing(10)
-    def _get_properties(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr):
-        """
-        Implements properties and variables extraction
-        """
-        prop, value = (_prop_split_re.split(c_property, 1) + [None])[:2]
-        try:
-            is_var = (c_property[len(prop)] == '=')
-        except IndexError:
-            is_var = False
-        prop = prop.strip()
-        prop = self.do_glob_math(prop, rule[CONTEXT], rule[OPTIONS], rule, True)
-        if prop:
-            if value:
-                value = value.strip()
-                value = self.calculate(value, rule[CONTEXT], rule[OPTIONS], rule)
-            _prop = (scope or '') + prop
-            if is_var or prop.startswith('$') and value is not None:
-                if isinstance(value, basestring):
-                    if '!default' in value:
-                        if _prop in rule[CONTEXT]:
-                            value = None
-                        else:
-                            value = value.replace('!default', '').replace('  ', ' ').strip()
-                elif isinstance(value, ListValue):
-                    value = ListValue(value)
-                    for k, v in value.value.items():
-                        if v == '!default':
-                            if _prop in rule[CONTEXT]:
-                                value = None
-                            else:
-                                del value.value[k]
-                                value = value.first() if len(value) == 1 else value
-                            break
-                if value is not None:
-                    rule[CONTEXT][_prop] = value
-            else:
-                _prop = self.apply_vars(_prop, rule[CONTEXT], rule[OPTIONS], rule, True)
-                rule[PROPERTIES].append((c_lineno, _prop, to_str(value) if value is not None else None))
-
-    @print_timing(10)
-    def _nest_rules(self, rule, p_selectors, p_parents, p_children, scope, media, c_lineno, c_property, c_codestr):
-        """
-        Implements Nested CSS rules
-        """
-        if c_property == self.construct and rule[MEDIA] == media:
-            rule[CODESTR] = c_codestr
-            self.manage_children(rule, p_selectors, p_parents, p_children, scope, media)
-        else:
-            c_property = self.apply_vars(c_property, rule[CONTEXT], rule[OPTIONS], rule, True)
-
-            c_selectors = self.normalize_selectors(c_property)
-            c_selectors, _, c_parents = c_selectors.partition(' extends ')
-
-            better_selectors = set()
-            c_selectors = c_selectors.split(',')
-            for c_selector in c_selectors:
-                for p_selector in p_selectors:
-                    if c_selector == self.construct:
-                        better_selectors.add(p_selector)
-                    elif '&' in c_selector:  # Parent References
-                        better_selectors.add(c_selector.replace('&', p_selector))
-                    elif p_selector:
-                        better_selectors.add(p_selector + ' ' + c_selector)
-                    else:
-                        better_selectors.add(c_selector)
-            better_selectors = ','.join(sorted(better_selectors))
-
-            if c_parents:
-                parents = set(p.strip() for p in c_parents.split('&'))
-                parents.discard('')
-                if parents:
-                    better_selectors += ' extends ' + '&'.join(sorted(parents))
-
-            _rule = spawn_rule(rule, codestr=c_codestr, deps=set(), context=rule[CONTEXT].copy(), options=rule[OPTIONS].copy(), selectors=better_selectors, properties=[], final=False, media=media, lineno=c_lineno)
-
-            p_children.appendleft(_rule)
-
-    @print_timing(4)
-    def link_with_parents(self, parent, c_selectors, c_rules):
-        """
-        Link with a parent for the current child rule.
-        If parents found, returns a list of parent rules to the child
-        """
-        parent_found = None
-        for p_selectors, p_rules in self.parts.items():
-            _p_selectors, _, _ = p_selectors.partition(' extends ')
-            _p_selectors = _p_selectors.split(',')
-
-            new_selectors = set()
-            found = False
-
-            # Finds all the parent selectors and parent selectors with another
-            # bind selectors behind. For example, if `.specialClass extends .baseClass`,
-            # and there is a `.baseClass` selector, the extension should create
-            # `.specialClass` for that rule, but if there's also a `.baseClass a`
-            # it also should create `.specialClass a`
-            for p_selector in _p_selectors:
-                if parent in p_selector:
-                    # get the new child selector to add (same as the parent selector but with the child name)
-                    # since selectors can be together, separated with # or . (i.e. something.parent) check that too:
-                    for c_selector in c_selectors.split(','):
-                        # Get whatever is different between the two selectors:
-                        _c_selector, _parent = c_selector, parent
-                        lcp = self.longest_common_prefix(_c_selector, _parent)
-                        if lcp:
-                            _c_selector = _c_selector[lcp:]
-                            _parent = _parent[lcp:]
-                        lcs = self.longest_common_suffix(_c_selector, _parent)
-                        if lcs:
-                            _c_selector = _c_selector[:-lcs]
-                            _parent = _parent[:-lcs]
-                        if _c_selector and _parent:
-                            # Get the new selectors:
-                            prev_symbol = '(?<![#.:])' if _parent[0] in ('#', '.', ':') else r'(?<![-\w#.:])'
-                            post_symbol = r'(?![-\w])'
-                            new_parent = re.sub(prev_symbol + _parent + post_symbol, _c_selector, p_selector)
-                            if p_selector != new_parent:
-                                new_selectors.add(new_parent)
-                                found = True
-
-            if found:
-                # add parent:
-                parent_found = parent_found or []
-                parent_found.extend(p_rules)
-
-            if new_selectors:
-                new_selectors = self.normalize_selectors(p_selectors, new_selectors)
-                # rename node:
-                if new_selectors != p_selectors:
-                    del self.parts[p_selectors]
-                    self.parts.setdefault(new_selectors, [])
-                    self.parts[new_selectors].extend(p_rules)
-
-                deps = set()
-                # save child dependencies:
-                for c_rule in c_rules or []:
-                    c_rule[SELECTORS] = c_selectors  # re-set the SELECTORS for the rules
-                    deps.add(c_rule[POSITION])
-
-                for p_rule in p_rules:
-                    p_rule[SELECTORS] = new_selectors  # re-set the SELECTORS for the rules
-                    p_rule[DEPS].update(deps)  # position is the "index" of the object
-
-        return parent_found
-
-    @print_timing(3)
-    def parse_extends(self):
-        """
-        For each part, create the inheritance parts from the ' extends '
-        """
-        # To be able to manage multiple extends, you need to
-        # destroy the actual node and create many nodes that have
-        # mono extend. The first one gets all the css rules
-        for _selectors, rules in self.parts.items():
-            if ' extends ' in _selectors:
-                selectors, _, parent = _selectors.partition(' extends ')
-                parents = parent.split('&')
-                del self.parts[_selectors]
-                for parent in parents:
-                    new_selectors = selectors + ' extends ' + parent
-                    self.parts.setdefault(new_selectors, [])
-                    self.parts[new_selectors].extend(rules)
-                    rules = []  # further rules extending other parents will be empty
-
-        cnt = 0
-        parents_left = True
-        while parents_left and cnt < 10:
-            cnt += 1
-            parents_left = False
-            for _selectors in self.parts.keys():
-                selectors, _, parent = _selectors.partition(' extends ')
-                if parent:
-                    parents_left = True
-                    if _selectors not in self.parts:
-                        continue  # Nodes might have been renamed while linking parents...
-
-                    rules = self.parts[_selectors]
-
-                    del self.parts[_selectors]
-                    self.parts.setdefault(selectors, [])
-                    self.parts[selectors].extend(rules)
-
-                    parents = self.link_with_parents(parent, selectors, rules)
-
-                    if parents is None:
-                        log.warn("Parent rule not found: %s", parent)
-                    else:
-                        # from the parent, inherit the context and the options:
-                        new_context = {}
-                        new_options = {}
-                        for parent in parents:
-                            new_context.update(parent[CONTEXT])
-                            new_options.update(parent[OPTIONS])
-                        for rule in rules:
-                            _new_context = new_context.copy()
-                            _new_context.update(rule[CONTEXT])
-                            rule[CONTEXT] = _new_context
-                            _new_options = new_options.copy()
-                            _new_options.update(rule[OPTIONS])
-                            rule[OPTIONS] = _new_options
-
-    @print_timing(3)
-    def manage_order(self):
-        # order rules according with their dependencies
-        for rule in self.rules:
-            if rule[POSITION] is not None:
-                rule[DEPS].add(rule[POSITION] + 1)
-                # This moves the rules just above the topmost dependency during the sorted() below:
-                rule[POSITION] = min(rule[DEPS])
-        self.rules = sorted(self.rules, key=lambda o: o[POSITION])
-
-    @print_timing(3)
-    def parse_properties(self):
-        self.css_files = []
-        self._rules = {}
-        css_files = set()
-        old_fileid = None
-        for rule in self.rules:
-            #print >>sys.stderr, rule[FILEID], rule[POSITION], [ c for c in rule[CONTEXT] if c[1] != '_' ], rule[OPTIONS].keys(), rule[SELECTORS], rule[DEPS]
-            if rule[POSITION] is not None and rule[PROPERTIES]:
-                fileid = rule[FILEID]
-                self._rules.setdefault(fileid, [])
-                self._rules[fileid].append(rule)
-                if old_fileid != fileid:
-                    old_fileid = fileid
-                    if fileid not in css_files:
-                        css_files.add(fileid)
-                        self.css_files.append(fileid)
-
-    @print_timing(3)
-    def create_css(self, fileid=None):
-        """
-        Generate the final CSS string
-        """
-        if fileid:
-            rules = self._rules.get(fileid) or []
-        else:
-            rules = self.rules
-
-        compress = self._scss_opts.get('compress', True)
-        if compress:
-            sc, sp, tb, nl = False, '', '', ''
-        else:
-            sc, sp, tb, nl = True, ' ', '  ', '\n'
-
-        scope = set()
-        return self._create_css(rules, scope, sc, sp, tb, nl, not compress and self._scss_opts.get('debug_info', False))
-
-    def _create_css(self, rules, scope=None, sc=True, sp=' ', tb='  ', nl='\n', debug_info=False):
-        scope = set() if scope is None else scope
-
-        open_selectors = False
-        old_selectors = None
-        open_media = False
-        old_media = None
-        old_property = None
-
-        wrap = textwrap.TextWrapper(break_long_words=False)
-        wrap.wordsep_re = re.compile(r'(?<=,)(\s*)')
-        wrap = wrap.wrap
-
-        result = ''
-        for rule in rules:
-            #print >>sys.stderr, rule[FILEID], rule[MEDIA], rule[POSITION], [ c for c in rule[CONTEXT] if not c.startswith('$__') ], rule[OPTIONS].keys(), rule[SELECTORS], rule[DEPS]
-            if rule[POSITION] is not None and rule[PROPERTIES]:
-                selectors = rule[SELECTORS]
-                media = rule[MEDIA]
-                _tb = tb if old_media else ''
-                if old_media != media or media is not None:
-                    if open_selectors:
-                        if not sc:
-                            if result[-1] == ';':
-                                result = result[:-1]
-                        result += _tb + '}' + nl
-                        open_selectors = False
-                    if open_media:
-                        if not sc:
-                            if result[-1] == ';':
-                                result = result[:-1]
-                        result += '}' + nl
-                        open_media = False
-                    if media:
-                        result += '@media ' + (' and ').join(set(media)) + sp + '{' + nl
-                        open_media = True
-                    old_media = media
-                    old_selectors = None  # force entrance to add a new selector
-                _tb = tb if media else ''
-                if old_selectors != selectors or selectors is not None:
-                    if open_selectors:
-                        if not sc:
-                            if result[-1] == ';':
-                                result = result[:-1]
-                        result += _tb + '}' + nl
-                        open_selectors = False
-                    if selectors:
-                        if debug_info:
-                            filename, lineno = rule[INDEX][rule[LINENO]].rsplit(':', 1)
-                            filename = _escape_chars_re.sub(r'\\\1', filename)
-                            sass_debug_info = '@media -sass-debug-info{filename{font-family:file\:\/\/%s}line{font-family:\\00003%s}}' % (filename, lineno)
-                            result += sass_debug_info + nl
-                        selector = (',' + sp).join(selectors.split(',')) + sp + '{'
-                        if nl:
-                            selector = nl.join(wrap(selector))
-                        result += _tb + selector + nl
-                        open_selectors = True
-                    old_selectors = selectors
-                    scope = set()
-                if selectors:
-                    _tb += tb
-                if rule[OPTIONS].get('verbosity', 0) > 1:
-                    result += _tb + '/* file: ' + rule[FILEID] + ' */' + nl
-                    if rule[CONTEXT]:
-                        result += _tb + '/* vars:' + nl
-                        for k, v in rule[CONTEXT].items():
-                            result += _tb + _tb + k + ' = ' + v + ';' + nl
-                        result += _tb + '*/' + nl
-                result += self._print_properties(rule[PROPERTIES], scope, [old_property], sc, sp, _tb, nl, wrap)
-
-        if open_media:
-            _tb = tb
-        else:
-            _tb = ''
-        if open_selectors:
-            if not sc:
-                if result[-1] == ';':
-                    result = result[:-1]
-            result += _tb + '}' + nl
-
-        if open_media:
-            if not sc:
-                if result[-1] == ';':
-                    result = result[:-1]
-            result += '}' + nl
-
-        return result + '\n'
-
-    def _print_properties(self, properties, scope=None, old_property=None, sc=True, sp=' ', _tb='', nl='\n', wrap=None):
-        if wrap is None:
-            wrap = textwrap.TextWrapper(break_long_words=False)
-            wrap.wordsep_re = re.compile(r'(?<=,)(\s*)')
-            wrap = wrap.wrap
-        result = ''
-        old_property = [None] if old_property is None else old_property
-        scope = set() if scope is None else scope
-        for lineno, prop, value in properties:
-            if value is not None:
-                if nl:
-                    value = (nl + _tb + _tb).join(wrap(value))
-                property = prop + ':' + sp + value
-            else:
-                property = prop
-            if '!default' in property:
-                property = property.replace('!default', '').replace('  ', ' ').strip()
-                if prop in scope:
-                    continue
-            if old_property[0] != property:
-                old_property[0] = property
-                scope.add(prop)
-                old_property[0] = property
-                result += _tb + property + ';' + nl
-        return result
-
-    def calculate(self, _base_str, context, options, rule):
-        try:
-            better_expr_str = self._replaces[_base_str]
-        except KeyError:
-            better_expr_str = _base_str
-
-            if _skip_word_re.match(better_expr_str) and '- ' not in better_expr_str and ' and ' not in better_expr_str and ' or ' not in better_expr_str and 'not ' not in better_expr_str:
-                self._replaces[_base_str] = better_expr_str
-                return better_expr_str
-
-            better_expr_str = self.do_glob_math(better_expr_str, context, options, rule)
-
-            better_expr_str = eval_expr(better_expr_str, rule, True)
-            if better_expr_str is None:
-                better_expr_str = self.apply_vars(_base_str, context, options, rule)
-
-            if '$' not in _base_str:
-                self._replaces[_base_str] = better_expr_str
-        return better_expr_str
-
-    def _calculate_expr(self, context, options, rule, _dequote):
-        def __calculate_expr(result):
-            _group0 = result.group(1)
-            _base_str = _group0
-            try:
-                better_expr_str = self._replaces[_group0]
-            except KeyError:
-                better_expr_str = _base_str
-
-                if _skip_re.match(better_expr_str) and '- ' not in better_expr_str:
-                    self._replaces[_group0] = better_expr_str
-                    return better_expr_str
-
-                better_expr_str = eval_expr(better_expr_str, rule)
-
-                if better_expr_str is None:
-                    better_expr_str = _base_str
-                elif _dequote:
-                    better_expr_str = dequote(better_expr_str)
-
-                if '$' not in _group0:
-                    self._replaces[_group0] = better_expr_str
-
-            return better_expr_str
-        return __calculate_expr
-
-    def do_glob_math(self, cont, context, options, rule, _dequote=False):
-        cont = str(cont)
-        if '#{' not in cont:
-            return cont
-        cont = _expr_glob_re.sub(self._calculate_expr(context, options, rule, _dequote), cont)
-        return cont
-
-    @print_timing(3)
-    def post_process(self, cont):
-        compress = self._scss_opts.get('compress', 1) and 'compress_' or ''
-        # short colors:
-        if self._scss_opts.get(compress + 'short_colors', 1):
-            cont = _short_color_re.sub(r'#\1\2\3', cont)
-            # color names:
-        if self._scss_opts.get(compress + 'reverse_colors', 1):
-            cont = _reverse_colors_re.sub(lambda m: _reverse_colors[m.group(0).lower()], cont)
-        if compress:
-            # zero units out (i.e. 0px or 0em -> 0):
-            cont = _zero_units_re.sub('0', cont)
-            # remove zeros before decimal point (i.e. 0.3 -> .3)
-            cont = _zero_re.sub('.', cont)
-        return cont
-
-
-import hashlib
-import base64
-import datetime
-import mimetypes
-import glob
-import math
-import operator
-import colorsys
-
-try:
-    import cStringIO as StringIO
-except:
-    import StringIO
-
-try:
-    from PIL import Image, ImageDraw
-except ImportError:
-    try:
-        import Image, ImageDraw
-    except:
-        Image = None
-
-
-################################################################################
-
-
-def to_str(num):
-    if isinstance(num, dict):
-        s = sorted(num.items())
-        sp = num.get('_', '')
-        return (sp + ' ').join(to_str(v) for n, v in s if n != '_')
-    elif isinstance(num, float):
-        num = ('%0.03f' % round(num, 3)).rstrip('0').rstrip('.')
-        return num
-    elif isinstance(num, bool):
-        return 'true' if num else 'false'
-    elif num is None:
-        return ''
-    return str(num)
-
-
-def to_float(num):
-    if isinstance(num, (float, int)):
-        return float(num)
-    num = to_str(num)
-    if num and num[-1] == '%':
-        return float(num[:-1]) / 100.0
-    else:
-        return float(num)
-
-
-hex2rgba = {
-    9: lambda c: (int(c[1:3], 16), int(c[3:5], 16), int(c[5:7], 16), int(c[7:9], 16)),
-    7: lambda c: (int(c[1:3], 16), int(c[3:5], 16), int(c[5:7], 16), 1.0),
-    5: lambda c: (int(c[1] * 2, 16), int(c[2] * 2, 16), int(c[3] * 2, 16), int(c[4] * 2, 16)),
-    4: lambda c: (int(c[1] * 2, 16), int(c[2] * 2, 16), int(c[3] * 2, 16), 1.0),
-    }
-
-
-def escape(s):
-    return re.sub(r'''(["'])''', r'\\\1', s)
-
-
-def unescape(s):
-    return re.sub(r'''\\(['"])''', r'\1', s)
-
-
-################################################################################
-# Sass/Compass Library Functions:
-
-
-def _rgb(r, g, b, type='rgb'):
-    return _rgba(r, g, b, 1.0, type)
-
-
-def _rgba(r, g, b, a, type='rgba'):
-    c = NumberValue(r), NumberValue(g), NumberValue(b), NumberValue(a)
-
-    col = [c[i].value * 255.0 if (c[i].unit == '%' or c[i].value > 0 and c[i].value <= 1) else
-           0.0 if c[i].value < 0 else
-           255.0 if c[i].value > 255 else
-           c[i].value
-           for i in range(3)
-    ]
-    col += [0.0 if c[3].value < 0 else 1.0 if c[3].value > 1 else c[3].value]
-    col += [type]
-    return ColorValue(col)
-
-
-def _color_type(color, a, type):
-    color = ColorValue(color).value
-    a = NumberValue(a).value if a is not None else color[3]
-    col = list(color[:3])
-    col += [0.0 if a < 0 else 1.0 if a > 1 else a]
-    col += [type]
-    return ColorValue(col)
-
-
-def _rgb2(color):
-    return _color_type(color, 1.0, 'rgb')
-
-
-def _rgba2(color, a=None):
-    return _color_type(color, a, 'rgba')
-
-
-def _hsl2(color):
-    return _color_type(color, 1.0, 'hsl')
-
-
-def _hsla2(color, a=None):
-    return _color_type(color, a, 'hsla')
-
-
-def _ie_hex_str(color):
-    c = ColorValue(color).value
-    return StringValue('#%02X%02X%02X%02X' % (round(c[3] * 255), round(c[0]), round(c[1]), round(c[2])))
-
-
-def _hsl(h, s, l, type='hsl'):
-    return _hsla(h, s, l, 1.0, type)
-
-
-def _hsla(h, s, l, a, type='hsla'):
-    c = NumberValue(h), NumberValue(s), NumberValue(l), NumberValue(a)
-    col = [c[0] if (c[0].unit == '%' and c[0].value > 0 and c[0].value <= 1) else (c[0].value % 360.0) / 360.0]
-    col += [0.0 if cl <= 0 else 1.0 if cl >= 1.0 else cl
-            for cl in [
-    c[i].value if (c[i].unit == '%' or c[i].value > 0 and c[i].value <= 1) else
-    c[i].value / 100.0
-    for i in range(1, 4)
-    ]
-    ]
-    col += [type]
-    c = [c * 255.0 for c in colorsys.hls_to_rgb(col[0], 0.999999 if col[2] == 1 else col[2], 0.999999 if col[1] == 1 else col[1])] + [col[3], type]
-    col = ColorValue(c)
-    return col
-
-
-def __rgba_op(op, color, r, g, b, a):
-    color = ColorValue(color)
-    c = color.value
-    a = [
-        None if r is None else NumberValue(r).value,
-        None if g is None else NumberValue(g).value,
-        None if b is None else NumberValue(b).value,
-        None if a is None else NumberValue(a).value,
-        ]
-    # Do the additions:
-    c = [op(c[i], a[i]) if op is not None and a[i] is not None else a[i] if a[i] is not None else c[i] for i in range(4)]
-    # Validations:
-    r = 255.0, 255.0, 255.0, 1.0
-    c = [0.0 if c[i] < 0 else r[i] if c[i] > r[i] else c[i] for i in range(4)]
-    color.value = tuple(c)
-    return color
-
-
-def _opacify(color, amount):
-    return __rgba_op(operator.__add__, color, 0, 0, 0, amount)
-
-
-def _transparentize(color, amount):
-    return __rgba_op(operator.__sub__, color, 0, 0, 0, amount)
-
-
-def __hsl_op(op, color, h, s, l):
-    color = ColorValue(color)
-    c = color.value
-    h = None if h is None else NumberValue(h)
-    s = None if s is None else NumberValue(s)
-    l = None if l is None else NumberValue(l)
-    a = [
-        None if h is None else h.value / 360.0,
-        None if s is None else s.value / 100.0 if s.unit != '%' and s.value >= 1 else s.value,
-        None if l is None else l.value / 100.0 if l.unit != '%' and l.value >= 1 else l.value,
-        ]
-    # Convert to HSL:
-    h, l, s = list(colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0))
-    c = h, s, l
-    # Do the additions:
-    c = [0.0 if c[i] < 0 else 1.0 if c[i] > 1 else op(c[i], a[i]) if op is not None and a[i] is not None else a[i] if a[i] is not None else c[i] for i in range(3)]
-    # Validations:
-    c[0] = (c[0] * 360.0) % 360
-    r = 360.0, 1.0, 1.0
-    c = [0.0 if c[i] < 0 else r[i] if c[i] > r[i] else c[i] for i in range(3)]
-    # Convert back to RGB:
-    c = colorsys.hls_to_rgb(c[0] / 360.0, 0.999999 if c[2] == 1 else c[2], 0.999999 if c[1] == 1 else c[1])
-    color.value = (c[0] * 255.0, c[1] * 255.0, c[2] * 255.0, color.value[3])
-    return color
-
-
-def _lighten(color, amount):
-    return __hsl_op(operator.__add__, color, 0, 0, amount)
-
-
-def _darken(color, amount):
-    return __hsl_op(operator.__sub__, color, 0, 0, amount)
-
-
-def _saturate(color, amount):
-    return __hsl_op(operator.__add__, color, 0, amount, 0)
-
-
-def _desaturate(color, amount):
-    return __hsl_op(operator.__sub__, color, 0, amount, 0)
-
-
-def _grayscale(color):
-    return __hsl_op(operator.__sub__, color, 0, 100.0, 0)
-
-
-def _adjust_hue(color, degrees):
-    return __hsl_op(operator.__add__, color, degrees, 0, 0)
-
-
-def _complement(color):
-    return __hsl_op(operator.__add__, color, 180.0, 0, 0)
-
-
-def _invert(color):
-    """
-    Returns the inverse (negative) of a color.
-    The red, green, and blue values are inverted, while the opacity is left alone.
-    """
-    col = ColorValue(color)
-    c = col.value
-    c[0] = 255.0 - c[0]
-    c[1] = 255.0 - c[1]
-    c[2] = 255.0 - c[2]
-    return col
-
-
-def _adjust_lightness(color, amount):
-    return __hsl_op(operator.__add__, color, 0, 0, amount)
-
-
-def _adjust_saturation(color, amount):
-    return __hsl_op(operator.__add__, color, 0, amount, 0)
-
-
-def _scale_lightness(color, amount):
-    return __hsl_op(operator.__mul__, color, 0, 0, amount)
-
-
-def _scale_saturation(color, amount):
-    return __hsl_op(operator.__mul__, color, 0, amount, 0)
-
-
-def _asc_color(op, color, saturation=None, lightness=None, red=None, green=None, blue=None, alpha=None):
-    if lightness or saturation:
-        color = __hsl_op(op, color, 0, saturation, lightness)
-    if red or green or blue or alpha:
-        color = __rgba_op(op, color, red, green, blue, alpha)
-    return color
-
-
-def _adjust_color(color, saturation=None, lightness=None, red=None, green=None, blue=None, alpha=None):
-    return _asc_color(operator.__add__, color, saturation, lightness, red, green, blue, alpha)
-
-
-def _scale_color(color, saturation=None, lightness=None, red=None, green=None, blue=None, alpha=None):
-    return _asc_color(operator.__mul__, color, saturation, lightness, red, green, blue, alpha)
-
-
-def _change_color(color, saturation=None, lightness=None, red=None, green=None, blue=None, alpha=None):
-    return _asc_color(None, color, saturation, lightness, red, green, blue, alpha)
-
-
-def _mix(color1, color2, weight=None):
-    """
-    Mixes together two colors. Specifically, takes the average of each of the
-    RGB components, optionally weighted by the given percentage.
-    The opacity of the colors is also considered when weighting the components.
-
-    Specifically, takes the average of each of the RGB components,
-    optionally weighted by the given percentage.
-    The opacity of the colors is also considered when weighting the components.
-
-    The weight specifies the amount of the first color that should be included
-    in the returned color.
-    50%, means that half the first color
-        and half the second color should be used.
-    25% means that a quarter of the first color
-        and three quarters of the second color should be used.
-
-    For example:
-
-        mix(#f00, #00f) => #7f007f
-        mix(#f00, #00f, 25%) => #3f00bf
-        mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
-
-    """
-    # This algorithm factors in both the user-provided weight
-    # and the difference between the alpha values of the two colors
-    # to decide how to perform the weighted average of the two RGB values.
-    #
-    # It works by first normalizing both parameters to be within [-1, 1],
-    # where 1 indicates "only use color1", -1 indicates "only use color 0",
-    # and all values in between indicated a proportionately weighted average.
-    #
-    # Once we have the normalized variables w and a,
-    # we apply the formula (w + a)/(1 + w*a)
-    # to get the combined weight (in [-1, 1]) of color1.
-    # This formula has two especially nice properties:
-    #
-    #   * When either w or a are -1 or 1, the combined weight is also that number
-    #     (cases where w * a == -1 are undefined, and handled as a special case).
-    #
-    #   * When a is 0, the combined weight is w, and vice versa
-    #
-    # Finally, the weight of color1 is renormalized to be within [0, 1]
-    # and the weight of color2 is given by 1 minus the weight of color1.
-    #
-    # Algorithm from the Sass project: http://sass-lang.com/
-
-    c1 = ColorValue(color1).value
-    c2 = ColorValue(color2).value
-    p = NumberValue(weight).value if weight is not None else 0.5
-    p = 0.0 if p < 0 else 1.0 if p > 1 else p
-
-    w = p * 2 - 1
-    a = c1[3] - c2[3]
-
-    w1 = ((w if (w * a == -1) else (w + a) / (1 + w * a)) + 1) / 2.0
-
-    w2 = 1 - w1
-    q = [w1, w1, w1, p]
-    r = [w2, w2, w2, 1 - p]
-
-    color = ColorValue(None).merge(c1).merge(c2)
-    color.value = [c1[i] * q[i] + c2[i] * r[i] for i in range(4)]
-
-    return color
-
-
-def _red(color):
-    c = ColorValue(color).value
-    return NumberValue(c[0])
-
-
-def _green(color):
-    c = ColorValue(color).value
-    return NumberValue(c[1])
-
-
-def _blue(color):
-    c = ColorValue(color).value
-    return NumberValue(c[2])
-
-
-def _alpha(color):
-    c = ColorValue(color).value
-    return NumberValue(c[3])
-
-
-def _hue(color):
-    c = ColorValue(color).value
-    h, l, s = colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0)
-    ret = NumberValue(h * 360.0)
-    ret.units = {'deg': _units_weights.get('deg', 1), '_': 'deg'}
-    return ret
-
-
-def _saturation(color):
-    c = ColorValue(color).value
-    h, l, s = colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0)
-    ret = NumberValue(s)
-    ret.units = {'%': _units_weights.get('%', 1), '_': '%'}
-    return ret
-
-
-def _lightness(color):
-    c = ColorValue(color).value
-    h, l, s = colorsys.rgb_to_hls(c[0] / 255.0, c[1] / 255.0, c[2] / 255.0)
-    ret = NumberValue(l)
-    ret.units = {'%': _units_weights.get('%', 1), '_': '%'}
-    return ret
-
-
-def __color_stops(percentages, *args):
-    if len(args) == 1:
-        if isinstance(args[0], list):
-            return args[0]
-        elif isinstance(args[0], StringValue):
-            color_stops = []
-            colors = split_params(args[0].value)
-            for color in colors:
-                color = color.strip()
-                if color.startswith('color-stop('):
-                    s, c = split_params(color[11:].rstrip(')'))
-                    s = s.strip()
-                    c = c.strip()
-                else:
-                    c, s = color.split()
-                color_stops.append((to_float(s), c))
-            return color_stops
-
-    colors = []
-    stops = []
-    prev_color = False
-    for c in args:
-        if isinstance(c, ListValue):
-            for i, c in c.items():
-                if isinstance(c, ColorValue):
-                    if prev_color:
-                        stops.append(None)
-                    colors.append(c)
-                    prev_color = True
-                elif isinstance(c, NumberValue):
-                    stops.append(c)
-                    prev_color = False
-        else:
-            if isinstance(c, ColorValue):
-                if prev_color:
-                    stops.append(None)
-                colors.append(c)
-                prev_color = True
-            elif isinstance(c, NumberValue):
-                stops.append(NumberValue(c))
-                prev_color = False
-    if prev_color:
-        stops.append(None)
-    stops = stops[:len(colors)]
-    if percentages:
-        max_stops = max(s and (s.value if s.unit != '%' else None) or None for s in stops)
-    else:
-        max_stops = max(s and (s if s.unit != '%' else None) or None for s in stops)
-    stops = [s and (s.value / max_stops if s.unit != '%' else s.value) for s in stops]