Documentation
This commit is contained in:
		@@ -0,0 +1,4 @@
 | 
			
		||||
"""
 | 
			
		||||
    Main lesscss parse library. Contains lexer and parser, along with 
 | 
			
		||||
    utility classes
 | 
			
		||||
"""
 | 
			
		||||
@@ -1,9 +1,11 @@
 | 
			
		||||
# -*- coding: utf8 -*-
 | 
			
		||||
"""
 | 
			
		||||
    LESSCPY Color functions
 | 
			
		||||
.. module:: lesscpy.lessc.color
 | 
			
		||||
    :synopsis: Lesscpy Color functions
 | 
			
		||||
    
 | 
			
		||||
    Copyright (c)
 | 
			
		||||
    See LICENSE for details.
 | 
			
		||||
    <jtm@robot.is>
 | 
			
		||||
.. moduleauthor:: Jóhann T. Maríusson <jtm@robot.is>
 | 
			
		||||
"""
 | 
			
		||||
import colorsys
 | 
			
		||||
from . import utility
 | 
			
		||||
@@ -11,8 +13,10 @@ from . import utility
 | 
			
		||||
class Color():
 | 
			
		||||
    def process(self, expression):
 | 
			
		||||
        """ Process color expression
 | 
			
		||||
            @param tuple: color expression
 | 
			
		||||
            @return: string
 | 
			
		||||
        args:
 | 
			
		||||
            expression (tuple): color expression
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        a, o, b = expression
 | 
			
		||||
        c1 = self._hextorgb(a)
 | 
			
		||||
@@ -25,23 +29,29 @@ class Color():
 | 
			
		||||
            r.append("%02x" % v)
 | 
			
		||||
        return ''.join(r)
 | 
			
		||||
    
 | 
			
		||||
    def operate(self, a, b, o):
 | 
			
		||||
    def operate(self, left, right, operation):
 | 
			
		||||
        """ Do operation on colors
 | 
			
		||||
            @param string: color
 | 
			
		||||
            @param string: color
 | 
			
		||||
            @param string: operator
 | 
			
		||||
        args:
 | 
			
		||||
            left (str): left side
 | 
			
		||||
            right (str): right side
 | 
			
		||||
            operation (str): Operation
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        operation = {
 | 
			
		||||
            '+': '__add__',
 | 
			
		||||
            '-': '__sub__',
 | 
			
		||||
            '*': '__mul__',
 | 
			
		||||
            '/': '__truediv__'
 | 
			
		||||
        }.get(o)
 | 
			
		||||
        v = getattr(a, operation)(b)
 | 
			
		||||
        return v
 | 
			
		||||
        }.get(operation)
 | 
			
		||||
        return getattr(left, operation)(right)
 | 
			
		||||
    
 | 
			
		||||
    def rgb(self, *args):
 | 
			
		||||
        """
 | 
			
		||||
        """ Translate rgb(...) to color string
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 4:
 | 
			
		||||
            return self.rgba(*args)
 | 
			
		||||
@@ -57,7 +67,11 @@ class Color():
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def rgba(self, *args):
 | 
			
		||||
        """
 | 
			
		||||
        """ Translate rgba(...) to color string
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 4:
 | 
			
		||||
            try:
 | 
			
		||||
@@ -71,7 +85,11 @@ class Color():
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def hsl(self, *args):
 | 
			
		||||
        """
 | 
			
		||||
        """ Translate hsl(...) to color string
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 4:
 | 
			
		||||
            return self.hsla(*args)
 | 
			
		||||
@@ -85,7 +103,11 @@ class Color():
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def hsla(self, *args):
 | 
			
		||||
        """
 | 
			
		||||
        """ Translate hsla(...) to color string
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 4:
 | 
			
		||||
            h, s, l, a = args
 | 
			
		||||
