Beispiel #1
0
class Calculator(object):
    """Expression evaluator."""

    ast_cache = {}

    def __init__(self, namespace=None):
        if namespace is None:
            self.namespace = Namespace()
        else:
            self.namespace = namespace

    def _pound_substitute(self, result):
        expr = result.group(1)
        value = self.evaluate_expression(expr)

        if value is None:
            return self.apply_vars(expr)
        elif value.is_null:
            return ""
        else:
            return dequote(value.render())

    def do_glob_math(self, cont):
        """Performs #{}-interpolation.  The result is always treated as a fixed
        syntactic unit and will not be re-evaluated.
        """
        # TODO that's a lie!  this should be in the parser for most cases.
        if not isinstance(cont, six.string_types):
            warn(FutureWarning(
                "do_glob_math was passed a non-string {0!r} "
                "-- this will no longer be supported in pyScss 2.0"
                .format(cont)
            ))
            cont = six.text_type(cont)
        if '#{' not in cont:
            return cont
        cont = _expr_glob_re.sub(self._pound_substitute, cont)
        return cont

    def apply_vars(self, cont):
        # TODO this is very complicated.  it should go away once everything
        # valid is actually parseable.
        if isinstance(cont, six.string_types) and '$' in cont:
            try:
                # Optimization: the full cont is a variable in the context,
                cont = self.namespace.variable(cont)
            except KeyError:
                # Interpolate variables:
                def _av(m):
                    v = None
                    n = m.group(2)
                    try:
                        v = self.namespace.variable(n)
                    except KeyError:
                        if config.FATAL_UNDEFINED:
                            raise SyntaxError("Undefined variable: '%s'." % n)
                        else:
                            if config.VERBOSITY > 1:
                                log.error("Undefined variable '%s'", n, extra={'stack': True})
                            return n
                    else:
                        if v:
                            if not isinstance(v, six.string_types):
                                v = v.render()
                            # TODO this used to test for _dequote
                            if m.group(1):
                                v = dequote(v)
                        else:
                            v = m.group(0)
                        return v

                cont = _interpolate_re.sub(_av, cont)
        # TODO this is surprising and shouldn't be here
        cont = self.do_glob_math(cont)
        return cont

    def calculate(self, _base_str, divide=False):
        better_expr_str = _base_str

        better_expr_str = self.do_glob_math(better_expr_str)

        better_expr_str = self.evaluate_expression(better_expr_str, divide=divide)

        if better_expr_str is None:
            better_expr_str = String.unquoted(self.apply_vars(_base_str))

        return better_expr_str

    # TODO only used by magic-import...?
    def interpolate(self, var):
        value = self.namespace.variable(var)
        if var != value and isinstance(value, six.string_types):
            _vi = self.evaluate_expression(value)
            if _vi is not None:
                value = _vi
        return value

    def evaluate_expression(self, expr, divide=False):
        try:
            ast = self.parse_expression(expr)
        except SassError as e:
            if config.DEBUG:
                raise
            else:
                return None

        try:
            return ast.evaluate(self, divide=divide)
        except Exception as e:
            six.reraise(SassEvaluationError, SassEvaluationError(e, expression=expr), sys.exc_info()[2])

    def parse_expression(self, expr, target='goal'):
        if isinstance(expr, six.text_type):
            # OK
            pass
        elif isinstance(expr, six.binary_type):
            # Dubious
            warn(FutureWarning(
                "parse_expression was passed binary data {0!r} "
                "-- this will no longer be supported in pyScss 2.0"
                .format(expr)
            ))
            # Don't guess an encoding; you reap what you sow
            expr = six.text_type(expr)
        else:
            raise TypeError("Expected string, got %r" % (expr,))

        key = (target, expr)
        if key in self.ast_cache:
            return self.ast_cache[key]

        try:
            parser = SassExpression(SassExpressionScanner(expr))
            ast = getattr(parser, target)()
        except SyntaxError as e:
            raise SassParseError(e, expression=expr, expression_pos=parser._char_pos)

        self.ast_cache[key] = ast
        return ast
