From f4ba41cc1d79e34dbf60533d7adf0dbb67837d20 Mon Sep 17 00:00:00 2001 From: robotis Date: Sat, 28 Jan 2012 14:52:09 +0000 Subject: [PATCH] initial commit --- .gitignore | 6 + LICENSE | 20 + README | 107 +++ bin/lesscpy | 19 + lesscpy/__init__.py | 0 lesscpy/less.ast | 154 ++++ lesscpy/lessc/__init__.py | 0 lesscpy/lessc/color.py | 217 ++++++ lesscpy/lessc/formatter.py | 87 +++ lesscpy/lessc/lexer.py | 202 +++++ lesscpy/lessc/parser.py | 691 ++++++++++++++++++ lesscpy/lessc/utility.py | 154 ++++ lesscpy/lib/__init__.py | 0 lesscpy/lib/css.py | 293 ++++++++ lesscpy/lib/dom.py | 142 ++++ lesscpy/plib/__init__.py | 18 + lesscpy/plib/block.py | 81 ++ lesscpy/plib/call.py | 101 +++ lesscpy/plib/expression.py | 100 +++ lesscpy/plib/mixin.py | 97 +++ lesscpy/plib/node.py | 18 + lesscpy/plib/process.py | 84 +++ lesscpy/plib/property.py | 40 + lesscpy/plib/statement.py | 20 + lesscpy/plib/string.py | 27 + lesscpy/plib/variable.py | 18 + lesscpy/scripts/__init__.py | 0 lesscpy/scripts/compiler.py | 127 ++++ lesscpy/test/__init__.py | 0 lesscpy/test/__main__.py | 33 + lesscpy/test/css/color-functions.css | 13 + lesscpy/test/css/color-functions.min.css | 1 + lesscpy/test/css/colors.css | 48 ++ lesscpy/test/css/colors.min.css | 16 + lesscpy/test/css/comments.css | 17 + lesscpy/test/css/comments.min.css | 3 + lesscpy/test/css/css-3.css | 46 ++ lesscpy/test/css/css-3.min.css | 14 + lesscpy/test/css/css-escapes.css | 16 + lesscpy/test/css/css-escapes.min.css | 5 + lesscpy/test/css/css.css | 89 +++ lesscpy/test/css/css.min.css | 27 + lesscpy/test/css/functions.css | 18 + lesscpy/test/css/functions.min.css | 2 + lesscpy/test/css/ie-filters.css | 9 + lesscpy/test/css/ie-filters.min.css | 3 + lesscpy/test/css/import.css | 7 + lesscpy/test/css/import.min.css | 1 + lesscpy/test/css/issues/inherit_scope.css | 3 + lesscpy/test/css/issues/mixins-closure.css | 9 + lesscpy/test/css/issues/mixins-nested.css | 14 + lesscpy/test/css/issues/mixins-pattern.css | 49 ++ lesscpy/test/css/issues/plus_identifier.css | 0 lesscpy/test/css/media.css | 21 + lesscpy/test/css/media.min.css | 8 + lesscpy/test/css/mixins-args.css | 50 ++ lesscpy/test/css/mixins-args.min.css | 13 + lesscpy/test/css/mixins-redundant.css | 25 + lesscpy/test/css/mixins-redundant.min.css | 8 + lesscpy/test/css/mixins.css | 74 ++ lesscpy/test/css/mixins.min.css | 21 + lesscpy/test/css/operations.css | 42 ++ lesscpy/test/css/operations.min.css | 8 + lesscpy/test/css/parens.css | 20 + lesscpy/test/css/parens.min.css | 4 + lesscpy/test/css/rulesets.css | 29 + lesscpy/test/css/rulesets.min.css | 13 + lesscpy/test/css/scope.css | 12 + lesscpy/test/css/scope.min.css | 3 + lesscpy/test/css/selectors.css | 53 ++ lesscpy/test/css/selectors.min.css | 24 + lesscpy/test/css/strings.css | 32 + lesscpy/test/css/strings.min.css | 7 + lesscpy/test/css/variables.css | 27 + lesscpy/test/css/variables.min.css | 7 + lesscpy/test/css/whitespace.css | 32 + lesscpy/test/css/whitespace.min.css | 10 + lesscpy/test/genast.py | 16 + lesscpy/test/less/color-functions.less | 12 + lesscpy/test/less/colors.less | 52 ++ lesscpy/test/less/comments.less | 65 ++ lesscpy/test/less/css-3.less | 66 ++ lesscpy/test/less/css-escapes.less | 23 + lesscpy/test/less/css.less | 118 +++ lesscpy/test/less/functions.less | 22 + lesscpy/test/less/ie-filters.less | 12 + lesscpy/test/less/import.less | 10 + lesscpy/test/less/imports/circular.less | 1 + lesscpy/test/less/imports/import.less | 5 + lesscpy/test/less/imports/import_f.less | 6 + lesscpy/test/less/issues/inherit_scope.less | 8 + lesscpy/test/less/issues/mixins-closure.less | 26 + lesscpy/test/less/issues/mixins-nested.less | 30 + lesscpy/test/less/issues/mixins-pattern.less | 96 +++ lesscpy/test/less/issues/plus_identifier.less | 5 + lesscpy/test/less/media.less | 25 + lesscpy/test/less/mixins-args.less | 110 +++ lesscpy/test/less/mixins-redundant.less | 29 + lesscpy/test/less/mixins.less | 72 ++ lesscpy/test/less/operations.less | 53 ++ lesscpy/test/less/parens.less | 26 + lesscpy/test/less/rulesets.less | 28 + lesscpy/test/less/scope.less | 22 + lesscpy/test/less/selectors.less | 48 ++ lesscpy/test/less/strings.less | 49 ++ lesscpy/test/less/variables.less | 59 ++ lesscpy/test/less/whitespace.less | 37 + lesscpy/test/mockp.py | 14 + lesscpy/test/testcolor.py | 63 ++ lesscpy/test/testexpression.py | 58 ++ lesscpy/test/testissues.py | 40 + lesscpy/test/testless.py | 54 ++ lesscpy/test/testprocess.py | 40 + lesscpy/test/testutility.py | 50 ++ setup.py | 27 + 115 files changed, 5286 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README create mode 100755 bin/lesscpy create mode 100644 lesscpy/__init__.py create mode 100644 lesscpy/less.ast create mode 100644 lesscpy/lessc/__init__.py create mode 100644 lesscpy/lessc/color.py create mode 100644 lesscpy/lessc/formatter.py create mode 100644 lesscpy/lessc/lexer.py create mode 100644 lesscpy/lessc/parser.py create mode 100644 lesscpy/lessc/utility.py create mode 100644 lesscpy/lib/__init__.py create mode 100644 lesscpy/lib/css.py create mode 100644 lesscpy/lib/dom.py create mode 100644 lesscpy/plib/__init__.py create mode 100644 lesscpy/plib/block.py create mode 100644 lesscpy/plib/call.py create mode 100644 lesscpy/plib/expression.py create mode 100644 lesscpy/plib/mixin.py create mode 100644 lesscpy/plib/node.py create mode 100644 lesscpy/plib/process.py create mode 100644 lesscpy/plib/property.py create mode 100644 lesscpy/plib/statement.py create mode 100644 lesscpy/plib/string.py create mode 100644 lesscpy/plib/variable.py create mode 100644 lesscpy/scripts/__init__.py create mode 100644 lesscpy/scripts/compiler.py create mode 100644 lesscpy/test/__init__.py create mode 100644 lesscpy/test/__main__.py create mode 100644 lesscpy/test/css/color-functions.css create mode 100644 lesscpy/test/css/color-functions.min.css create mode 100644 lesscpy/test/css/colors.css create mode 100644 lesscpy/test/css/colors.min.css create mode 100644 lesscpy/test/css/comments.css create mode 100644 lesscpy/test/css/comments.min.css create mode 100644 lesscpy/test/css/css-3.css create mode 100644 lesscpy/test/css/css-3.min.css create mode 100644 lesscpy/test/css/css-escapes.css create mode 100644 lesscpy/test/css/css-escapes.min.css create mode 100644 lesscpy/test/css/css.css create mode 100644 lesscpy/test/css/css.min.css create mode 100644 lesscpy/test/css/functions.css create mode 100644 lesscpy/test/css/functions.min.css create mode 100644 lesscpy/test/css/ie-filters.css create mode 100644 lesscpy/test/css/ie-filters.min.css create mode 100644 lesscpy/test/css/import.css create mode 100644 lesscpy/test/css/import.min.css create mode 100644 lesscpy/test/css/issues/inherit_scope.css create mode 100644 lesscpy/test/css/issues/mixins-closure.css create mode 100644 lesscpy/test/css/issues/mixins-nested.css create mode 100644 lesscpy/test/css/issues/mixins-pattern.css create mode 100644 lesscpy/test/css/issues/plus_identifier.css create mode 100644 lesscpy/test/css/media.css create mode 100644 lesscpy/test/css/media.min.css create mode 100644 lesscpy/test/css/mixins-args.css create mode 100644 lesscpy/test/css/mixins-args.min.css create mode 100644 lesscpy/test/css/mixins-redundant.css create mode 100644 lesscpy/test/css/mixins-redundant.min.css create mode 100644 lesscpy/test/css/mixins.css create mode 100644 lesscpy/test/css/mixins.min.css create mode 100644 lesscpy/test/css/operations.css create mode 100644 lesscpy/test/css/operations.min.css create mode 100644 lesscpy/test/css/parens.css create mode 100644 lesscpy/test/css/parens.min.css create mode 100644 lesscpy/test/css/rulesets.css create mode 100644 lesscpy/test/css/rulesets.min.css create mode 100644 lesscpy/test/css/scope.css create mode 100644 lesscpy/test/css/scope.min.css create mode 100644 lesscpy/test/css/selectors.css create mode 100644 lesscpy/test/css/selectors.min.css create mode 100644 lesscpy/test/css/strings.css create mode 100644 lesscpy/test/css/strings.min.css create mode 100644 lesscpy/test/css/variables.css create mode 100644 lesscpy/test/css/variables.min.css create mode 100644 lesscpy/test/css/whitespace.css create mode 100644 lesscpy/test/css/whitespace.min.css create mode 100644 lesscpy/test/genast.py create mode 100644 lesscpy/test/less/color-functions.less create mode 100644 lesscpy/test/less/colors.less create mode 100644 lesscpy/test/less/comments.less create mode 100644 lesscpy/test/less/css-3.less create mode 100644 lesscpy/test/less/css-escapes.less create mode 100644 lesscpy/test/less/css.less create mode 100644 lesscpy/test/less/functions.less create mode 100644 lesscpy/test/less/ie-filters.less create mode 100644 lesscpy/test/less/import.less create mode 100644 lesscpy/test/less/imports/circular.less create mode 100644 lesscpy/test/less/imports/import.less create mode 100644 lesscpy/test/less/imports/import_f.less create mode 100644 lesscpy/test/less/issues/inherit_scope.less create mode 100644 lesscpy/test/less/issues/mixins-closure.less create mode 100644 lesscpy/test/less/issues/mixins-nested.less create mode 100644 lesscpy/test/less/issues/mixins-pattern.less create mode 100644 lesscpy/test/less/issues/plus_identifier.less create mode 100644 lesscpy/test/less/media.less create mode 100644 lesscpy/test/less/mixins-args.less create mode 100644 lesscpy/test/less/mixins-redundant.less create mode 100644 lesscpy/test/less/mixins.less create mode 100644 lesscpy/test/less/operations.less create mode 100644 lesscpy/test/less/parens.less create mode 100644 lesscpy/test/less/rulesets.less create mode 100644 lesscpy/test/less/scope.less create mode 100644 lesscpy/test/less/selectors.less create mode 100644 lesscpy/test/less/strings.less create mode 100644 lesscpy/test/less/variables.less create mode 100644 lesscpy/test/less/whitespace.less create mode 100644 lesscpy/test/mockp.py create mode 100644 lesscpy/test/testcolor.py create mode 100644 lesscpy/test/testexpression.py create mode 100644 lesscpy/test/testissues.py create mode 100644 lesscpy/test/testless.py create mode 100644 lesscpy/test/testprocess.py create mode 100644 lesscpy/test/testutility.py create mode 100644 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af4a8a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__ +.project +.pydevproject +parser.out +.settings +build diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d56a718 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012 Jóhann T Maríusson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..0062491 --- /dev/null +++ b/README @@ -0,0 +1,107 @@ +* +* LESSCPY * +* +python LessCss Compiler. +v0.6 + +A compiler written in python 3 for the lesscss language. For those of us not willing/able to +have node.js installed in our environment. Not all features of lesscss are supported (yet). +Some features wil probably never be supported (JavaScript evaluation). This program uses PLY +(Python Lex-Yacc) to tokenize/parse the input. + +This is an early version, so you are likly to find bugs. + +For more information on lesscss: + * http://lesscss.org/ + * https://github.com/cloudhead/less.js + +Requirements +============ + +* python 3+ +* ply (Python Lex-Yacc) python 3 version + +For more information on ply: +* http://www.dabeaz.com/ply/ + +Installation +============ + +python3 setup.py install + +or simply place the package into your python path. + +Compiler script Usage +===================== + +usage: lesscpy [-h] [-I INCLUDE] [-x] [-X] [-m] [-D] [-v] [-o OUT] [-S] [-V] + [-L] [-N] + target + +positional arguments: + target + +optional arguments: + -h, --help show this help message and exit + -I INCLUDE, --include INCLUDE + Included less-files (comma separated) + -x, --minify Minify output + -X, --xminify Minify output, no end of block newlines + -m, --min-ending Add '.min' into output filename. eg, name.min.css + -D, --dry-run Dry run, do not write files + -v, --verbose Verbose mode + -o OUT, --out OUT Output directory + +Debugging: + -S, --scopemap Scopemap + -V, --debug Debug mode + -L, --lex-only Run lexer on target + -N, --no-css No css output + +Supported features +================== +* Variables +* String interpolation +* Mixins +* Parametered mixins (class) +* @arguments +* Nesting +* Escapes ~/e() +* Expressions +* Color functions: +** lighten +** darken +** saturate +** desaturate +** spin +** hue +** saturation +** lightness +* Other functions: +** round +** increment +** decrement +** format '%(' +** add + +Differences from lessc.js +========================= +* All MS filters and other strange vendor constructs must be escaped +* All colors are auto-formatted to #nnnnnn. eg, #f7e923 +* Does not preserve css comments + +Not supported (yet) +=================== +* Keyframe blocks +* Parametered mixins (id) +* mixins (closures) +* mixins (Nested) +* Pattern-matching +* Guard expressions +* JavaScript evaluation + +License +======= +See the LICENSE file + + \ No newline at end of file diff --git a/bin/lesscpy b/bin/lesscpy new file mode 100755 index 0000000..1fd30eb --- /dev/null +++ b/bin/lesscpy @@ -0,0 +1,19 @@ +#! /usr/bin/python3 +""" + CSS/LESSCSS run script + + http://lesscss.org/#docs + + +""" +import sys, os + +path = os.path.abspath(sys.argv[0]) +while os.path.dirname(path) != path: + if os.path.exists(os.path.join(path, 'lesscpy', '__init__.py')): + sys.path.insert(0, path) + break + path = os.path.dirname(path) + +from lesscpy.scripts import compiler +compiler.run() \ No newline at end of file diff --git a/lesscpy/__init__.py b/lesscpy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lesscpy/less.ast b/lesscpy/less.ast new file mode 100644 index 0000000..013f1d3 --- /dev/null +++ b/lesscpy/less.ast @@ -0,0 +1,154 @@ + unit : statement_list + + statement_list : statement_list statement + | statement + + statement : block_decl + | variable_decl + | mixin_decl + | css_charset css_string ';' + | css_namespace css_string ';' + | css_namespace css_ident css_string ';' + | css_import css_string ';' + + mixin_decl : block_open_mixin declaration_list brace_close + + block_decl : block_open declaration_list brace_close + | block_open brace_close + + block_open_mixin : css_class t_popen block_mixin_args t_pclose brace_open + | css_class t_popen less_arguments t_pclose brace_open + | css_class t_popen t_pclose brace_open + + block_mixin_args : block_mixin_args ',' block_mixin_arg + | block_mixin_arg + + block_mixin_arg : less_variable ':' block_mixin_factor + | block_mixin_factor + | less_variable + + block_mixin_factor : css_number + | css_color + | css_ident + | css_string + + block_open : identifier_list brace_open + + mixin : identifier_list ';' + + identifier_list : identifier_group + | identifier_page + | css_font_face + + identifier_page : identifier_page dom_filter + | css_page + + identifier_group : identifier_group ',' identifier + | identifier_group '+' identifier + | identifier_group identifier + | identifier + | css_media + + identifier : css_dom + | css_id + | css_class + | dom_filter + | css_color + | less_combine + | '*' + | '>' + + declaration_list : declaration_list declaration + | declaration + | property_decl + | block_decl + | variable_decl + + variable_decl : less_variable ':' style_list ';' + + property_decl : identifier_list t_popen argument_list t_pclose ';' + | identifier_list t_popen t_pclose ';' + | property ':' style_list ';' + | property ':' style_list + | property ':' ';' + | mixin + + property : css_property + | css_vendor_property + | css_ident + + style_list : style_group + | less_arguments + + style_group : style_group ',' style + | style_group style + | style + + style : expression + | css_important + | css_string + | istring + | css_vendor_property + | css_property + | css_ident + | '~' istring + | '~' css_string + + dom_filter : css_dom filter_group + | css_id filter_group + | css_class filter_group + | less_combine filter_group + + filter_group : filter filter + | filter + + filter : css_filter + | ':' css_ident + | ':' css_filter + | ':' ':' css_ident + +expression : expression '+' expression + | expression '-' expression + | expression '*' expression + | expression '/' expression + | '-' t_popen expression t_pclose + | t_popen expression t_pclose + | factor + +factor : color + | number + | variable + | css_dom + | fcall + + fcall : css_ident t_popen argument_list t_pclose + | css_property t_popen argument_list t_pclose + | css_vendor_property t_popen argument_list t_pclose + | less_open_format argument_list t_pclose + + argument_list : argument_list ',' argument + | argument_list argument + | argument + + argument : expression + | css_string + | istring + | css_ident + | css_id + | css_uri + | '=' + + istring : less_string + + variable : '-' variable + | t_popen variable t_pclose + | less_variable + + color : css_color + + number : css_number + | css_number_unit + + brace_open : '{' + + brace_close : '}' \ No newline at end of file diff --git a/lesscpy/lessc/__init__.py b/lesscpy/lessc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lesscpy/lessc/color.py b/lesscpy/lessc/color.py new file mode 100644 index 0000000..304848e --- /dev/null +++ b/lesscpy/lessc/color.py @@ -0,0 +1,217 @@ +""" + LESSCPY Color functions + + Copyright (c) + See LICENSE for details. + +""" +import colorsys + +class LessColor(): + def process(self, expression): + """ Process color expression + @param tuple: color expression + @return: string + """ + a, o, b = expression + c1 = self.hex_to_rgb(a) + c2 = self.hex_to_rgb(b) + r = ['#'] + for i in range(3): + v = self.operate(c1[i], c2[i], o) + if v > 0xff: v = 0xff + if v < 0: v = 0 + r.append("%02x" % v) + return ''.join(r) + + def operate(self, a, b, o): + """ Do operation on colors + @param string: color + @param string: color + @param string: operator + """ + operation = { + '+': '__add__', + '-': '__sub__', + '*': '__mul__', + '/': '__truediv__' + }.get(o) + v = getattr(a, operation)(b) + return v + + def rgb(self, r, g, b): + """ RGB color function + @param str: RED channel + @param str: GREEN channel + @param str: BLUE channel + @return: str + """ + return self.__str_rgb((int(r), int(g), int(b))) + + def hex_to_rgb(self, hex, *args): + """ Convert HEX color code to rgb + @param str: hex code + @return tuple + """ + hex = hex.strip() + if hex[0] == '#': + hex = hex.strip('#').strip(';') + if len(hex) == 3: + hex = [c * 2 for c in hex] + else: + hex = [hex[i:i+2] for i in range(0, len(hex), 2)] + return tuple(int(c, 16) for c in hex) + return [int(hex, 16)] * 3 + + def hex_to_hls(self, hex, *args): + """ Convert Hex color code to hls + @param str: hex code + @return tuple + """ + rgb = self.hex_to_rgb(hex) + return colorsys.rgb_to_hls(*[c / 255.0 for c in rgb]) + + def hls(self, h, l, s, *args): + """ + Create HEX color value from hls + @param h: hue (0 <= h <= 360) + @param l: lightness (0 <= l <= 100) + @param s: saturation (0 <= l <= 100) + """ + if type(l) == str: l = int(l.strip('%')) + if type(s) == str: s = int(s.strip('%')) + rgb = colorsys.hls_to_rgb(int(h) / 360, l / 100, s / 100) + return self.__format_rgb(rgb) + + def hsl(self, h, s, l, *args): + """ + Wrapper for hls + """ + return self.hls(h, l, s) + + def hue(self, color, *args): + """ + Returns the hue channel of color + @param str: hex code + @return int + """ + h, l, s = self.hex_to_hls(color) + return round(h * 360, 3) + + def saturation(self, color, *args): + """ + Returns the saturation channel of color + @param str: hex code + @return int + """ + h, l, s = self.hex_to_hls(color) + return round(s * 100) + + def lightness(self, color, *args): + """ + Returns the lightness channel of color + @param str: hex code + @return int + """ + h, l, s = self.hex_to_hls(color) + return round(l * 100) + + def saturate(self, color, p, *args): + """ + """ + if type(p) == str: p = int(p.strip('%')) + h, l, s = self.hex_to_hls(color) + rgb = colorsys.hls_to_rgb(h, l, s + (p / 100)) + return self.__format_rgb(rgb) + + def desaturate(self, color, p, *args): + """ + """ + if type(p) == str: p = int(p.strip('%')) + h, l, s = self.hex_to_hls(color) + rgb = colorsys.hls_to_rgb(h, l, s - (p / 100)) + return self.__format_rgb(rgb) + + def lighten(self, color, p, *args): + """ + """ + if type(p) == str: p = int(p.strip('%')) + h, l, s = self.hex_to_hls(color) + rgb = colorsys.hls_to_rgb(h, l + (p / 100), s) + return self.__format_rgb(rgb) + + def darken(self, color, p, *args): + """ + """ + if type(p) == str: p = int(p.strip('%')) + h, l, s = self.hex_to_hls(color) + rgb = colorsys.hls_to_rgb(h, l - (p / 100), s) + return self.__format_rgb(rgb) + + def greyscale(self, color, *args): + """ + """ + return self.desaturate(color, 100) + + def fadein(self, color, pc, *args): + """ + """ + pass + + def fadeout(self, color, pc, *args): + """ + """ + pass + + def fade(self): + """ + """ + pass + + def spin(self, color, deg): + """ + """ + if type(deg) == str: deg = int(deg.strip('%')) + h, l, s = self.hex_to_hls(color) + h = ((h * 360) + deg) % 360 + h = 360 + h if h < 0 else h + rgb = colorsys.hls_to_rgb(h / 360, l, s) + return self.__format_rgb(rgb) + + def mix(self, color1, color2, weight): + """ + """ + pass + + def format(self, color): + """ + Format CSS Hex color code. + uppercase becomes lowercase, 3 digit codes expand to 6 digit. + @param string: color + """ + if type(color) == str and color[0] == '#': + color = color.lower().strip().strip('#').strip(';') + if len(color) == 3: + color = ''.join([c * 2 for c in color]) + return '#%s' % color + raise ValueError('Cannot format non-color') + + def __format_rgb(self, rgb): + """ Format RGB tuple to string + @param tuple: RGB tuple + @return: string + """ + color = (round(c * 255) for c in rgb) + return self.__str_rgb(color) + + def __str_rgb(self, rgb): + """ + """ + return '#%s' % ''.join(["%02x" % v for v in + [0xff + if h > 0xff else + 0 if h < 0 else h + for h in rgb] + ]) + + \ No newline at end of file diff --git a/lesscpy/lessc/formatter.py b/lesscpy/lessc/formatter.py new file mode 100644 index 0000000..61cac56 --- /dev/null +++ b/lesscpy/lessc/formatter.py @@ -0,0 +1,87 @@ +""" + CSS Formatter class. + + Copyright (c) + See LICENSE for details. + +""" +class Formatter(object): + def format(self, parse, minify=False, xminify=False): + """ Format css output from parser + @param Parse-result object: Parse-result object + @param bool: Minify flag + @param bool: Skip end of block newlines + @return: string + """ + eb = '\n' + if xminify: + eb = '' + minify = True + self.items = {} + if minify: + self.items.update({ + 'nl': '', + 'tab': '', + 'ws': '', + 'endblock': eb + }) + else: + self.items.update({ + 'nl': '\n', + 'tab': '\t', + 'ws': ' ', + 'endblock': eb + }) + self.out = [] + if parse.result: + for u in parse.result: + self.out.extend(self.fprint(u)) + return ''.join(self.out) + + def fprint(self, node): + """ Format node. + @param Node object: Node object + """ + out = [] + if not node: return out + if 'proplist' in node.parsed: + node.parsed['proplist'] = ''.join([self.sprintf(p.format, p.parsed) + for p in node.parsed['proplist'] + if p]) + if node.parsed['proplist']: + out.append(self.sprintf(node.format, node.parsed)) + else: + out.append(self.sprintf(node.format, node.parsed)) + if 'inner' in node.parsed: + if node._blocktype: + out.append(self.fblockinner(node)) + else: + for iu in node.parsed['inner']: + out.extend(self.fprint(iu)) + return out + + def fblockinner(self, node): + """ Format inner block type + @param Node: node + @return: str + """ + sub = [] + for iu in node.parsed['inner']: + sub.extend(self.fprint(iu)) + sub = ''.join(sub) + if sub: + if self.items['tab']: + sub = '\t'+''.join(sub) + sub = sub.replace('\n', '\n\t').rstrip('\t') + node.parsed['proplist'] = sub + return self.sprintf(node.format, node.parsed) + return '' + + def sprintf(self, frm, items): + """ Perform format action + @param string: Format string + @param dict: format items + @return: string + """ + items.update(self.items) + return frm % items diff --git a/lesscpy/lessc/lexer.py b/lesscpy/lessc/lexer.py new file mode 100644 index 0000000..3dd876c --- /dev/null +++ b/lesscpy/lessc/lexer.py @@ -0,0 +1,202 @@ +""" + Lexer for LESSCSS. + + http://www.dabeaz.com/ply/ply.html + http://www.w3.org/TR/CSS21/grammar.html#scanner + http://lesscss.org/#docs + + Copyright (c) + See LICENSE for details. + +""" +import re +import ply.lex as lex + +from lesscpy.lib import dom +from lesscpy.lib import css + +class LessLexer: + states = ( + ('parn', 'inclusive'), + ) + literals = ',{}>=%!/*-+:;()~'; + tokens = [ + 'css_ident', + 'css_dom', + 'css_class', + 'css_id', + 'css_property', + 'css_vendor_property', + 'css_comment', + 'css_string', + 'css_color', + 'css_filter', + 'css_number', + 'css_number_unit', + 'css_important', + 'css_vendor_hack', + 'css_uri', + + 'less_variable', + 'less_comment', + 'less_string', + 'less_open_format', + 'less_combine', + + 't_ws', + 't_popen', + 't_pclose', + ] + reserved = { + '@media' : 'css_media', + '@page': 'css_page', + '@import' : 'css_import', + '@charset' : 'css_charset', + '@font-face' : 'css_font_face', + '@namespace' : 'css_namespace', + '@keyframes' : 'css_keyframes', + '@-moz-keyframes' : 'css_keyframes', + '@-webkit-keyframes' : 'css_keyframes', + + '@arguments': 'less_arguments', + } + tokens = tokens + list(set(reserved.values())) + + def __init__(self): + self.build(reflags=re.UNICODE|re.IGNORECASE) + + def t_css_filter(self, t): + (r'\[[^\]]*\]' + '|(not|lang|nth-[a-z\-]+)\(.+\)' + '|and[ \t]\(.+\)') + return t + + def t_css_ident(self, t): + (r'([\-\.\#]?' + '|@[@\-]?)' + '([_a-z]' + '|[\200-\377]' + '|\\\[0-9a-f]{1,6}([ \t\f])?' + '|\\\[^\s\r\n0-9a-f])' + '([_a-z0-9\-]|[\200-\377]' + '|\\\[0-9a-f]{1,6}([ \t\f])?' + '|\\\[^\s\r\n0-9a-f])*[ \t]?') + v = t.value.strip() + c = v[0] + if c == '.': + t.type = 'css_class' + elif c == '#': + t.type = 'css_id' + try: + int(v[1:], 16) + t.type = 'css_color' + except ValueError: + pass + elif v in css.propertys: + t.type = 'css_property' + t.value = t.value.strip() + elif v.lower() in dom.html: + t.type = 'css_dom' + t.value = t.value + elif c == '@': + if v in LessLexer.reserved: + t.type = LessLexer.reserved[v] + else: + t.type = 'less_variable' + elif c == '-': + t.type = 'css_vendor_property' + return t + + def t_css_color(self, t): + r'\#[0-9]([0-9a-f]{5}|[0-9a-f]{2})' + return t + + def t_parn_css_uri(self, t): + (r'data:[^\)]+' + '|(([a-z]+://)?' + '(' + '([\.\w:]+[\\/])+' + '|([a-z][\w\.\-]+(\.[a-z0-9]+))' + '(\#[a-z]+)?)' + ')+') + return t + + def t_parn_css_ident(self, t): + (r'(([_a-z]' + '|[\200-\377]' + '|\\\[0-9a-f]{1,6}([ \t\f])?' + '|\\\[^\r\n\s0-9a-f])' + '([_a-z0-9\-]|[\200-\377]' + '|\\\[0-9a-f]{1,6}([ \t\f])?' + '|\\\[^\r\n\s0-9a-f])*)') + return t + + def t_css_number(self, t): + r'-?(\d*\.\d+|\d+)(s|%|in|ex|[ecm]m|p[txc]|deg|g?rad|ms?|k?hz)?' + return t + + def t_newline(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + + def t_css_comment(self, t): + r'(/\*(.|\n)*?\*/)' + t.lexer.lineno += t.value.count('\n') + pass + + def t_less_comment(self, t): + r'//.*' + pass + + def t_css_important(self, t): + r'!\s*important' + t.value = '!important' + return t + + def t_t_ws(self, t): + r'[ \t]+' + pass + + def t_t_popen(self, t): + r'\(' + t.lexer.push_state('parn') + return t + + def t_less_open_format(self, t): + r'%\(' + t.lexer.push_state('parn') + return t + + def t_t_pclose(self, t): + r'\)' + t.lexer.pop_state() + return t + + def t_less_combine(self, t): + r'&[ \t]?' + return t + + t_less_string = (r'"([^"@]*@\{[^"\}]+\}[^"]*)+"' + '|\'([^\'@]*@\{[^\'\}]+\}[^\']*)+\'') + t_css_string = r'"[^"]*"|\'[^\']*\'' + + # Error handling rule + def t_error(self, t): + raise SyntaxError("Illegal character '%s' line %d" % (t.value[0], t.lexer.lineno)) + t.lexer.skip(1) + + # Build the lexer + def build(self, **kwargs): + self.lexer = lex.lex(module=self, **kwargs) + + def file(self, filename): + with open(filename) as f: + self.lexer.input(f.read()) + return self.lexer + + def input(self, filename): + with open(filename) as f: + self.lexer.input(f.read()) + + def token(self): + return self.lexer.token() diff --git a/lesscpy/lessc/parser.py b/lesscpy/lessc/parser.py new file mode 100644 index 0000000..29cc065 --- /dev/null +++ b/lesscpy/lessc/parser.py @@ -0,0 +1,691 @@ +""" + LESSCSS Parser. + + http://www.dabeaz.com/ply/ply.html + http://www.w3.org/TR/CSS21/grammar.html#scanner + http://lesscss.org/#docs + + Copyright (c) + See LICENSE for details. + +""" +import os +import ply.yacc +from . import lexer +from . import utility +from .color import LessColor +from lesscpy.plib import * + +class LessParser(object): + precedence = ( + ('left', '+', '-'), + ('left', '*', '/'), + ) + def __init__(self, + lex_optimize=True, + yacc_optimize=True, + yacctab='yacctab', + yacc_debug=False, + scope=None, + outputdir='/tmp', + importlvl=0): + """ Parser object + @param bool: Optimized lexer + @param bool: Optimized parser + @param string: Yacc tables file + @param bool: Debug mode + @param dict: Included scope + """ + self.importlvl = importlvl + self.lex = lexer.LessLexer() + if not yacctab: + yacctab = 'yacctab' + + self.ignored = ('t_ws', 'css_comment', 'less_comment', + 'css_vendor_hack', 'css_keyframes') + + self.tokens = [t for t in self.lex.tokens + if t not in self.ignored] + self.parser = ply.yacc.yacc( + module=self, + start='unit', + debug=yacc_debug, + optimize=yacc_optimize, + tabmodule=yacctab, + outputdir=outputdir + ) + self.scope = scope if scope else [] + self.stash = {} + self.result = None + self.target = None + + def parse(self, filename='', debuglevel=0): + """ Parse file. + @param string: Filename + @param int: Debuglevel + """ + self._create_scope() + self.target = filename + self.result = self.parser.parse(filename, lexer=self.lex, debug=debuglevel) + + def scopemap(self): + """ Output scopemap. + """ + if self.result: + utility.print_recursive(self.result) + + def p_unit(self, p): + """ unit : statement_list + """ + p[0] = p[1] + + def p_statement_list_aux(self, p): + """ statement_list : statement_list statement + """ + p[1].extend([p[2]]) + p[0] = p[1] + + def p_statement_list(self, p): + """ statement_list : statement + """ + p[0] = [p[1]] + + def p_statement(self, p): + """ statement : block_decl + | variable_decl + | mixin_decl + """ + p[0] = p[1] + + def p_statement_aux(self, p): + """ statement : css_charset css_string ';' + | css_namespace css_string ';' + """ + p[0] = Statement(p) + p[0].parse(None) + + def p_statement_namespace(self, p): + """ statement : css_namespace css_ident css_string ';' + """ + p[0] = Statement(p) + p[0].parse(None) + + def p_statement_import(self, p): + """ statement : css_import css_string ';' + """ + if self.importlvl > 8: + raise ImportError('Recrusive import level too deep > 8 (circular import ?)') + ipath = utility.destring(p[2]) + fn, fe = os.path.splitext(ipath) + if not fe or fe.lower() == '.less': + try: + cpath = os.path.dirname(os.path.abspath(self.target)) + if not fe: ipath += '.less' + filename = "%s%s%s" % (cpath, os.sep, ipath) + if os.path.exists(filename): + recurse = LessParser(importlvl=self.importlvl+1) + recurse.parse(filename=filename, debuglevel=0) + self.update_scope(recurse.scope) + else: + err = "Cannot import '%s', file not found" % filename + self.handle_error(err, p, 'W') + p[0] = None + except ImportError as e: + self.handle_error(e, p) + else: + p[0] = Statement(p) + p[0].parse(None) +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + def p_mixin_decl(self, p): + """ mixin_decl : block_open_mixin declaration_list brace_close + """ + try: + mixin = Mixin(p) + mixin.parse(self.scope, self.stash) + self.scope[-1]['__mixins__'][mixin.name.strip()] = mixin + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + + def p_block_decl(self, p): + """ block_decl : block_open declaration_list brace_close + """ + try: + block = Block(p) + block.parse(self.scope) + self.scope[-1]['__blocks__'].append(block) + p[0] = block + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + + def p_block_empty(self, p): + """ block_decl : block_open brace_close + """ + p[0] = None + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def p_block_open_mixin(self, p): + """ block_open_mixin : css_class t_popen block_mixin_args t_pclose brace_open + """ + self.scope[-1]['current'] = '__mixin__' + p[0] = list(p)[1:5] + + def p_block_open_mixin_aux(self, p): + """ block_open_mixin : css_class t_popen less_arguments t_pclose brace_open + """ + self.scope[-1]['current'] = '__mixin__' + p[0] = list(p)[1:5] + + def p_block_open_mixin_empty(self, p): + """ block_open_mixin : css_class t_popen t_pclose brace_open + """ + self.scope[-1]['current'] = '__mixin__' + p[0] = [p[1]] + + def p_block_mixin_args_aux(self, p): + """ block_mixin_args : block_mixin_args ',' block_mixin_arg + """ + p[1].extend([p[2], p[3]]) + p[0] = p[1] + + def p_block_mixin_args(self, p): + """ block_mixin_args : block_mixin_arg + """ + p[0] = [p[1]] + + def p_block_mixin_arg_def(self, p): + """ block_mixin_arg : less_variable ':' block_mixin_factor + """ + p[0] = list(p)[1:4] + + def p_block_mixin_arg(self, p): + """ block_mixin_arg : block_mixin_factor + | less_variable + """ + p[0] = p[1] + + def p_block_mixin_factor(self, p): + """ block_mixin_factor : css_number + | css_color + | css_ident + | css_string + """ + p[0] = p[1] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def p_block_open(self, p): + """ block_open : identifier_list brace_open + """ + name = ["%s " % t + if t in '>+' + else t + for t in utility.flatten(p[1])] + self.scope[-1]['current'] = ''.join(name).strip() + p[0] = p[1] + + def p_identifier_list_mixin(self, p): + """ mixin : identifier_list ';' + """ + p[0] = p[1] + + def p_identifier_list(self, p): + """ identifier_list : identifier_group + | identifier_page + | css_font_face + """ + if type(p[1]) is list: + p[0] = p[1] + else: + p[0] = [p[1]] + + def p_identifier_page_aux(self, p): + """ identifier_page : identifier_page dom_filter + """ + p[1].extend(p[2]) + p[0] = p[1] + + def p_identifier_page(self, p): + """ identifier_page : css_page + """ + p[0] = [p[1]] + + def p_identifier_group_op(self, p): + """ identifier_group : identifier_group ',' identifier + | identifier_group '+' identifier + """ + p[1].extend([p[2], p[3]]) + p[0] = p[1] + + def p_identifier_group_aux(self, p): + """ identifier_group : identifier_group identifier + """ + p[1].extend([p[2]]) + p[0] = p[1] + + def p_identifier_group(self, p): + """ identifier_group : identifier + """ + p[0] = [p[1]] + + def p_identifier_group_media(self, p): + """ identifier_group : css_media + """ + p[0] = [p[1]] + + def p_identifier(self, p): + """ identifier : css_dom + | css_id + | css_class + | dom_filter + | css_color + | less_combine + | '*' + | '>' + """ + p[0] = p[1] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def p_declaration_list_aux(self, p): + """ declaration_list : declaration_list declaration + """ + p[1].extend([p[2]]) + p[0] = p[1] + + def p_declaration_list(self, p): + """ declaration_list : declaration + """ + p[0] = [p[1]] + + def p_declaration(self, p): + """ declaration : property_decl + """ + p[0] = p[1] + + def p_declaration_block(self, p): + """ declaration : block_decl + | variable_decl + """ + p[0] = p[1] + + def p_variable_decl(self, p): + """ variable_decl : less_variable ':' style_list ';' + """ + current = self.scope[-1] + try: + v = Variable(p) + v.parse(self.scope) + if 'current' in current and current['current'] == '__mixin__': + self.stash[v.name()] = v + else: + current[v.name()] = v + except SyntaxError as e: + print(e) + p[0] = None + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + def p_property_mixin_call(self, p): + """ property_decl : identifier_list t_popen argument_list t_pclose ';' + """ + n = p[1][0] + if n in self.scope[0]['__mixins__']: + if self.scope[-1]['current'] != '__mixin__': + try: + p[0] = self.scope[0]['__mixins__'][n].call(p[3], self.scope) + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + else: + p[0] = self.scope[0]['__mixins__'][n] + else: + self.handle_error('Mixin not found in scope: ´%s´' % n, p) + p[0] = None + + + def p_property_mixin_call_empty(self, p): + """ property_decl : identifier_list t_popen t_pclose ';' + """ + n = p[1][0] + if n in self.scope[0]['__mixins__']: + try: + p[0] = self.scope[0]['__mixins__'][n].call(None) + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + else: + p[0] = None + + def p_property_mixin(self, p): + """ property_decl : mixin + """ + m = ''.join([u.strip() for u in p[1]]) + l = utility.block_search(m, self.scope) + if l: + p[0] = [b.parsed['proplist'] for b in l] + elif m in self.scope[0]['__mixins__']: + try: + p[0] = self.scope[0]['__mixins__'][m].call(None) + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + else: + p[0] = [] + + + def p_property_decl(self, p): + """ property_decl : property ':' style_list ';' + | property ':' style_list + """ + p[0] = Property(p) + if self.scope[-1]['current'] != '__mixin__': + try: + p[0].parse(self.scope) + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + + def p_prop_decl_bad(self, p): + """ property_decl : property ':' ';' + """ + p[0] = None + + def p_property(self, p): + """ property : css_property + | css_vendor_property + | css_ident + """ + p[0] = p[1] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + def p_style_list(self, p): + """ style_list : style_group + """ + p[0] = p[1] + + def p_less_style_list(self, p): + """ style_list : less_arguments + """ + p[0] = p[1] + + def p_style_group_sep(self, p): + """ style_group : style_group ',' style + """ + p[1].extend([p[2], p[3]]) + p[0] = p[1] + + def p_style_group_aux(self, p): + """ style_group : style_group style + """ + p[1].extend([p[2]]) + p[0] = p[1] + + def p_style_group(self, p): + """ style_group : style + """ + p[0] = [p[1]] + + def p_style(self, p): + """ style : expression + | css_important + | css_string + | istring + | css_vendor_property + | css_property + | css_ident + """ + p[0] = p[1] + + def p_style_escape(self, p): + """ style : '~' istring + | '~' css_string + """ + try: + p[0] = Call(p)#.parse(self.scope) + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def p_dom_filter(self, p): + """ dom_filter : css_dom filter_group + | css_id filter_group + | css_class filter_group + | less_combine filter_group + """ + p[0] = [p[1], p[2]] + + def p_filter_group_aux(self, p): + """ filter_group : filter filter + """ + p[1].extend([p[2]]) + p[0] = p[1] + + def p_filter_group(self, p): + """ filter_group : filter + """ + p[0] = [p[1]] + + def p_filter(self, p): + """ filter : css_filter + | ':' css_ident + | ':' css_filter + | ':' ':' css_ident + """ + p[0] = list(p)[1:] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + def p_expression_aux(self, p): + '''expression : expression '+' expression + | expression '-' expression + | expression '*' expression + | expression '/' expression + ''' + try: + p[0] = Expression(p) + except SyntaxError as e: + print(e) + p[0] = None + + def p_expression_p_neg(self, p): + """ expression : '-' t_popen expression t_pclose + """ + p[0] = [p[1], p[3]] + + def p_expression_p(self, p): + """ expression : t_popen expression t_pclose + """ + p[0] = p[2] + + def p_expression(self, p): + """ expression : factor + """ + p[0] = p[1] + + def p_factor(self, p): + """factor : color + | number + | variable + | css_dom + | fcall + """ + p[0] = p[1] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def p_fcall(self, p): + """ fcall : css_ident t_popen argument_list t_pclose + | css_property t_popen argument_list t_pclose + | css_vendor_property t_popen argument_list t_pclose + """ + p[0] = Call(p) + if self.scope[-1]['current'] != '__mixin__': + try: + p[0] = p[0].parse(self.scope) + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + + def p_fcall_format(self, p): + """ fcall : less_open_format argument_list t_pclose + """ + try: + p[0] = Call(p).parse(self.scope) + except SyntaxError as e: + self.handle_error(e, p) + p[0] = None + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def p_argument_list_aux_1(self, p): + """ argument_list : argument_list ',' argument + """ + p[1].extend([p[2], p[3]]) + p[0] = p[1] + + def p_argument_list_aux(self, p): + """ argument_list : argument_list argument + """ + p[1].extend([p[2]]) + p[0] = p[1] + + def p_argument_list(self, p): + """ argument_list : argument + """ + p[0] = [p[1]] + + def p_argument(self, p): + """ argument : expression + | css_string + | istring + | css_ident + | css_id + | css_uri + | '=' + """ + p[0] = p[1] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + def p_interpolated_str(self, p): + """ istring : less_string + """ + try: + p[0] = String(p)#.parse(self.scope) + except SyntaxError as e: + self.handle_error(e, p, 'W') + p[0] = p[1] + + def p_variable_neg(self, p): + """ variable : '-' variable + """ + p[0] = '-' + p[2] + + def p_variable_strange(self, p): + """ variable : t_popen variable t_pclose + """ + p[0] = p[2] + + def p_variable(self, p): + """ variable : less_variable + """ + p[0] = p[1] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def p_color(self, p): + """ color : css_color + """ + p[0] = LessColor().format(p[1]) + + def p_number(self, p): + """ number : css_number + | css_number_unit + """ + p[0] = p[1] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def p_scope_open(self, p): + """ brace_open : '{' + """ + self._create_scope() + p[0] = p[1] + + def p_scope_close(self, p): + """ brace_close : '}' + """ + self.scope.pop() + p[0] = p[1] + +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + + def _create_scope(self): + """ Create a scope. + """ + self.scope.append({'__blocks__': [], '__mixins__': {}}) + + def update_scope(self, scope): + """ + """ + blocks = [b for b in self.scope[0]['__blocks__']] + blocks.extend([b for b in scope[0]['__blocks__']]) + self.scope[0].update(scope[0]) + self.scope[0]['__blocks__'] = blocks + + def p_error(self, t): + """ Internal error handler + @param Lex token: Error token + """ + if t: print("E: line: %d, Syntax Error, token: `%s`, `%s`" + % (t.lineno, t.type, t.value)) + while True: + t = self.lex.token() + if not t or t.value == '}': + if len(self.scope) > 1: + self.scope.pop() + break + self.parser.restart() + return t + + def handle_error(self, e, p, t='E'): + """ Custom error handler + @param Exception: Exception + @param Parser token: Parser token + @param string: Error level + """ + l = [n for n in [p.lineno(i) for i in range(len(p))] if n] + l = l[0] if l else -1 + print("%s: line: %d: " % (t, l), end='') + print(e) + diff --git a/lesscpy/lessc/utility.py b/lesscpy/lessc/utility.py new file mode 100644 index 0000000..8974dcd --- /dev/null +++ b/lesscpy/lessc/utility.py @@ -0,0 +1,154 @@ +""" + Utility functions + + Copyright (c) + See LICENSE for details. + +""" +import collections + +def flatten(l): + """ + Flatten list. + @param l: list + @return: generator + """ + for el in l: + if isinstance(el, collections.Iterable) and not isinstance(el, str): + for sub in flatten(el): + yield sub + else: + yield el + +def block_search(name, scope): + """ Search scope blocks for name + @param str: name + @param list: scope + @return: list + """ + def search(m, l): + """ + """ + prop = [] + for b in l: + n = b._cident + if m == n: + prop.append(b) + elif m in b._parts: + prop.append(b) + elif m.startswith(n): + r = search(m, b.parsed['inner']) + if r: prop.extend(r) + return prop + l = len(scope) + i = 1 + prop = [] + while (l-i) >= 0: + if scope[-i]['__blocks__']: + b = search(name, scope[-i]['__blocks__']) + if b: prop.extend(b) + i += 1 + # HACK + if '>' in name: + name = ''.join([c for c in name if c != '>']) + i = 0 + while (l-i) >= 0: + if scope[-i]['__blocks__']: + b = search(name, scope[-i]['__blocks__']) + if b: prop.extend(b) + i += 1 + return prop + +def destring(v): + """ Strip quotes + @param string: value + @return: string + """ + return v.strip('"\'') + +def analyze_number(var, err=''): + """ Analyse number for type and split from unit + @param str: value + @return: tuple (number, unit) + """ + u = None + if type(var) is not str: + return (var, u) + if is_color(var): + return (var, 'color') + var = var.strip() + n = var + if not '.' in var: + try: + n = int(var) + return (n, u) + except (ValueError, TypeError): + pass + try: + n = float(var) + except (ValueError, TypeError): + try: + n = ''.join([c for c in var if c in '0123456789.-']) + n = float(n) if '.' in n else int(n) + u = ''.join([c for c in var if c not in '0123456789.-']) + except ValueError: + raise SyntaxError('%s ´%s´' % (err, var)) + return (n, u) + +def with_unit(n, u): + """ Return number with unit + @param int/float: value + @param str: unit + @return: mixed + """ + if n == 0: return 0 + if u: + return "%s%s" % (str(n), u) + return n + +def is_color(v): + """ Is CSS color + @param mixed: value + @return: bool + """ + if not v or type(v) is not str: + return False + l = len(v) + if l == 4 or l == 7: + try: + int(v[1:], 16) + return True + except Exception: + return False + +def is_variable(v): + """ Check if string is LESS variable + @param string: check + @return: bool + """ + if type(v) is str: + return (v.startswith('@') or v.startswith('-@')) + return False + +def print_recursive(ll, lvl=0): + """ Scopemap printer + @param list: parser result + @param int: depth + """ + if not ll: return + pad = ''.join(['. '] * lvl) + if type(ll) is list: + ll = flatten(ll) + for l in ll: + t = type(l) + if t == str: + print(pad + l) + else: + print_recursive(l, lvl+1) + elif hasattr(ll, '_p'): + print(pad + str(type(ll))) + print_recursive(ll._p, lvl+1) + else: + print(pad + ll) + + \ No newline at end of file diff --git a/lesscpy/lib/__init__.py b/lesscpy/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lesscpy/lib/css.py b/lesscpy/lib/css.py new file mode 100644 index 0000000..81711b4 --- /dev/null +++ b/lesscpy/lib/css.py @@ -0,0 +1,293 @@ +""" + CSS syntax names. + + Copyright (c) + See LICENSE for details. + +""" +css2 = [ + 'azimuth', + 'background-attachment', + 'background-color', + 'background-image', + 'background-position', + 'background-repeat', + 'background', + 'border-collapse', + 'border-color', + 'border-spacing', + 'border-style', + 'border-top', + 'border-right', + 'border-bottom', + 'border-left', + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color', + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style', + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width', + 'border-width', + 'border', + 'bottom', + 'caption-side', + 'clear', + 'clip', + 'color', + 'content', + 'counter-increment', + 'counter-reset', + 'cue-after', + 'cue-before', + 'cue', + 'cursor', + 'direction', + 'display', + 'elevation', + 'empty-cells', + 'float', + 'font-family', + 'font-size', + 'font-style', + 'font-variant', + 'font-weight', + 'font', + 'height', + 'left', + 'letter-spacing', + 'line-height', + 'list-style-image', + 'list-style-position', + 'list-style-type', + 'list-style', + 'margin-right', + 'margin-left', + 'margin-top', + 'margin-bottom', + 'margin', + 'max-height', + 'max-width', + 'min-height', + 'min-width', + 'orphans', + 'outline-color', + 'outline-style', + 'outline-width', + 'outline', + 'overflow', + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left', + 'padding', + 'page-break-after', + 'page-break-before', + 'page-break-inside', + 'pause-after', + 'pause-before', + 'pause', + 'pitch-range', + 'pitch', + 'play-during', + 'position', + 'quotes', + 'richness', + 'right', + 'speak-header', + 'speak-numeral', + 'speak-punctuation', + 'speak', + 'speech-rate', + 'stress', + 'table-layout', + 'text-align', + 'text-decoration', + 'text-indent', + 'text-transform', + 'top', + 'unicode-bidi', + 'vertical-align', + 'visibility', + 'voice-family', + 'volume', + 'white-space', + 'widows', + 'width', + 'word-spacing', + 'z-index', +] +css3 = [ + 'alignment-adjust', + 'alignment-baseline', + 'animation', + 'animation-delay', + 'animation-direction', + 'animation-duration', + 'animation-iteration-count', + 'animation-name', + 'animation-play-state', + 'animation-timing-function', + 'appearance', + 'backface-visibility', + 'background-clip', + 'background-origin', + 'background-size', + 'baseline-shift', + 'bookmark-label', + 'bookmark-level', + 'bookmark-target', + 'border-bottom-left-radius', + 'border-bottom-right-radius', + 'border-image', + 'border-image-outset', + 'border-image-repeat', + 'border-image-slice', + 'border-image-source', + 'border-image-width', + 'border-radius', + 'border-top-left-radius', + 'border-top-right-radius', + 'box-align', + 'box-decoration-break', + 'box-direction', + 'box-flex', + 'box-flex-group', + 'box-lines', + 'box-ordinal-group', + 'box-orient', + 'box-pack', + 'box-shadow', + 'box-sizing', + 'color-profile', + 'column-count', + 'column-fill', + 'column-gap', + 'column-rule', + 'column-rule-color', + 'column-rule-style', + 'column-rule-width', + 'column-span', + 'column-width', + 'columns', + 'crop', + 'dominant-baseline', + 'drop-initial-after-adjust', + 'drop-initial-after-align', + 'drop-initial-before-adjust', + 'drop-initial-before-align', + 'drop-initial-size', + 'drop-initial-value', + 'fit', + 'fit-position', + 'float-offset', + 'font-size-adjust', + 'font-stretch', + 'grid-columns', + 'grid-rows', + 'hanging-punctuation', + 'hyphenate-after', + 'hyphenate-before', + 'hyphenate-character', + 'hyphenate-lines', + 'hyphenate-resource', + 'hyphens', + 'icon', + 'image-orientation', + 'image-resolution', + 'inline-box-align', + 'line-stacking', + 'line-stacking-ruby', + 'line-stacking-shift', + 'line-stacking-strategy', + 'mark', + 'mark-after', + 'mark-before', + 'marks', + 'marquee-direction', + 'marquee-play-count', + 'marquee-speed', + 'marquee-style', + 'move-to', + 'nav-down', + 'nav-index', + 'nav-left', + 'nav-right', + 'nav-up', + 'opacity', + 'outline-offset', + 'overflow-style', + 'overflow-x', + 'overflow-y', + 'page', + 'page-policy', + 'perspective', + 'perspective-origin', + 'phonemes', + 'punctuation-trim', + 'rendering-intent', + 'resize', + 'rest', + 'rest-after', + 'rest-before', + 'rotation', + 'rotation-point', + 'ruby-align', + 'ruby-overhang', + 'ruby-position', + 'ruby-span', + 'size', + 'string-set', + 'target', + 'target-name', + 'target-new', + 'target-position', + 'text-align-last', + 'text-height', + 'text-justify', + 'text-outline', + 'text-overflow', + 'text-shadow', + 'text-wrap', + 'transform', + 'transform-origin', + 'transform-style', + 'transition', + 'transition-delay', + 'transition-duration', + 'transition-property', + 'transition-timing-function', + 'voice-balance', + 'voice-duration', + 'voice-pitch', + 'voice-pitch-range', + 'voice-rate', + 'voice-stress', + 'voice-volume', + 'word-break', + 'word-wrap' +] +vendor_prefix = [ + '-ms-', + '-moz-', + '-o-', + '-atsc-', + '-wap-', + '-webkit-', + '-khtml-' + '-xv-', + 'mso-', +] +vendor_ugly = [ + 'filter', + 'accelerator', + 'behavior', + 'filter', + 'zoom', +] +propertys = css2 + css3 + vendor_ugly diff --git a/lesscpy/lib/dom.py b/lesscpy/lib/dom.py new file mode 100644 index 0000000..f110e60 --- /dev/null +++ b/lesscpy/lib/dom.py @@ -0,0 +1,142 @@ +""" + HTML DOM names + + Copyright (c) + See LICENSE for details. + +""" +html4 = [ + 'a', + 'abbr', + 'acronym', + 'address', + 'applet', + 'area', + 'b', + 'base', + 'basefont', + 'bdo', + 'big', + 'blockquote', + 'body', + 'br', + 'button', + 'caption', + 'center', + 'cite', + 'code', + 'col', + 'colgroup', + 'dd', + 'del', + 'dfn', + 'dir', + 'div', + 'dl', + 'dt', + 'em', + 'fieldset', + 'font', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'hr', + 'html', + 'i', + 'iframe', + 'img', + 'input', + 'ins', + 'kbd', + 'label', + 'legend', + 'li', +# 'link', + 'map', + 'mark', + 'menu', + 'meta', + 'noframes', + 'noscript', + 'object', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'pre', + 'q', + 's', + 'samp', + 'script', + 'select', + 'small', + 'span', + 'strike', + 'strong', + 'style', + 'sub', + 'sup', + 'table', + 'tbody', + 'td', + 'textarea', + 'tfoot', + 'th', + 'thead', + 'title', + 'tr', + 'tt', + 'u', + 'ul', + 'var', + + 'print', + 'screen', + 'all', + 'projection', + +# 'focus', +# 'hover', +] +html5 = [ + 'article', + 'aside', + 'audio', + 'bdi', + 'canvas', + 'command', + 'datalist', + 'details', + 'embed', + 'figcaption', + 'figure', + 'footer', + 'header', + 'hgroup', + 'keygen', + 'mark', + 'meter', + 'nav', + 'output', + 'progress', + 'rp', + 'rt', + 'ruby', + 'section', + 'source', + 'summary', + 'time', + 'track', + 'video', + 'wbr', +] +html = html4 +html.extend(html5) \ No newline at end of file diff --git a/lesscpy/plib/__init__.py b/lesscpy/plib/__init__.py new file mode 100644 index 0000000..018f85d --- /dev/null +++ b/lesscpy/plib/__init__.py @@ -0,0 +1,18 @@ +__all__ = [ + 'Block', + 'Call', + 'Expression', + 'Mixin', + 'Property', + 'Statement', + 'String', + 'Variable' +] +from .block import Block +from .call import Call +from .expression import Expression +from .mixin import Mixin +from .property import Property +from .statement import Statement +from .string import String +from .variable import Variable \ No newline at end of file diff --git a/lesscpy/plib/block.py b/lesscpy/plib/block.py new file mode 100644 index 0000000..4f9264e --- /dev/null +++ b/lesscpy/plib/block.py @@ -0,0 +1,81 @@ +""" + Block node. + + Copyright (c) + See LICENSE for details. + +""" +from collections import OrderedDict + +import lesscpy.lessc.utility as utility +from .process import Process + +class Block(Process): + format = "%(identifier)s%(ws)s{%(nl)s%(proplist)s}%(endblock)s" + + def parse(self, scope): + """ Parse Node + @param list: current scope + """ + self._blocktype = None + self.scope = scope + self._proplist() + self.parsed['inner'] = [p for p in self._p[2] + if type(p) is type(self)] + self._pname() + if self._name.startswith('@media'): + self._blocktype = 'inner' + self.parsed['identifier'] = self._ident.strip() + return self + + def merge(self, block): + """ + """ + assert(type(block) is Block) + self.parsed['proplist'].extend(block.parsed['proplist']) + + def _pname(self): + """ Parse block name and identifier + """ + name = ["%s " % t + if t in '>+' + else t + for t in utility.flatten(self._p[1])] + self._name = ''.join(name) + self._ident = self._fullname(name) + self._cident = self._ident.replace(' ', '') + + def _fullname(self, name): + """ + """ + parent = list(utility.flatten([s['current'] + for s in self.scope + if 'current' in s])) + if parent: + parent.reverse() + if parent[-1].startswith('@media'): + parent.pop() + names = [p.strip() + for p in self._name.split(',')] + self._parts = names + for p in parent: + parts = p.split(',') + names = [n.replace('&', p) + if '&' in n + else + "%s %s" % (p.strip(), n) + for n in names + for p in parts] + return ', '.join(names) if len(names) < 6 else ',\n'.join(names) + + def _proplist(self): + """ Reduce property list to remove redundant styles. + Multiple porperties of the same type overwrite, + so we can safely take only the last. + """ + proplist = [p for p in utility.flatten(self._p[2]) + if p and type(p) != type(self)] + store = OrderedDict() + for p in proplist: + store[p.parsed['property']] = p + self.parsed['proplist'] = store.values() diff --git a/lesscpy/plib/call.py b/lesscpy/plib/call.py new file mode 100644 index 0000000..084904c --- /dev/null +++ b/lesscpy/plib/call.py @@ -0,0 +1,101 @@ +""" + Call Node. + + Copyright (c) + See LICENSE for details. + +""" +import re +from urllib.parse import quote as urlquote +from .process import Process +from lesscpy.lessc.color import LessColor +from .expression import Expression +import lesscpy.lessc.utility as utility + +class Call(Process): + def parse(self, scope): + """ Parse Node + @param list: current scope + """ + self.scope = scope + call = list(utility.flatten(self._p[1:])) + name = call[0] + if name == '%(': + name = 'sformat' + elif name == '~': + name = 'e' + color = LessColor() + call = self.process_tokens(call[1:]) + args = [t for t in call + if type(t) is not str or t not in '(),'] + if hasattr(self, name): + try: + return getattr(self, name)(*args) + except ValueError: + pass + if hasattr(color, name): + try: + return getattr(color, name)(*args) + except ValueError: + pass + call = ' '.join([str(t) for t in call[1:-1]]).replace(' = ', '=') + return ["%s(%s)" % (name, call)] + + def e(self, string): + """ Less Escape. + @param string: value + @return string + """ + return utility.destring(string.strip('~')) + + def sformat(self, *args): + """ String format + @param list: values + @return string + """ + format = args[0] + items = [] + m = re.findall('(%[asdA])', format) + i = 1 + for n in m: + v = { + '%d' : int, + '%A' : urlquote, + '%s' : utility.destring, + }.get(n, str)(args[i]) + items.append(v) + i += 1 + format = format.replace('%A', '%s') + return format % tuple(items) + + def increment(self, v): + """ Increment function + @param Mixed: value + @return: incremented value + """ + n, u = utility.analyze_number(v) + return utility.with_unit(n+1, u) + + def decrement(self, v): + """ Decrement function + @param Mixed: value + @return: incremented value + """ + n, u = utility.analyze_number(v) + return utility.with_unit(n-1, u) + + def add(self, *args): + """ Add integers + @param list: values + @return: int + """ + return sum([int(v) for v in args]) + + def round(self, v): + """ Round number + @param Mixed: value + @return: rounded value + """ + n, u = utility.analyze_number(v) + return utility.with_unit(round(float(n)), u) + \ No newline at end of file diff --git a/lesscpy/plib/expression.py b/lesscpy/plib/expression.py new file mode 100644 index 0000000..8dee8b8 --- /dev/null +++ b/lesscpy/plib/expression.py @@ -0,0 +1,100 @@ +""" + Expression Node. + + Copyright (c) + See LICENSE for details. + +""" +import lesscpy.lessc.utility as utility +from .process import Process +from lesscpy.lessc import color + +class Expression(Process): + def parse(self, scope): + """ Parse Node + @param list: current scope + """ + self.scope = scope + expr = [self._p[1], self._p[2], self._p[3]] + while True: + done = True + expr = [self.neg(t) for t in expr] + if any(t for t in expr if hasattr(t, 'parse')): + expr = [t.parse(scope) if hasattr(t, 'parse') + else t + for t in expr] + done = False + if any(t for t in expr if utility.is_variable(t)): + expr = self.replace_vars(expr) + done = False + expr = list(utility.flatten(expr)) + if done: break + p = self.process(expr) + return p + + def neg(self, t): + """ + """ + if t and type(t) is list and t[0] == '-': + v = t[1] + if len(t) > 1 and hasattr(t[1], 'parse'): + v = t[1].parse(self.scope) + if type(v) is str: + return '-' + v + return -v + return t + + def process(self, expression): + """ + """ + assert(len(expression) == 3) + A, O, B = expression + a, ua = utility.analyze_number(A, 'Illegal element in expression') + b, ub = utility.analyze_number(B, 'Illegal element in expression') + if(a is False or b is False): + return self.expression() + if ua == 'color' or ub == 'color': + return color.LessColor().process(expression) + out = self.operate(a, b, O) + if type(a) is int and type(b) is int: + out = int(out) + return self.units(out, ua, ub) + + def units(self, v, ua, ub): + """ + """ + if v == 0: return v; + if ua and ub: + if ua == ub: + return str(v) + ua + else: + raise SyntaxError("Error in expression %s != %s" % (ua, ub)) + elif ua: + return str(v) + ua + elif ub: + return str(v) + ub + return v + + def operate(self, a, b, o): + """ + """ +# print("´%s´ ´%s´ ´%s´" % (a, b, o)) + operation = { + '+': '__add__', + '-': '__sub__', + '*': '__mul__', + '/': '__truediv__' + }.get(o) + v = getattr(a, operation)(b) + if v is NotImplemented: + v = getattr(b, operation)(a) + return v + + def expression(self): + """ + """ + return [self._p[1], self._p[2], self._p[3]] + + + + \ No newline at end of file diff --git a/lesscpy/plib/mixin.py b/lesscpy/plib/mixin.py new file mode 100644 index 0000000..916bb9e --- /dev/null +++ b/lesscpy/plib/mixin.py @@ -0,0 +1,97 @@ +""" + Parametered Mixin Node. + + Copyright (c) + See LICENSE for details. + +""" +from collections import deque +import lesscpy.lessc.utility as utility +import copy +from .process import Process +from .node import Node + +class Mixin(Process): + """ Mixin Node. + """ + def parse(self, scope, stash=None): + """ Parse Node + @param list: current scope + """ + self.stash = stash + self.name = self._p[1][0] + if len(self._p[1]) > 1: + if type(self._p[1][2]) is list: + self.argv = [[u[0], u[2]] if type(u) is list + else [u, None] + for u in self._p[1][2] + if u and u != ','] + else: + self.argv = self._p[1][2] + self.argc = len(self.argv) + else: + self.argv = [] + self.argc = 0 + self.prop = self._p[2] + + def call(self, args, scope=[{}]): + """ Call mixin function. + @param list: Arguments passed to mixin + @return: Property list + """ + if self.argv == '@arguments': + return [self.replace_arguments(copy.copy(p), args).parse(self.scope) + for p in self.prop + if p] + self.scope = scope + self.scope[0].update(self.stash) + prop = [copy.copy(p) for p in self.prop if p] + prop = utility.flatten([p.call(args, self.scope) + if type(p) is Mixin else p + for p in prop]) + self._process_args(args) + return [p.parse(self.scope) for p in prop] + + def _process_args(self, args): + """ Process arguments to mixin call. + Handle the @arguments variable + @param list: arguments + """ + variables = [] + if args: + args = [a for a in self.process_tokens(args) + if a != ','] + else: + args = [] + args = deque(args) + for v in self.argv: + if args: + u = args.popleft() + variables.append((v[0], u)) + else: + variables.append(v) + for v in variables: + self._create_variable(v) + # Special @arguments + arguments = [v[1] for v in variables] + arguments.extend(args) + self._create_variable(('@arguments', [arguments])) + + def _create_variable(self, l): + """ Create new variable in scope, from list or index + @param tuple: name/value pair + """ + assert(len(l) == 2) + var = Node() + var._name, var._value = l + self.scope[0][var._name] = var + + def replace_arguments(self, p, args): + """ Replace the special @arguments variable + @param Property object: Property object + @param list: Replacement list + @return: Property object + """ + p._p[3] = args + return p + diff --git a/lesscpy/plib/node.py b/lesscpy/plib/node.py new file mode 100644 index 0000000..6c0a20a --- /dev/null +++ b/lesscpy/plib/node.py @@ -0,0 +1,18 @@ +""" + Parser node base class. + + Copyright (c) + See LICENSE for details. + +""" +class Node(object): + def name(self): + """ @return: node name""" + return self._name + + def value(self): + """ @return: node value""" + return self._value + + def parse(self, scope): + raise NotImplementedError() \ No newline at end of file diff --git a/lesscpy/plib/process.py b/lesscpy/plib/process.py new file mode 100644 index 0000000..e16b724 --- /dev/null +++ b/lesscpy/plib/process.py @@ -0,0 +1,84 @@ +""" + Base process Node + + Copyright (c) + See LICENSE for details. + +""" +import lesscpy.lessc.utility as utility +from .node import Node + +class Process(Node): + def __init__(self, p): + self._p = list(p) + self.lines = [j for j in [p.lineno(i) + for i in range(len(self._p))] + if j] + self.scope = None + self.parsed = {} + + def process_tokens(self, tokens): + """ + """ + while True: + done = True + if any(t for t in tokens if hasattr(t, 'parse')): + tokens = [t.parse(self.scope) if hasattr(t, 'parse') + else t + for t in tokens] + done = False + if any(t for t in tokens if utility.is_variable(t)): + tokens = self.replace_vars(tokens) + done = False + tokens = list(utility.flatten(tokens)) + if done: break + return tokens + + def replace_vars(self, tokens): + """ + Replace variables in tokenlist + """ + return [self.swap(t) + if utility.is_variable(t) + else t + for t in tokens] + + def swap(self, var): + """ + Swap single variable + """ + if not self.scope: + raise SyntaxError("Unknown variable ´%s´" % var) + pad = '' + pre = '' + if var.endswith(' '): + var = var.strip() + pad = ' ' + if var.startswith('-'): + var = ''.join(var[1:]) + pre = '-' + r = var.startswith('@@') + t = ''.join(var[1:]) if r else var + i = len(self.scope) + while i >=0: + i -= 1 + if t in self.scope[i]: + f = self.scope[i][t].value() + if r: + return self.swap("%s@%s%s" % (pre, f[0].strip('"\''), pad)) + return self.ftok(f, pre, pad) + raise SyntaxError("Unknown variable ´%s´" % var) + + def ftok(self, t, pre, pad): + """ + """ + try: + r = ''.join(t) + except TypeError: + r = t[0] if type(t) is list else str(t) + if pad and type(r) is str: + r += pad + if pre and type(r) is str: + r = pre + r + return r + diff --git a/lesscpy/plib/property.py b/lesscpy/plib/property.py new file mode 100644 index 0000000..24928b0 --- /dev/null +++ b/lesscpy/plib/property.py @@ -0,0 +1,40 @@ +""" + Property Node + + Copyright (c) + See LICENSE for details. + +""" +import lesscpy.lessc.utility as utility +from .process import Process + +class Property(Process): + format = "%(tab)s%(property)s:%(ws)s%(style)s;%(nl)s" + + def parse(self, scope): + """ Parse Node + @param list: current scope + """ + self.scope = scope + self.parsed = {} + tokens = list(utility.flatten([self._p[1], self._p[3]])) + self.parsed['property'] = tokens[0] + style = self.preprocess(tokens[1:]) + style = self.process_tokens(style) + style = ' '.join([str(t).strip() for t in style + if t is not None]).replace(' , ', ', ') + self.parsed['style'] = style + return self + + def preprocess(self, style): + """ Preprocess for annoying shorthand CSS declarations + @param list: style + @return: list + """ + if self.parsed['property'] == 'font': + style = [''.join(u.expression()) + if hasattr(u, 'expression') + else u + for u in style] + return style + diff --git a/lesscpy/plib/statement.py b/lesscpy/plib/statement.py new file mode 100644 index 0000000..2fb8a81 --- /dev/null +++ b/lesscpy/plib/statement.py @@ -0,0 +1,20 @@ +""" + Statement Node + + Copyright (c) + See LICENSE for details. + +""" +from .process import Process + +class Statement(Process): + format = "%(identifier)s%(ws)s%(value)s;%(nl)s" + + def parse(self, scope): + """ Parse Node + @param list: current scope + """ + self._ident = self._p[1].strip() + self.parsed['identifier'] = self._ident + self.parsed['value'] = self._p[2] + diff --git a/lesscpy/plib/string.py b/lesscpy/plib/string.py new file mode 100644 index 0000000..97ce61b --- /dev/null +++ b/lesscpy/plib/string.py @@ -0,0 +1,27 @@ +""" + String Node + + Copyright (c) + See LICENSE for details. + +""" +import re +from .process import Process + +class String(Process): + def parse(self, scope): + """ Parse Node + @param list: current scope + @return: string + """ + self.scope = scope + return re.sub(r'@\{([^\}]+)\}', lambda m: self.format(m.group(1)), self._p[1]) + + def format(self, var): + """ Format variable for replacement + @param string: var + @return: string + """ + var = '@' + var + var = self.swap(var) + return var.strip("\"'") diff --git a/lesscpy/plib/variable.py b/lesscpy/plib/variable.py new file mode 100644 index 0000000..5b27566 --- /dev/null +++ b/lesscpy/plib/variable.py @@ -0,0 +1,18 @@ +""" + Variable Node + + Copyright (c) + See LICENSE for details. + +""" +from .process import Process + +class Variable(Process): + def name(self): + return self._p[1] + + def value(self): + return self._p[3] + + def parse(self, scope): + return '' \ No newline at end of file diff --git a/lesscpy/scripts/__init__.py b/lesscpy/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lesscpy/scripts/compiler.py b/lesscpy/scripts/compiler.py new file mode 100644 index 0000000..4fcf727 --- /dev/null +++ b/lesscpy/scripts/compiler.py @@ -0,0 +1,127 @@ +""" + CSS/LESSCSS run script + + http://lesscss.org/#docs + + Copyright (c) + See LICENSE for details + +""" +import os +import sys +import glob +import argparse +sys.path.append(os.path.abspath(os.path.dirname(__file__))) +from lesscpy.lessc import parser +from lesscpy.lessc import lexer +from lesscpy.lessc import formatter +# +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +def run(): + aparse = argparse.ArgumentParser(description='LessCss Compiler', epilog='<< jtm@robot.is @_o >>') + aparse.add_argument('-I', '--include', action="store", type=str, + help="Included less-files (comma separated)") + aparse.add_argument('-x', '--minify', action="store_true", + default=False, help="Minify output") + aparse.add_argument('-X', '--xminify', action="store_true", + default=False, help="Minify output, no end of block newlines") + aparse.add_argument('-m', '--min-ending', action="store_true", + default=False, help="Add '.min' into output filename. eg, name.min.css") + aparse.add_argument('-D', '--dry-run', action="store_true", + default=False, help="Dry run, do not write files") + aparse.add_argument('-v', '--verbose', action="store_true", + default=False, help="Verbose mode") + aparse.add_argument('-o', '--out', action="store", help="Output directory") + group = aparse.add_argument_group('Debugging') + group.add_argument('-S', '--scopemap', action="store_true", + default=False, help="Scopemap") + group.add_argument('-V', '--debug', action="store_true", + default=False, help="Debug mode") + group.add_argument('-L', '--lex-only', action="store_true", + default=False, help="Run lexer on target") + group.add_argument('-N', '--no-css', action="store_true", + default=False, help="No css output") + aparse.add_argument('target') + args = aparse.parse_args() + # + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # + if args.lex_only: + lex = lexer.LessLexer() + ll = lex.file(args.target) + while True: + tok = ll.token() + if not tok: break + print(tok) + print('EOF') + sys.exit() + # + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + # + yacctab = 'yacctab' if args.debug else None + scope = None + if args.include: + for u in args.include.split(','): + if args.debug: print("compiling include: %s" % u) + p = parser.LessParser( + yacc_debug=False, + lex_optimize=True, + yacc_optimize=True, + yacctab=yacctab) + p.parse(filename=u, debuglevel=0) + if not scope: + scope = p.scope + else: + scope[0].update(p.scope[0]) + else: + scope = None + p = None + f = formatter.Formatter() + if not os.path.exists(args.target): + sys.exit("Target not found '%s' ..." % args.target) + if os.path.isdir(args.target): + if not args.out: + sys.exit("Compile directory option needs -o ...") + elif os.path.isdir(args.out) and not os.listdir(args.out) == []: + sys.exit("Output directory not empty...") + else: + if not os.path.isdir(args.out): + if args.verbose: + print("Creating '%s'" % args.out) + if not args.dry_run: + os.mkdir(args.out) + less = glob.glob(os.path.join(args.target, '*.less')) + for lf in less: + outf = os.path.splitext(os.path.basename(lf)) + min = '.min' if args.min_ending else '' + outf = "%s/%s%s.css" % (args.out, outf[0], min) + if args.verbose: print("%s -> %s" % (lf, outf)) + + p = parser.LessParser(yacc_debug=False, + lex_optimize=True, + yacc_optimize=True, + scope=scope, + yacctab=yacctab) + p.parse(filename=lf, debuglevel=0) + css = f.format(p, args.minify, args.xminify) + if not args.dry_run: + with open(outf, 'w') as outfile: + outfile.write(css) + if args.dry_run: + print('Dry run, nothing done.') + + else: + if args.verbose: print("compiling target: %s" % args.target) + p = parser.LessParser(yacc_debug=(args.debug), + lex_optimize=True, + yacc_optimize=(not args.debug), + scope=scope) + p.parse(filename=args.target, debuglevel=0) + if args.scopemap: + args.no_css = True + p.scopemap() + if not args.no_css and p: + out = f.format(p, args.minify, args.xminify) + print(out) + \ No newline at end of file diff --git a/lesscpy/test/__init__.py b/lesscpy/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lesscpy/test/__main__.py b/lesscpy/test/__main__.py new file mode 100644 index 0000000..feeeb28 --- /dev/null +++ b/lesscpy/test/__main__.py @@ -0,0 +1,33 @@ +""" + LessCss tests +""" +import unittest +import sys, os +import re + +here = os.path.dirname(__file__) +path = os.path.abspath(here) +while os.path.dirname(path) != path: + if os.path.exists(os.path.join(path, 'lesscpy', '__init__.py')): + sys.path.insert(0, path) + break + path = os.path.dirname(path) + +def find(): + svn = re.compile('\.svn') + test = re.compile('test.+\.py$') + skip = re.compile('testissues.*') + alltests = unittest.TestSuite() + for path, dirs, files in os.walk(here): + if svn.search(path): + continue + for f in files: + if skip.search(f): + continue + if test.search(f): + module = __import__(f.split('.')[0]) + alltests.addTest(unittest.findTestCases(module)) + return alltests + +if __name__ == '__main__': + unittest.main(defaultTest='find') \ No newline at end of file diff --git a/lesscpy/test/css/color-functions.css b/lesscpy/test/css/color-functions.css new file mode 100644 index 0000000..b47f013 --- /dev/null +++ b/lesscpy/test/css/color-functions.css @@ -0,0 +1,13 @@ +.color_functions { + lighten: #ffcccc; + darken: #330000; + saturate: #203c31; + desaturate: #29332f; + greyscale: #4e0e27; + spin-p: #bf6b40; + spin-n: #bf4055; + hue: 100.0; + saturation: 12; + lightness: 95; +} + diff --git a/lesscpy/test/css/color-functions.min.css b/lesscpy/test/css/color-functions.min.css new file mode 100644 index 0000000..4d3dc83 --- /dev/null +++ b/lesscpy/test/css/color-functions.min.css @@ -0,0 +1 @@ +.color_functions{lighten:#ffcccc;darken:#330000;saturate:#203c31;desaturate:#29332f;greyscale:#4e0e27;spin-p:#bf6b40;spin-n:#bf4055;hue:100.0;saturation:12;lightness:95;} diff --git a/lesscpy/test/css/colors.css b/lesscpy/test/css/colors.css new file mode 100644 index 0000000..bc3c3f1 --- /dev/null +++ b/lesscpy/test/css/colors.css @@ -0,0 +1,48 @@ +#yelow #short { + color: #ffeeaa; +} +#yelow #long { + color: #ffeeaa; +} +#yelow #rgba { + color: rgba(255, 238, 170, 0.1); +} +#yelow #argb { + color: argb(rgba(255, 238, 170, 0.1)); +} +#blue #short { + color: #0000ff; +} +#blue #long { + color: #0000ff; +} +#blue #rgba { + color: rgba(0, 0, 255, 0.1); +} +#blue #argb { + color: argb(rgba(0, 0, 255, 0.1)); +} +#alpha #hsla { + color: hsla(11, 20%, 20%, 0.6); +} +#overflow .a { + color: #000000; +} +#overflow .b { + color: #ffffff; +} +#overflow .c { + color: #ffffff; +} +#overflow .d { + color: #00ff00; +} +#grey { + color: #c8c8c8; +} +#808080 { + color: #808080; +} +#00ff00 { + color: #00ff00; +} diff --git a/lesscpy/test/css/colors.min.css b/lesscpy/test/css/colors.min.css new file mode 100644 index 0000000..78c1a34 --- /dev/null +++ b/lesscpy/test/css/colors.min.css @@ -0,0 +1,16 @@ +#yelow #short{color:#ffeeaa;} +#yelow #long{color:#ffeeaa;} +#yelow #rgba{color:rgba(255, 238, 170, 0.1);} +#yelow #argb{color:argb(rgba(255, 238, 170, 0.1));} +#blue #short{color:#0000ff;} +#blue #long{color:#0000ff;} +#blue #rgba{color:rgba(0, 0, 255, 0.1);} +#blue #argb{color:argb(rgba(0, 0, 255, 0.1));} +#alpha #hsla{color:hsla(11, 20%, 20%, 0.6);} +#overflow .a{color:#000000;} +#overflow .b{color:#ffffff;} +#overflow .c{color:#ffffff;} +#overflow .d{color:#00ff00;} +#grey{color:#c8c8c8;} +#808080{color:#808080;} +#00ff00{color:#00ff00;} diff --git a/lesscpy/test/css/comments.css b/lesscpy/test/css/comments.css new file mode 100644 index 0000000..ddd2e12 --- /dev/null +++ b/lesscpy/test/css/comments.css @@ -0,0 +1,17 @@ +#comments { + color: red; + background-color: orange; + font-size: 12px; + content: "content"; + border: 1px solid black; + padding: 0; + margin: 2em; +} +.selector, .lots, .comments { + color: grey, orange; + -webkit-border-radius: 2px; + -moz-border-radius: 8px; +} +#last { + color: blue; +} diff --git a/lesscpy/test/css/comments.min.css b/lesscpy/test/css/comments.min.css new file mode 100644 index 0000000..c77f08a --- /dev/null +++ b/lesscpy/test/css/comments.min.css @@ -0,0 +1,3 @@ +#comments{color:red;background-color:orange;font-size:12px;content:"content";border:1px solid black;padding:0;margin:2em;} +.selector, .lots, .comments{color:grey, orange;-webkit-border-radius:2px;-moz-border-radius:8px;} +#last{color:blue;} diff --git a/lesscpy/test/css/css-3.css b/lesscpy/test/css/css-3.css new file mode 100644 index 0000000..3ecec69 --- /dev/null +++ b/lesscpy/test/css/css-3.css @@ -0,0 +1,46 @@ +.comma-delimited { + background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg); + text-shadow: -1px -1px 1px red, 6px 5px 5px yellow; + -moz-box-shadow: 0pt 0pt 2px rgba(255, 255, 255, 0.4) inset, 0pt 4px 6px rgba(255, 255, 255, 0.4) inset; +} +@font-face { + font-family: Headline; + src: local(Futura-Medium), url(fonts.svg#MyGeometricModern) format("svg"); +} +.other { + -moz-transform: translate(0, 11em) rotate(-90deg); +} +p:not([class*="lead"]) { + color: black; +} +input[type="text"].class#id[attr=32]:not(1) { + color: white; +} +div#id.class[a=1][b=2].class:not(1) { + color: white; +} +ul.comma > li:not(:only-child)::after { + color: white; +} +ol.comma > li:nth-last-child(2)::after { + color: white; +} +li:nth-child(4n+1), li:nth-child(-5n), li:nth-child(-n+2) { + color: white; +} +a[href^="http://"] { + color: black; +} +a[href$="http://"] { + color: black; +} +form[data-disabled] { + color: black; +} +p::before { + color: black; +} +#issue322 { + -webkit-animation: anim2 7s infinite ease-in-out; +} + diff --git a/lesscpy/test/css/css-3.min.css b/lesscpy/test/css/css-3.min.css new file mode 100644 index 0000000..3fff791 --- /dev/null +++ b/lesscpy/test/css/css-3.min.css @@ -0,0 +1,14 @@ +.comma-delimited{background:url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);text-shadow:-1px -1px 1px red, 6px 5px 5px yellow;-moz-box-shadow:0pt 0pt 2px rgba(255, 255, 255, 0.4) inset, 0pt 4px 6px rgba(255, 255, 255, 0.4) inset;} +@font-face{font-family:Headline;src:local(Futura-Medium), url(fonts.svg#MyGeometricModern) format("svg");} +.other{-moz-transform:translate(0, 11em) rotate(-90deg);} +p:not([class*="lead"]){color:black;} +input[type="text"].class#id[attr=32]:not(1){color:white;} +div#id.class[a=1][b=2].class:not(1){color:white;} +ul.comma > li:not(:only-child)::after{color:white;} +ol.comma > li:nth-last-child(2)::after{color:white;} +li:nth-child(4n+1), li:nth-child(-5n), li:nth-child(-n+2){color:white;} +a[href^="http://"]{color:black;} +a[href$="http://"]{color:black;} +form[data-disabled]{color:black;} +p::before{color:black;} +#issue322{-webkit-animation:anim2 7s infinite ease-in-out;} diff --git a/lesscpy/test/css/css-escapes.css b/lesscpy/test/css/css-escapes.css new file mode 100644 index 0000000..1ca1ba6 --- /dev/null +++ b/lesscpy/test/css/css-escapes.css @@ -0,0 +1,16 @@ +.escape\|random\|char { + color: red; +} +.mixin\!tUp { + font-weight: bold; +} +.\34 04 { + background: red; +} +.\34 04 strong { + color: fuchsia; + font-weight: bold; +} +.trailingTest\+ { + color: red; +} diff --git a/lesscpy/test/css/css-escapes.min.css b/lesscpy/test/css/css-escapes.min.css new file mode 100644 index 0000000..24bddb6 --- /dev/null +++ b/lesscpy/test/css/css-escapes.min.css @@ -0,0 +1,5 @@ +.escape\|random\|char{color:red;} +.mixin\!tUp{font-weight:bold;} +.\34 04{background:red;} +.\34 04 strong{color:fuchsia;font-weight:bold;} +.trailingTest\+{color:red;} diff --git a/lesscpy/test/css/css.css b/lesscpy/test/css/css.css new file mode 100644 index 0000000..f91d296 --- /dev/null +++ b/lesscpy/test/css/css.css @@ -0,0 +1,89 @@ +@charset "utf-8"; +div { + color: black; +} +div { + width: 99%; +} +* { + min-width: 45em; +} +h1, h2 > a > p, h3 { + color: none; +} +div.class { + color: blue; +} +div#id { + color: green; +} +.class#id { + color: purple; +} +.one.two.three { + color: grey; +} +.one .two .three { + color: grey; +} +@media print { + font-size: 3em; +} +@media screen { + font-size: 10px; +} +@font-face { + font-family: 'Garamond Pro'; + src: url("/fonts/garamond-pro.ttf"); +} +a:hover, a:link { + color: #999999; +} +p, p:first-child { + text-transform: none; +} +q:lang(no) { + quotes: none; +} +p + h1 { + font-size: 2.2em; +} +#shorthands { + border: 1px solid #000000; + font: 12px/16px Arial; + margin: 1px 0; + padding: 0 auto; + background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px; +} +#shorthands { + font: 100%/16px Arial; +} +#more-shorthands { + margin: 0; + padding: 1px 0 2px 0; + font: normal small/20px 'Trebuchet MS', Verdana, sans-serif; +} +.misc { + -moz-border-radius: 2px; + display: -moz-inline-stack; + width: .1em; + background-color: #009998; + background-image: url(images/image.jpg); + background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue)); + filter: alpha(opacity=100); +} +#important { + color: red !important; + width: 100% !important; + height: 20px !important; +} +#data-uri { + background: url(data:image/png;charset=utf-8;base64, + kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/ + k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U + kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC); + background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==); +} +#svg-data-uri { + background: transparent url('data:image/svg+xml, '); +} diff --git a/lesscpy/test/css/css.min.css b/lesscpy/test/css/css.min.css new file mode 100644 index 0000000..fa7c9fe --- /dev/null +++ b/lesscpy/test/css/css.min.css @@ -0,0 +1,27 @@ +@charset"utf-8";div{color:black;} +div{width:99%;} +*{min-width:45em;} +h1, h2 > a > p, h3{color:none;} +div.class{color:blue;} +div#id{color:green;} +.class#id{color:purple;} +.one.two.three{color:grey;} +.one .two .three{color:grey;} +@media print{font-size:3em;} +@media screen{font-size:10px;} +@font-face{font-family:'Garamond Pro';src:url("/fonts/garamond-pro.ttf");} +a:hover, a:link{color:#999999;} +p, p:first-child{text-transform:none;} +q:lang(no){quotes:none;} +p + h1{font-size:2.2em;} +#shorthands{border:1px solid #000000;font:12px/16px Arial;margin:1px 0;padding:0 auto;background:url("http://www.lesscss.org/spec.html") no-repeat 0 4px;} +#shorthands{font:100%/16px Arial;} +#more-shorthands{margin:0;padding:1px 0 2px 0;font:normal small/20px 'Trebuchet MS', Verdana, sans-serif;} +.misc{-moz-border-radius:2px;display:-moz-inline-stack;width:.1em;background-color:#009998;background-image:url(images/image.jpg);background:-webkit-gradient(linear, left top, left bottom, from(red), to(blue));filter:alpha(opacity=100);} +#important{color:red !important;width:100% !important;height:20px !important;} +#data-uri{background:url(data:image/png;charset=utf-8;base64, + kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/ + k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U + kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);background-image:url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);} +#svg-data-uri{background:transparent url('data:image/svg+xml, ');} +.uri_test{background-image:url(fonts.svg#MyGeometricModern);behavior:url(border-radius.htc);} diff --git a/lesscpy/test/css/functions.css b/lesscpy/test/css/functions.css new file mode 100644 index 0000000..2957737 --- /dev/null +++ b/lesscpy/test/css/functions.css @@ -0,0 +1,18 @@ +#functions { + width: 16; + height: undefined("self"); + border-width: 5; + variable: 11; + decrement: 9; +} +#built-in { + escaped: -Some::weird(#thing, y); + escaped1: -Some::weird(#thing, z); + format: "rgb(32, 128, 64)"; + format-string: "hello world"; + format-multiple: "hello earth 2"; + format-url-encode: 'red is %23ff0000'; + eformat: rgb(32, 128, 64); + rounded: 10; + roundedpx: 3px; +} diff --git a/lesscpy/test/css/functions.min.css b/lesscpy/test/css/functions.min.css new file mode 100644 index 0000000..a8d14e7 --- /dev/null +++ b/lesscpy/test/css/functions.min.css @@ -0,0 +1,2 @@ +#functions{width:16;height:undefined("self");border-width:5;variable:11;decrement:9;} +#built-in{escaped:-Some::weird(#thing, y);escaped1:-Some::weird(#thing, z);format:"rgb(32, 128, 64)";format-string:"hello world";format-multiple:"hello earth 2";format-url-encode:'red is %23ff0000';eformat:rgb(32, 128, 64);rounded:10;roundedpx:3px;} diff --git a/lesscpy/test/css/ie-filters.css b/lesscpy/test/css/ie-filters.css new file mode 100644 index 0000000..545554f --- /dev/null +++ b/lesscpy/test/css/ie-filters.css @@ -0,0 +1,9 @@ +.nav { + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0); +} +.nav { + filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0); +} +.nav { + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0); +} diff --git a/lesscpy/test/css/ie-filters.min.css b/lesscpy/test/css/ie-filters.min.css new file mode 100644 index 0000000..6bfb481 --- /dev/null +++ b/lesscpy/test/css/ie-filters.min.css @@ -0,0 +1,3 @@ +.nav{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0);} +.nav{filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);} +.nav{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0);} diff --git a/lesscpy/test/css/import.css b/lesscpy/test/css/import.css new file mode 100644 index 0000000..bf0e3c5 --- /dev/null +++ b/lesscpy/test/css/import.css @@ -0,0 +1,7 @@ +@import 'some.css.file.css'; +@import 'some/other.css.file.CSS'; +.import { + color: red; + width: 6px; + height: 9px; +} \ No newline at end of file diff --git a/lesscpy/test/css/import.min.css b/lesscpy/test/css/import.min.css new file mode 100644 index 0000000..510b4a2 --- /dev/null +++ b/lesscpy/test/css/import.min.css @@ -0,0 +1 @@ +@import'some.css.file.css';@import'some/other.css.file.CSS';.import{color:red;width:6px;height:9px;} diff --git a/lesscpy/test/css/issues/inherit_scope.css b/lesscpy/test/css/issues/inherit_scope.css new file mode 100644 index 0000000..ba58136 --- /dev/null +++ b/lesscpy/test/css/issues/inherit_scope.css @@ -0,0 +1,3 @@ +.tiny-scope { + color: #998899; +} \ No newline at end of file diff --git a/lesscpy/test/css/issues/mixins-closure.css b/lesscpy/test/css/issues/mixins-closure.css new file mode 100644 index 0000000..b1021b6 --- /dev/null +++ b/lesscpy/test/css/issues/mixins-closure.css @@ -0,0 +1,9 @@ +.class { + width: 99px; +} +.overwrite { + width: 99px; +} +.nested .class { + width: 5px; +} diff --git a/lesscpy/test/css/issues/mixins-nested.css b/lesscpy/test/css/issues/mixins-nested.css new file mode 100644 index 0000000..6378c47 --- /dev/null +++ b/lesscpy/test/css/issues/mixins-nested.css @@ -0,0 +1,14 @@ +.class .inner { + height: 300; +} +.class .inner .innest { + width: 30; + border-width: 60; +} +.class2 .inner { + height: 600; +} +.class2 .inner .innest { + width: 60; + border-width: 120; +} diff --git a/lesscpy/test/css/issues/mixins-pattern.css b/lesscpy/test/css/issues/mixins-pattern.css new file mode 100644 index 0000000..eeaadec --- /dev/null +++ b/lesscpy/test/css/issues/mixins-pattern.css @@ -0,0 +1,49 @@ +.zero { + zero: 0; + one: 1; + two: 2; + three: 3; +} +.one { + zero: 0; + one: 1; + one-req: 1; + two: 2; + three: 3; +} +.two { + zero: 0; + one: 1; + two: 2; + three: 3; +} +.three { + zero: 0; + one: 1; + two: 2; + three-req: 3; + three: 3; +} +.left { + left: 1; +} +.right { + right: 1; +} +.border-right { + color: black; + border-right: 4px; +} +.border-left { + color: black; + border-left: 4px; +} +.only-right { + right: 33; +} +.only-left { + left: 33; +} +.left-right { + both: 330; +} diff --git a/lesscpy/test/css/issues/plus_identifier.css b/lesscpy/test/css/issues/plus_identifier.css new file mode 100644 index 0000000..e69de29 diff --git a/lesscpy/test/css/media.css b/lesscpy/test/css/media.css new file mode 100644 index 0000000..b2444d3 --- /dev/null +++ b/lesscpy/test/css/media.css @@ -0,0 +1,21 @@ +@media print { + .class { + color: blue; + } + .class .sub { + width: 42; + } + .top, header > h1 { + color: #444444; + } +} +@media screen { + body { + max-width: 480; + } +} +@media all and (orientation:portrait) { + aside { + float: none; + } +} diff --git a/lesscpy/test/css/media.min.css b/lesscpy/test/css/media.min.css new file mode 100644 index 0000000..eb8f54a --- /dev/null +++ b/lesscpy/test/css/media.min.css @@ -0,0 +1,8 @@ +@media print{.class{color:blue;} +.class .sub{width:42;} +.top, header > h1{color:#444444;} +} +@media screen{body{max-width:480;} +} +@media all and (orientation:portrait){aside{float:none;} +} diff --git a/lesscpy/test/css/mixins-args.css b/lesscpy/test/css/mixins-args.css new file mode 100644 index 0000000..0fa68a6 --- /dev/null +++ b/lesscpy/test/css/mixins-args.css @@ -0,0 +1,50 @@ +#hidden { + color: transparent; +} +.two-args { + color: blue; + width: 10px; + height: 99%; + border: 2px dotted black; +} +.one-arg { + width: 15px; + height: 49%; +} +.no-parens { + width: 5px; + height: 49%; +} +.no-args { + width: 5px; + height: 49%; +} +.var-args { + width: 45; + height: 17%; +} +.multi-mix { + width: 10px; + height: 29%; + margin: 4; + padding: 5; +} +body { + padding: 30px; + color: #f00; +} +.scope-mix { + width: 8; +} +#same-var-name { + radius: 5px; +} +#var-inside { + width: 10px; +} +.arguments { + border: 1px solid black; +} +.arguments2 { + border: 0px; +} diff --git a/lesscpy/test/css/mixins-args.min.css b/lesscpy/test/css/mixins-args.min.css new file mode 100644 index 0000000..cf6205e --- /dev/null +++ b/lesscpy/test/css/mixins-args.min.css @@ -0,0 +1,13 @@ +#hidden{color:transparent;} +.two-args{color:blue;width:10px;height:99%;border:2px dotted black;} +.one-arg{width:15px;height:49%;} +.no-parens{width:5px;height:49%;} +.no-args{width:5px;height:49%;} +.var-args{width:45;height:17%;} +.multi-mix{width:10px;height:29%;margin:4;padding:5;} +body{padding:30px;color:#f00;} +.scope-mix{width:8;} +#same-var-name{radius:5px;} +#var-inside{width:10px;} +.arguments{border:1px solid black;} +.arguments2{border:0px;} diff --git a/lesscpy/test/css/mixins-redundant.css b/lesscpy/test/css/mixins-redundant.css new file mode 100644 index 0000000..81bb7ea --- /dev/null +++ b/lesscpy/test/css/mixins-redundant.css @@ -0,0 +1,25 @@ +.ext { + color: red; +} +.ext { + color: blue; +} +.ext { + color: red; +} +.ext { + color: black; +} +.ext { + color: red; +} +.ext { + color: green; +} +p { + width: 1px; + color: green; +} +a { + color: green; +} \ No newline at end of file diff --git a/lesscpy/test/css/mixins-redundant.min.css b/lesscpy/test/css/mixins-redundant.min.css new file mode 100644 index 0000000..1419dea --- /dev/null +++ b/lesscpy/test/css/mixins-redundant.min.css @@ -0,0 +1,8 @@ +.ext{color:red;} +.ext{color:blue;} +.ext{color:red;} +.ext{color:black;} +.ext{color:red;} +.ext{color:green;} +p{width:1px;color:green;} +a{color:green;} diff --git a/lesscpy/test/css/mixins.css b/lesscpy/test/css/mixins.css new file mode 100644 index 0000000..2693002 --- /dev/null +++ b/lesscpy/test/css/mixins.css @@ -0,0 +1,74 @@ +.mixin { + border: 1px solid black; +} +.mixout { + border-color: orange; +} +.borders { + border-style: dashed; +} +#namespace .borders { + border-style: dotted; +} +#namespace .biohazard { + content: "death"; +} +#namespace .biohazard .man { + color: transparent; +} +#theme > .mixin { + background-color: grey; +} +#container { + color: black; + border: 1px solid black; + border-color: orange; + background-color: grey; +} +#header .milk { + color: white; + border: 1px solid black; + background-color: grey; +} +#header #cookie { + border-style: dashed; +} +#header #cookie .chips { + border-style: dotted; +} +#header #cookie .chips .calories { + color: black; + border: 1px solid black; + border-color: orange; + background-color: grey; +} +.secure-zone { + color: transparent; +} +.direct { + border-style: dotted; +} +.bo, .bar { + width: 100%; +} +.bo { + border: 1px; +} +.bo { + color: red; +} +.ar.bo.ca { + color: black; +} +.jo.ki { + background: none; +} +.extended { + width: 100%; + border: 1px; + color: red; + background: none; +} +.foo .bar { + width: 100%; +} diff --git a/lesscpy/test/css/mixins.min.css b/lesscpy/test/css/mixins.min.css new file mode 100644 index 0000000..c0f1644 --- /dev/null +++ b/lesscpy/test/css/mixins.min.css @@ -0,0 +1,21 @@ +.mixin{border:1px solid black;} +.mixout{border-color:orange;} +.borders{border-style:dashed;} +#namespace .borders{border-style:dotted;} +#namespace .biohazard{content:"death";} +#namespace .biohazard .man{color:transparent;} +#theme > .mixin{background-color:grey;} +#container{color:black;border:1px solid black;border-color:orange;background-color:grey;} +#header .milk{color:white;border:1px solid black;background-color:grey;} +#header #cookie{border-style:dashed;} +#header #cookie .chips{border-style:dotted;} +#header #cookie .chips .calories{color:black;border:1px solid black;border-color:orange;background-color:grey;} +.secure-zone{color:transparent;} +.direct{border-style:dotted;} +.bo, .bar{width:100%;} +.bo{border:1px;} +.bo{color:red;} +.ar.bo.ca{color:black;} +.jo.ki{background:none;} +.extended{width:100%;border:1px;color:red;background:none;} +.foo .bar{width:100%;} diff --git a/lesscpy/test/css/operations.css b/lesscpy/test/css/operations.css new file mode 100644 index 0000000..d790646 --- /dev/null +++ b/lesscpy/test/css/operations.css @@ -0,0 +1,42 @@ +#operations { + color: #111111; + height: 9px; + width: 3em; + text-shadow: -1px -1px 1px red, 6px 5px 5px yellow; + substraction: 0; + division: 1; +} +#operations .spacing { + height: 9px; + width: 3em; +} +.with-variables { + height: 16em; + width: 24em; + size: 1cm; +} +.negative { + height: 0; + width: 4px; +} +.shorthands { + padding: -1px 2px 0 -4px; +} +.colors { + color: #112233; + border-color: #334455; + background-color: #000000; +} +.colors .other { + color: #222222; + border-color: #222222; +} +.negations { + variable: -4px; + variable1: 0; + variable2: 0; + variable3: 8px; + variable4: 0; + paren: -4px; + paren2: 16px; +} diff --git a/lesscpy/test/css/operations.min.css b/lesscpy/test/css/operations.min.css new file mode 100644 index 0000000..e0ab369 --- /dev/null +++ b/lesscpy/test/css/operations.min.css @@ -0,0 +1,8 @@ +#operations{color:#111111;height:9px;width:3em;text-shadow:-1px -1px 1px red, 6px 5px 5px yellow;substraction:0;division:1;} +#operations .spacing{height:9px;width:3em;} +.with-variables{height:16em;width:24em;size:1cm;} +.negative{height:0;width:4px;} +.shorthands{padding:-1px 2px 0 -4px;} +.colors{color:#112233;border-color:#334455;background-color:#000000;} +.colors .other{color:#222222;border-color:#222222;} +.negations{variable:-4px;variable1:0;variable2:0;variable3:8px;variable4:0;paren:-4px;paren2:16px;} diff --git a/lesscpy/test/css/parens.css b/lesscpy/test/css/parens.css new file mode 100644 index 0000000..4673aad --- /dev/null +++ b/lesscpy/test/css/parens.css @@ -0,0 +1,20 @@ +.parens { + border: 2px solid black; + margin: 1px 3px 16 3; + width: 36; + padding: 2px 36px; +} +.more-parens { + padding: 8 4 4 4px; + width: 96; + height: 113; + margin: 12; +} +.nested-parens { + width: 71; + height: 6; +} +.mixed-units { + margin: 2px 4em 1 5pc; + padding: 6px 1em 2px 2; +} diff --git a/lesscpy/test/css/parens.min.css b/lesscpy/test/css/parens.min.css new file mode 100644 index 0000000..4024a89 --- /dev/null +++ b/lesscpy/test/css/parens.min.css @@ -0,0 +1,4 @@ +.parens{border:2px solid black;margin:1px 3px 16 3;width:36;padding:2px 36px;} +.more-parens{padding:8 4 4 4px;width:96;height:113;margin:12;} +.nested-parens{width:71;height:6;} +.mixed-units{margin:2px 4em 1 5pc;padding:6px 1em 2px 2;} diff --git a/lesscpy/test/css/rulesets.css b/lesscpy/test/css/rulesets.css new file mode 100644 index 0000000..6b5b854 --- /dev/null +++ b/lesscpy/test/css/rulesets.css @@ -0,0 +1,29 @@ +#first > .one { + font-size: 2em; +} +#first > .one > #second .two > #deux { + width: 50%; +} +#first > .one > #second .two > #deux #third { + height: 100%; +} +#first > .one > #second .two > #deux #third:focus { + color: black; +} +#first > .one > #second .two > #deux #third:focus #fifth > #sixth .seventh #eighth { + color: purple; +} +#first > .one > #second .two > #deux #fourth, #first > .one > #second .two > #deux #five, #first > .one > #second .two > #deux #six { + color: #110000; +} +#first > .one > #second .two > #deux #fourth .seven, +#first > .one > #second .two > #deux #five .seven, +#first > .one > #second .two > #deux #six .seven, +#first > .one > #second .two > #deux #fourth .eight > #nine, +#first > .one > #second .two > #deux #five .eight > #nine, +#first > .one > #second .two > #deux #six .eight > #nine { + border: 1px solid black; +} +#first > .one > #second .two > #deux #fourth #ten, #first > .one > #second .two > #deux #five #ten, #first > .one > #second .two > #deux #six #ten { + color: red; +} diff --git a/lesscpy/test/css/rulesets.min.css b/lesscpy/test/css/rulesets.min.css new file mode 100644 index 0000000..1903622 --- /dev/null +++ b/lesscpy/test/css/rulesets.min.css @@ -0,0 +1,13 @@ +#first > .one{font-size:2em;} +#first > .one > #second .two > #deux{width:50%;} +#first > .one > #second .two > #deux #third{height:100%;} +#first > .one > #second .two > #deux #third:focus{color:black;} +#first > .one > #second .two > #deux #third:focus #fifth > #sixth .seventh #eighth{color:purple;} +#first > .one > #second .two > #deux #fourth, #first > .one > #second .two > #deux #five, #first > .one > #second .two > #deux #six{color:#110000;} +#first > .one > #second .two > #deux #fourth .seven, +#first > .one > #second .two > #deux #five .seven, +#first > .one > #second .two > #deux #six .seven, +#first > .one > #second .two > #deux #fourth .eight > #nine, +#first > .one > #second .two > #deux #five .eight > #nine, +#first > .one > #second .two > #deux #six .eight > #nine{border:1px solid black;} +#first > .one > #second .two > #deux #fourth #ten, #first > .one > #second .two > #deux #five #ten, #first > .one > #second .two > #deux #six #ten{color:red;} diff --git a/lesscpy/test/css/scope.css b/lesscpy/test/css/scope.css new file mode 100644 index 0000000..86cc294 --- /dev/null +++ b/lesscpy/test/css/scope.css @@ -0,0 +1,12 @@ +.scope1 { + color: blue; + border-color: black; +} +.scope1 .scope2 { + color: blue; +} +.scope1 .scope2 .scope3 { + color: red; + border-color: black; + background-color: white; +} diff --git a/lesscpy/test/css/scope.min.css b/lesscpy/test/css/scope.min.css new file mode 100644 index 0000000..cc04e57 --- /dev/null +++ b/lesscpy/test/css/scope.min.css @@ -0,0 +1,3 @@ +.scope1{color:blue;border-color:black;} +.scope1 .scope2{color:blue;} +.scope1 .scope2 .scope3{color:red;border-color:black;background-color:white;} diff --git a/lesscpy/test/css/selectors.css b/lesscpy/test/css/selectors.css new file mode 100644 index 0000000..040aef3 --- /dev/null +++ b/lesscpy/test/css/selectors.css @@ -0,0 +1,53 @@ +h1 a:hover, +h2 a:hover, +h3 a:hover, +h1 p:hover, +h2 p:hover, +h3 p:hover { + color: red; +} +#all { + color: blue; +} +#the { + color: blue; +} +#same { + color: blue; +} +ul, +li, +div, +q, +blockquote, +textarea { + margin: 0; +} +td { + margin: 0; + padding: 0; +} +td, input { + line-height: 1em; +} +a { + color: red; +} +a:hover { + color: blue; +} +div a { + color: green; +} +p a span { + color: yellow; +} +.foo .bar .qux, .foo .baz .qux { + display: block; +} +.foo .qux .bar, .foo .qux .baz { + display: inline; +} +.foo .qux .bar .biz, .foo .qux .baz .biz { + display: none; +} diff --git a/lesscpy/test/css/selectors.min.css b/lesscpy/test/css/selectors.min.css new file mode 100644 index 0000000..b8e6f3b --- /dev/null +++ b/lesscpy/test/css/selectors.min.css @@ -0,0 +1,24 @@ +h1 a:hover, +h2 a:hover, +h3 a:hover, +h1 p:hover, +h2 p:hover, +h3 p:hover{color:red;} +#all{color:blue;} +#the{color:blue;} +#same{color:blue;} +ul, +li, +div, +q, +blockquote, +textarea{margin:0;} +td{margin:0;padding:0;} +td, input{line-height:1em;} +a{color:red;} +a:hover{color:blue;} +div a{color:green;} +p a span{color:yellow;} +.foo .bar .qux, .foo .baz .qux{display:block;} +.foo .qux .bar, .foo .qux .baz{display:inline;} +.foo .qux .bar .biz, .foo .qux .baz .biz{display:none;} diff --git a/lesscpy/test/css/strings.css b/lesscpy/test/css/strings.css new file mode 100644 index 0000000..a50ac4d --- /dev/null +++ b/lesscpy/test/css/strings.css @@ -0,0 +1,32 @@ +#strings { + background-image: url("http://son-of-a-banana.com"); + quotes: "~" "~"; + content: "#*%:&^,)!.(~*})"; + empty: ""; + brackets: "{" "}"; +} +#comments { + content: "/* hello */ // not-so-secret"; +} +#single-quote { + quotes: "'" "'"; + content: '""#!&""'; + empty: ''; + semi-colon: ';'; +} +#escaped { + filter: DX.Transform.MS.BS.filter(opacity=50); +} +#one-line { + image: url(http://tooks.com); +} +#interpolation { + url: "http://lesscss.org/dev/image.jpg"; + url2: "http://lesscss.org/image-256.jpg"; + url3: "http://lesscss.org#445566"; + url4: "http://lesscss.org/hello"; + url5: "http://lesscss.org/54.4px"; +} +.mix-mul-class { + color: orange; +} diff --git a/lesscpy/test/css/strings.min.css b/lesscpy/test/css/strings.min.css new file mode 100644 index 0000000..c0551ad --- /dev/null +++ b/lesscpy/test/css/strings.min.css @@ -0,0 +1,7 @@ +#strings{background-image:url("http://son-of-a-banana.com");quotes:"~" "~";content:"#*%:&^,)!.(~*})";empty:"";brackets:"{" "}";} +#comments{content:"/* hello */ // not-so-secret";} +#single-quote{quotes:"'" "'";content:'""#!&""';empty:'';semi-colon:';';} +#escaped{filter:DX.Transform.MS.BS.filter(opacity=50);} +#one-line{image:url(http://tooks.com);} +#interpolation{url:"http://lesscss.org/dev/image.jpg";url2:"http://lesscss.org/image-256.jpg";url3:"http://lesscss.org#445566";url4:"http://lesscss.org/hello";url5:"http://lesscss.org/54.4px";} +.mix-mul-class{color:orange;} diff --git a/lesscpy/test/css/variables.css b/lesscpy/test/css/variables.css new file mode 100644 index 0000000..ac06339 --- /dev/null +++ b/lesscpy/test/css/variables.css @@ -0,0 +1,27 @@ +.variables { + width: 14cm; +} +.variables { + height: 24px; + color: #888888; + font-family: "Trebuchet MS",Verdana,sans-serif; + quotes: "~""~"; +} +.redefinition { + three: 3; +} +.values { + font-family: 'Trebuchet', 'Trebuchet', 'Trebuchet'; + color: #888888 !important; + url: url('Trebuchet'); + multi: something 'A',B,C, 'Trebuchet'; +} +.variable-names { + name: 'hello'; +} +.alpha { + filter: alpha(opacity=42); +} +.lazy-eval { + width: 100%; +} \ No newline at end of file diff --git a/lesscpy/test/css/variables.min.css b/lesscpy/test/css/variables.min.css new file mode 100644 index 0000000..09d2e9e --- /dev/null +++ b/lesscpy/test/css/variables.min.css @@ -0,0 +1,7 @@ +.variables{width:14cm;} +.variables{height:24px;color:#888888;font-family:"Trebuchet MS",Verdana,sans-serif;quotes:"~""~";} +.redefinition{three:3;} +.values{font-family:'Trebuchet', 'Trebuchet', 'Trebuchet';color:#888888 !important;url:url('Trebuchet');multi:something 'A',B,C, 'Trebuchet';} +.variable-names{name:'hello';} +.alpha{filter:alpha(opacity=42);} +.lazy-eval{width:100%;} diff --git a/lesscpy/test/css/whitespace.css b/lesscpy/test/css/whitespace.css new file mode 100644 index 0000000..c82bc51 --- /dev/null +++ b/lesscpy/test/css/whitespace.css @@ -0,0 +1,32 @@ +.whitespace { + color: white; +} +.whitespace { + color: white; +} +.whitespace { + color: white; +} +.whitespace { + color: white; +} +.whitespace { + color: white; +} +.white, .space, .mania { + color: white; +} +.no-semi-column { + color: white; +} +.no-semi-column { + color: white; + white-space: pre; +} +.no-semi-column { + border: 2px solid white; +} +.newlines { + background: the, great, wall; + border: 2px solid black; +} diff --git a/lesscpy/test/css/whitespace.min.css b/lesscpy/test/css/whitespace.min.css new file mode 100644 index 0000000..94a46fa --- /dev/null +++ b/lesscpy/test/css/whitespace.min.css @@ -0,0 +1,10 @@ +.whitespace{color:white;} +.whitespace{color:white;} +.whitespace{color:white;} +.whitespace{color:white;} +.whitespace{color:white;} +.white, .space, .mania{color:white;} +.no-semi-column{color:white;} +.no-semi-column{color:white;white-space:pre;} +.no-semi-column{border:2px solid white;} +.newlines{background:the, great, wall;border:2px solid black;} diff --git a/lesscpy/test/genast.py b/lesscpy/test/genast.py new file mode 100644 index 0000000..a47f62d --- /dev/null +++ b/lesscpy/test/genast.py @@ -0,0 +1,16 @@ +import sys, re +sys.path.append('..') +import lesscpy.lessc.parser + +m = [] + +with open('../lessc/parser.py') as f: + for l in f.readlines(): + r = re.findall('def[ \t]+(p_[a-z_0-9]+)([^\(]*)', l) + if r: m.append(r[0][0]) + +p = lesscpy.lessc.parser.LessParser + +for u in m: + if u == 'p_error': continue + print(getattr(p, u).__doc__) \ No newline at end of file diff --git a/lesscpy/test/less/color-functions.less b/lesscpy/test/less/color-functions.less new file mode 100644 index 0000000..ccb704d --- /dev/null +++ b/lesscpy/test/less/color-functions.less @@ -0,0 +1,12 @@ +.color_functions { + lighten: lighten(#ff0000, 40%); + darken: darken(#ff0000, 40%); + saturate: saturate(#29332f, 20%); + desaturate: desaturate(#203c31, 20%); + greyscale: greyscale(#203c31); + spin-p: spin(hsl(340, 50%, 50%), 40); + spin-n: spin(hsl(30, 50%, 50%), -40); + hue: hue(hsl(98, 12%, 95%)); + saturation: saturation(hsl(98, 12%, 95%)); + lightness: lightness(hsl(98, 12%, 95%)); +} \ No newline at end of file diff --git a/lesscpy/test/less/colors.less b/lesscpy/test/less/colors.less new file mode 100644 index 0000000..5744e16 --- /dev/null +++ b/lesscpy/test/less/colors.less @@ -0,0 +1,52 @@ +#yelow { + #short { + color: #fea; + } + #long { + color: #ffeeaa; + } + #rgba { + color: rgba(255, 238, 170, 0.1); + } + #argb { + color: argb(rgba(255, 238, 170, 0.1)); + } +} + +#blue { + #short { + color: #00f; + } + #long { + color: #0000ff; + } + #rgba { + color: rgba(0, 0, 255, 0.1); + } + #argb { + color: argb(rgba(0, 0, 255, 0.1)); + } +} + +#alpha #hsla { + color: hsla(11, 20%, 20%, 0.6); +} + +#overflow { + .a { color: #111111 - #444444; } // #000000 + .b { color: #eee + #fff; } // #ffffff + .c { color: #aaa * 3; } // #ffffff + .d { color: #00ee00 + #009900; } // #00ff00 +} + +#grey { + color: rgb(200, 200, 200); +} + +#808080 { + color: hsl(50, 0%, 50%); +} + +#00ff00 { + color: hsl(120, 100%, 50%); +} diff --git a/lesscpy/test/less/comments.less b/lesscpy/test/less/comments.less new file mode 100644 index 0000000..1b5c63e --- /dev/null +++ b/lesscpy/test/less/comments.less @@ -0,0 +1,65 @@ +/******************\ +* * +* Comment Header * +* * +\******************/ + +/* + + Comment + +*/ + +/* + * Comment Test + * + * - cloudhead (http://cloudhead.net) + * + */ + +//////////////// +@var: "content"; +//////////////// + +/* Colors + * ------ + * #EDF8FC (background blue) + * #166C89 (darkest blue) + * + * Text: + * #333 (standard text) // A comment within a comment! + * #1F9EC9 (standard link) + * + */ + +/* @group Variables +------------------- */ +#comments /* boo */ { + /**/ // An empty comment + color: red; /* A C-style comment */ + background-color: orange; // A little comment + font-size: 12px; + + /* lost comment */ content: @var; + + border: 1px solid black; + + // padding & margin // + padding: 0; // }{ '" + margin: 2em; +} // + +/* commented out + #more-comments { + color: grey; + } +*/ + +.selector /* .with */, .lots, /* of */ .comments { + color: grey, /* blue */ orange; + -webkit-border-radius: 2px /* webkit only */; + -moz-border-radius: 2px * 4 /* moz only with operation */; +} + +#last { color: blue } +// diff --git a/lesscpy/test/less/css-3.less b/lesscpy/test/less/css-3.less new file mode 100644 index 0000000..1c823fd --- /dev/null +++ b/lesscpy/test/less/css-3.less @@ -0,0 +1,66 @@ +.comma-delimited { + background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg); + text-shadow: -1px -1px 1px red, 6px 5px 5px yellow; + -moz-box-shadow: 0pt 0pt 2px rgba(255, 255, 255, 0.4) inset, + 0pt 4px 6px rgba(255, 255, 255, 0.4) inset; +} +@font-face { + font-family: Headline; + src: local(Futura-Medium), + url(fonts.svg#MyGeometricModern) format("svg"); +} +.other { + -moz-transform: translate(0, 11em) rotate(-90deg); +} +p:not([class*="lead"]) { + color: black; +} + +input[type="text"].class#id[attr=32]:not(1) { + color: white; +} + +div#id.class[a=1][b=2].class:not(1) { + color: white; +} + +ul.comma > li:not(:only-child)::after { + color: white; +} + +ol.comma > li:nth-last-child(2)::after { + color: white; +} + +li:nth-child(4n+1), +li:nth-child(-5n), +li:nth-child(-n+2) { + color: white; +} + +a[href^="http://"] { + color: black; +} + +a[href$="http://"] { + color: black; +} + +form[data-disabled] { + color: black; +} + +p::before { + color: black; +} + +#issue322 { + -webkit-animation: anim2 7s infinite ease-in-out; +} +/* +@-webkit-keyframes frames { + 0% { border: 1px } + 5.5% { border: 2px } + 100% { border: 3px } +} +*/ \ No newline at end of file diff --git a/lesscpy/test/less/css-escapes.less b/lesscpy/test/less/css-escapes.less new file mode 100644 index 0000000..20af037 --- /dev/null +++ b/lesscpy/test/less/css-escapes.less @@ -0,0 +1,23 @@ +@ugly: fuchsia; + +.escape\|random\|char { + color: red; +} + +.mixin\!tUp { + font-weight: bold; +} + +// class="404" +.\34 04 { + background: red; + + strong { + color: @ugly; + .mixin\!tUp; + } +} + +.trailingTest\+ { + color: red; +} \ No newline at end of file diff --git a/lesscpy/test/less/css.less b/lesscpy/test/less/css.less new file mode 100644 index 0000000..487905a --- /dev/null +++ b/lesscpy/test/less/css.less @@ -0,0 +1,118 @@ +@charset "utf-8"; +div { color: black; } +div { width: 99%; } + +* { + min-width: 45em; +} + +h1, h2 > a > p, h3 { + color: none; +} + +div.class { + color: blue; +} + +div#id { + color: green; +} + +.class#id { + color: purple; +} + +.one.two.three { + color: grey; +} + +.one .two .three { + color: grey; +} + +@media print { + font-size: 3em; +} + +@media screen { + font-size: 10px; +} + +@font-face { + font-family: 'Garamond Pro'; + src: url("/fonts/garamond-pro.ttf"); +} + +a:hover, a:link { + color: #999; +} + +p, p:first-child { + text-transform: none; +} + +q:lang(no) { + quotes: none; +} + +p + h1 { + font-size: 2.2em; +} + +#shorthands { + border: 1px solid #000; + font : 12px/16px Arial; + margin: 1px 0; + padding: 0 auto; + background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px; +} + +#shorthands { + font: 100%/16px Arial; +} + +#more-shorthands { + margin: 0; + padding: 1px 0 2px 0; + font: normal small/20px 'Trebuchet MS', Verdana, sans-serif; +} + +.misc { + -moz-border-radius: 2px; + display: -moz-inline-stack; + width: .1em; + background-color: #009998; + background-image: url(images/image.jpg); + background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue)); + margin: ; + filter: alpha(opacity=100); +} + +#important { + color: red !important; + width: 100%!important; + height: 20px ! important; +} + +#data-uri { + background: url(data:image/png;charset=utf-8;base64, + kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/ + k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U + kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC); + background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==); +} + +#svg-data-uri { + background: transparent url('data:image/svg+xml, '); +} + +.uri_test { + background-image: url(images/image.jpg); + background-image: url(../some/path); + background-image: url(./../some/path); + background-image: url(./images/image.jpg); + background-image: url(http://some/path/img.jpeg); + background-image: url(https://some.server.com:9696/path/img.jpeg); + behavior:url(border-radius.htc); + background-image: url(fonts.svg#MyGeometricModern); +} diff --git a/lesscpy/test/less/functions.less b/lesscpy/test/less/functions.less new file mode 100644 index 0000000..7a2e5fb --- /dev/null +++ b/lesscpy/test/less/functions.less @@ -0,0 +1,22 @@ +#functions { + @var: 10; + width: increment(15); + height: undefined("self"); + border-width: add(2, 3); + variable: increment(@var); + decrement: decrement(@var); +} + +#built-in { + @r: 32; + escaped: e("-Some::weird(#thing, y)"); + escaped1: ~"-Some::weird(#thing, z)"; + format: %("rgb(%d, %d, %d)", @r, 128, 64); + format-string: %("hello %s", "world"); + format-multiple: %("hello %s %d", "earth", 2); + format-url-encode: %('red is %A', #ff0000); + eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64)); + rounded: round(@r/3); + roundedpx: round(10px / 3); +} + diff --git a/lesscpy/test/less/ie-filters.less b/lesscpy/test/less/ie-filters.less new file mode 100644 index 0000000..4f22d80 --- /dev/null +++ b/lesscpy/test/less/ie-filters.less @@ -0,0 +1,12 @@ +@fat: 0; +@cloudhead: "#000000"; + +.nav { + filter: ~'progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="@{cloudhead}", GradientType=@{fat})'; +} +.nav { + filter: ~"progid:DXImageTransform.Microsoft.Alpha(opacity=@{fat})"; +} +.nav { + filter: ~'progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="@{cloudhead}", GradientType=@{fat})'; +} \ No newline at end of file diff --git a/lesscpy/test/less/import.less b/lesscpy/test/less/import.less new file mode 100644 index 0000000..bdd8e29 --- /dev/null +++ b/lesscpy/test/less/import.less @@ -0,0 +1,10 @@ +@import './imports/import.less'; +@import 'some.css.file.css'; +@import './imports/import.less'; // redundant +@import './imports/import_f'; // No ext +@import 'some/other.css.file.CSS'; +.import { + .mixin; + .mixf(6px); + height: @imported; +} diff --git a/lesscpy/test/less/imports/circular.less b/lesscpy/test/less/imports/circular.less new file mode 100644 index 0000000..92094f7 --- /dev/null +++ b/lesscpy/test/less/imports/circular.less @@ -0,0 +1 @@ +@import 'circular.less'; \ No newline at end of file diff --git a/lesscpy/test/less/imports/import.less b/lesscpy/test/less/imports/import.less new file mode 100644 index 0000000..4338bba --- /dev/null +++ b/lesscpy/test/less/imports/import.less @@ -0,0 +1,5 @@ +@imported: 9px; + +.mixin { + color: red; +} \ No newline at end of file diff --git a/lesscpy/test/less/imports/import_f.less b/lesscpy/test/less/imports/import_f.less new file mode 100644 index 0000000..4325fc1 --- /dev/null +++ b/lesscpy/test/less/imports/import_f.less @@ -0,0 +1,6 @@ +/* + +*/ +.mixf (@var: 7px) { + width: @var; +} \ No newline at end of file diff --git a/lesscpy/test/less/issues/inherit_scope.less b/lesscpy/test/less/issues/inherit_scope.less new file mode 100644 index 0000000..179361c --- /dev/null +++ b/lesscpy/test/less/issues/inherit_scope.less @@ -0,0 +1,8 @@ +@mix: none; +.mixin { + @mix: #989; +} +.tiny-scope { + color: @mix; // #989 + .mixin; +} \ No newline at end of file diff --git a/lesscpy/test/less/issues/mixins-closure.less b/lesscpy/test/less/issues/mixins-closure.less new file mode 100644 index 0000000..01251d2 --- /dev/null +++ b/lesscpy/test/less/issues/mixins-closure.less @@ -0,0 +1,26 @@ +.scope { + @var: 99px; + .mixin () { + width: @var; + } +} + +.class { + .scope > .mixin; +} + +.overwrite { + @var: 0px; + .scope > .mixin; +} + +.nested { + @var: 5px; + .mixin () { + width: @var; + } + .class { + @var: 10px; + .mixin; + } +} diff --git a/lesscpy/test/less/issues/mixins-nested.less b/lesscpy/test/less/issues/mixins-nested.less new file mode 100644 index 0000000..5c3f7e4 --- /dev/null +++ b/lesscpy/test/less/issues/mixins-nested.less @@ -0,0 +1,30 @@ +.nested-ruleset (@width: 200px) { + width: @width; + .column { margin: @width; } +} +.content { + .nested-ruleset(600px); +} + +.mix-inner (@var) { + border-width: @var; +} + +.mix (@a: 10) { + .inner { + height: @a * 10; + + .innest { + width: @a; + .mix-inner(@a * 2); + } + } +} + +.class { + .mix(30); +} + +.class2 { + .mix(60); +} diff --git a/lesscpy/test/less/issues/mixins-pattern.less b/lesscpy/test/less/issues/mixins-pattern.less new file mode 100644 index 0000000..6392df0 --- /dev/null +++ b/lesscpy/test/less/issues/mixins-pattern.less @@ -0,0 +1,96 @@ +.mixin () { + zero: 0; +} +.mixin (@a: 1px) { + one: 1; +} +.mixin (@a) { + one-req: 1; +} +.mixin (@a: 1px, @b: 2px) { + two: 2; +} + +.mixin (@a, @b, @c) { + three-req: 3; +} + +.mixin (@a: 1px, @b: 2px, @c: 3px) { + three: 3; +} + +.zero { + .mixin(); +} + +.one { + .mixin(1); +} + +.two { + .mixin(1, 2); +} + +.three { + .mixin(1, 2, 3); +} + +// + +.mixout ('left') { + left: 1; +} + +.mixout ('right') { + right: 1; +} + +.left { + .mixout('left'); +} +.right { + .mixout('right'); +} + +// + +.border (@side, @width) { + color: black; + .border-side(@side, @width); +} +.border-side (left, @w) { + border-left: @w; +} +.border-side (right, @w) { + border-right: @w; +} + +.border-right { + .border(right, 4px); +} +.border-left { + .border(left, 4px); +} + +// + + +.border-radius (@r) { + both: @r * 10; +} +.border-radius (@r, left) { + left: @r; +} +.border-radius (@r, right) { + right: @r; +} + +.only-right { + .border-radius(33, right); +} +.only-left { + .border-radius(33, left); +} +.left-right { + .border-radius(33); +} diff --git a/lesscpy/test/less/issues/plus_identifier.less b/lesscpy/test/less/issues/plus_identifier.less new file mode 100644 index 0000000..bab999a --- /dev/null +++ b/lesscpy/test/less/issues/plus_identifier.less @@ -0,0 +1,5 @@ +#foo { + + .one { + font-size: 2em; + } +} \ No newline at end of file diff --git a/lesscpy/test/less/media.less b/lesscpy/test/less/media.less new file mode 100644 index 0000000..0b08a59 --- /dev/null +++ b/lesscpy/test/less/media.less @@ -0,0 +1,25 @@ + +// For now, variables can't be declared inside @media blocks. + +@var: 42; + +@media print { + .class { + color: blue; + .sub { + width: @var; + } + } + .top, header > h1 { + color: #222 * 2; + } +} + +@media screen { + @base: 8; + body { max-width: @base * 60; } +} + +@media all and (orientation:portrait) { + aside { float: none; } +} diff --git a/lesscpy/test/less/mixins-args.less b/lesscpy/test/less/mixins-args.less new file mode 100644 index 0000000..683654f --- /dev/null +++ b/lesscpy/test/less/mixins-args.less @@ -0,0 +1,110 @@ +.mixin (@a: 1px, @b: 50%) { + width: @a * 5; + height: @b - 1%; +} + +.mixina (@style, @width, @color: black) { + border: @width @style @color; +} + +.mixiny +(@a: 0, @b: 0) { + margin: @a; + padding: @b; +} + +.hidden() { + color: transparent; // asd +} + +#hidden { + .hidden; + .hidden(); +} + +.two-args { + color: blue; + .mixin(2px, 100%); + .mixina(dotted, 2px); +} + +.one-arg { + .mixin(3px); +} + +.no-parens { + .mixin; +} + +.no-args { + .mixin(); +} + +.var-args { + @var: 9; + .mixin(@var, @var * 2); +} + +.multi-mix { + .mixin(2px, 30%); + .mixiny(4, 5); +} + +.maxa(@arg1: 10, @arg2: #f00) { + padding: @arg1 * 2px; + color: @arg2; +} + +body { + .maxa(15); +} + +@glob: 5; +.global-mixin(@a:2) { + width: @glob + @a; +} + +.scope-mix { + .global-mixin(3); +} + +// + +.same-var-name2(@radius) { + radius: @radius; +} +.same-var-name(@radius) { + .same-var-name2(@radius); +} +#same-var-name { + .same-var-name(5px); +} + +// + +.var-inside () { + @var: 10px; + width: @var; +} +#var-inside { .var-inside; } + +// # mixins + +/* #id-mixin () { + color: red; +} */ +.id-class { + #id-mixin(); + #id-mixin; +} + +.mixin-arguments (@width: 0px) { + border: @arguments; +} + +.arguments { + .mixin-arguments(1px, solid, black); +} +.arguments2 { + .mixin-arguments(); +} diff --git a/lesscpy/test/less/mixins-redundant.less b/lesscpy/test/less/mixins-redundant.less new file mode 100644 index 0000000..3fa794e --- /dev/null +++ b/lesscpy/test/less/mixins-redundant.less @@ -0,0 +1,29 @@ +.ext { + color: red; +} +.ext { + color: blue; +} +.ext { + color: red; +} +.ext { + color: black; +} +.ext { + color: red; +} +.ext { + color: green; +} +p { + width: 1px; + .ext; +} +a { + .ext; + .ext; + .ext; + .ext; + .ext; +} \ No newline at end of file diff --git a/lesscpy/test/less/mixins.less b/lesscpy/test/less/mixins.less new file mode 100644 index 0000000..195feb6 --- /dev/null +++ b/lesscpy/test/less/mixins.less @@ -0,0 +1,72 @@ +.mixin { border: 1px solid black; } +.mixout { border-color: orange; } +.borders { border-style: dashed; } + +#namespace { + .borders { + border-style: dotted; + } + .biohazard { + content: "death"; + .man { + color: transparent; + } + } +} + +#theme { + > .mixin { + background-color: grey; + } +} + +#container { + color: black; + .mixin; + .mixout; + #theme > .mixin; +} + +#header { + .milk { + color: white; + .mixin; + #theme > .mixin; + } + #cookie { + .chips { + #namespace .borders; + .calories { + #container; + } + } + .borders; + } +} +.secure-zone { #namespace .biohazard .man; } + +.direct { + #namespace > .borders; +} +.bo, .bar { + width: 100%; +} +.bo { + border: 1px; +} +.bo { + color: red; +} +.ar.bo.ca { + color: black; +} +.jo.ki { + background: none; +} +.extended { + .bo; + .jo.ki; +} +.foo .bar { + .bar; +} diff --git a/lesscpy/test/less/operations.less b/lesscpy/test/less/operations.less new file mode 100644 index 0000000..aa3563b --- /dev/null +++ b/lesscpy/test/less/operations.less @@ -0,0 +1,53 @@ +#operations { + color: #110000 + #000011 + #001100; // #111111 + height: 10px / 2px + 6px - 1px * 2; // 9px + width: 2 * 4 - 5em; // 3em + text-shadow: -1px -1px 1px red, 6px 5px 5px yellow; + .spacing { + height: 10px / 2px+6px- 1px*2; + width: 2 * 4 - 5em; + } + substraction: 20 - 10 - 5 - 5; // 0 + division: 20 / 5 / 4; // 1 +} + +@x: 4; +@y: 12em; + +.with-variables { + height: @x + @y; // 16em + width: 12 + @y; // 24em + size: 5cm - @x; // 1cm +} + +@z: -2; + +.negative { + height: 2px + @z; // 0px + width: 2px - @z; // 4px +} + +.shorthands { + padding: -1px 2px 0 -4px; // +} + +.colors { + color: #123; // #112233 + border-color: #234 + #111111; // #334455 + background-color: #222222 - #fff; // #000000 + .other { + color: 2 * #111; // #222222 + border-color: #333333 / 3 + #111; // #222222 + } +} + +.negations { + @var: 4px; + variable: -@var; // 4 + variable1: -@var + @var; // 0 + variable2: @var + -@var; // 0 + variable3: @var - -@var; // 8 + variable4: -@var - -@var; // 0 + paren: -(@var); // -4px + paren2: -(2 + 2) * -@var; // 16 +} diff --git a/lesscpy/test/less/parens.less b/lesscpy/test/less/parens.less new file mode 100644 index 0000000..fcc2db8 --- /dev/null +++ b/lesscpy/test/less/parens.less @@ -0,0 +1,26 @@ +.parens { + @var: 1px; + border: (@var * 2) solid black; + margin: (@var * 1) (@var + 2) (4 * 4) 3; + width: (6 * 6); + padding: 2px (6px * 6px); +} + +.more-parens { + @var: (2 * 2); + padding: (2 * @var) 4 4 (@var * 1px); + width: (@var * @var) * 6; + height: (7 * 7) + (8 * 8); + margin: 4 * (5 + 5) / 2 - (@var * 2); + //margin: (6 * 6)px; +} + +.nested-parens { + width: 2 * (4 * (2 + (1 + 6))) - 1; + height: ((2+3)*(2+3) / (9 - 4)) + 1; +} + +.mixed-units { + margin: 2px 4em 1 5pc; + padding: (2px + 4px) 1em 2px 2; +} diff --git a/lesscpy/test/less/rulesets.less b/lesscpy/test/less/rulesets.less new file mode 100644 index 0000000..89a41f8 --- /dev/null +++ b/lesscpy/test/less/rulesets.less @@ -0,0 +1,28 @@ +#first > .one { + > #second .two > #deux { + width: 50%; + #third { + &:focus { + color: black; + #fifth { + > #sixth { + .seventh #eighth { + color: purple; + } + } + } + } + height: 100%; + } + #fourth, #five, #six { + color: #110000; + .seven, .eight > #nine { + border: 1px solid black; + } + #ten { + color: red; + } + } + } + font-size: 2em; +} diff --git a/lesscpy/test/less/scope.less b/lesscpy/test/less/scope.less new file mode 100644 index 0000000..f001622 --- /dev/null +++ b/lesscpy/test/less/scope.less @@ -0,0 +1,22 @@ +@x: blue; +@z: transparent; + +.scope1 { + @y: orange; + @z: black; + color: @x; // blue + border-color: @z; // black + .hidden { + @x: #131313; + } + .scope2 { + @y: red; + color: @x; // blue + .scope3 { + @local: white; + color: @y; // red + border-color: @z; // black + background-color: @local; // white + } + } +} diff --git a/lesscpy/test/less/selectors.less b/lesscpy/test/less/selectors.less new file mode 100644 index 0000000..5bc2bb1 --- /dev/null +++ b/lesscpy/test/less/selectors.less @@ -0,0 +1,48 @@ +h1, h2, h3 { + a, p { + &:hover { + color: red; + } + } +} + +#all { color: blue; } +#the { color: blue; } +#same { color: blue; } + +ul, li, div, q, blockquote, textarea { + margin: 0; +} + +td { + margin: 0; + padding: 0; +} + +td, input { + line-height: 1em; +} + +a { + color: red; + + &:hover { color: blue; } + + div & { color: green; } + + p & span { color: yellow; } +} + +.foo { + .bar, .baz { + & .qux { + display: block; + } + .qux & { + display: inline; + } + .qux & .biz { + display: none; + } + } +} \ No newline at end of file diff --git a/lesscpy/test/less/strings.less b/lesscpy/test/less/strings.less new file mode 100644 index 0000000..0072aa5 --- /dev/null +++ b/lesscpy/test/less/strings.less @@ -0,0 +1,49 @@ +#strings { + background-image: url("http://son-of-a-banana.com"); + quotes: "~" "~"; + content: "#*%:&^,)!.(~*})"; + empty: ""; + brackets: "{" "}"; +} +#comments { + content: "/* hello */ // not-so-secret"; +} +#single-quote { + quotes: "'" "'"; + content: '""#!&""'; + empty: ''; + semi-colon: ';'; +} +#escaped { + filter: ~"DX.Transform.MS.BS.filter(opacity=50)"; +} +#one-line { image: url(http://tooks.com) } +//#crazy { image: url(http://), "}", url("http://}") } +#interpolation { + @var: '/dev'; + url: "http://lesscss.org@{var}/image.jpg"; + + @var2: 256; + url2: "http://lesscss.org/image-@{var2}.jpg"; + + @var3: #456; + url3: "http://lesscss.org@{var3}"; + + @var4: hello; + url4: "http://lesscss.org/@{var4}"; + + @var5: 54.4px; + url5: "http://lesscss.org/@{var5}"; +} + +// multiple calls with string interpolation + +.mix-mul (@a: green) { + color: ~"@{a}"; +} +.mix-mul-class { + .mix-mul(blue); + .mix-mul(red); + .mix-mul(blue); + .mix-mul(orange); +} diff --git a/lesscpy/test/less/variables.less b/lesscpy/test/less/variables.less new file mode 100644 index 0000000..623f12f --- /dev/null +++ b/lesscpy/test/less/variables.less @@ -0,0 +1,59 @@ +@a: 2; +@x: @a * @a; +@y: @x + 1; +@z: @x * 2 + @y; + + +.variables { + width: @z + 1cm; // 14cm +} + +@b: @a * 10; +@c: #888; + +@fonts: "Trebuchet MS", Verdana, sans-serif; +@f: @fonts; + +@quotes: "~" "~"; +@q: @quotes; + +.variables { + height: @b + @x + 0px; // 24px + color: @c; + font-family: @f; + quotes: @q; +} + +.redefinition { + @var: 4; + @var: 2; + @var: 3; + three: @var; +} + +.values { + @a: 'Trebuchet'; + @multi: 'A', B, C; + font-family: @a, @a, @a; + color: @c !important; + url: url(@a); + multi: something @multi, @a; +} + +.variable-names { + @var: 'hello'; + @name: 'var'; + name: @@name; +} + +.alpha { + @var: 42; + filter: alpha(opacity=@var); +} + +@lazy: @j; +@j: 100%; + +.lazy-eval { + width: @lazy; +} diff --git a/lesscpy/test/less/whitespace.less b/lesscpy/test/less/whitespace.less new file mode 100644 index 0000000..cc0a8a3 --- /dev/null +++ b/lesscpy/test/less/whitespace.less @@ -0,0 +1,37 @@ + + +.whitespace + { color: white; } + +.whitespace +{ + color: white; +} + .whitespace +{ color: white; } + +.whitespace{color:white;} +.whitespace { color : white ; } + +.white, +.space, +.mania +{ color: white; } + +.no-semi-column { color: white } +.no-semi-column { + color: white; + white-space: pre +} +.no-semi-column {border: 2px solid white} +.newlines { + background: the, + great, + wall; + border: 2px + solid + black; +} +.empty { + +} diff --git a/lesscpy/test/mockp.py b/lesscpy/test/mockp.py new file mode 100644 index 0000000..afe362f --- /dev/null +++ b/lesscpy/test/mockp.py @@ -0,0 +1,14 @@ + +class Mockp(object): + def __init__(self, l): + self.l = [None] + self.l.extend(l) + + def __iter__(self): + return self.l.__iter__() + + def next(self): + return self.l.next() + + def lineno(self, n): + return 1 \ No newline at end of file diff --git a/lesscpy/test/testcolor.py b/lesscpy/test/testcolor.py new file mode 100644 index 0000000..f241e93 --- /dev/null +++ b/lesscpy/test/testcolor.py @@ -0,0 +1,63 @@ +""" + lesscpy tests. +""" +import unittest +from lesscpy.lessc import color + + +class TestLessColor(unittest.TestCase): + def setUp(self): + self.color = color.LessColor() + + def test_hls(self): + self.assertEqual('#bf406a', self.color.hls(340, 50, 50)) + self.assertEqual('#bf8040', self.color.hls(30, 50, 50)) + self.assertEqual('#bf406a', self.color.hls('340', '50%', '50%')) + self.assertEqual('#bf8040', self.color.hls('30', '50%', '50%')) + + def test_hue(self): + pass + + def test_saturation(self): + pass + + def test_lightness(self): + pass + + def test_alpha(self): + pass + + def test_saturate(self): + self.assertEqual('#203c31', self.color.saturate('#29332f', 20)) + + def test_desaturate(self): + self.assertEqual('#29332f', self.color.desaturate('#203c31', 20)) + + def test_lighten(self): + self.assertEqual('#ffcccc', self.color.lighten('#f00', 40)) + + def test_darken(self): + self.assertEqual('#330000', self.color.darken('#f00', 40)) + + def test_greyscale(self): + return + self.assertEqual('#2e2e2e', self.color.greyscale('#203c31')) + + def test_fadein(self): + pass + + def test_fadeout(self): + pass + + def test_fade(self): + pass + + def test_spin(self): + self.assertEqual('#bf6b40', self.color.spin('#bf406a', 40)) + self.assertEqual('#bf4055', self.color.spin('#bf8040', -40)) + + def test_mix(self): + pass + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/lesscpy/test/testexpression.py b/lesscpy/test/testexpression.py new file mode 100644 index 0000000..ed31f87 --- /dev/null +++ b/lesscpy/test/testexpression.py @@ -0,0 +1,58 @@ +import unittest +from lesscpy.plib.expression import Expression +from mockp import Mockp + +class TestExpression(unittest.TestCase): + def test_basic(self): + for test in [ + ['0', '+', '0', 0], + ['2', '+', '2', 4], + ['2.0', '+', '2', 4.0], + ['2', '+', '2.0', 4.0], + ['2.0', '+', '2.0', 4.0], + ]: + e = Expression(Mockp(test[:3])) + self.assertEqual(test[3], e.parse(None), str(test)) + + def test_neg(self): + for test in [ + ['-0', '+', '0', 0], + ['-2', '+', '-2', -4], + ['-2.0', '+', '-2', -4.0], + ['-2', '+', '-2.0', -4.0], + ['-2.0', '+', '-2.0', -4.0], + ['-0', '-', '0', 0], + ['-2', '-', '-2', 0], + ['-2.0', '-', '2', -4.0], + ['-2', '-', '-2.0', 0], + ['2.0', '-', '-2.0', 4.0], + ['-0px', '+', '0', 0], + ['-2px', '+', '-2', '-4px'], + ['-2.0', '+', '-2px', '-4.0px'], + ['-2em', '+', '-2.0', '-4.0em'], + ['-2.0s', '+', '-2.0s', '-4.0s'], + ]: + e = Expression(Mockp(test[:3])) + self.assertEqual(test[3], e.parse(None), str(test)) + + def testnest(self): + e = Expression(Mockp(['2', '-', '-2'])) + f = Expression(Mockp([['-', e], '*', '-3'])) + self.assertEqual(f.parse(None), 12) + g = Expression(Mockp(['2', '*', ['-', f]])) + self.assertEqual(g.parse(None), -24) + h = Expression(Mockp(['34', '-', ['-', g]])) + self.assertEqual(h.parse(None), 10) + i = Expression(Mockp([f, '-', h])) + self.assertEqual(i.parse(None), 2) + + def testdiv(self): + e = Expression(Mockp(['1', '/', '3'])) + + def testinput(self): + e = Expression(Mockp(['1a', '+', '1b'])) + self.assertRaises(SyntaxError, e.parse, None) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/lesscpy/test/testissues.py b/lesscpy/test/testissues.py new file mode 100644 index 0000000..5501078 --- /dev/null +++ b/lesscpy/test/testissues.py @@ -0,0 +1,40 @@ +""" + lesscpy tests. Issues +""" +import unittest +import os +import glob +import sys +import filecmp +sys.path.append('..') +from lessc import parser +from lessc import formatter + +class TestCase(unittest.TestCase): + pass + +def create_test (pair): + def do_test_expected(self): + if os.path.exists(pair[1]): + p = parser.LessParser() + p.parse(filename=pair[0]) + f = formatter.Formatter() + pout = f.format(p).split('\n') + i = 0 + with open(pair[1]) as cssf: + for line in cssf.readlines(): + self.assertEqual(line.rstrip(), pout[i], '%s: Line %d' % (pair[1], i+1)) + i += 1 + else: self.fail('%s not found' % pair[1]) + return do_test_expected + +LESS = glob.glob( os.path.join('less/issues/', '*.less')) +for less in LESS: + css = less.split('.')[0].split('/')[-1] + css = 'css/issues/' + css + '.css' + test_method = create_test((less, css)) + test_method.__name__ = 'test_%s' % less.replace('./-', '_') + setattr(TestCase, test_method.__name__, test_method) + +if __name__=="__main__": + unittest.main() \ No newline at end of file diff --git a/lesscpy/test/testless.py b/lesscpy/test/testless.py new file mode 100644 index 0000000..ad116f3 --- /dev/null +++ b/lesscpy/test/testless.py @@ -0,0 +1,54 @@ +""" + lesscpy tests. +""" +import unittest +import os +import glob +import sys +sys.path.append('..') +from lessc import parser +from lessc import formatter + +class TestCase(unittest.TestCase): + pass + +def create_test (args): + def do_test_expected(self): + lessf, cssf, minf = args + if os.path.exists(cssf): + p = parser.LessParser() + p.parse(filename=lessf) + f = formatter.Formatter() + pout = f.format(p).split('\n') + i = 0 + with open(cssf) as cssf: + for line in cssf.readlines(): + self.assertEqual(line.rstrip(), pout[i], '%s: Line %d' % (cssf, i+1)) + i += 1 + else: + self.fail("%s not found..." % cssf) + if os.path.exists(minf): + p = parser.LessParser() + p.parse(filename=lessf) + f = formatter.Formatter() + mout = f.format(p, True).split('\n') + i = 0 + with open(minf) as cssf: + for line in cssf.readlines(): + self.assertEqual(line.rstrip(), mout[i], '%s: Line %d' % (minf, i+1)) + i += 1 + else: + self.fail("%s not found..." % minf) + return do_test_expected + +LESS = glob.glob( os.path.join('less/', '*.less')) +for less in LESS: + lessf = less.split('.')[0].split('/')[-1] + css = 'css/' + lessf + '.css' + mincss = 'css/' + lessf + '.min.css' + test_method = create_test((less, css, mincss)) + test_method.__name__ = 'test_%s' % less.replace('./-', '_') + setattr(TestCase, test_method.__name__, test_method) + +if __name__=="__main__": + unittest.main() diff --git a/lesscpy/test/testprocess.py b/lesscpy/test/testprocess.py new file mode 100644 index 0000000..a529b94 --- /dev/null +++ b/lesscpy/test/testprocess.py @@ -0,0 +1,40 @@ +import unittest +from lesscpy.plib.process import Process +from lesscpy.plib.variable import Variable +from mockp import Mockp + +class TestProcess(unittest.TestCase): + def testswap(self): + p = Process(Mockp([])) + p.scope = [{}] + self.assertRaises(SyntaxError, p.swap, '@var') + p.scope = [{'@var': Variable(Mockp(['@var', ':', ['1']]))}] + self.assertEqual('1', p.swap('@var')) + p.scope.append({'@var': Variable(Mockp(['@var', ':', ['2']]))}) + self.assertEqual('2', p.swap('@var')) + p.scope.pop() + self.assertEqual('1', p.swap('@var')) + self.assertEqual('1 ', p.swap('@var ')) + self.assertEqual('-1', p.swap('-@var')) + self.assertEqual('-1 ', p.swap('-@var ')) + + def testswapvarvar(self): + p = Process(Mockp([])) + p.scope = [{'@var': Variable(Mockp(['@var', ':', ['1']]))}] + p.scope.append({'@name': Variable(Mockp(['@name', ':', ['var']]))}) + self.assertEqual('1', p.swap('@@name')) + self.assertEqual('1 ', p.swap('@@name ')) + self.assertEqual('-1', p.swap('-@@name')) + self.assertEqual('-1 ', p.swap('-@@name ')) + + def testreplace(self): + p = Process(Mockp([])) + p.scope = [{'@var': Variable(Mockp(['@var', ':', ['1']]))}] + p.scope.append({'@var2': Variable(Mockp(['@var2', ':', ['2']]))}) + t = p.replace_vars(['1', '@var2', 'm', '@var']) + self.assertEqual(t, ['1', '2', 'm', '1']) + t = p.replace_vars(['1', '-@var2 ', 'm', '-@var']) + self.assertEqual(t, ['1', '-2 ', 'm', '-1']) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/lesscpy/test/testutility.py b/lesscpy/test/testutility.py new file mode 100644 index 0000000..4ecf00d --- /dev/null +++ b/lesscpy/test/testutility.py @@ -0,0 +1,50 @@ + +import unittest +import lesscpy.lessc.utility as utility + +class TestUtility(unittest.TestCase): + def testanalyze(self): + test = utility.analyze_number + self.assertEqual((0, None), test('0')) + self.assertEqual((1, None), test('1')) + self.assertEqual(type(test('1')[0]), int) + self.assertEqual(type(test('-1')[0]), int) + self.assertEqual((1.0, None), test('1.0')) + self.assertEqual(type(test('-1.0')[0]), float) + self.assertEqual((0, 'px'), test('0px')) + self.assertEqual((1, 'px'), test('1px')) + self.assertEqual((1.0, 'px'), test('1.0px')) + self.assertEqual((0, 'px'), test('-0px')) + self.assertEqual((-1, 'px'), test('-1px')) + self.assertEqual(type(test('-1px')[0]), int) + self.assertEqual((-1.0, 'px'), test('-1.0px')) + self.assertEqual(type(test('-1.0px')[0]), float) + self.assertRaises(SyntaxError, test, 'gg') + self.assertRaises(SyntaxError, test, '-o') + self.assertRaises(SyntaxError, test, '') + + def testis_color(self): + test = utility.is_color + self.assertTrue(test('#123')) + self.assertTrue(test('#123456')) + self.assertTrue(test('#Df3')) + self.assertTrue(test('#AbCdEf')) + self.assertFalse(test('#AbCdEg')) + self.assertFalse(test('#h12345')) + self.assertFalse(test('#12345')) + self.assertFalse(test('AbCdEf')) + self.assertFalse(test('')) + self.assertFalse(test(False)) + self.assertFalse(test([])) + + def testis_variable(self): + test = utility.is_variable + self.assertTrue(test('@var')) + self.assertTrue(test('-@var')) + self.assertFalse(test('var')) + self.assertFalse(test('')) + self.assertFalse(test(False)) + self.assertFalse(test([])) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fe2fc19 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +""" +""" +from distutils.core import setup + +setup( + name='lesscpy', + version='0.6', + description='LESSCSS compiler.', + author='Jóhann T Maríusson', + author_email='jtm@robot.is', + url='https://github.com/robotis/lesscpy', + packages=['lesscpy', + 'lesscpy/lessc', + 'lesscpy/lib', + 'lesscpy/plib', + 'lesscpy/scripts', + 'lesscpy/test'], + scripts = ['bin/lesscpy'], + package_data={'lesscpy': ['lesscpy/less.ast', + 'lesscpy/test/css/*.css', + 'lesscpy/test/css/issues/*.css', + 'lesscpy/test/less/*.less', + 'lesscpy/test/less/issues/*.less',]}, + license='LICENSE', + long_description=open('README').read(), +) \ No newline at end of file