@@ -97,28 +119,46 @@ class Color():
 | 
			
		||||
            return "rgba(%s,%s,%s,%s)" % tuple(color)
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def hue(self, *args):
 | 
			
		||||
    def hue(self, color, *args):
 | 
			
		||||
        """ Return the hue value of a color
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            float
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if args:
 | 
			
		||||
            h, l, s = self._hextohls(args[0])
 | 
			
		||||
            return round(h * 360, 3)
 | 
			
		||||
        if color:
 | 
			
		||||
            h, l, s = self._hextohls(color)
 | 
			
		||||
            return round(h * 360.0, 3)
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def saturation(self, *args):
 | 
			
		||||
    def saturation(self, color, *args):
 | 
			
		||||
        """ Return the saturation value of a color
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            float
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if args:
 | 
			
		||||
            h, l, s = self._hextohls(args[0])
 | 
			
		||||
            return s * 100
 | 
			
		||||
        if color:
 | 
			
		||||
            h, l, s = self._hextohls(color)
 | 
			
		||||
            return s * 100.0
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def lightness(self, *args):
 | 
			
		||||
    def lightness(self, color, *args):
 | 
			
		||||
        """ Return the lightness value of a color
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            float
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if args:
 | 
			
		||||
            h, l, s = self._hextohls(args[0])
 | 
			
		||||
            return l * 100
 | 
			
		||||
        if color:
 | 
			
		||||
            h, l, s = self._hextohls(color)
 | 
			
		||||
            return l * 100.0
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def opacity(self, *args):
 | 
			
		||||
@@ -126,74 +166,97 @@ class Color():
 | 
			
		||||
        """
 | 
			
		||||
        pass
 | 
			
		||||
    
 | 
			
		||||
    def lighten(self, *args):
 | 
			
		||||
    def lighten(self, color, diff, *args):
 | 
			
		||||
        """ Lighten a color
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
            diff (str): percentage
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 2:
 | 
			
		||||
            color, diff = args
 | 
			
		||||
        if color and diff:
 | 
			
		||||
            return self._ophsl(color, diff, 1, '__add__')
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def darken(self, *args):
 | 
			
		||||
    def darken(self, color, diff, *args):
 | 
			
		||||
        """ Darken a color
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
            diff (str): percentage
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 2:
 | 
			
		||||
            color, diff = args
 | 
			
		||||
        if color and diff:
 | 
			
		||||
            return self._ophsl(color, diff, 1, '__sub__')
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def saturate(self, *args):
 | 
			
		||||
    def saturate(self, color, diff, *args):
 | 
			
		||||
        """ Saturate a color
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
            diff (str): percentage
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 2:
 | 
			
		||||
            color, diff = args
 | 
			
		||||
        if color and diff:
 | 
			
		||||
            return self._ophsl(color, diff, 2, '__add__')
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def desaturate(self, *args):
 | 
			
		||||
    def desaturate(self, color, diff, *args):
 | 
			
		||||
        """ Desaturate a color
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
            diff (str): percentage
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 2:
 | 
			
		||||
            color, diff = args
 | 
			
		||||
        if color and diff:
 | 
			
		||||
            return self._ophsl(color, diff, 2, '__sub__')
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def clamp(self, v):
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        return min(1, max(0, v))
 | 
			
		||||
    def _clamp(self, value):
 | 
			
		||||
        # Clamp value
 | 
			
		||||
        return min(1, max(0, value))
 | 
			
		||||
    
 | 
			
		||||
    def grayscale(self, *args):
 | 
			
		||||
    def grayscale(self, color, *args):
 | 
			
		||||
        """ Simply 100% desaturate.
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        Simply 100% desaturate.
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 2:
 | 
			
		||||
            return self.desaturate(args[0], 100)
 | 
			
		||||
        if color:
 | 
			
		||||
            return self.desaturate(color, 100.0)
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
 | 
			
		||||
    def greyscale(self, *args):
 | 
			
		||||
    def greyscale(self, color, *args):
 | 
			
		||||
        """Wrapper for grayscale, other spelling
 | 
			
		||||
        """
 | 
			
		||||
        Wrapper for grayscale
 | 
			
		||||
        """
 | 
			
		||||
        return self.grayscale(*args)
 | 
			
		||||
        return self.grayscale(color, *args)
 | 
			
		||||
    
 | 
			
		||||
    def spin(self, *args):
 | 
			
		||||
    def spin(self, color, degree, *args):
 | 
			
		||||
        """ Spin color by degree. (Increase / decrease hue)
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
            degree (str): percentage
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) == 2:
 | 
			
		||||
            color, deg = args
 | 
			
		||||
            if type(deg) == str: deg = int(deg.strip('%'))
 | 
			
		||||
        if color and degree:
 | 
			
		||||
            if type(degree) == str: 
 | 
			
		||||
                degree = int(degree.strip('%'))
 | 
			
		||||
            h, l, s = self._hextohls(color)
 | 
			
		||||
            h = ((h * 360) + deg) % 360
 | 
			
		||||
            h = 360 + h if h < 0 else h
 | 
			
		||||
            rgb = colorsys.hls_to_rgb(h / 360, l, s)
 | 
			
		||||
            h = ((h * 360.0) + degree) % 360.0
 | 
			
		||||
            h = 360.0 + h if h < 0 else h
 | 
			
		||||
            rgb = colorsys.hls_to_rgb(h / 360.0, l, s)
 | 
			
		||||
            color = (round(c * 255) for c in rgb)
 | 
			
		||||
            return self._rgbatohex(color)
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
    
 | 
			
		||||
    def mix(self, *args):
 | 
			
		||||
        """
 | 
			
		||||
        This algorithm factors in both the user-provided weight
 | 
			
		||||
    def mix(self, color1, color2, weight=50, *args):
 | 
			
		||||
        """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.
 | 
			
		||||
        
 | 
			
		||||
@@ -216,19 +279,24 @@ class Color():
 | 
			
		||||
        
 | 
			
		||||
        Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein
 | 
			
		||||
        http://sass-lang.com
 | 
			
		||||
        args:
 | 
			
		||||
            color1 (str): first color
 | 
			
		||||
            color2 (str): second color
 | 
			
		||||
            weight (int/str): weight
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        if len(args) >= 2:
 | 
			
		||||
            try:
 | 
			
		||||
                c1, c2, w = args
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                c1, c2 = args
 | 
			
		||||
                w = 50
 | 
			
		||||
            if type(w) == str: w = int(w.strip('%'))
 | 
			
		||||
            w = ((w / 100.0) * 2) - 1
 | 
			
		||||
            rgb1 = self._hextorgb(c1)
 | 
			
		||||
            rgb2 = self._hextorgb(c2)
 | 
			
		||||
            a = 0
 | 
			
		||||
            w1 = (((w if w * a == -1 else w + a) / (1 + w * a)) + 1)
 | 
			
		||||
        if color1 and color2:
 | 
			
		||||
            if type(weight) == str: 
 | 
			
		||||
                weight = int(weight.strip('%'))
 | 
			
		||||
            weight = ((weight / 100.0) * 2) - 1
 | 
			
		||||
            rgb1 = self._hextorgb(color1)
 | 
			
		||||
            rgb2 = self._hextorgb(color2)
 | 
			
		||||
            alpha = 0
 | 
			
		||||
            w1 = (((weight if weight * alpha == -1 
 | 
			
		||||
                    else weight + alpha) / (1 + weight * alpha)) + 1)
 | 
			
		||||
            w1 = w1 / 2.0
 | 
			
		||||
            w2 = 1 - w1
 | 
			
		||||
            rgb = [
 | 
			
		||||
@@ -240,10 +308,14 @@ class Color():
 | 
			
		||||
        raise ValueError('Illegal color values')
 | 
			
		||||
        
 | 
			
		||||
    def fmt(self, color):
 | 
			
		||||
        """
 | 
			
		||||
            Format CSS Hex color code.
 | 
			
		||||
            uppercase becomes lowercase, 3 digit codes expand to 6 digit.
 | 
			
		||||
            @param string: color
 | 
			
		||||
        """ Format CSS Hex color code.
 | 
			
		||||
        uppercase becomes lowercase, 3 digit codes expand to 6 digit.
 | 
			
		||||
        args:
 | 
			
		||||
            color (str): color
 | 
			
		||||
        raises:
 | 
			
		||||
            ValueError
 | 
			
		||||
        returns:
 | 
			
		||||
            str
 | 
			
		||||
        """
 | 
			
		||||
        if utility.is_color(color):
 | 
			
		||||
            color = color.lower().strip('#')
 | 
			
		||||