Beispiel #2
0
class Calculator(object):
    """Expression evaluator."""

    ast_cache = {}

    def __init__(
        self,
        namespace=None,
        ignore_parse_errors=False,
        undefined_variables_fatal=True,
    ):
        if namespace is None:
            self.namespace = Namespace()
        else:
            self.namespace = namespace

        self.ignore_parse_errors = ignore_parse_errors
        self.undefined_variables_fatal = undefined_variables_fatal

    def _pound_substitute(self, result):
        expr = result.group(1)
        value = self.evaluate_expression(expr)

        if value is None:
            return self.apply_vars(expr)
        elif value.is_null:
            return ""
        else:
            return dequote(value.render())

    def do_glob_math(self, cont):
        """Performs #{}-interpolation.  The result is always treated as a fixed
        syntactic unit and will not be re-evaluated.
        """
        # TODO that's a lie!  this should be in the parser for most cases.
        if not isinstance(cont, six.string_types):
            warn(
                FutureWarning(
                    "do_glob_math was passed a non-string {0!r} "
                    "-- this will no longer be supported in pyScss 2.0".format(
                        cont)))
            cont = six.text_type(cont)
        if '#{' not in cont:
            return cont
        cont = _expr_glob_re.sub(self._pound_substitute, cont)
        return cont

    def apply_vars(self, cont):
        # TODO this is very complicated.  it should go away once everything
        # valid is actually parseable.
        if isinstance(cont, six.string_types) and '$' in cont:
            try:
                # Optimization: the full cont is a variable in the context,
                cont = self.namespace.variable(cont)
            except KeyError:
                # Interpolate variables:
                def _av(m):
                    v = None
                    n = m.group(2)
                    try:
                        v = self.namespace.variable(n)
                    except KeyError:
                        if self.undefined_variables_fatal:
                            raise SyntaxError("Undefined variable: '%s'." % n)
                        else:
                            log.error("Undefined variable '%s'",
                                      n,
                                      extra={'stack': True})
                            return n
                    else:
                        if v:
                            if not isinstance(v, Value):
                                raise TypeError(
                                    "Somehow got a variable {0!r} "
                                    "with a non-Sass value: {1!r}".format(
                                        n, v))
                            v = v.render()
                            # TODO this used to test for _dequote
                            if m.group(1):
                                v = dequote(v)
                        else:
                            v = m.group(0)
                        return v

                cont = _interpolate_re.sub(_av, cont)

            else:
                # Variable succeeded, so we need to render it
                cont = cont.render()
        # TODO this is surprising and shouldn't be here
        cont = self.do_glob_math(cont)
        return cont

    def calculate(self, expression, divide=False):
        result = self.evaluate_expression(expression, divide=divide)

        if result is None:
            return String.unquoted(self.apply_vars(expression))

        return result

    # TODO only used by magic-import...?
    def interpolate(self, var):
        value = self.namespace.variable(var)
        if var != value and isinstance(value, six.string_types):
            _vi = self.evaluate_expression(value)
            if _vi is not None:
                value = _vi
        return value

    def evaluate_expression(self, expr, divide=False):
        try:
            ast = self.parse_expression(expr)
        except SassError as e:
            if self.ignore_parse_errors:
                return None
            raise

        try:
            return ast.evaluate(self, divide=divide)
        except Exception as e:
            six.reraise(SassEvaluationError,
                        SassEvaluationError(e, expression=expr),
                        sys.exc_info()[2])

    def parse_expression(self, expr, target='goal'):
        if isinstance(expr, six.text_type):
            # OK
            pass
        elif isinstance(expr, six.binary_type):
            # Dubious
            warn(
                FutureWarning(
                    "parse_expression was passed binary data {0!r} "
                    "-- this will no longer be supported in pyScss 2.0".format(
                        expr)))
            # Don't guess an encoding; you reap what you sow
            expr = six.text_type(expr)
        else:
            raise TypeError("Expected string, got %r" % (expr, ))

        key = (target, expr)
        if key in self.ast_cache:
            return self.ast_cache[key]

        try:
            parser = SassExpression(SassExpressionScanner(expr))
            ast = getattr(parser, target)()
        except SyntaxError as e:
            raise SassParseError(e,
                                 expression=expr,
                                 expression_pos=parser._char_pos)

        self.ast_cache[key] = ast
        return ast

    def parse_interpolations(self, string):
        """Parse a string for interpolations, but don't treat anything else as
        Sass syntax.  Returns an AST node.
        """
        # Shortcut: if there are no #s in the string in the first place, it
        # must not have any interpolations, right?
        if '#' not in string:
            return Literal(String.unquoted(string))
        return self.parse_expression(string, 'goal_interpolated_literal')

    def parse_vars_and_interpolations(self, string):
        """Parse a string for variables and interpolations, but don't treat
        anything else as Sass syntax.  Returns an AST node.
        """
        # Shortcut: if there are no #s or $s in the string in the first place,
        # it must not have anything of interest.
        if '#' not in string and '$' not in string:
            return Literal(String.unquoted(string))
        return self.parse_expression(string,
                                     'goal_interpolated_literal_with_vars')
