From 05f9a41631150dad3c63be2f8aa6444e6b82fd48 Mon Sep 17 00:00:00 2001 From: Jared Forsyth Date: Thu, 24 Jun 2010 21:15:16 -0600 Subject: [PATCH] removing old --- clevercss_old/__init__.py | 264 ----- clevercss_old/clevercss.py | 1884 -------------------------------- clevercss_old/consts.py | 205 ---- clevercss_old/engine.py | 535 --------- clevercss_old/errors.py | 25 - clevercss_old/expressions.py | 680 ------------ clevercss_old/line_iterator.py | 94 -- clevercss_old/utils.py | 70 -- 8 files changed, 3757 deletions(-) delete mode 100644 clevercss_old/__init__.py delete mode 100644 clevercss_old/clevercss.py delete mode 100644 clevercss_old/consts.py delete mode 100644 clevercss_old/engine.py delete mode 100644 clevercss_old/errors.py delete mode 100644 clevercss_old/expressions.py delete mode 100644 clevercss_old/line_iterator.py delete mode 100644 clevercss_old/utils.py diff --git a/clevercss_old/__init__.py b/clevercss_old/__init__.py deleted file mode 100644 index 16b6b47..0000000 --- a/clevercss_old/__init__.py +++ /dev/null @@ -1,264 +0,0 @@ -#!/usr/bin/env python -""" - CleverCSS - ~~~~~~~~~ - - The Pythonic way of CSS files. - - To convert this into a normal css file just call the `convert` - function in the clevercss module. It's that easy :-) - - Example:: - - base_padding = 2px - background_color = #eee - text_color = #111 - link_color = #ff0000 - - body: - font-family: serif, sans-serif, 'Verdana', 'Times New Roman' - color: $text_color - padding-> - top: $base_padding + 2 - right: $base_padding + 3 - left: $base_padding + 3 - bottom: $base_padding + 2 - background-color: $background_color - - div.foo: - width: "Hello World".length() * 20px - foo: (foo, bar, baz, 42).join('/') - - a: - color: $link_color - &:hover: - color: $link_color.darken(30%) - &:active: - color: $link_color.brighten(10%) - - div.navigation: - height: 1.2em - padding: 0.2em - ul: - margin: 0 - padding: 0 - list-style: none - li: - float: left - height: 1.2em - a: - display: block - height: 1em - padding: 0.1em - foo: (1 2 3).string() - - __END__ - this is ignored, but __END__ as such is completely optional. - - To get the converted example module as css just run this file as script - with the "--eigen-test" parameter. - - Literals - -------- - - CleverCSS supports most of the standard CSS literals. Some syntax - elements are not supported by now, some will probably never. - - Strings: - everything (except of dangling method calls and whitespace) that - cannot be parsed with a different rule is considered being a - string. If you want to have whitespace in your strings or use - something as string that would otherwise have a different semantic - you can use double or single quotes. - - these are all valid strings:: - - = - foo-bar-baz - "blub" - 'foo bar baz' - Verdana - - Numbers - Numbers are just that. Numbers with unit postfix are values. - - Values - Values are numbers with an associated unit. Most obvious difference - between those two are the different semantics in arithmetic - operations. Some units can be converted, some are just not compatible - (for example you won't be able to convert 1em in percent because - there is no fixed conversion possible) - Additionally to the CSS supported colors this module supports the - netscape color codes. - - Colors - Colors are so far only supported in hexadecimal notation. You can - also use the `rgb()` literal to some amount. But that means you - cannot use "orange" as color. - - URLs: - URLs work like strings, the only difference is that the syntax looks - like ``url(...)``. - - Variables: - variables are quite simple. Once they are defined in the root section - you can use them in every expression:: - - foo = 42px - - div: - width: $foo * 100; - - Lists: - Sometimes you want to assign more than one element to a CSS rule. For - example if you work with font families. In that situation just use - the comma operator to define a list:: - - font-family: Verdana, Arial, sans-serif - - Additionally lists have methods, you can for example do this (although - probably completely useless in real world cases):: - - width: (1, 2, 3, 4).length() * 20 - - - Implicit Concatenation - ---------------------- - - CleverCSS ignores whitespace. But whitespace keeps the tokens apart. If - the parser now stumbles upon something it doesn't know how to handle, it - assumes that there was a whitespace. In some situations CSS even requires - that behavior:: - - padding: 2px 3px - - But because CleverCSS has expressions this could lead to this situation:: - - padding: $x + 1 $x + 2 - - This if course works too because ``$x + 1`` is one expression and - ``$x + 2`` another one. This however can lead to code that is harder to - read. In that situation it's recommended to parentize the expressions:: - - padding: ($x + 1) ($x + 2) - - or remove the whitespace between the operators:: - - padding: $x+1 $x+2 - - - Operators - --------- - - ``+`` add two numbers, a number and a value or two compatible - values (for example ``1cm + 12mm``). This also works as - concatenate operator for strings. Using this operator - on color objects allows some basic color composition. - ``-`` subtract one number from another, a number from a value - or a value from a compatible one. Like the plus operator - this also works on colors. - ``*`` Multiply numbers, numbers with a value. Multiplying strings - repeats it. (eg: ``= * 5`` gives '=====') - ``/`` divide one number or value by a number. - ``%`` do a modulo division on a number or value by a number. - - Keep in mind that whitespace matters. For example ``20% 10`` is something - completely different than ``20 % 10``. The first one is an implicit - concatenation expression with the values 20% and 10, the second one a - modulo epression. The same applies to ``no-wrap`` versus ``no - wrap`` - and others. - - Additionally there are two operators used to keep list items apart. The - comma (``,``) and semicolon (``;``) operator both keep list items apart. - - If you want to group expressions you can use parentheses. - - Methods - ------- - - Objects have some methods you can call: - - - `Number.abs()` get the absolute value of the number - - `Number.round(places)` round to (default = 0) places - - `Value.abs()` get the absolute value for this value - - `Value.round(places)` round the value to (default = 0) places - - `Color.brighten(amount)` brighten the color by amount percent of - the current lightness, or by 0 - 100. - brighening by 100 will result in white. - - `Color.darken(amount)` darken the color by amount percent of the - current lightness, or by 0 - 100. - darkening by 100 will result in black. - - `String.length()` the length of the string. - - `String.upper()` uppercase version of the string. - - `String.lower()` lowercase version of the string. - - `String.strip()` version with leading an trailing whitespace - removed. - - `String.split(delim)` return a list of substrings, splitted by - whitespace or delim. - - `String.eval()` eval a css rule inside of a string. For - example a string "42" would return the - number 42 when parsed. But this can also - contain complex expressions such as - "(1 + 2) * 3px". - - `String.string()` just return the string itself. - - `List.length()` number of elements in a list. - - `List.join(delim)` join a list by space char or delim. - - Additionally all objects and expressions have a `.string()` method that - converts the object into a string, and a `.type()` method that returns - the type of the object as string. - - If you have implicit concatenated expressions you can convert them into - a list using the `list` method:: - - (1 2 3 4 5).list() - - does the same as:: - - 1, 2, 3, 4, 5 - - Spritemaps - ---------- - - Commonly in CSS, you'll have an image of all your UI elements, and then use - background positioning to extract a part of that image. CleverCSS helps you - with this, via the `spritemap(fn)` call. For example:: - - ui = spritemap('ui.sprites') - some_button = $ui.sprite('some_button.png') - other_button = $ui.sprite('other_button.png') - - div.some_button: - background: $some_button - - div.other_button: - background: $other_button - width: $other_button.width() - height: $other_button.height() - - :copyright: Copyright 2007 by Armin Ronacher, Georg Brandl. - :license: BSD License -""" - -import consts -import utils -import expressions -import engine - -VERSION = '0.2' - -class Context(dict): - def __init__(self, *args, **kwargs): - if args == (None,): - args = () - super(Context, self).__init__(*args, **kwargs) - -def convert(source, context=None, fname=None, minified=False): - """Convert CleverCSS text into normal CSS.""" - context = Context(context) - context.minified = minified - return engine.Engine(source, fname=fname).to_css(context) - -__all__ = ['convert'] - -# vim: et sw=4 sts=4 diff --git a/clevercss_old/clevercss.py b/clevercss_old/clevercss.py deleted file mode 100644 index 1dfd5dd..0000000 --- a/clevercss_old/clevercss.py +++ /dev/null @@ -1,1884 +0,0 @@ -# -*- coding: utf-8 -*- -""" - CleverCSS - ~~~~~~~~~ - - The Pythonic way of CSS files. - - To convert this into a normal css file just call the `convert` - function in the clevercss module. It's that easy :-) - - Example:: - - base_padding = 2px - background_color = #eee - text_color = #111 - link_color = #ff0000 - - body: - font-family: serif, sans-serif, 'Verdana', 'Times New Roman' - color: $text_color - padding-> - top: $base_padding + 2 - right: $base_padding + 3 - left: $base_padding + 3 - bottom: $base_padding + 2 - background-color: $background_color - - div.foo: - width: "Hello World".length() * 20px - foo: (foo, bar, baz, 42).join('/') - - a: - color: $link_color - &:hover: - color: $link_color.darken(30%) - &:active: - color: $link_color.brighten(10%) - - div.navigation: - height: 1.2em - padding: 0.2em - ul: - margin: 0 - padding: 0 - list-style: none - li: - float: left - height: 1.2em - a: - display: block - height: 1em - padding: 0.1em - foo: (1 2 3).string() - - __END__ - this is ignored, but __END__ as such is completely optional. - - To get the converted example module as css just run this file as script - with the "--eigen-test" parameter. - - Literals - -------- - - CleverCSS supports most of the standard CSS literals. Some syntax - elements are not supported by now, some will probably never. - - Strings: - everything (except of dangling method calls and whitespace) that - cannot be parsed with a different rule is considered being a - string. If you want to have whitespace in your strings or use - something as string that would otherwise have a different semantic - you can use double or single quotes. - - these are all valid strings:: - - = - foo-bar-baz - "blub" - 'foo bar baz' - Verdana - - Numbers - Numbers are just that. Numbers with unit postfix are values. - - Values - Values are numbers with an associated unit. Most obvious difference - between those two are the different semantics in arithmetic - operations. Some units can be converted, some are just not compatible - (for example you won't be able to convert 1em in percent because - there is no fixed conversion possible) - Additionally to the CSS supported colors this module supports the - netscape color codes. - - Colors - Colors are so far only supported in hexadecimal notation. You can - also use the `rgb()` literal to some amount. But that means you - cannot use "orange" as color. - - URLs: - URLs work like strings, the only difference is that the syntax looks - like ``url(...)``. - - Variables: - variables are quite simple. Once they are defined in the root section - you can use them in every expression:: - - foo = 42px - - div: - width: $foo * 100; - - Lists: - Sometimes you want to assign more than one element to a CSS rule. For - example if you work with font families. In that situation just use - the comma operator to define a list:: - - font-family: Verdana, Arial, sans-serif - - Additionally lists have methods, you can for example do this (although - probably completely useless in real world cases):: - - width: (1, 2, 3, 4).length() * 20 - - - Implicit Concatenation - ---------------------- - - CleverCSS ignores whitespace. But whitespace keeps the tokens apart. If - the parser now stumbles upon something it doesn't know how to handle, it - assumes that there was a whitespace. In some situations CSS even requires - that behavior:: - - padding: 2px 3px - - But because CleverCSS has expressions this could lead to this situation:: - - padding: $x + 1 $x + 2 - - This if course works too because ``$x + 1`` is one expression and - ``$x + 2`` another one. This however can lead to code that is harder to - read. In that situation it's recommended to parentize the expressions:: - - padding: ($x + 1) ($x + 2) - - or remove the whitespace between the operators:: - - padding: $x+1 $x+2 - - - Operators - --------- - - ``+`` add two numbers, a number and a value or two compatible - values (for example ``1cm + 12mm``). This also works as - concatenate operator for strings. Using this operator - on color objects allows some basic color composition. - ``-`` subtract one number from another, a number from a value - or a value from a compatible one. Like the plus operator - this also works on colors. - ``*`` Multiply numbers, numbers with a value. Multiplying strings - repeats it. (eg: ``= * 5`` gives '=====') - ``/`` divide one number or value by a number. - ``%`` do a modulo division on a number or value by a number. - - Keep in mind that whitespace matters. For example ``20% 10`` is something - completely different than ``20 % 10``. The first one is an implicit - concatenation expression with the values 20% and 10, the second one a - modulo epression. The same applies to ``no-wrap`` versus ``no - wrap`` - and others. - - Additionally there are two operators used to keep list items apart. The - comma (``,``) and semicolon (``;``) operator both keep list items apart. - - If you want to group expressions you can use parentheses. - - Methods - ------- - - Objects have some methods you can call: - - - `Number.abs()` get the absolute value of the number - - `Number.round(places)` round to (default = 0) places - - `Value.abs()` get the absolute value for this value - - `Value.round(places)` round the value to (default = 0) places - - `Color.brighten(amount)` brighten the color by amount percent of - the current lightness, or by 0 - 100. - brighening by 100 will result in white. - - `Color.darken(amount)` darken the color by amount percent of the - current lightness, or by 0 - 100. - darkening by 100 will result in black. - - `String.length()` the length of the string. - - `String.upper()` uppercase version of the string. - - `String.lower()` lowercase version of the string. - - `String.strip()` version with leading an trailing whitespace - removed. - - `String.split(delim)` return a list of substrings, splitted by - whitespace or delim. - - `String.eval()` eval a css rule inside of a string. For - example a string "42" would return the - number 42 when parsed. But this can also - contain complex expressions such as - "(1 + 2) * 3px". - - `String.string()` just return the string itself. - - `List.length()` number of elements in a list. - - `List.join(delim)` join a list by space char or delim. - - Additionally all objects and expressions have a `.string()` method that - converts the object into a string, and a `.type()` method that returns - the type of the object as string. - - If you have implicit concatenated expressions you can convert them into - a list using the `list` method:: - - (1 2 3 4 5).list() - - does the same as:: - - 1, 2, 3, 4, 5 - - Spritemaps - ---------- - - Commonly in CSS, you'll have an image of all your UI elements, and then use - background positioning to extract a part of that image. CleverCSS helps you - with this, via the `spritemap(fn)` call. For example:: - - ui = spritemap('ui.sprites') - some_button = $ui.sprite('some_button.png') - other_button = $ui.sprite('other_button.png') - - div.some_button: - background: $some_button - - div.other_button: - background: $other_button - width: $other_button.width() - height: $other_button.height() - - See the accompanying file "sprites_format.txt" for more information on that - file format. - - :copyright: Copyright 2007 by Armin Ronacher, Georg Brandl. - :license: BSD License -""" -import os -import re -import colorsys -import operator -import optparse -import os -import re -import sys - - -VERSION = '0.1.6' - -__all__ = ['convert'] - - -# regular expresssions for the normal parser -_var_def_re = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)') -_macros_def_re = re.compile(r'^def ([a-zA-Z-]+)\s*:\s*') -_def_re = re.compile(r'^([a-zA-Z-]+)\s*:\s*(.+)') -_macros_call_re = re.compile(r'^\$([a-zA-Z-]+)') -_line_comment_re = re.compile(r'(?>> li = LineIterator(u'foo\nbar\n\n/* foo */bar') - >>> li.next() - 1, u'foo' - >>> li.next() - 2, 'bar' - >>> li.next() - 4, 'bar' - >>> li.next() - Traceback (most recent call last): - File "", line 1, in - StopIteration - """ - - def __init__(self, source, emit_endmarker=False): - """ - If `emit_endmarkers` is set to `True` the line iterator will send - the string ``'__END__'`` before closing down. - """ - lines = source.splitlines() - self.lineno = 0 - self.lines = len(lines) - self.emit_endmarker = emit_endmarker - self._lineiter = iter(lines) - - def __iter__(self): - return self - - def _read_line(self): - """Read the next non empty line. This strips line comments.""" - line = '' - while not line.strip(): - line += _line_comment_re.sub('', self._lineiter.next()).rstrip() - self.lineno += 1 - return line - - def _next(self): - """ - Get the next line without mutliline comments. - """ - # XXX: this fails for a line like this: "/* foo */bar/*" - line = self._read_line() - comment_start = line.find('/*') - if comment_start < 0: - return self.lineno, line - - stripped_line = line[:comment_start] - comment_end = line.find('*/', comment_start) - if comment_end >= 0: - return self.lineno, stripped_line + line[comment_end + 2:] - - start_lineno = self.lineno - try: - while True: - line = self._read_line() - comment_end = line.find('*/') - if comment_end >= 0: - stripped_line += line[comment_end + 2:] - break - except StopIteration: - raise ParserError(self.lineno, 'missing end of multiline comment') - return start_lineno, stripped_line - - def next(self): - """ - Get the next non-whitespace line without multiline comments and emit - the endmarker if we reached the end of the sourcecode and endmarkers - were requested. - """ - try: - while True: - lineno, stripped_line = self._next() - if stripped_line: - return lineno, stripped_line - except StopIteration: - if self.emit_endmarker: - self.emit_endmarker = False - return self.lineno, '__END__' - raise - - -class Engine(object): - """ - The central object that brings parser and evaluation together. Usually - nobody uses this because the `convert` function wraps it. - """ - - def __init__(self, source, parser=None, fname=None): - if parser is None: - parser = Parser(fname=fname) - self._parser = parser - self.rules, self._vars, self._imports = parser.parse(source) - - def evaluate(self, context=None): - """Evaluate code.""" - expr = None - if not isinstance(context, dict): - context = {} - - for key, value in context.iteritems(): - if isinstance(value, str): - expr = self._parser.parse_expr(1, value) - context[key] = expr - context.update(self._vars) - - # pull in imports - for fname, source in self._imports.iteritems(): - for selectors, defs in Engine(source[1], fname=fname).evaluate(context): - yield selectors, defs - - for selectors, defs in self.rules: - yield selectors, [(key, expr.to_string(context)) - for key, expr in defs] - - def to_css(self, context=None): - """Evaluate the code and generate a CSS file.""" - if context.minified: - return self.to_css_min(context) - blocks = [] - for selectors, defs in self.evaluate(context): - block = [] - block.append(u',\n'.join(selectors) + ' {') - for key, value in defs: - block.append(u' %s: %s;' % (key, value)) - block.append('}') - blocks.append(u'\n'.join(block)) - return u'\n\n'.join(blocks) - - def to_css_min(self, context=None): - """Evaluate the code and generate a CSS file.""" - return u''.join(u'%s{%s}' % ( - u','.join(s), - u';'.join(u'%s:%s' % kv for kv in d)) - for s, d in self.evaluate(context)) - - -class TokenStream(object): - """ - This is used by the expression parser to manage the tokens. - """ - - def __init__(self, lineno, gen): - self.lineno = lineno - self.gen = gen - self.next() - - def next(self): - try: - self.current = self.gen.next() - except StopIteration: - self.current = None, 'eof' - - def expect(self, value, token): - if self.current != (value, token): - raise ParserError(self.lineno, "expected '%s', got '%s'." % - (value, self.current[0])) - self.next() - - -class Expr(object): - """ - Baseclass for all expressions. - """ - - #: name for exceptions - name = 'expression' - - #: empty iterable of dict with methods - methods = () - - def __init__(self, lineno=None): - self.lineno = lineno - - def evaluate(self, context): - return self - - def add(self, other, context): - return String(self.to_string(context) + other.to_string(context)) - - def sub(self, other, context): - raise EvalException(self.lineno, 'cannot substract %s from %s' % - (self.name, other.name)) - - def mul(self, other, context): - raise EvalException(self.lineno, 'cannot multiply %s with %s' % - (self.name, other.name)) - - def div(self, other, context): - raise EvalException(self.lineno, 'cannot divide %s by %s' % - (self.name, other.name)) - - def mod(self, other, context): - raise EvalException(self.lineno, 'cannot use the modulo operator for ' - '%s and %s. Misplaced unit symbol?' % - (self.name, other.name)) - - def neg(self, context): - raise EvalException(self.lineno, 'cannot negate %s by %s' % self.name) - - def to_string(self, context): - return self.evaluate(context).to_string(context) - - def call(self, name, args, context): - if name == 'string': - if isinstance(self, String): - return self - return String(self.to_string(context)) - elif name == 'type': - return String(self.name) - if name not in self.methods: - raise EvalException(self.lineno, '%s objects don\'t have a method' - ' called "%s". If you want to use this' - ' construct as string, quote it.' % - (self.name, name)) - return self.methods[name](self, context, *args) - - def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - ', '.join('%s=%r' % item for item in - self.__dict__.iteritems()) - ) - - -class ImplicitConcat(Expr): - """ - Holds multiple expressions that are delimited by whitespace. - """ - name = 'concatenated' - methods = { - 'list': lambda x, c: List(x.nodes) - } - - def __init__(self, nodes, lineno=None): - Expr.__init__(self, lineno) - self.nodes = nodes - - def to_string(self, context): - return u' '.join(x.to_string(context) for x in self.nodes) - - -class Bin(Expr): - - def __init__(self, left, right, lineno=None): - Expr.__init__(self, lineno) - self.left = left - self.right = right - - -class Add(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).add( - self.right.evaluate(context), context) - - -class Sub(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).sub( - self.right.evaluate(context), context) - - -class Mul(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).mul( - self.right.evaluate(context), context) - - -class Div(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).div( - self.right.evaluate(context), context) - - -class Mod(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).mod( - self.right.evaluate(context), context) - - -class Neg(Expr): - - def __init__(self, node, lineno=None): - Expr.__init__(self, lineno) - self.node = node - - def evaluate(self, context): - return self.node.evaluate(context).neg(context) - - -class Call(Expr): - - def __init__(self, node, method, args, lineno=None): - Expr.__init__(self, lineno) - self.node = node - self.method = method - self.args = args - - def evaluate(self, context): - return self.node.evaluate(context) \ - .call(self.method, [x.evaluate(context) - for x in self.args], - context) - - -class Literal(Expr): - - def __init__(self, value, lineno=None): - Expr.__init__(self, lineno) - self.value = value - - def to_string(self, context): - rv = unicode(self.value) - if len(rv.split(None, 1)) > 1: - return u"'%s'" % rv.replace('\\', '\\\\') \ - .replace('\n', '\\\n') \ - .replace('\t', '\\\t') \ - .replace('\'', '\\\'') - return rv - - -class Number(Literal): - name = 'number' - - methods = { - 'abs': lambda x, c: Number(abs(x.value)), - 'round': lambda x, c, p=0: Number(round(x.value, p)) - } - - def __init__(self, value, lineno=None): - Literal.__init__(self, float(value), lineno) - - def add(self, other, context): - if isinstance(other, Number): - return Number(self.value + other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value + other.value, other.unit, - lineno=self.lineno) - return Literal.add(self, other, context) - - def sub(self, other, context): - if isinstance(other, Number): - return Number(self.value - other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value - other.value, other.unit, - lineno=self.lineno) - return Literal.sub(self, other, context) - - def mul(self, other, context): - if isinstance(other, Number): - return Number(self.value * other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value * other.value, other.unit, - lineno=self.lineno) - return Literal.mul(self, other, context) - - def div(self, other, context): - try: - if isinstance(other, Number): - return Number(self.value / other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value / other.value, other.unit, - lineno=self.lineno) - return Literal.div(self, other, context) - except ZeroDivisionError: - raise EvalException(self.lineno, 'cannot divide by zero') - - def mod(self, other, context): - try: - if isinstance(other, Number): - return Number(self.value % other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value % other.value, other.unit, - lineno=self.lineno) - return Literal.mod(self, other, context) - except ZeroDivisionError: - raise EvalException(self.lineno, 'cannot divide by zero') - - def neg(self, context): - return Number(-self.value) - - def to_string(self, context): - return number_repr(self.value) - - -class Value(Literal): - name = 'value' - - methods = { - 'abs': lambda x, c: Value(abs(x.value), x.unit), - 'round': lambda x, c, p=0: Value(round(x.value, p), x.unit) - } - - def __init__(self, value, unit, lineno=None): - Literal.__init__(self, float(value), lineno) - self.unit = unit - - def add(self, other, context): - return self._conv_calc(other, context, operator.add, Literal.add, - 'cannot add %s and %s') - - def sub(self, other, context): - return self._conv_calc(other, context, operator.sub, Literal.sub, - 'cannot subtract %s from %s') - - def mul(self, other, context): - if isinstance(other, Number): - return Value(self.value * other.value, self.unit, - lineno=self.lineno) - return Literal.mul(self, other, context) - - def div(self, other, context): - if isinstance(other, Number): - try: - return Value(self.value / other.value, self.unit, - lineno=self.lineno) - except ZeroDivisionError: - raise EvalException(self.lineno, 'cannot divide by zero', - lineno=self.lineno) - return Literal.div(self, other, context) - - def mod(self, other, context): - if isinstance(other, Number): - try: - return Value(self.value % other.value, self.unit, - lineno=self.lineno) - except ZeroDivisionError: - raise EvalException(self.lineno, 'cannot divide by zero') - return Literal.mod(self, other, context) - - def _conv_calc(self, other, context, calc, fallback, msg): - if isinstance(other, Number): - return Value(calc(self.value, other.value), self.unit) - elif isinstance(other, Value): - if self.unit == other.unit: - return Value(calc(self.value,other.value), other.unit, - lineno=self.lineno) - self_unit_type = _conv_mapping.get(self.unit) - other_unit_type = _conv_mapping.get(other.unit) - if not self_unit_type or not other_unit_type or \ - self_unit_type != other_unit_type: - raise EvalException(self.lineno, msg % (self.unit, other.unit) - + ' because the two units are ' - 'not compatible.') - self_unit = _conv[self_unit_type][self.unit] - other_unit = _conv[other_unit_type][other.unit] - if self_unit > other_unit: - return Value(calc(self.value / other_unit * self_unit, - other.value), other.unit, - lineno=self.lineno) - return Value(calc(other.value / self_unit * other_unit, - self.value), self.unit, lineno=self.lineno) - return fallback(self, other, context) - - def neg(self, context): - return Value(-self.value, self.unit, lineno=self.lineno) - - def to_string(self, context): - return number_repr(self.value) + self.unit - - -def brighten_color(color, context, amount=None): - if amount is None: - amount = Value(10.0, '%') - hue, lightness, saturation = rgb_to_hls(*color.value) - if isinstance(amount, Value): - if amount.unit == '%': - if not amount.value: - return color - lightness *= 1.0 + amount.value / 100.0 - else: - raise EvalException(self.lineno, 'invalid unit %s for color ' - 'calculations.' % amount.unit) - elif isinstance(amount, Number): - lightness += (amount.value / 100.0) - if lightness > 1: - lightness = 1.0 - return Color(hls_to_rgb(hue, lightness, saturation)) - - -def darken_color(color, context, amount=None): - if amount is None: - amount = Value(10.0, '%') - hue, lightness, saturation = rgb_to_hls(*color.value) - if isinstance(amount, Value): - if amount.unit == '%': - if not amount.value: - return color - lightness *= amount.value / 100.0 - else: - raise EvalException(self.lineno, 'invalid unit %s for color ' - 'calculations.' % amount.unit) - elif isinstance(amount, Number): - lightness -= (amount.value / 100.0) - if lightness < 0: - lightness = 0.0 - return Color(hls_to_rgb(hue, lightness, saturation)) - - -class Color(Literal): - name = 'color' - - methods = { - 'brighten': brighten_color, - 'darken': darken_color, - 'hex': lambda x, c: Color(x.value, x.lineno) - } - - def __init__(self, value, lineno=None): - self.from_name = False - if isinstance(value, basestring): - if not value.startswith('#'): - value = _colors.get(value) - if not value: - raise ParserError(lineno, 'unknown color name') - self.from_name = True - try: - if len(value) == 4: - value = [int(x * 2, 16) for x in value[1:]] - elif len(value) == 7: - value = [int(value[i:i + 2], 16) for i in xrange(1, 7, 2)] - else: - raise ValueError() - except ValueError, e: - raise ParserError(lineno, 'invalid color value') - Literal.__init__(self, tuple(value), lineno) - - def add(self, other, context): - if isinstance(other, (Color, Number)): - return self._calc(other, operator.add) - return Literal.add(self, other, context) - - def sub(self, other, context): - if isinstance(other, (Color, Number)): - return self._calc(other, operator.sub) - return Literal.sub(self, other, context) - - def mul(self, other, context): - if isinstance(other, (Color, Number)): - return self._calc(other, operator.mul) - return Literal.mul(self, other, context) - - def div(self, other, context): - if isinstance(other, (Color, Number)): - return self._calc(other, operator.sub) - return Literal.div(self, other, context) - - def to_string(self, context): - if context.minified and all(x >> 4 == x & 15 for x in self.value): - return '#%x%x%x' % tuple(x & 15 for x in self.value) - code = '#%02x%02x%02x' % self.value - return self.from_name and _reverse_colors.get(code) or code - - def _calc(self, other, method): - is_number = isinstance(other, Number) - channels = [] - for idx, val in enumerate(self.value): - if is_number: - other_val = int(other.value) - else: - other_val = other.value[idx] - new_val = method(val, other_val) - if new_val > 255: - new_val = 255 - elif new_val < 0: - new_val = 0 - channels.append(new_val) - return Color(tuple(channels), lineno=self.lineno) - - -class RGB(Expr): - """ - an expression that hopefully returns a Color object. - """ - - def __init__(self, rgb, lineno=None): - Expr.__init__(self, lineno) - self.rgb = rgb - - def evaluate(self, context): - args = [] - for arg in self.rgb: - arg = arg.evaluate(context) - if isinstance(arg, Number): - value = int(arg.value) - elif isinstance(arg, Value) and arg.unit == '%': - value = int(arg.value / 100.0 * 255) - else: - raise EvalException(self.lineno, 'colors defined using the ' - 'rgb() literal only accept numbers and ' - 'percentages.') - if value < 0 or value > 255: - raise EvalException(self.lineno, 'rgb components must be in ' - 'the range 0 to 255.') - args.append(value) - return Color(args, lineno=self.lineno) - - -class Backstring(Literal): - """ - A string meant to be escaped directly to output. - """ - name = "backstring" - - def __init__(self, nodes, lineno=None): - Expr.__init__(self, lineno) - self.nodes = nodes - - def to_string(self, context): - return unicode(self.nodes) - - -class String(Literal): - name = 'string' - - methods = { - 'length': lambda x, c: Number(len(x.value)), - 'upper': lambda x, c: String(x.value.upper()), - 'lower': lambda x, c: String(x.value.lower()), - 'strip': lambda x, c: String(x.value.strip()), - 'split': lambda x, c, d=None: String(x.value.split(d)), - 'eval': lambda x, c: Parser().parse_expr(x.lineno, x.value) - .evaluate(c) - } - - def mul(self, other, context): - if isinstance(other, Number): - return String(self.value * int(other.value), lineno=self.lineno) - return Literal.mul(self, other, context, lineno=self.lineno) - - -class URL(Literal): - name = 'URL' - methods = { - 'length': lambda x, c: Number(len(self.value)) - } - - def add(self, other, context): - return URL(self.value + other.to_string(context), - lineno=self.lineno) - - def mul(self, other, context): - if isinstance(other, Number): - return URL(self.value * int(other.value), lineno=self.lineno) - return Literal.mul(self, other, context) - - def to_string(self, context): - return 'url(%s)' % Literal.to_string(self, context) - - -class SpriteMap(Expr): - name = 'SpriteMap' - methods = { - 'sprite': lambda x, c, v: Sprite(x, v.value, lineno=v.lineno) - } - _magic_names = { - "__url__": "image_url", - "__resources__": "sprite_resource_dir", - "__passthru__": "sprite_passthru_url", - } - - image_url = None - sprite_resource_dir = None - sprite_passthru_url = None - - def __init__(self, map_fname, fname=None, lineno=None): - Expr.__init__(self, lineno=lineno) - self.map_fname = map_fname - self.fname = fname - - def evaluate(self, context): - self.map_fpath = os.path.join(os.path.dirname(self.fname), - self.map_fname.to_string(context)) - self.mapping = self.read_spritemap(self.map_fpath) - return self - - def read_spritemap(self, fpath): - fo = open(fpath, "U") - spritemap = {} - try: - for line in fo: - line = line.rstrip("\n") - rest = line.split(",") - key = rest.pop(0) - if key[-2:] == key[:2] == "__": - if key not in self._magic_names: - raise ValueError("%r is not a valid field" % (key,)) - att = self._magic_names[key] - setattr(self, att, rest[0]) - elif len(rest) != 4: - raise ValueError("unexpected line: %r" % (line,)) - else: - x1, y1, x2, y2 = rest - spritemap[key] = map(int, (x1, y1, x2, y2)) - finally: - fo.close() - return spritemap - - def get_sprite_def(self, name): - if name in self.mapping: - return self.mapping[name] - elif self.sprite_passthru_url: - return self._load_sprite(name) - else: - raise KeyError(name) - - def _load_sprite(self, name): - try: - from PIL import Image - except ImportError: - raise KeyError(name) - - spr_fname = os.path.join(os.path.dirname(self.map_fpath), name) - if not os.path.exists(spr_fname): - raise KeyError(name) - - im = Image.open(spr_fname) - spr_def = (0, 0) + tuple(im.size) - self.mapping[name] = spr_def - return spr_def - - def get_sprite_url(self, sprite): - if self.sprite_passthru_url: - return self.sprite_passthru_url + sprite.name - else: - return self.image_url - - def annotate_used(self, sprite): - pass - - -class AnnotatingSpriteMap(SpriteMap): - sprite_maps = [] - - def __init__(self, *args, **kwds): - SpriteMap.__init__(self, *args, **kwds) - self._sprites_used = {} - self.sprite_maps.append(self) - - def read_spritemap(self, fname): - self.image_url = "" - return {} - - def get_sprite_def(self, name): - return 0, 0, 100, 100 - - def get_sprite_url(self, sprite): - return "" % (sprite,) - - def annotate_used(self, sprite): - self._sprites_used[sprite.name] = sprite - - @classmethod - def all_used_sprites(cls): - for smap in cls.sprite_maps: - yield smap, smap._sprites_used.values() - - -class Sprite(Expr): - name = 'Sprite' - methods = { - 'url': lambda x, c: String("url('%s')" % x.spritemap.get_sprite_url(x)), - 'position': lambda x, c: ImplicitConcat(x._pos_vals(c)), - 'height': lambda x, c: Value(x.height, "px"), - 'width': lambda x, c: Value(x.width, "px"), - 'x1': lambda x, c: Value(x.x1, "px"), - 'y1': lambda x, c: Value(x.y1, "px"), - 'x2': lambda x, c: Value(x.x2, "px"), - 'y2': lambda x, c: Value(x.y2, "px") - } - - def __init__(self, spritemap, name, lineno=None): - self.lineno = lineno if lineno else name.lineno - self.name = name - self.spritemap = spritemap - self.spritemap.annotate_used(self) - try: - self.coords = spritemap.get_sprite_def(name) - except KeyError: - msg = "Couldn't find sprite %r in mapping" % name - raise EvalException(self.lineno, msg) - - def _get_coords(self): - return self.x1, self.y1, self.x2, self.y2 - def _set_coords(self, value): - self.x1, self.y1, self.x2, self.y2 = value - coords = property(_get_coords, _set_coords) - - @property - def width(self): return self.x2 - self.x1 - @property - def height(self): return self.y2 - self.y1 - - def _pos_vals(self, context): - """Get a list of position values.""" - meths = self.methods - call_names = "x1", "y1", "x2", "y2" - return [meths[n](self, context) for n in call_names] - - def to_string(self, context): - sprite_url = self.spritemap.get_sprite_url(self) - return "url('%s') -%dpx -%dpx" % (sprite_url, self.x1, self.y1) - - -class Var(Expr): - - def __init__(self, name, lineno=None): - self.name = name - self.lineno = lineno - - def evaluate(self, context): - if self.name not in context: - raise EvalException(self.lineno, 'variable %s is not defined' % - (self.name,)) - val = context[self.name] - context[self.name] = FailingVar(self, self.lineno) - try: - return val.evaluate(context) - finally: - context[self.name] = val - - -class FailingVar(Expr): - - def __init__(self, var, lineno=None): - Expr.__init__(self, lineno or var.lineno) - self.var = var - - def evaluate(self, context): - raise EvalException(self.lineno, 'Circular variable dependencies ' - 'detected when resolving %s.' % (self.var.name,)) - - -class List(Expr): - name = 'list' - - methods = { - 'length': lambda x, c: Number(len(x.items)), - 'join': lambda x, c, d=String(' '): String(d.value.join( - a.to_string(c) for a in x.items)) - } - - def __init__(self, items, lineno=None): - Expr.__init__(self, lineno) - self.items = items - - def add(self, other): - if isinstance(other, List): - return List(self.items + other.items, lineno=self.lineno) - return List(self.items + [other], lineno=self.lineno) - - def to_string(self, context): - return u', '.join(x.to_string(context) for x in self.items) - - -class Parser(object): - """ - Class with a bunch of methods that implement a tokenizer and parser. In - fact this class has two parsers. One that splits up the code line by - line and keeps track of indentions, and a second one for expressions in - the value parts. - """ - - def __init__(self, fname=None): - self.fname = fname - - sprite_map_cls = SpriteMap - - def preparse(self, source): - """ - Do the line wise parsing and resolve indents. - """ - rule = (None, [], []) - vars = {} - imports = {} - indention_stack = [0] - state_stack = ['root'] - group_block_stack = [] - rule_stack = [rule] - sub_rules = [] - root_rules = rule[1] - macroses = {} - new_state = None - lineiter = LineIterator(source, emit_endmarker=True) - - def fail(msg): - raise ParserError(lineno, msg) - - def parse_definition(): - m = _def_re.search(line) - if not m is None: - return lineiter.lineno, m.group(1), m.group(2) - m = _macros_call_re.search(line) - if not m is None: - return lineiter.lineno, '__macros_call__', m.groups()[0] - fail('invalid syntax for style definition') - - for lineno, line in lineiter: - raw_line = line.rstrip().expandtabs() - line = raw_line.lstrip() - indention = len(raw_line) - len(line) - - # indenting - if indention > indention_stack[-1]: - if not new_state: - fail('unexpected indent') - state_stack.append(new_state) - indention_stack.append(indention) - new_state = None - - # dedenting - elif indention < indention_stack[-1]: - for level in indention_stack: - if level == indention: - while indention_stack[-1] != level: - if state_stack[-1] == 'rule': - rule = rule_stack.pop() - elif state_stack[-1] == 'group_block': - name, part_defs = group_block_stack.pop() - for lineno, key, val in part_defs: - rule[2].append((lineno, name + '-' + - key, val)) - indention_stack.pop() - state_stack.pop() - break - else: - fail('invalid dedent') - - # new state but no indention. bummer - elif new_state: - fail('expected definitions, found nothing') - - # end of data - if line == '__END__': - break - - # root and rules - elif state_stack[-1] in ('rule', 'root', 'macros'): - # macros blocks - if line.startswith('def ') and line.endswith(":")\ - and state_stack[-1] == 'root': - s_macros = _macros_def_re.search(line).groups()[0] - if s_macros in vars: - fail('name "%s" already bound to variable' % s_macros) - new_state = 'macros' - macros = [] - macroses[s_macros] = macros - - # new rule blocks - if line.endswith(','): - sub_rules.append(line) - - elif line.endswith(':'): - sub_rules.append(line[:-1].rstrip()) - s_rule = ' '.join(sub_rules) - sub_rules = [] - if not s_rule: - fail('empty rule') - new_state = 'rule' - new_rule = (s_rule, [], []) - rule[1].append(new_rule) - rule_stack.append(rule) - rule = new_rule - - # if we in a root block we don't consume group blocks - # or style definitions but variable defs - elif state_stack[-1] == 'root': - if '=' in line: - m = _var_def_re.search(line) - if m is None: - fail('invalid syntax') - key = m.group(1) - if key in vars: - fail('variable "%s" defined twice' % key) - if key in macroses: - fail('name "%s" already bound to macros' % key) - vars[key] = (lineiter.lineno, m.group(2)) - elif line.startswith("@"): - m = _import_re.search(line) - if m is None: - fail('invalid import syntax') - url = m.group(1) - if url in imports: - fail('file "%s" imported twice' % url) - if not os.path.isfile(url): - fail('file "%s" was not found' % url) - imports[url] = (lineiter.lineno, open(url).read()) - - else: - fail('Style definitions or group blocks are only ' - 'allowed inside a rule or group block.') - - # definition group blocks - elif line.endswith('->'): - group_prefix = line[:-2].rstrip() - if not group_prefix: - fail('no group prefix defined') - new_state = 'group_block' - group_block_stack.append((group_prefix, [])) - - # otherwise parse a style definition. - else: - if state_stack[-1] == 'rule': - rule[2].append(parse_definition()) - elif state_stack[-1] == 'macros': - macros.append(parse_definition()) - - # group blocks - elif state_stack[-1] == 'group_block': - group_block_stack[-1][1].append(parse_definition()) - - # something unparseable happened - else: - fail('unexpected character %s' % line[0]) - - return root_rules, vars, imports, macroses - - def parse(self, source): - """ - Create a flat structure and parse inline expressions. - """ - expand_def = lambda (lineno, k, v): (k, self.parse_expr(lineno, v)) - expand_defs = lambda it: map(expand_def, it) - - def handle_rule(rule, children, defs, macroses): - def recurse(macroses): - if defs: - styles = [] - for lineno, k, v in defs: - if k == '__macros_call__': - macros_defs = macroses.get(v, None) - if macros_defs is None: - fail('No macros with name "%s" is defined' % v) - styles.extend(expand_defs(macros_defs)) - else: - styles.append(expand_def((lineno, k, v))) - result.append((get_selectors(), styles)) - for i_r, i_c, i_d in children: - handle_rule(i_r, i_c, i_d, macroses) - local_rules = [] - reference_rules = [] - for r in rule.split(','): - r = r.strip() - if '&' in r: - reference_rules.append(r) - else: - local_rules.append(r) - - if local_rules: - stack.append(local_rules) - recurse(macroses) - stack.pop() - - if reference_rules: - if stack: - parent_rules = stack.pop() - push_back = True - else: - parent_rules = ['*'] - push_back = False - virtual_rules = [] - for parent_rule in parent_rules: - for tmpl in reference_rules: - virtual_rules.append(tmpl.replace('&', parent_rule)) - stack.append(virtual_rules) - recurse(macroses) - stack.pop() - if push_back: - stack.append(parent_rules) - - def get_selectors(): - branches = [()] - for level in stack: - new_branches = [] - for rule in level: - for item in branches: - new_branches.append(item + (rule,)) - branches = new_branches - return [' '.join(branch) for branch in branches] - - root_rules, vars, imports, macroses = self.preparse(source) - result = [] - stack = [] - for i_r, i_c, i_d in root_rules: - handle_rule(i_r, i_c, i_d, macroses) - real_vars = {} - for name, args in vars.iteritems(): - real_vars[name] = self.parse_expr(*args) - - return result, real_vars, imports - - def parse_expr(self, lineno, s): - def parse(): - pos = 0 - end = len(s) - - def process(token, group=0): - return lambda m: (m.group(group), token) - - def process_string(m): - value = m.group(0) - try: - if value[:1] == value[-1:] and value[0] in '"\'': - value = value[1:-1].encode('utf-8') \ - .decode('string-escape') \ - .encode('utf-8') - elif value == 'rgb': - return None, 'rgb' - elif value in _colors: - return value, 'color' - except UnicodeError: - raise ParserError(lineno, 'invalid string escape') - return value, 'string' - - rules = ( - (_value_re, lambda m: (m.groups(), 'value')), - (_operator_re, process('op')), - (_call_re, process('call', 1)), - (_color_re, process('color')), - (_number_re, process('number')), - (_url_re, process('url', 1)), - (_import_re, process('import', 1)), - (_spritemap_re, process('spritemap', 1)), - (_backstring_re, process('backstring', 1)), - (_string_re, process_string), - (_var_re, lambda m: (m.group(1) or m.group(2), 'var')), - (_whitespace_re, None)) - - while pos < end: - for rule, processor in rules: - m = rule.match(s, pos) - if m is not None: - if processor is not None: - yield processor(m) - pos = m.end() - break - else: - raise ParserError(lineno, 'Syntax error') - - s = s.rstrip(';') - return self.expr(TokenStream(lineno, parse())) - - def expr(self, stream, ignore_comma=False): - args = [self.concat(stream)] - list_delim = [(';', 'op')] - if not ignore_comma: - list_delim.append((',', 'op')) - while stream.current in list_delim: - stream.next() - args.append(self.concat(stream)) - if len(args) == 1: - return args[0] - return List(args, lineno=stream.lineno) - - def concat(self, stream): - args = [self.add(stream)] - while stream.current[1] != 'eof' and \ - stream.current not in ((',', 'op'), (';', 'op'), - (')', 'op')): - args.append(self.add(stream)) - if len(args) == 1: - node = args[0] - else: - node = ImplicitConcat(args, lineno=stream.lineno) - return node - - def add(self, stream): - left = self.sub(stream) - while stream.current == ('+', 'op'): - stream.next() - left = Add(left, self.sub(stream), lineno=stream.lineno) - return left - - def sub(self, stream): - left = self.mul(stream) - while stream.current == ('-', 'op'): - stream.next() - left = Sub(left, self.mul(stream), lineno=stream.lineno) - return left - - def mul(self, stream): - left = self.div(stream) - while stream.current == ('*', 'op'): - stream.next() - left = Mul(left, self.div(stream), lineno=stream.lineno) - return left - - def div(self, stream): - left = self.mod(stream) - while stream.current == ('/', 'op'): - stream.next() - left = Div(left, self.mod(stream), lineno=stream.lineno) - return left - - def mod(self, stream): - left = self.neg(stream) - while stream.current == ('%', 'op'): - stream.next() - left = Mod(left, self.neg(stream), lineno=stream.lineno) - return left - - def neg(self, stream): - if stream.current == ('-', 'op'): - stream.next() - return Neg(self.primary(stream), lineno=stream.lineno) - return self.primary(stream) - - def primary(self, stream): - value, token = stream.current - if token == 'number': - stream.next() - node = Number(value, lineno=stream.lineno) - elif token == 'value': - stream.next() - node = Value(lineno=stream.lineno, *value) - elif token == 'color': - stream.next() - node = Color(value, lineno=stream.lineno) - elif token == 'rgb': - stream.next() - if stream.current == ('(', 'op'): - stream.next() - args = [] - while len(args) < 3: - if args: - stream.expect(',', 'op') - args.append(self.expr(stream, True)) - stream.expect(')', 'op') - return RGB(tuple(args), lineno=stream.lineno) - else: - node = String('rgb') - elif token == 'backstring': - stream.next() - node = Backstring(value, lineno=stream.lineno) - elif token == 'string': - stream.next() - node = String(value, lineno=stream.lineno) - elif token == 'url': - stream.next() - node = URL(value, lineno=stream.lineno) - elif token == 'import': - stream.next() - node = Import(value, lineno=stream.lineno) - elif token == 'spritemap': - stream.next() - if value[0] == value[-1] and value[0] in '"\'': - value = value[1:-1] - value = String(value, lineno=stream.lineno) - node = self.sprite_map_cls(value, fname=self.fname, - lineno=stream.lineno) - elif token == 'var': - stream.next() - node = Var(value, lineno=stream.lineno) - elif token == 'op' and value == '(': - stream.next() - if stream.current == (')', 'op'): - raise ParserError(stream.lineno, 'empty parentheses are ' - 'not valid. If you want to use them as ' - 'string you have to quote them.') - node = self.expr(stream) - stream.expect(')', 'op') - else: - if token == 'call': - raise ParserError(stream.lineno, 'You cannot call standalone ' - 'methods. If you wanted to use it as a ' - 'string you have to quote it.') - stream.next() - node = String(value, lineno=stream.lineno) - while stream.current[1] == 'call': - node = self.call(stream, node) - return node - - def call(self, stream, node): - method, token = stream.current - assert token == 'call' - stream.next() - args = [] - while stream.current != (')', 'op'): - if args: - stream.expect(',', 'op') - args.append(self.expr(stream)) - stream.expect(')', 'op') - return Call(node, method, args, lineno=stream.lineno) - -def eigen_test(): - return convert('\n'.join(l[8:].rstrip() for l in - re.compile(r'Example::\n(.*?)__END__(?ms)') - .search(__doc__).group(1).splitlines())) - -class Context(dict): - def __init__(self, *args, **kwargs): - if args == (None,): - args = () - super(Context, self).__init__(*args, **kwargs) - -def convert(source, context=None, minified=False): - """Convert a CleverCSS file into a normal stylesheet.""" - context = Context(context) - context.minified = minified - return Engine(source).to_css(context) - - -def main(): - """Entrypoint for the shell.""" - import sys - import optparse - - if sys.argv[0] is None: - sys.argv[0] = "clevercss.py" - parser = optparse.OptionParser() - parser.add_option("-o", "--out", metavar="FILE", help="Send output to FILE.") - parser.add_option("--eigen-test", action="store_true", help="Run eigen test.") - parser.add_option("--list-colors", action="store_true", help="List defined colors.") - parser.add_option("-V", "--version", action="store_true", help="Print out version info.") - parser.set_usage("""usage: %prog ... - -If called with some filenames it will read each file, cut of -the extension and append a ".css" extension and save. If -the target file has the same name as the source file it will -abort, but if it overrides a file during this process it will -continue. This is a desired functionality. To avoid that you -must not give your source file a .css extension. - -If you call it without arguments it will read from stdin and -write the converted css to stdout. - -Called with the --eigen-test parameter it will evaluate the -example from the module docstring. - -To get a list of known color names call it with --list-colors""") - opts, args = parser.parse_args() - - # version - if opts.version: - print 'CleverCSS Version %s' % VERSION - print 'Licensed under the BSD license.' - print '(c) Copyright 2007 by Armin Ronacher and Georg Brandl.' - return - - # evaluate the example from the docstring. - if options.eigen_test: - print convert('\n'.join(l[8:].rstrip() for l in - re.compile(r'Example::\n(.*?)__END__(?ms)') - .search(__doc__).group(1).splitlines()), - minified=options.minified) - return - # color list - elif opts.list_colors: - print '%s known colors:' % len(_colors) - for color in sorted(_colors.items()): - print ' %-30s%s' % color - return - - # read from stdin and write to stdout - elif not args: - try: - print convert(sys.stdin.read(), minified=options.minified) - except (ParserError, EvalException), e: - sys.stderr.write('Error: %s\n' % e) - sys.exit(1) - - # convert some files diff --git a/clevercss_old/consts.py b/clevercss_old/consts.py deleted file mode 100644 index d2d712b..0000000 --- a/clevercss_old/consts.py +++ /dev/null @@ -1,205 +0,0 @@ - -import re - -# list of operators -OPERATORS = ['+', '-', '*', '/', '%', '(', ')', ';', ','] - -# units and conversions -UNITS = ['em', 'ex', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'deg', 'rad' - 'grad', 'ms', 's', 'Hz', 'kHz', '%'] -CONV = { - '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 - } -} -UNIT_MAPPING = {} -for measures, units in CONV.iteritems(): - UNIT_MAPPING.update(dict((unit, measures) for unit in units)) - -# 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' -} -REV_COLORS = dict((v, k) for k, v in COLORS.iteritems()) - -# partial regular expressions for the expr parser -r_number = '(?:\s\-)?(?:\d+(?:\.\d+)?|\.\d+)' -r_string = r"(?:'(?:[^'\\]*(?:\\.[^'\\]*)*)'|" \ - r'\"(?:[^"\\]*(?:\\.[^"\\]*)*)")' -r_call = r'([a-zA-Z_][a-zA-Z0-9_]*)\(' - -regex = { - # regular expressions for the normal parser - 'var_def': re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)'), - 'def': re.compile(r'^([a-zA-Z-]+)\s*:\s*(.+)'), - 'line_comment': re.compile(r'(? indention_stack[-1]: - if not new_state: - fail('unexpected indent') - state_stack.append(new_state) - indention_stack.append(indention) - new_state = None - - # dedenting - elif indention < indention_stack[-1]: - for level in indention_stack: - if level == indention: - while indention_stack[-1] != level: - if state_stack[-1] == 'rule': - rule = rule_stack.pop() - elif state_stack[-1] == 'group_block': - name, part_defs = group_block_stack.pop() - for lineno, key, val in part_defs: - rule[2].append((lineno, name + '-' + - key, val)) - indention_stack.pop() - state_stack.pop() - break - else: - fail('invalid dedent') - - # new state but no indention. bummer - elif new_state: - fail('expected definitions, found nothing') - - # end of data - if line == '__END__': - break - - # root and rules - elif state_stack[-1] in ('rule', 'root', 'macros'): - # macros blocks - if line.startswith('def ') and line.strip().endswith(":")\ - and state_stack[-1] == 'root': - s_macros = consts.regex['macros_def'].search(line).groups()[0] - if s_macros in vars: - fail('name "%s" already bound to variable' % s_macros) - new_state = 'macros' - macros = [] - macroses[s_macros] = macros - - # new rule blocks - elif line.endswith(','): - sub_rules.append(line) - - elif line.endswith(':'): - sub_rules.append(line[:-1].rstrip()) - s_rule = ' '.join(sub_rules) - sub_rules = [] - if not s_rule: - fail('empty rule') - new_state = 'rule' - new_rule = (s_rule, [], []) - rule[1].append(new_rule) - rule_stack.append(rule) - rule = new_rule - # if we in a root block we don't consume group blocks - # or style definitions but variable defs - elif state_stack[-1] == 'root': - if '=' in line: - m = consts.regex['var_def'].search(line) - if m is None: - fail('invalid syntax') - key = m.group(1) - if key in vars: - fail('variable "%s" defined twice' % key) - if key in macroses: - fail('name "%s" already bound to macros' % key) - vars[key] = (lineiter.lineno, m.group(2)) - elif line.startswith("@"): - m = consts.regex['import'].search(line) - if m is None: - fail('invalid import syntax') - url = m.group(1) - if url in imports: - fail('file "%s" imported twice' % url) - if not os.path.isfile(url): - fail('file "%s" was not found' % url) - imports[url] = (lineiter.lineno, open(url).read()) - else: - fail('Style definitions or group blocks are only ' - 'allowed inside a rule or group block.') - - # definition group blocks - elif line.endswith('->'): - group_prefix = line[:-2].rstrip() - if not group_prefix: - fail('no group prefix defined') - new_state = 'group_block' - group_block_stack.append((group_prefix, [])) - - # otherwise parse a style definition. - else: - if state_stack[-1] == 'rule': - rule[2].append(parse_definition()) - elif state_stack[-1] == 'macros': - macros.append(parse_definition()) - - # group blocks - elif state_stack[-1] == 'group_block': - group_block_stack[-1][1].append(parse_definition()) - - # something unparseable happened - else: - fail('unexpected character %s' % line[0]) - - return root_rules, vars, imports, macroses - - def parse(self, source): - """ - Create a flat structure and parse inline expressions. - """ - expand_def = lambda (lineno, k, v): (k, self.parse_expr(lineno, v)) - expand_defs = lambda it: map(expand_def, it) - - def handle_rule(rule, children, defs, macroses): - def recurse(macroses): - if defs: - styles = [] - for lineno, k, v in defs: - if k == '__macros_call__': - macros_defs = macroses.get(v, None) - if macros_defs is None: - fail('No macros with name "%s" is defined' % v) - styles.extend(expand_defs(macros_defs)) - else: - styles.append(expand_def((lineno, k, v))) - result.append((get_selectors(), styles)) - for i_r, i_c, i_d in children: - handle_rule(i_r, i_c, i_d, macroses) - - local_rules = [] - reference_rules = [] - for r in rule.split(','): - r = r.strip() - if '&' in r: - reference_rules.append(r) - else: - local_rules.append(r) - - if local_rules: - stack.append(local_rules) - recurse(macroses) - stack.pop() - - if reference_rules: - if stack: - parent_rules = stack.pop() - push_back = True - else: - parent_rules = ['*'] - push_back = False - virtual_rules = [] - for parent_rule in parent_rules: - for tmpl in reference_rules: - virtual_rules.append(tmpl.replace('&', parent_rule)) - stack.append(virtual_rules) - recurse(macroses) - stack.pop() - if push_back: - stack.append(parent_rules) - - def get_selectors(): - branches = [()] - for level in stack: - new_branches = [] - for rule in level: - for item in branches: - new_branches.append(item + (rule,)) - branches = new_branches - return [' '.join(branch) for branch in branches] - - root_rules, vars, imports, macroses = self.preparse(source) - result = [] - stack = [] - for i_r, i_c, i_d in root_rules: - handle_rule(i_r, i_c, i_d, macroses) - - real_vars = {} - for name, args in vars.iteritems(): - real_vars[name] = self.parse_expr(*args) - - return result, real_vars, imports - - def parse_expr(self, lineno, s): - def parse(): - pos = 0 - end = len(s) - - def process(token, group=0): - return lambda m: (m.group(group), token) - - def process_string(m): - value = m.group(0) - try: - if value[:1] == value[-1:] and value[0] in '"\'': - value = value[1:-1].encode('utf-8') \ - .decode('string-escape') \ - .encode('utf-8') - elif value == 'rgb': - return None, 'rgb' - elif value == 'rgba': - return None, 'rgba' - elif value in consts.COLORS: - return value, 'color' - except UnicodeError: - raise ParserError(lineno, 'invalid string escape') - return value, 'string' - - rules = ((consts.regex['operator'], process('op')), - (consts.regex['call'], process('call', 1)), - (consts.regex['value'], lambda m: (m.groups(), 'value')), - (consts.regex['color'], process('color')), - (consts.regex['number'], process('number')), - (consts.regex['url'], process('url', 1)), - (consts.regex['import'], process('import', 1)), - (consts.regex['spritemap'], process('spritemap', 1)), - (consts.regex['backstring'], process('backstring', 1)), - (consts.regex['string'], process_string), - (consts.regex['var'], lambda m: (m.group(1) or m.group(2), 'var')), - (consts.regex['whitespace'], None)) - - while pos < end: - for rule, processor in rules: - m = rule.match(s, pos) - if m is not None and m.group(): - if processor is not None: - yield processor(m) - pos = m.end() - break - else: - raise ParserError(lineno, 'Syntax error') - - s = s.rstrip(';') - return self.expr(TokenStream(lineno, parse())) - - def expr(self, stream, ignore_comma=False): - args = [self.concat(stream)] - list_delim = [(';', 'op')] - if not ignore_comma: - list_delim.append((',', 'op')) - while stream.current in list_delim: - stream.next() - args.append(self.concat(stream)) - if len(args) == 1: - return args[0] - return expressions.List(args, lineno=stream.lineno) - - def concat(self, stream): - args = [self.add(stream)] - while stream.current[1] != 'eof' and \ - stream.current not in ((',', 'op'), (';', 'op'), - (')', 'op')): - args.append(self.add(stream)) - if len(args) == 1: - node = args[0] - else: - node = expressions.ImplicitConcat(args, lineno=stream.lineno) - return node - - def add(self, stream): - left = self.sub(stream) - while stream.current == ('+', 'op'): - stream.next() - left = expressions.Add(left, self.sub(stream), lineno=stream.lineno) - return left - - def sub(self, stream): - left = self.mul(stream) - while stream.current == ('-', 'op'): - stream.next() - left = expressions.Sub(left, self.mul(stream), lineno=stream.lineno) - return left - - def mul(self, stream): - left = self.div(stream) - while stream.current == ('*', 'op'): - stream.next() - left = expressions.Mul(left, self.div(stream), lineno=stream.lineno) - return left - - def div(self, stream): - left = self.mod(stream) - while stream.current == ('/', 'op'): - stream.next() - left = expressions.Div(left, self.mod(stream), lineno=stream.lineno) - return left - - def mod(self, stream): - left = self.neg(stream) - while stream.current == ('%', 'op'): - stream.next() - left = expressions.Mod(left, self.neg(stream), lineno=stream.lineno) - return left - - def neg(self, stream): - if stream.current == ('-', 'op'): - stream.next() - return expressions.Neg(self.primary(stream), lineno=stream.lineno) - return self.primary(stream) - - def primary(self, stream): - value, token = stream.current - if token == 'number': - stream.next() - node = expressions.Number(value, lineno=stream.lineno) - elif token == 'value': - stream.next() - node = expressions.Value(lineno=stream.lineno, *value) - elif token == 'color': - stream.next() - node = expressions.Color(value, lineno=stream.lineno) - elif token == 'rgb': - stream.next() - if stream.current == ('(', 'op'): - stream.next() - args = [] - while len(args) < 3: - if args: - stream.expect(',', 'op') - args.append(self.expr(stream, True)) - stream.expect(')', 'op') - return expressions.RGB(tuple(args), lineno=stream.lineno) - else: - node = expressions.String('rgb') - elif token == 'rgba': - stream.next() - if stream.current == ('(', 'op'): - stream.next() - args = [] - while len(args) < 4: - if args: - stream.expect(',', 'op') - args.append(self.expr(stream, True)) - stream.expect(')', 'op') - return expressions.RGBA(args) - elif token == 'backstring': - stream.next() - node = expressions.Backstring(value, lineno=stream.lineno) - elif token == 'string': - stream.next() - node = expressions.String(value, lineno=stream.lineno) - elif token == 'url': - stream.next() - node = expressions.URL(value, lineno=stream.lineno) - elif token == 'import': - stream.next() - node = expressions.Import(value, lineno=stream.lineno) - elif token == 'spritemap': - stream.next() - if value[0] == value[-1] and value[0] in '"\'': - value = value[1:-1] - value = expressions.String(value, lineno=stream.lineno) - node = self.sprite_map_cls(value, fname=self.fname, - lineno=stream.lineno) - elif token == 'var': - stream.next() - node = expressions.Var(value, lineno=stream.lineno) - elif token == 'op' and value == '(': - stream.next() - if stream.current == (')', 'op'): - raise ParserError(stream.lineno, 'empty parentheses are ' - 'not valid. If you want to use them as ' - 'string you have to quote them.') - node = self.expr(stream) - stream.expect(')', 'op') - else: - if token == 'call': - raise ParserError(stream.lineno, 'You cannot call standalone ' - 'methods. If you wanted to use it as a ' - 'string you have to quote it.') - stream.next() - node = expressions.String(value, lineno=stream.lineno) - while stream.current[1] == 'call': - node = self.call(stream, node) - return node - - def call(self, stream, node): - method, token = stream.current - assert token == 'call' - stream.next() - args = [] - while stream.current != (')', 'op'): - if args: - stream.expect(',', 'op') - args.append(self.expr(stream)) - stream.expect(')', 'op') - return expressions.Call(node, method, args, lineno=stream.lineno) - - - diff --git a/clevercss_old/errors.py b/clevercss_old/errors.py deleted file mode 100644 index 7aec1de..0000000 --- a/clevercss_old/errors.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python - -class CleverCssException(Exception): - """Base class for exceptions raised by CleverCSS.""" - - def __init__(self, lineno, message): - self.lineno = lineno - self.msg = message - Exception.__init__(self, message) - - def __str__(self): - return '%s (line %s)' % ( - self.msg, - self.lineno - ) - - -class ParserError(CleverCssException): - """Raised on syntax errors.""" - - -class EvalException(CleverCssException): - """Raised during evaluation.""" - -# vim: et sw=4 sts=4 diff --git a/clevercss_old/expressions.py b/clevercss_old/expressions.py deleted file mode 100644 index addd3c6..0000000 --- a/clevercss_old/expressions.py +++ /dev/null @@ -1,680 +0,0 @@ -#!/usr/bin/env python - -import os - -import utils -import operator -import consts -from errors import * - -class Expr(object): - """ - Baseclass for all expressions. - """ - - #: name for exceptions - name = 'expression' - - #: empty iterable of dict with methods - methods = () - - def __init__(self, lineno=None): - self.lineno = lineno - - def evaluate(self, context): - return self - - def add(self, other, context): - return String(self.to_string(context) + other.to_string(context)) - - def sub(self, other, context): - raise EvalException(self.lineno, 'cannot substract %s from %s' % - (self.name, other.name)) - - def mul(self, other, context): - raise EvalException(self.lineno, 'cannot multiply %s with %s' % - (self.name, other.name)) - - def div(self, other, context): - raise EvalException(self.lineno, 'cannot divide %s by %s' % - (self.name, other.name)) - - def mod(self, other, context): - raise EvalException(self.lineno, 'cannot use the modulo operator for ' - '%s and %s. Misplaced unit symbol?' % - (self.name, other.name)) - - def neg(self, context): - raise EvalException(self.lineno, 'cannot negate %s by %s' % self.name) - - def to_string(self, context): - return self.evaluate(context).to_string(context) - - def call(self, name, args, context): - if name == 'string': - if isinstance(self, String): - return self - return String(self.to_string(context)) - elif name == 'type': - return String(self.name) - if name not in self.methods: - raise EvalException(self.lineno, '%s objects don\'t have a method' - ' called "%s". If you want to use this' - ' construct as string, quote it.' % - (self.name, name)) - return self.methods[name](self, context, *args) - - def __repr__(self): - return '%s(%s)' % ( - self.__class__.__name__, - ', '.join('%s=%r' % item for item in - self.__dict__.iteritems()) - ) - - -class ImplicitConcat(Expr): - """ - Holds multiple expressions that are delimited by whitespace. - """ - name = 'concatenated' - methods = { - 'list': lambda x, c: List(x.nodes) - } - - def __init__(self, nodes, lineno=None): - Expr.__init__(self, lineno) - self.nodes = nodes - - def to_string(self, context): - return u' '.join(x.to_string(context) for x in self.nodes) - -class Bin(Expr): - - def __init__(self, left, right, lineno=None): - Expr.__init__(self, lineno) - self.left = left - self.right = right - -class Add(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).add( - self.right.evaluate(context), context) - -class Sub(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).sub( - self.right.evaluate(context), context) - -class Mul(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).mul( - self.right.evaluate(context), context) - -class Div(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).div( - self.right.evaluate(context), context) - -class Mod(Bin): - - def evaluate(self, context): - return self.left.evaluate(context).mod( - self.right.evaluate(context), context) - -class Neg(Expr): - - def __init__(self, node, lineno=None): - Expr.__init__(self, lineno) - self.node = node - - def evaluate(self, context): - return self.node.evaluate(context).neg(context) - -class Call(Expr): - - def __init__(self, node, method, args, lineno=None): - Expr.__init__(self, lineno) - self.node = node - self.method = method - self.args = args - - def evaluate(self, context): - return self.node.evaluate(context) \ - .call(self.method, [x.evaluate(context) - for x in self.args], - context) - -class Literal(Expr): - - def __init__(self, value, lineno=None): - Expr.__init__(self, lineno) - self.value = value - - def to_string(self, context): - rv = unicode(self.value) - if len(rv.split(None, 1)) > 1: - return u"'%s'" % rv.replace('\\', '\\\\') \ - .replace('\n', '\\\n') \ - .replace('\t', '\\\t') \ - .replace('\'', '\\\'') - return rv - -class Number(Literal): - name = 'number' - - methods = { - 'abs': lambda x, c: Number(abs(x.value)), - 'round': lambda x, c, p=0: Number(round(x.value, p)) - } - - def __init__(self, value, lineno=None): - Literal.__init__(self, float(value), lineno) - - def add(self, other, context): - if isinstance(other, Number): - return Number(self.value + other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value + other.value, other.unit, - lineno=self.lineno) - return Literal.add(self, other, context) - - def sub(self, other, context): - if isinstance(other, Number): - return Number(self.value - other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value - other.value, other.unit, - lineno=self.lineno) - return Literal.sub(self, other, context) - - def mul(self, other, context): - if isinstance(other, Number): - return Number(self.value * other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value * other.value, other.unit, - lineno=self.lineno) - return Literal.mul(self, other, context) - - def div(self, other, context): - try: - if isinstance(other, Number): - return Number(self.value / other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value / other.value, other.unit, - lineno=self.lineno) - return Literal.div(self, other, context) - except ZeroDivisionError: - raise EvalException(self.lineno, 'cannot divide by zero') - - def mod(self, other, context): - try: - if isinstance(other, Number): - return Number(self.value % other.value, lineno=self.lineno) - elif isinstance(other, Value): - return Value(self.value % other.value, other.unit, - lineno=self.lineno) - return Literal.mod(self, other, context) - except ZeroDivisionError: - raise EvalException(self.lineno, 'cannot divide by zero') - - def neg(self, context): - return Number(-self.value) - - def to_string(self, context): - return utils.number_repr(self.value) - -class Value(Literal): - name = 'value' - - methods = { - 'abs': lambda x, c: Value(abs(x.value), x.unit), - 'round': lambda x, c, p=0: Value(round(x.value, p), x.unit) - } - - def __init__(self, value, unit, lineno=None): - Literal.__init__(self, float(value), lineno) - self.unit = unit - - def add(self, other, context): - return self._conv_calc(other, context, operator.add, Literal.add, - 'cannot add %s and %s') - - def sub(self, other, context): - return self._conv_calc(other, context, operator.sub, Literal.sub, - 'cannot subtract %s from %s') - - def mul(self, other, context): - if isinstance(other, Number): - return Value(self.value * other.value, self.unit, - lineno=self.lineno) - return Literal.mul(self, other, context) - - def div(self, other, context): - if isinstance(other, Number): - try: - return Value(self.value / other.value, self.unit, - lineno=self.lineno) - except ZeroDivisionError: - raise EvalException(self.lineno, 'cannot divide by zero', - lineno=self.lineno) - return Literal.div(self, other, context) - - def mod(self, other, context): - if isinstance(other, Number): - try: - return Value(self.value % other.value, self.unit, - lineno=self.lineno) - except ZeroDivisionError: - raise EvalException(self.lineno, 'cannot divide by zero') - return Literal.mod(self, other, context) - - def _conv_calc(self, other, context, calc, fallback, msg): - if isinstance(other, Number): - return Value(calc(self.value, other.value), self.unit) - elif isinstance(other, Value): - if self.unit == other.unit: - return Value(calc(self.value,other.value), other.unit, - lineno=self.lineno) - self_unit_type = consts.CONV_mapping.get(self.unit) - other_unit_type = consts.CONV_mapping.get(other.unit) - if not self_unit_type or not other_unit_type or \ - self_unit_type != other_unit_type: - raise EvalException(self.lineno, msg % (self.unit, other.unit) - + ' because the two units are ' - 'not compatible.') - self_unit = consts.CONV[self_unit_type][self.unit] - other_unit = consts.CONV[other_unit_type][other.unit] - if self_unit > other_unit: - return Value(calc(self.value / other_unit * self_unit, - other.value), other.unit, - lineno=self.lineno) - return Value(calc(other.value / self_unit * other_unit, - self.value), self.unit, lineno=self.lineno) - return fallback(self, other, context) - - def neg(self, context): - return Value(-self.value, self.unit, lineno=self.lineno) - - def to_string(self, context): - return utils.number_repr(self.value) + self.unit - -class Color(Literal): - name = 'color' - - methods = { - 'brighten': utils.brighten_color, - 'darken': utils.darken_color, - 'hex': lambda x, c: Color(x.value, x.lineno) - } - - def __init__(self, value, lineno=None): - self.from_name = False - if isinstance(value, basestring): - if not value.startswith('#'): - value = consts.COLORS.get(value) - if not value: - raise ParserError(lineno, 'unknown color name') - self.from_name = True - try: - if len(value) == 4: - value = [int(x * 2, 16) for x in value[1:]] - elif len(value) == 7: - value = [int(value[i:i + 2], 16) for i in xrange(1, 7, 2)] - else: - raise ValueError() - except ValueError, e: - raise ParserError(lineno, 'invalid color value') - Literal.__init__(self, tuple(value), lineno) - - def add(self, other, context): - if isinstance(other, (Color, Number)): - return self._calc(other, operator.add) - return Literal.add(self, other, context) - - def sub(self, other, context): - if isinstance(other, (Color, Number)): - return self._calc(other, operator.sub) - return Literal.sub(self, other, context) - - def mul(self, other, context): - if isinstance(other, (Color, Number)): - return self._calc(other, operator.mul) - return Literal.mul(self, other, context) - - def div(self, other, context): - if isinstance(other, (Color, Number)): - return self._calc(other, operator.sub) - return Literal.div(self, other, context) - - def to_string(self, context): - if context.minified and all(x >> 4 == x & 15 for x in self.value): - return '#%x%x%x' % tuple(x & 15 for x in self.value) - code = '#%02x%02x%02x' % self.value - return self.from_name and consts.REV_COLORS.get(code) or code - - def _calc(self, other, method): - is_number = isinstance(other, Number) - channels = [] - for idx, val in enumerate(self.value): - if is_number: - other_val = int(other.value) - else: - other_val = other.value[idx] - new_val = method(val, other_val) - if new_val > 255: - new_val = 255 - elif new_val < 0: - new_val = 0 - channels.append(new_val) - return Color(tuple(channels), lineno=self.lineno) - -class RGB(Expr): - """ - an expression that hopefully returns a Color object. - """ - - def __init__(self, rgb, lineno=None): - Expr.__init__(self, lineno) - self.rgb = rgb - - def evaluate(self, context): - args = [] - for arg in self.rgb: - arg = arg.evaluate(context) - if isinstance(arg, Number): - value = int(arg.value) - elif isinstance(arg, Value) and arg.unit == '%': - value = int(arg.value / 100.0 * 255) - else: - raise EvalException(self.lineno, 'colors defined using the ' - 'rgb() literal only accept numbers and ' - 'percentages.') - if value < 0 or value > 255: - raise EvalException(self.lineno, 'rgb components must be in ' - 'the range 0 to 255.') - args.append(value) - return Color(args, lineno=self.lineno) - -class RGBA(RGB): - """ - an expression for dealing w/ rgba colors - """ - - def to_string(self, context): - args = [] - for i, arg in enumerate(self.rgb): - arg = arg.evaluate(context) - if isinstance(arg, Number): - if i == 3: - value = float(arg.value) - else: - value = int(arg.value) - elif isinstance(arg, Value) and arg.unit == '%': - if i == 3: - value = float(arg.value / 100.0) - else: - value = int(arg.value / 100.0 * 255) - else: - raise EvalException(self.lineno, 'colors defined using the ' - 'rgb() literal only accept numbers and ' - 'percentages. (got %s)' % arg) - if value < 0 or value > 255: - raise EvalError(self.lineno, 'rgb components must be in ' - 'the range 0 to 255.') - args.append(value) - return 'rgba(%s)' % (', '.join(str(n) for n in args)) - -class Backstring(Literal): - """ - A string meant to be escaped directly to output. - """ - name = "backstring" - - def __init__(self, nodes, lineno=None): - Expr.__init__(self, lineno) - self.nodes = nodes - - def to_string(self, context): - return unicode(self.nodes) - -class String(Literal): - name = 'string' - - methods = { - 'length': lambda x, c: Number(len(x.value)), - 'upper': lambda x, c: String(x.value.upper()), - 'lower': lambda x, c: String(x.value.lower()), - 'strip': lambda x, c: String(x.value.strip()), - 'split': lambda x, c, d=None: String(x.value.split(d)), - 'eval': lambda x, c: Parser().parse_expr(x.lineno, x.value) - .evaluate(c) - } - - def mul(self, other, context): - if isinstance(other, Number): - return String(self.value * int(other.value), lineno=self.lineno) - return Literal.mul(self, other, context, lineno=self.lineno) - -class URL(Literal): - name = 'URL' - methods = { - 'length': lambda x, c: Number(len(self.value)) - } - - def add(self, other, context): - return URL(self.value + other.to_string(context), - lineno=self.lineno) - - def mul(self, other, context): - if isinstance(other, Number): - return URL(self.value * int(other.value), lineno=self.lineno) - return Literal.mul(self, other, context) - - def to_string(self, context): - return 'url(%s)' % Literal.to_string(self, context) - -class SpriteMap(Expr): - name = 'SpriteMap' - methods = { - 'sprite': lambda x, c, v: Sprite(x, v.value, lineno=v.lineno) - } - _magic_names = { - "__url__": "image_url", - "__resources__": "sprite_resource_dir", - "__passthru__": "sprite_passthru_url", - } - - image_url = None - sprite_resource_dir = None - sprite_passthru_url = None - - def __init__(self, map_fname, fname=None, lineno=None): - Expr.__init__(self, lineno=lineno) - self.map_fname = map_fname - self.fname = fname - - def evaluate(self, context): - self.map_fpath = os.path.join(os.path.dirname(self.fname), - self.map_fname.to_string(context)) - self.mapping = self.read_spritemap(self.map_fpath) - return self - - def read_spritemap(self, fpath): - fo = open(fpath, "U") - spritemap = {} - try: - for line in fo: - line = line.rstrip("\n") - if not line.strip(): - continue - rest = line.split(",") - key = rest.pop(0).strip() - if key[-2:] == key[:2] == "__": - if key not in self._magic_names: - raise ValueError("%r is not a valid field" % (key,)) - att = self._magic_names[key] - setattr(self, att, rest[0].strip()) - elif len(rest) != 4: - raise ValueError("unexpected line: %r" % (line,)) - else: - x1, y1, x2, y2 = rest - spritemap[key] = map(int, (x1, y1, x2, y2)) - finally: - fo.close() - return spritemap - - def get_sprite_def(self, name): - if name in self.mapping: - return self.mapping[name] - elif self.sprite_passthru_url: - return self._load_sprite(name) - else: - raise KeyError(name) - - def _load_sprite(self, name): - try: - from PIL import Image - except ImportError: - raise KeyError(name) - - spr_fname = os.path.join(os.path.dirname(self.map_fpath), name) - if not os.path.exists(spr_fname): - raise KeyError(name) - - im = Image.open(spr_fname) - spr_def = (0, 0) + tuple(im.size) - self.mapping[name] = spr_def - return spr_def - - def get_sprite_url(self, sprite): - if self.sprite_passthru_url: - return self.sprite_passthru_url + sprite.name - else: - return self.image_url - - def annotate_used(self, sprite): - pass - -class AnnotatingSpriteMap(SpriteMap): - sprite_maps = [] - - def __init__(self, *args, **kwds): - SpriteMap.__init__(self, *args, **kwds) - self._sprites_used = {} - self.sprite_maps.append(self) - - def read_spritemap(self, fname): - self.image_url = "" - return {} - - def get_sprite_def(self, name): - return 0, 0, 100, 100 - - def get_sprite_url(self, sprite): - return "" % (sprite,) - - def annotate_used(self, sprite): - self._sprites_used[sprite.name] = sprite - - @classmethod - def all_used_sprites(cls): - for smap in cls.sprite_maps: - yield smap, smap._sprites_used.values() - -class Sprite(Expr): - name = 'Sprite' - methods = { - 'url': lambda x, c: String("url('%s')" % x.spritemap.get_sprite_url(x)), - 'position': lambda x, c: ImplicitConcat(x._pos_vals(c)), - 'height': lambda x, c: Value(x.height, "px"), - 'width': lambda x, c: Value(x.width, "px"), - 'x1': lambda x, c: Value(x.x1, "px"), - 'y1': lambda x, c: Value(x.y1, "px"), - 'x2': lambda x, c: Value(x.x2, "px"), - 'y2': lambda x, c: Value(x.y2, "px") - } - - def __init__(self, spritemap, name, lineno=None): - self.lineno = lineno if lineno else name.lineno - self.name = name - self.spritemap = spritemap - self.spritemap.annotate_used(self) - try: - self.coords = spritemap.get_sprite_def(name) - except KeyError: - msg = "Couldn't find sprite %r in mapping" % name - raise EvalException(self.lineno, msg) - - def _get_coords(self): - return self.x1, self.y1, self.x2, self.y2 - def _set_coords(self, value): - self.x1, self.y1, self.x2, self.y2 = value - coords = property(_get_coords, _set_coords) - - @property - def width(self): return self.x2 - self.x1 - @property - def height(self): return self.y2 - self.y1 - - def _pos_vals(self, context): - """Get a list of position values.""" - meths = self.methods - call_names = "x1", "y1", "x2", "y2" - return [meths[n](self, context) for n in call_names] - - def to_string(self, context): - sprite_url = self.spritemap.get_sprite_url(self) - return "url('%s') -%dpx -%dpx" % (sprite_url, self.x1, self.y1) - -class Var(Expr): - - def __init__(self, name, lineno=None): - self.name = name - self.lineno = lineno - - def evaluate(self, context): - if self.name not in context: - raise EvalException(self.lineno, 'variable %s is not defined' % - (self.name,)) - val = context[self.name] - context[self.name] = FailingVar(self, self.lineno) - try: - return val.evaluate(context) - finally: - context[self.name] = val - -class FailingVar(Expr): - - def __init__(self, var, lineno=None): - Expr.__init__(self, lineno or var.lineno) - self.var = var - - def evaluate(self, context): - raise EvalException(self.lineno, 'Circular variable dependencies ' - 'detected when resolving %s.' % (self.var.name,)) - -class List(Expr): - name = 'list' - - methods = { - 'length': lambda x, c: Number(len(x.items)), - 'join': lambda x, c, d=String(' '): String(d.value.join( - a.to_string(c) for a in x.items)) - } - - def __init__(self, items, lineno=None): - Expr.__init__(self, lineno) - self.items = items - - def add(self, other): - if isinstance(other, List): - return List(self.items + other.items, lineno=self.lineno) - return List(self.items + [other], lineno=self.lineno) - - def to_string(self, context): - return u', '.join(x.to_string(context) for x in self.items) - -# vim: et sw=4 sts=4 diff --git a/clevercss_old/line_iterator.py b/clevercss_old/line_iterator.py deleted file mode 100644 index 6be9684..0000000 --- a/clevercss_old/line_iterator.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python - -import consts -import utils - -class LineIterator(object): - """ - This class acts as an iterator for sourcecode. It yields the lines - without comments or empty lines and keeps track of the real line - number. - - Example:: - - >>> li = LineIterator(u'foo\nbar\n\n/* foo */bar') - >>> li.next() - 1, u'foo' - >>> li.next() - 2, 'bar' - >>> li.next() - 4, 'bar' - >>> li.next() - Traceback (most recent call last): - File "", line 1, in - StopIteration - """ - - def __init__(self, source, emit_endmarker=False): - """ - If `emit_endmarkers` is set to `True` the line iterator will send - the string ``'__END__'`` before closing down. - """ - lines = consts.regex['multi_comment'].sub('', source).splitlines() - self.lineno = 0 - self.lines = len(lines) - self.emit_endmarker = emit_endmarker - self._lineiter = iter(lines) - - def __iter__(self): - return self - - def _read_line(self): - """Read the next non empty line. This strips line comments.""" - line = '' - while not line.strip(): - line += consts.regex['line_comment'].sub('', self._lineiter.next()).rstrip() - self.lineno += 1 - return line - - def _next(self): - """ - Get the next line without mutliline comments. - """ - # XXX: this fails for a line like this: "/* foo */bar/*" - line = self._read_line() - comment_start = line.find('/*') - if comment_start < 0: - return self.lineno, line - - stripped_line = line[:comment_start] - comment_end = line.find('*/', comment_start) - if comment_end >= 0: - return self.lineno, stripped_line + line[comment_end + 2:] - - start_lineno = self.lineno - try: - while True: - line = self._read_line() - comment_end = line.find('*/') - if comment_end >= 0: - stripped_line += line[comment_end + 2:] - break - except StopIteration: - raise ParserError(self.lineno, 'missing end of multiline comment') - return start_lineno, stripped_line - - def next(self): - """ - Get the next line without multiline comments and emit the - endmarker if we reached the end of the sourcecode and endmarkers - were requested. - """ - try: - while True: - lineno, stripped_line = self._next() - if stripped_line: - return lineno, stripped_line - except StopIteration: - if self.emit_endmarker: - self.emit_endmarker = False - return self.lineno, '__END__' - raise - - -# vim: et sw=4 sts=4 diff --git a/clevercss_old/utils.py b/clevercss_old/utils.py deleted file mode 100644 index 5763480..0000000 --- a/clevercss_old/utils.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python - -import colorsys -import errors - -def brighten_color(color, context, amount=None): - if amount is None: - amount = expressions.Value(10.0, '%') - hue, lightness, saturation = rgb_to_hls(*color.value) - if isinstance(amount, expressions.Value): - if amount.unit == '%': - if not amount.value: - return color - lightness *= 1.0 + amount.value / 100.0 - else: - raise errors.EvalException(self.lineno, 'invalid unit %s for color ' - 'calculations.' % amount.unit) - elif isinstance(amount, expressions.Number): - lightness += (amount.value / 100.0) - if lightness > 1: - lightness = 1.0 - return expressions.Color(hls_to_rgb(hue, lightness, saturation)) - -def darken_color(color, context, amount=None): - if amount is None: - amount = expressions.Value(10.0, '%') - hue, lightness, saturation = rgb_to_hls(*color.value) - if isinstance(amount, expressions.Value): - if amount.unit == '%': - if not amount.value: - return color - lightness *= amount.value / 100.0 - else: - raise errors.EvalException(self.lineno, 'invalid unit %s for color ' - 'calculations.' % amount.unit) - elif isinstance(amount, expressions.Number): - lightness -= (amount.value / 100.0) - if lightness < 0: - lightness = 0.0 - return expressions.Color(hls_to_rgb(hue, lightness, saturation)) - -def number_repr(value): - """ - CleverCSS uses floats internally. To keep the string representation - of the numbers small cut off the places if this is possible without - loosing much information. - """ - value = unicode(value) - parts = value.rsplit('.') - if len(parts) == 2 and parts[-1] == '0': - return parts[0] - return value - - -def rgb_to_hls(red, green, blue): - """ - Convert RGB to HSL. The RGB values we use are in the range 0-255, but - HSL is in the range 0-1! - """ - return colorsys.rgb_to_hls(red / 255.0, green / 255.0, blue / 255.0) - - -def hls_to_rgb(hue, saturation, lightness): - """Convert HSL back to RGB.""" - t = colorsys.hls_to_rgb(hue, saturation, lightness) - return tuple(int(round(x * 255)) for x in t) - -import expressions - -# vim: et sw=4 sts=4