@@ -253,8 +325,6 @@ class Color():
 | 
			
		||||
        raise ValueError('Cannot format non-color')
 | 
			
		||||
    
 | 
			
		||||
    def _rgbatohex(self, rgba):
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        return '#%s' % ''.join(["%02x" % v for v in 
 | 
			
		||||
                                [0xff 
 | 
			
		||||
                                 if h > 0xff else 
 | 
			
		||||
@@ -262,8 +332,6 @@ class Color():
 | 
			
		||||
                                 for h in rgba]
 | 
			
		||||
                                ])
 | 
			
		||||
    def _hextorgb(self, hex):    
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        hex = hex.strip()
 | 
			
		||||
        if hex[0] == '#':
 | 
			
		||||
            hex = hex.strip('#').strip(';')
 | 
			
		||||
@@ -275,17 +343,13 @@ class Color():
 | 
			
		||||
        return [int(hex, 16)] * 3
 | 
			
		||||
        
 | 
			
		||||
    def _hextohls(self, hex):
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        rgb = self._hextorgb(hex)
 | 
			
		||||
        return colorsys.rgb_to_hls(*[c / 255.0 for c in rgb])
 | 
			
		||||
    
 | 
			
		||||
    def _ophsl(self, color, diff, idx, op):
 | 
			
		||||
        """
 | 
			
		||||
        """
 | 
			
		||||
        if type(diff) == str: diff = int(diff.strip('%'))
 | 
			
		||||
        hls = list(self._hextohls(color))
 | 
			
		||||
        hls[idx] = self.clamp(getattr(hls[idx], op)(diff / 100))
 | 
			
		||||
        hls[idx] = self._clamp(getattr(hls[idx], op)(diff / 100))
 | 
			
		||||
        rgb = colorsys.hls_to_rgb(*hls)
 | 
			
		||||
        color = (round(c * 255) for c in rgb)
 | 
			
		||||
        return self._rgbatohex(color)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
# -*- coding: utf8 -*-
 | 
			
		||||