Beispiel #3
0
class Calculator(object):
    """Expression evaluator."""

    ast_cache = {}

    def __init__(
            self, namespace=None,
            ignore_parse_errors=False,
            undefined_variables_fatal=True,
            ):
        if namespace is None:
            self.namespace = Namespace()
        else:
            self.namespace = namespace

        self.ignore_parse_errors = ignore_parse_errors
        self.undefined_variables_fatal = undefined_variables_fatal

    def _pound_substitute(self, result):
        expr = result.group(1)
        value = self.evaluate_expression(expr)

        if value is None:
            return self.apply_vars(expr)
        elif value.is_null:
            return ""
        else:
            return dequote(value.render())

    def do_glob_math(self, cont):
        """Performs #{}-interpolation.  The result is always treated as a fixed
        syntactic unit and will not be re-evaluated.
        """
        # TODO that's a lie!  this should be in the parser for most cases.
        if not isinstance(cont, six.string_types):
            warn(FutureWarning(
                "do_glob_math was passed a non-string {0!r} "
                "-- this will no longer be supported in pyScss 2.0"
                .format(cont)
            ))
            cont = six.text_type(cont)
        if '#{' not in cont:
            return cont
        cont = _expr_glob_re.sub(self._pound_substitute, cont)
        return cont

    def apply_vars(self, cont):
        # TODO this is very complicated.  it should go away once everything
        # valid is actually parseable.
        if isinstance(cont, six.string_types) and '$' in cont:
            try:
                # Optimization: the full cont is a variable in the context,
                cont = self.namespace.variable(cont)
            except KeyError:
                # Interpolate variables:
                def _av(m):
                    v = None
                    n = m.group(2)
                    try:
                        v = self.namespace.variable(n)
                    except KeyError:
                        if self.undefined_variables_fatal:
                            raise SyntaxError("Undefined variable: '%s'." % n)
                        else:
                            log.error("Undefined variable '%s'", n, extra={'stack': True})
                            return n
                    else:
                        if v:
                            if not isinstance(v, Value):
                                raise TypeError(
                                    "Somehow got a variable {0!r} "
                                    "with a non-Sass value: {1!r}"
                                    .format(n, v)
                                )
                            v = v.render()
                            # TODO this used to test for _dequote
                            if m.group(1):
                                v = dequote(v)
                        else:
                            v = m.group(0)
                        return v

                cont = _interpolate_re.sub(_av, cont)

            else:
                # Variable succeeded, so we need to render it
                cont = cont.render()
        # TODO this is surprising and shouldn't be here
        cont = self.do_glob_math(cont)
        return cont

    def calculate(self, expression, divide=False):
        expression = self.evaluate_expression(expression, divide=divide)

        if expression is None:
            return String.unquoted(self.apply_vars(expression))

        return expression

    # TODO only used by magic-import...?
    def interpolate(self, var):
        value = self.namespace.variable(var)
        if var != value and isinstance(value, six.string_types):
            _vi = self.evaluate_expression(value)
            if _vi is not None:
                value = _vi
        return value

    def evaluate_expression(self, expr, divide=False):
        try:
            ast = self.parse_expression(expr)
        except SassError as e:
            if self.ignore_parse_errors:
                return None
            raise

        try:
            return ast.evaluate(self, divide=divide)
        except Exception as e:
            six.reraise(SassEvaluationError, SassEvaluationError(e, expression=expr), sys.exc_info()[2])

    def parse_expression(self, expr, target='goal'):
        if isinstance(expr, six.text_type):
            # OK
            pass
        elif isinstance(expr, six.binary_type):
            # Dubious
            warn(FutureWarning(
                "parse_expression was passed binary data {0!r} "
                "-- this will no longer be supported in pyScss 2.0"
                .format(expr)
            ))
            # Don't guess an encoding; you reap what you sow
            expr = six.text_type(expr)
        else:
            raise TypeError("Expected string, got %r" % (expr,))

        key = (target, expr)
        if key in self.ast_cache:
            return self.ast_cache[key]

        try:
            parser = SassExpression(SassExpressionScanner(expr))
            ast = getattr(parser, target)()
        except SyntaxError as e:
            raise SassParseError(e, expression=expr, expression_pos=parser._char_pos)

        self.ast_cache[key] = ast
        return ast

    def parse_interpolations(self, string):
        """Parse a string for interpolations, but don't treat anything else as
        Sass syntax.  Returns an AST node.
        """
        # Shortcut: if there are no #s in the string in the first place, it
        # must not have any interpolations, right?
        if '#' not in string:
            return Literal(String.unquoted(string))
        return self.parse_expression(string, 'goal_interpolated_anything')
