Source

python-sass / sass / expr.py

# -*- coding: utf-8 -*-

import threading
import string
import ast


SEP_CHARS = '()+*/-,[]=: '


class ExprParser:
    
    
    def __init__(self):
        pass
    
    
    def parse(self, data):
        with threading.Lock():
            self.data = data
            self.state = 'space'
            self.quote = None
            out = self._cleanup()
            node = ast.parse(out).body[0]
            if type(node) is ast.Expr:
                return node.value
            return node
    
    
    def cleanup(self, data):
        with threading.Lock():
            self.data = data
            self.state = 'space'
            self.quote = None
            return self._cleanup()
    
    
    def _cleanup(self):
        out = ''
        token = ''
        tokens = []
        for i in range(len(self.data)):
            next = self.data[i+1] if len(self.data) > i+1 else None
            state = self.state
            char = self.handle(out[:-1], self.data[i], next, token)
            out += char
            if state != self.state:
                if token:
                    tokens.append((token, state))
                token = ''
            token += char
        if token:
            if tokens and tokens[-1][1] == self.state:
                tokens[-1] = (tokens[-1][0] + token, self.state)
            else:
                tokens.append((token, self.state))
        out = ''
        for token, state in tokens:
            if state == 'name':
                out += token.lstrip('!')
            elif state == 'color':
                out += 'Color("%s")' % token
            elif state == 'number':
                if token.isdigit():
                    out += token
                else:
                    for i in range(len(token)):
                        if token[i] not in string.digits + '.':
                            break
                    out += 'Number(%s, "%s")' % (token[:i], token[i:])
            else:
                out += token
        
        return out.strip()
    
    
    def handle(self, prev, char, next, token):
        
        if self.state == 'space':
            if char in SEP_CHARS:
                return char
            elif char == '!':
                if next not in string.ascii_letters:
                    raise SyntaxError
                self.state = 'name'
                return '!'
            elif char in '"\'':
                self.state = 'string'
                self.quote = char
                return char
            elif char in string.digits:
                self.state = 'number'
                return char
            elif char == '#':
                self.state = 'color'
                return char
            elif char in string.ascii_letters + '_':
                self.state = 'name'
                return char
            else:
                raise SyntaxError
        
        elif self.state == 'name':
            if char in string.ascii_letters + string.digits + '_':
                return char
            elif char in SEP_CHARS:
                self.state = 'space'
                return char
            else:
                raise SyntaxError
        
        elif self.state == 'number':
            if char in string.digits + '.':
                return char
            elif char in string.ascii_letters + '%':
                return char
            elif char in SEP_CHARS:
                self.state = 'space'
                return char
            else:
                raise SyntaxError
        
        elif self.state == 'color':
            if char in string.digits + 'abcdefABCDEF':
                return char
            elif char in SEP_CHARS:
                self.state = 'space'
                return char
            else:
                raise SyntaxError
        
        elif self.state == 'string':
            if char == self.quote and prev != '\\':
                self.state = 'space'
            return char
        
        else:
            raise SyntaxError


def sass_expr_parse(data):
    parser = ExprParser()
    return parser.parse(data)


def sass_expr_cleanup(data):
    parse = ExprParser()
    return parser.cleanup(data)