"""
 | 
			
		||||
.. module:: parser
 | 
			
		||||
.. module:: lesscpy.lessc.parser
 | 
			
		||||
    :synopsis: Lesscss parser.
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    http://www.dabeaz.com/ply/ply.html
 | 
			
		||||
    http://www.w3.org/TR/CSS21/grammar.html#scanner
 | 
			
		||||
    http://lesscss.org/#docs
 | 
			
		||||
@@ -33,7 +33,8 @@ class LessParser(object):
 | 
			
		||||
            scope=None,
 | 
			
		||||
            outputdir='/tmp',
 | 
			
		||||
            importlvl=0,
 | 
			
		||||
            verbose=False):
 | 
			
		||||
            verbose=False
 | 
			
		||||
        ):
 | 
			
		||||
        """ Parser object
 | 
			
		||||
            
 | 
			
		||||
            Kwargs:
 | 
			
		||||
@@ -46,9 +47,9 @@ class LessParser(object):
 | 
			
		||||
                importlvl (int): Import depth
 | 
			
		||||
                verbose (bool): Verbose mode
 | 
			
		||||
        """
 | 
			
		||||
        self.verbose = verbose
 | 
			
		||||
        self.importlvl = importlvl
 | 
			
		||||
        self.lex = lexer.LessLexer()
 | 
			
		||||
        self.verbose    = verbose
 | 
			
		||||
        self.importlvl  = importlvl
 | 
			
		||||
        self.lex        = lexer.LessLexer()
 | 
			
		||||
        if not tabfile:
 | 
			
		||||
            tabfile = 'yacctab'
 | 
			
		||||
            
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,29 @@
 | 
			
		||||
"""
 | 
			
		||||
.. module:: lesscpy.lessc.scope
 | 
			
		||||
    :synopsis: Scope class.
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    Copyright (c)
 | 
			
		||||
    See LICENSE for details.
 | 
			
		||||
.. moduleauthor:: Jóhann T. Maríusson <jtm@robot.is>
 | 
			
		||||