Beispiel #4
0
class Calculator(object):
    """Expression evaluator."""

    ast_cache = {}

    def __init__(self, namespace=None):
        if namespace is None:
            self.namespace = Namespace()
        else:
            self.namespace = namespace

    def _pound_substitute(self, result):
        expr = result.group(1)
        value = self.evaluate_expression(expr)

        if value is None:
            return self.apply_vars(expr)
        elif value.is_null:
            return ""
        else:
            return dequote(value.render())

    def do_glob_math(self, cont):
        """Performs #{}-interpolation.  The result is always treated as a fixed
        syntactic unit and will not be re-evaluated.
        """
        # TODO that's a lie!  this should be in the parser for most cases.
        if not isinstance(cont, six.string_types):
            warn(
                FutureWarning(
                    "do_glob_math was passed a non-string {0!r} "
                    "-- this will no longer be supported in pyScss 2.0".format(
                        cont)))
            cont = six.text_type(cont)
        if '#{' not in cont:
            return cont
        cont = _expr_glob_re.sub(self._pound_substitute, cont)
        return cont

    def apply_vars(self, cont):
        # TODO this is very complicated.  it should go away once everything
        # valid is actually parseable.
        if isinstance(cont, six.string_types) and '$' in cont:
            try:
                # Optimization: the full cont is a variable in the context,
                cont = self.namespace.variable(cont)
            except KeyError:
                # Interpolate variables:
                def _av(m):
                    v = None
                    n = m.group(2)
                    try:
                        v = self.namespace.variable(n)
                    except KeyError:
                        if config.FATAL_UNDEFINED:
                            raise SyntaxError("Undefined variable: '%s'." % n)
                        else:
                            if config.VERBOSITY > 1:
                                log.error("Undefined variable '%s'",
                                          n,
                                          extra={'stack': True})
                            return n
                    else:
                        if v:
                            if not isinstance(v, six.string_types):
                                v = v.render()
                            # TODO this used to test for _dequote
                            if m.group(1):
                                v = dequote(v)
                        else:
                            v = m.group(0)
                        return v

                cont = _interpolate_re.sub(_av, cont)
        # TODO this is surprising and shouldn't be here
        cont = self.do_glob_math(cont)
        return cont

    def calculate(self, _base_str, divide=False):
        better_expr_str = _base_str

        better_expr_str = self.do_glob_math(better_expr_str)

        better_expr_str = self.evaluate_expression(better_expr_str,
                                                   divide=divide)

        if better_expr_str is None:
            better_expr_str = String.unquoted(self.apply_vars(_base_str))

        return better_expr_str

    # TODO only used by magic-import...?
    def interpolate(self, var):
        value = self.namespace.variable(var)
        if var != value and isinstance(value, six.string_types):
            _vi = self.evaluate_expression(value)
            if _vi is not None:
                value = _vi
        return value

    def evaluate_expression(self, expr, divide=False):
        try:
            ast = self.parse_expression(expr)
        except SassError as e:
            if config.DEBUG:
                raise
            else:
                return None

        try:
            return ast.evaluate(self, divide=divide)
        except Exception as e:
            raise SassEvaluationError(e, expression=expr)

    def parse_expression(self, expr, target='goal'):
        if isinstance(expr, six.text_type):
            # OK
            pass
        elif isinstance(expr, six.binary_type):
            # Dubious
            warn(
                FutureWarning(
                    "parse_expression was passed binary data {0!r} "
                    "-- this will no longer be supported in pyScss 2.0".format(
                        expr)))
            # Don't guess an encoding; you reap what you sow
            expr = six.text_type(expr)
        else:
            raise TypeError("Expected string, got %r" % (expr, ))

        key = (target, expr)
        if key in self.ast_cache:
            return self.ast_cache[key]

        try:
            parser = SassExpression(SassExpressionScanner(expr))
            ast = getattr(parser, target)()
        except SyntaxError as e:
            raise SassParseError(e,
                                 expression=expr,
                                 expression_pos=parser._char_pos)

        self.ast_cache[key] = ast
        return ast
Beispiel #5
0
class Calculator(object):
    """Expression evaluator."""
    def __init__(self, namespace=None):
        if namespace is None:
            self.namespace = Namespace()
        else:
            self.namespace = namespace

    def _calculate_expr(self, result):
        _group0 = result.group(1)
        _base_str = _group0
        better_expr_str = self.evaluate_expression(_base_str)

        if better_expr_str is None:
            better_expr_str = self.apply_vars(_base_str)
        else:
            better_expr_str = dequote(better_expr_str.render())

        return better_expr_str

    def do_glob_math(self, cont):
        """Performs #{}-interpolation.  The result is always treated as a fixed
        syntactic unit and will not be re-evaluated.
        """
        # TODO this should really accept and/or parse an *expression* and
        # return a type  :|
        cont = str(cont)
        if '#{' not in cont:
            return cont
        cont = _expr_glob_re.sub(self._calculate_expr, cont)
        return cont

    def apply_vars(self, cont):
        if isinstance(cont, six.string_types) and '$' in cont:
            try:
                # Optimization: the full cont is a variable in the context,
                cont = self.namespace.variable(cont)
            except KeyError:
                # Interpolate variables:
                def _av(m):
                    v = self.namespace.variable(m.group(2))
                    if v:
                        if not isinstance(v, six.string_types):
                            v = v.render()
                        # TODO this used to test for _dequote
                        if m.group(1):
                            v = dequote(v)
                    else:
                        v = m.group(0)
                    return v

                cont = _interpolate_re.sub(_av, cont)
        # XXX what?: if options is not None:
        # ...apply math:
        cont = self.do_glob_math(cont)
        return cont

    def calculate(self, _base_str, divide=False):
        better_expr_str = _base_str

        better_expr_str = self.do_glob_math(better_expr_str)

        better_expr_str = self.evaluate_expression(better_expr_str,
                                                   divide=divide)

        if better_expr_str is None:
            better_expr_str = self.apply_vars(_base_str)

        return better_expr_str

    # TODO only used by magic-import...?
    def interpolate(self, var):
        value = self.namespace.variable(var)
        if var != value and isinstance(value, six.string_types):
            _vi = self.evaluate_expression(value)
            if _vi is not None:
                value = _vi
        return value

    def evaluate_expression(self, expr, divide=False):
        if not isinstance(expr, six.string_types):
            raise TypeError("Expected string, got %r" % (expr, ))

        if expr in ast_cache:
            ast = ast_cache[expr]

        elif _variable_re.match(expr):
            # Short-circuit for variable names
            ast = Variable(expr)

        else:
            try:
                P = SassExpression(SassExpressionScanner())
                P.reset(expr)
                ast = P.goal()
            except SyntaxError:
                if config.DEBUG:
                    raise
                return None
            except Exception:
                # TODO hoist me up since the rule is gone
                #log.exception("Exception raised: %s in `%s' (%s)", e, expr, rule.file_and_line)
                if config.DEBUG:
                    raise
                return None
            else:
                ast_cache[expr] = ast

        return ast.evaluate(self, divide=divide)

    def parse_expression(self, expr, target='goal'):
        if expr in ast_cache:
            return ast_cache[expr]

        parser = SassExpression(SassExpressionScanner())
        parser.reset(expr)
        ast = getattr(parser, target)()

        if target == 'goal':
            ast_cache[expr] = ast

        return ast