"""
 | 
			
		||||
from . import utility
 | 
			
		||||
 | 
			
		||||
class Scope(list):
 | 
			
		||||
    """ Scope class. A stack implementation.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, init=False):
 | 
			
		||||
        """Scope
 | 
			
		||||
        Args:
 | 
			
		||||
            init (bool): Initiate scope
 | 
			
		||||
        """
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self._mixins = {}
 | 
			
		||||
        if init: self.push()
 | 
			
		||||
        self.in_mixin = False
 | 
			
		||||
        
 | 
			
		||||
    def push(self):
 | 
			
		||||
        """
 | 
			
		||||
        """Push level on scope
 | 
			
		||||
        """
 | 
			
		||||
        self.append({
 | 
			
		||||
            '__variables__' : {},
 | 
			
		||||
@@ -29,7 +42,9 @@ class Scope(list):
 | 
			
		||||
        
 | 
			
		||||
    @property
 | 
			
		||||
    def scopename(self):
 | 
			
		||||
        """
 | 
			
		||||
        """Current scope name as list
 | 
			
		||||
        Returns:
 | 
			
		||||
            list
 | 
			
		||||
        """
 | 
			
		||||
        return [r['__current__'] 
 | 
			
		||||
                for r in self 
 | 
			
		||||
@@ -37,13 +52,17 @@ class Scope(list):
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
    def add_block(self, block):
 | 
			
		||||
        """
 | 
			
		||||
        """Add block element to scope
 | 
			
		||||
        Args:
 | 
			
		||||
            block (Block): Block object
 | 
			
		||||
        """
 | 
			
		||||
        self[-1]['__blocks__'].append(block)
 | 
			
		||||
        self[-1]['__names__'].append(block.raw())
 | 
			
		||||
        
 | 
			
		||||
    def add_mixin(self, mixin):
 | 
			
		||||
        """
 | 
			
		||||
        """Add mixin to scope
 | 
			
		||||
        Args:
 | 
			
		||||
            mixin (Mixin): Mixin object
 | 
			
		||||
        """
 | 
			
		||||
        raw = mixin.name.raw()
 | 
			
		||||
        if raw in self._mixins:
 | 
			
		||||
@@ -52,13 +71,18 @@ class Scope(list):
 | 
			
		||||
            self._mixins[raw] = [mixin]
 | 
			
		||||
        
 | 
			
		||||
    def add_variable(self, variable):
 | 
			
		||||
        """
 | 
			
		||||
        """Add variable to scope
 | 
			
		||||
        Args:
 | 
			
		||||
            variable (Variable): Variable object
 | 
			
		||||
        """
 | 
			
		||||
        self[-1]['__variables__'][variable.name] = variable
 | 
			
		||||
        
 | 
			
		||||
    def variables(self, name):
 | 
			
		||||
        """
 | 
			
		||||
        Search for variable by name
 | 
			
		||||
        """Search for variable by name. Searches scope top down
 | 
			
		||||
        Args:
 | 
			
		||||
            name (string): Search term
 | 
			
		||||
        Returns:
 | 
			
		||||
            Variable object OR False
 | 
			
		||||
        """
 | 
			
		||||
        if type(name) is tuple:
 | 
			
		||||
            name = name[0]
 | 
			
		||||
@@ -70,17 +94,19 @@ class Scope(list):
 | 
			
		||||
        return False
 | 
			
		||||
    
 | 
			
		||||
    def mixins(self, name):
 | 
			
		||||
        """
 | 
			
		||||
        Search mixins for name.
 | 
			
		||||
        Allow '>' to be ignored.
 | 
			
		||||
        """ Search mixins for name.
 | 
			
		||||
        Allow '>' to be ignored. '.a .b()' == '.a > .b()'
 | 
			
		||||
        Args:
 | 
			
		||||
            name (string): Search term
 | 
			
		||||
        Returns:
 | 
			
		||||
            Mixin object list OR False
 | 
			
		||||
        """
 | 
			
		||||
        m = self._smixins(name)
 | 
			
		||||
        if m: return m
 | 
			
		||||
        return self._smixins(name.replace('?>?', ' '))
 | 
			
		||||
        
 | 
			
		||||
    def _smixins(self, name):
 | 
			
		||||
        """
 | 
			
		||||
        Inner wrapper to search for mixins by name.
 | 
			
		||||
        """Inner wrapper to search for mixins by name.
 | 
			
		||||
        """
 | 
			
		||||
        return (self._mixins[name] 
 | 
			
		||||
                if name in self._mixins
 | 
			
		||||
@@ -89,15 +115,18 @@ class Scope(list):
 | 
			
		||||
    def blocks(self, name):
 | 
			
		||||
        """
 | 
			
		||||
        Search for defined blocks recursively.
 | 
			
		||||
        Allow '>' to be ignored.
 | 
			
		||||
        Allow '>' to be ignored. '.a .b' == '.a > .b'
 | 
			
		||||
        Args:
 | 
			
		||||
            name (string): Search term
 | 
			
		||||
        Returns:
 | 
			
		||||
            Block object OR False
 | 
			
		||||
        """
 | 
			
		||||
        b = self._blocks(name)
 | 
			
		||||
        if b: return b
 | 
			
		||||
        return self._blocks(name.replace('?>?', ' '))
 | 
			
		||||
    
 | 
			
		||||
    def _blocks(self, name):
 | 
			
		||||
        """
 | 
			
		||||
        Inner wrapper to search for blocks by name.
 | 
			
		||||
        """Inner wrapper to search for blocks by name.
 | 
			
		||||
        """
 | 
			
		||||
        i = len(self)
 | 
			
		||||
        while i >= 0:
 | 
			
		||||
@@ -116,7 +145,11 @@ class Scope(list):
 | 
			
		||||
        return False
 | 
			
		||||
        
 | 
			
		||||
    def update(self, scope, at=0):
 | 
			
		||||
        """
 | 
			
		||||
        """Update scope. Add another scope to this one.
 | 
			
		||||
        Args:
 | 
			
		||||
            scope (Scope): Scope object
 | 
			
		||||
        Kwargs:
 | 
			
		||||
            at (int): Level to update
 | 
			
		||||
        """
 | 
			
		||||
        if hasattr(scope, '_mixins') and not at:
 | 
			
		||||
            self._mixins.update(scope._mixins)
 | 
			
		||||
@@ -125,13 +158,19 @@ class Scope(list):
 | 
			
		||||
        self[at]['__names__'].extend(scope[at]['__names__'])
 | 
			
		||||
        
 | 
			
		||||
    def swap(self, name):
 | 
			
		||||
        """
 | 
			
		||||
        """ Swap variable name for variable value
 | 
			
		||||
        Args:
 | 
			
		||||
            name (str): Variable name
 | 
			
		||||
        Returns:
 | 
			
		||||
            Variable value (Mixed)
 | 
			
		||||
        """
 | 
			
		||||
        if name.startswith('@@'):
 | 
			
		||||
            var = self.variables(name[1:])
 | 
			
		||||
            if var is False: raise SyntaxError('Unknown variable %s' % name)
 | 
			
		||||
            if var is False: 
 | 
			
		||||
                raise SyntaxError('Unknown variable %s' % name)
 | 
			
		||||
            name = '@' + utility.destring(var.value[0])
 | 
			
		||||
        var = self.variables(name)
 | 
			
		||||
        if var is False: raise SyntaxError('Unknown variable %s' % name)
 | 
			
		||||
        if var is False: 
 | 
			
		||||
            raise SyntaxError('Unknown variable %s' % name)
 | 
			
		||||
        return var.value
 | 
			
		||||
        
 | 
			
		||||
@@ -1,41 +1,52 @@
 | 
			
		||||
# -*- coding: utf8 -*-
 | 
			
		||||
"""
 | 
			
		||||
    Utility functions
 | 
			
		||||
.. module:: lesscpy.lessc.utility
 | 
			
		||||
    :synopsis: various utility functions
 | 
			
		||||
    
 | 
			
		||||
    Copyright (c)
 | 
			
		||||
    See LICENSE for details.
 | 
			
		||||
    <jtm@robot.is>
 | 
			
		||||
.. moduleauthor:: Jóhann T. Maríusson <jtm@robot.is>
 | 
			
		||||
"""
 | 
			
		||||
import collections
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
def flatten(ll):
 | 
			
		||||
def flatten(lst):
 | 
			
		||||
    """Flatten list.
 | 
			
		||||
    Args:
 | 
			
		||||
        lst (list): List to flatten
 | 
			
		||||
    Returns: 
 | 
			
		||||
        generator
 | 
			
		||||
    """
 | 
			
		||||
    Flatten list.
 | 
			
		||||
    @param ll: list
 | 
			
		||||
    @return: generator
 | 
			
		||||
    """
 | 
			
		||||
    for el in ll:
 | 
			
		||||
        if isinstance(el, collections.Iterable) and not isinstance(el, str):
 | 
			
		||||
            for sub in flatten(el):
 | 
			
		||||
    for elm in lst:
 | 
			
		||||
        if isinstance(elm, collections.Iterable) and not isinstance(elm, str):
 | 
			
		||||
            for sub in flatten(elm):
 | 
			
		||||
                yield sub
 | 
			
		||||
        else:
 | 
			
		||||
            yield el
 | 
			
		||||
            yield elm
 | 
			
		||||
            
 | 
			
		||||
def pairwise(lst):
 | 
			
		||||
    """ yield item i and item i+1 in lst. e.g.
 | 
			
		||||
        (lst[0], lst[1]), (lst[1], lst[2]), ..., (lst[-1], None)
 | 
			
		||||
    Args:
 | 
			
		||||
        lst (list): List to process
 | 
			
		||||
    Returns:
 | 
			
		||||
        list
 | 
			
		||||
    """
 | 
			
		||||
    if not lst: return
 | 
			
		||||
    l = len(lst)
 | 
			
		||||
    for i in range(l-1):
 | 
			
		||||
    if not lst: 
 | 
			
		||||
        return
 | 
			
		||||
    length = len(lst)
 | 
			
		||||
    for i in range(length-1):
 | 
			
		||||
        yield lst[i], lst[i+1]
 | 
			
		||||
    yield lst[-1], None
 | 
			
		||||
    
 | 
			
		||||
def rename(ll, scope):
 | 
			
		||||
def rename(blocks, scope):
 | 
			
		||||
    """ Rename all sub-blocks moved under another 
 | 
			
		||||
        block. (mixins)
 | 
			
		||||
    Args:
 | 
			
		||||
        lst (list): block list
 | 
			
		||||
        scope (object): Scope object
 | 
			
		||||
    """
 | 
			
		||||
    for p in ll:
 | 
			
		||||
    for p in blocks:
 | 
			
		||||
        if hasattr(p, 'inner'):
 | 
			
		||||
            p.name.parse(scope)
 | 
			
		||||
            if p.inner: 
 | 
			
		||||
@@ -45,7 +56,11 @@ def rename(ll, scope):
 | 
			
		||||
                scope.pop()
 | 
			
		||||
            
 | 
			
		||||
def blocksearch(block, name):
 | 
			
		||||
    """ Recursive search for name in block
 | 
			
		||||
    """ Recursive search for name in block (inner blocks)
 | 
			
		||||
    Args:
 | 
			
		||||
        name (str): search term
 | 
			
		||||
    Returns:
 | 
			
		||||
        Block OR False
 | 
			
		||||
    """
 | 
			
		||||
    for b in block.inner:
 | 
			
		||||
        b = (b if b.raw() == name 
 | 
			
		||||
@@ -53,43 +68,59 @@ def blocksearch(block, name):
 | 
			
		||||
        if b: return b
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
def reverse_guard(ll):
 | 
			
		||||
    """
 | 
			
		||||
def reverse_guard(lst):
 | 
			
		||||
    """ Reverse guard expression. not 
 | 
			
		||||
        (@a > 5) ->  (@a <= 5)
 | 
			
		||||
    Args:
 | 
			
		||||
        lst (list): Expression
 | 
			
		||||
    returns:
 | 
			
		||||
        list
 | 
			
		||||
    """
 | 
			
		||||
    rev = {
 | 
			
		||||
        '<': '>',
 | 
			
		||||
        '>': '<',
 | 
			
		||||
        '<': '>=',
 | 
			
		||||
        '>': '<=',
 | 
			
		||||
        '=': '!=',
 | 
			
		||||
        '!=': '=',
 | 
			
		||||
        '>=': '<=',
 | 
			
		||||
        '<=': '>='
 | 
			
		||||
        '>=': '<',
 | 
			
		||||
        '<=': '>'
 | 
			
		||||
    }
 | 
			
		||||
    return [rev[l] if l in rev else l for l in ll]
 | 
			
		||||
    return [rev[l] if l in rev else l for l in lst]
 | 
			
		||||
 | 
			
		||||
def debug_print(ll, lvl=0):
 | 
			
		||||
    """
 | 
			
		||||
def debug_print(lst, lvl=0):
 | 
			
		||||
    """ Print scope tree
 | 
			
		||||
    args:
 | 
			
		||||
        lst (list): parse result
 | 
			
		||||
        lvl (int): current nesting level
 | 
			
		||||
    """
 | 
			
		||||
    pad = ''.join(['\t.'] * lvl)
 | 
			
		||||
    t = type(ll)
 | 
			
		||||
    t = type(lst)
 | 
			
		||||
    if t is list:
 | 
			
		||||
        for p in ll:
 | 
			
		||||
        for p in lst:
 | 
			
		||||
            debug_print(p, lvl)
 | 
			
		||||
    elif hasattr(ll, 'tokens'):
 | 
			
		||||
    elif hasattr(lst, 'tokens'):
 | 
			
		||||
        print(pad, t) 
 | 
			
		||||
        debug_print(list(flatten(ll.tokens)), lvl+1)
 | 
			
		||||
        debug_print(list(flatten(lst.tokens)), lvl+1)
 | 
			
		||||
 | 
			
		||||
def destring(v):
 | 
			
		||||
    """ Strip quotes
 | 
			
		||||
        @param string: value
 | 
			
		||||
        @return: string
 | 
			
		||||
def destring(value):
 | 
			
		||||
    """ Strip quotes from string
 | 
			
		||||
    args:
 | 
			
		||||
        value (str)
 | 
			
		||||
    returns:
 | 
			
		||||
        str
 | 
			
		||||
    """
 | 
			
		||||
    return v.strip('"\'')
 | 
			
		||||
    return value.strip('"\'')
 | 
			
		||||
 | 
			
		||||
def analyze_number(var, err=''):
 | 
			
		||||
    """ Analyse number for type and split from unit
 | 
			
		||||
        @param str: value
 | 
			
		||||
        @raises: SyntaxError
 | 
			
		||||
        @return: tuple (number, unit)
 | 
			
		||||
        1px -> (q, 'px')
 | 
			
		||||
    args:
 | 
			
		||||
        var (str): number string
 | 
			
		||||
    kwargs:
 | 
			
		||||
        err (str): Error message
 | 
			
		||||
    raises:
 | 
			
		||||
        SyntaxError
 | 
			
		||||
    returns:
 | 
			
		||||
        tuple
 | 
			
		||||
    """
 | 
			
		||||
    n, u = split_unit(var)
 | 
			
		||||
    if type(var) is not str:
 | 
			
		||||
@@ -104,74 +135,94 @@ def analyze_number(var, err=''):
 | 
			
		||||
        raise SyntaxError('%s ´%s´' % (err, var))
 | 
			
		||||
    return (n, u)
 | 
			
		||||
 | 
			
		||||
def with_unit(n, u=None):
 | 
			
		||||
def with_unit(number, unit=None):
 | 
			
		||||
    """ Return number with unit
 | 
			
		||||
        @param int/float: value
 | 
			
		||||
        @param str: unit
 | 
			
		||||
        @return: mixed
 | 
			
		||||
    args:
 | 
			
		||||
        number (mixed): Number
 | 
			
		||||
        unit (str): Unit
 | 
			
		||||
    returns:
 | 
			
		||||
        str
 | 
			
		||||
    """
 | 
			
		||||
    if type(n) is tuple:
 | 
			
		||||
        n, u = n
 | 
			
		||||
    if n == 0: return 0
 | 
			
		||||
    if u:
 | 
			
		||||
        n = str(n)
 | 
			
		||||
        if n.startswith('.'):
 | 
			
		||||
            n = '0' + n 
 | 
			
		||||
        return "%s%s" % (n, u)
 | 
			
		||||
    return n
 | 
			
		||||
    if type(number) is tuple:
 | 
			
		||||
        number, unit = number
 | 
			
		||||
    if number == 0: 
 | 
			
		||||
        return '0'
 | 
			
		||||
    if unit:
 | 
			
		||||
        number = str(number)
 | 
			
		||||
        if number.startswith('.'):
 | 
			
		||||
            number = '0' + number
 | 
			
		||||
        return "%s%s" % (number, unit)
 | 
			
		||||
    return number if type(number) is str else str(number)
 | 
			
		||||
            
 | 
			
		||||
def is_color(v):
 | 
			
		||||
    """ Is CSS color
 | 
			
		||||
        @param mixed: value
 | 
			
		||||
        @return: bool
 | 
			
		||||
def is_color(value):
 | 
			
		||||
    """ Is string CSS color
 | 
			
		||||
    args:
 | 
			
		||||
        value (str): string
 | 
			
		||||
    returns:
 | 
			
		||||
        bool
 | 
			
		||||
    """
 | 
			
		||||
    if not v or type(v) is not str: 
 | 
			
		||||
    if not value or type(value) is not str: 
 | 
			
		||||
        return False
 | 
			
		||||
    if v[0] == '#' and len(v) in [4, 5, 7, 9]:
 | 
			
		||||
    if value[0] == '#' and len(value) in [4, 5, 7, 9]:
 | 
			
		||||
        try:
 | 
			
		||||
            int(v[1:], 16)
 | 
			
		||||
            int(value[1:], 16)
 | 
			
		||||
            return True
 | 
			
		||||
        except Exception:
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            pass
 | 
			
		||||
    return False
 | 
			
		||||
            
 | 
			
		||||
def is_variable(v):
 | 
			
		||||
def is_variable(value):
 | 
			
		||||
    """ Check if string is LESS variable
 | 
			
		||||
        @param string: check
 | 
			
		||||
        @return: bool
 | 
			
		||||
    args:
 | 
			
		||||
        value (str): string
 | 
			
		||||
    returns:
 | 
			
		||||
        bool
 | 
			
		||||
    """
 | 
			
		||||
    if type(v) is str:
 | 
			
		||||
        return (v.startswith('@') or v.startswith('-@'))
 | 
			
		||||
    elif type(v) is tuple:
 | 
			
		||||
        v = ''.join(v)
 | 
			
		||||
        return (v.startswith('@') or v.startswith('-@'))
 | 
			
		||||
    if type(value) is str:
 | 
			
		||||
        return (value.startswith('@') or value.startswith('-@'))
 | 
			
		||||
    elif type(value) is tuple:
 | 
			
		||||
        value = ''.join(value)
 | 
			
		||||
        return (value.startswith('@') or value.startswith('-@'))
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
def is_int(v):
 | 
			
		||||
def is_int(value):
 | 
			
		||||
    """ Is value integer
 | 
			
		||||
    args:
 | 
			
		||||
        value (str): string
 | 
			
		||||
    returns:
 | 
			
		||||
        bool
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        int(str(v))
 | 
			
		||||
        int(str(value))
 | 
			
		||||
        return True
 | 
			
		||||
    except (ValueError, TypeError):
 | 
			
		||||
        pass
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
def is_float(v):
 | 
			
		||||
def is_float(value):
 | 
			
		||||
    """ Is value float
 | 
			
		||||
    args:
 | 
			
		||||
        value (str): string
 | 
			
		||||
    returns:
 | 
			
		||||
        bool
 | 
			
		||||
    """
 | 
			
		||||
    if not is_int(v):
 | 
			
		||||
    if not is_int(value):
 | 
			
		||||
        try:
 | 
			
		||||
            float(str(v))
 | 
			
		||||
            float(str(value))
 | 
			
		||||
            return True
 | 
			
		||||
        except (ValueError, TypeError):
 | 
			
		||||
            pass
 | 
			
		||||
    return False
 | 
			
		||||
 | 
			
		||||
def split_unit(v):
 | 
			
		||||
def split_unit(value):
 | 
			
		||||
    """ Split a number from its unit
 | 
			
		||||
        1px -> (q, 'px')
 | 
			
		||||
    Args:
 | 
			
		||||
        value (str): input
 | 
			
		||||
    returns:
 | 
			
		||||
        tuple
 | 
			
		||||
    """
 | 
			
		||||
    r = re.search('^(\-?[\d\.]+)(.*)$', str(v))
 | 
			
		||||
    r = re.search('^(\-?[\d\.]+)(.*)$', str(value))
 | 
			
		||||
    return r.groups() if r else ('','')
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -86,9 +86,9 @@ class TestUtility(unittest.TestCase):
 | 
			
		||||
        self.assertEqual('1.0px', test(1.0, 'px'))
 | 
			
		||||
        self.assertEqual('0.0px', test('.0', 'px'))
 | 
			
		||||
        self.assertEqual('0.6px', test(.6, 'px'))
 | 
			
		||||
        self.assertEqual(1, test(1))
 | 
			
		||||
        self.assertEqual(1, test(1, None))
 | 
			
		||||
        self.assertEqual(1, test(1,))
 | 
			
		||||
        self.assertEqual('1', test(1))
 | 
			
		||||
        self.assertEqual('1', test(1, None))
 | 
			
		||||
        self.assertEqual('1', test(1,))
 | 
			
		||||
        
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user