Beispiel #6
0
class Calculator(object):
    """Expression evaluator."""

    ast_cache = {}

    def __init__(self, namespace=None):
        if namespace is None:
            self.namespace = Namespace()
        else:
            self.namespace = namespace

    def _pound_substitute(self, result):
        expr = result.group(1)
        value = self.evaluate_expression(expr)

        if value is None:
            return self.apply_vars(expr)
        elif value.is_null:
            return ""
        else:
            return dequote(value.render())

    def do_glob_math(self, cont):
        """Performs #{}-interpolation.  The result is always treated as a fixed
        syntactic unit and will not be re-evaluated.
        """
        # TODO this should really accept and/or parse an *expression* and
        # return a type  :|
        cont = str(cont)
        if '#{' not in cont:
            return cont
        cont = _expr_glob_re.sub(self._pound_substitute, cont)
        return cont

    def apply_vars(self, cont):
        if isinstance(cont, six.string_types) and '$' in cont:
            try:
                # Optimization: the full cont is a variable in the context,
                cont = self.namespace.variable(cont)
            except KeyError:
                # Interpolate variables:
                def _av(m):
                    v = None
                    n = m.group(2)
                    try:
                        v = self.namespace.variable(n)
                    except KeyError:
                        if config.FATAL_UNDEFINED:
                            raise
                        else:
                            if config.VERBOSITY > 1:
                                log.error("Undefined variable '%s'", n, extra={'stack': True})
                            return n
                    else:
                        if v:
                            if not isinstance(v, six.string_types):
                                v = v.render()
                            # TODO this used to test for _dequote
                            if m.group(1):
                                v = dequote(v)
                        else:
                            v = m.group(0)
                        return v

                cont = _interpolate_re.sub(_av, cont)
        # XXX what?: if options is not None:
        # ...apply math:
        cont = self.do_glob_math(cont)
        return cont

    def calculate(self, _base_str, divide=False):
        better_expr_str = _base_str

        better_expr_str = self.do_glob_math(better_expr_str)

        better_expr_str = self.evaluate_expression(better_expr_str, divide=divide)

        if better_expr_str is None:
            better_expr_str = String.unquoted(self.apply_vars(_base_str))

        return better_expr_str

    # TODO only used by magic-import...?
    def interpolate(self, var):
        value = self.namespace.variable(var)
        if var != value and isinstance(value, six.string_types):
            _vi = self.evaluate_expression(value)
            if _vi is not None:
                value = _vi
        return value

    def evaluate_expression(self, expr, divide=False):
        try:
            ast = self.parse_expression(expr)
        except SassError:
            if config.DEBUG:
                raise
            else:
                return None

        try:
            return ast.evaluate(self, divide=divide)
        except Exception as e:
            raise SassEvaluationError(e, expression=expr)

    def parse_expression(self, expr, target='goal'):
        if not isinstance(expr, six.string_types):
            raise TypeError("Expected string, got %r" % (expr,))

        key = (target, expr)
        if key in self.ast_cache:
            return self.ast_cache[key]

        try:
            parser = SassExpression(SassExpressionScanner(expr))
            ast = getattr(parser, target)()
        except SyntaxError as e:
            raise SassParseError(e, expression=expr, expression_pos=parser._char_pos)

        self.ast_cache[key] = ast
        return ast