diff --git a/.gitignore b/.gitignore index fb269b5..46eae60 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ parser.out build dist *.pyc +MANIFEST diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..84f25ff --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: python +python: + - "2.6" + - "2.7" + - "3.3" +# command to install dependencies +install: "pip install -r requirements.txt --use-mirrors" +# command to run tests +script: python lesscpy/test/__main__.py + diff --git a/MANIFEST.in b/MANIFEST.in index 9575a1c..ed77be9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,4 @@ include LICENSE -include README +include README.rst +include requirements.txt +include tox.ini diff --git a/README b/README deleted file mode 100644 index b2238ca..0000000 --- a/README +++ /dev/null @@ -1,136 +0,0 @@ -* -* LESSCPY * -* -python LessCss Compiler. -v0.9h - -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 and is -considerably slower than the nodejs compiler. The plan is to utilize this -to build in proper syntax checking and perhaps YUI compressing. - -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 - -Development files - * https://github.com/robotis/Lesscpy - -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] [-v] [-I INCLUDE] [-V] [-x] [-X] [-t] [-s SPACES] [-o OUT] - [-r] [-f] [-m] [-D] [-g] [-S] [-L] [-N] - target - -LessCss Compiler - -positional arguments: - target less file or directory - -optional arguments: - -h, --help show this help message and exit - -v, --version show program's version number and exit - -I INCLUDE, --include INCLUDE - Included less-files (comma separated) - -V, --verbose Verbose mode - -Formatting options: - -x, --minify Minify output - -X, --xminify Minify output, no end of block newlines - -t, --tabs Use tabs - -s SPACES, --spaces SPACES - Number of startline spaces (default 2) - -Directory options: - Compiles all *.less files in directory that have a newer timestamp than - it's css file. - - -o OUT, --out OUT Output directory - -r, --recurse Recursive into subdirectorys - -f, --force Force recompile on all files - -m, --min-ending Add '.min' into output filename. eg, name.min.css - -D, --dry-run Dry run, do not write files - -Debugging: - -g, --debug Debugging information - -S, --scopemap Scopemap - -L, --lex-only Run lexer on target - -N, --no-css No css output - -Supported features -================== -* Variables -* String interpolation -* Mixins -* mixins (Nested) -* mixins (Nested (Calls)) -* mixins (closures) -* mixins (recursive) -* Guard expressions -* Parametered mixins (class) -* Parametered mixins (id) -* @arguments -* Nesting -* Escapes ~/e() -* Expressions -* Keyframe blocks -* Color functions: -** lighten -** darken -** saturate -** desaturate -** spin -** hue -** mix -** saturation -** lightness -* Other functions: -** round -** increment -** decrement -** format '%(' -** add -** iscolor -** isnumber -** isurl -** isstring -** iskeyword -* Keyframe blocks - -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 -=================== -* JavaScript evaluation - -License -======= -See the LICENSE file - - diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..43306bb --- /dev/null +++ b/README.rst @@ -0,0 +1,150 @@ +LESSCPY +======= + +Python LESS Compiler. + +A compiler written in Python for the LESS language. For those of us not willing +or able to have node.js installed in our environment. Not all features of LESS +are supported (yet). Some features wil probably never be supported (JavaScript +evaluation). This program uses PLY (Python Lex-Yacc) to tokenize / parse the +input and is considerably slower than the NodeJS compiler. The plan is to +utilize this to build in proper syntax checking and perhaps YUI compressing. + +This is an early version, so you are likly to find bugs. + +For more information on LESS: + http://lesscss.org/ or https://github.com/cloudhead/less.js + +Development files: + https://github.com/robotis/Lesscpy + + +Requirements +------------ + +- Python 2.6 or 2.7 +- ply (Python Lex-Yacc) + +For more information on ply: + http://www.dabeaz.com/ply/ + + +Installation +------------ + +.. code-block:: bash + + python setup.py install + +Or simply place the package into your Python path. + + +Compiler script Usage +--------------------- + +.. code-block:: text + + usage: lesscpy [-h] [-v] [-I INCLUDE] [-V] [-x] [-X] [-t] [-s SPACES] [-o OUT] + [-r] [-f] [-m] [-D] [-g] [-S] [-L] [-N] + target + + LessCss Compiler + + positional arguments: + target less file or directory + + optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + -I INCLUDE, --include INCLUDE + Included less-files (comma separated) + -V, --verbose Verbose mode + + Formatting options: + -x, --minify Minify output + -X, --xminify Minify output, no end of block newlines + -t, --tabs Use tabs + -s SPACES, --spaces SPACES + Number of startline spaces (default 2) + + Directory options: + Compiles all \*.less files in directory that have a newer timestamp than + it's css file. + + -o OUT, --out OUT Output directory + -r, --recurse Recursive into subdirectorys + -f, --force Force recompile on all files + -m, --min-ending Add '.min' into output filename. eg, name.min.css + -D, --dry-run Dry run, do not write files + + Debugging: + -g, --debug Debugging information + -S, --scopemap Scopemap + -L, --lex-only Run lexer on target + -N, --no-css No css output + + +Supported features +------------------ + +- Variables +- String interpolation +- Mixins +- mixins (Nested) +- mixins (Nested (Calls)) +- mixins (closures) +- mixins (recursive) +- Guard expressions +- Parametered mixins (class) +- Parametered mixins (id) +- @arguments +- Nesting +- Escapes ~/e() +- Expressions +- Keyframe blocks +- Color functions: + + - lighten + - darken + - saturate + - desaturate + - spin + - hue + - mix + - saturation + - lightness + +- Other functions: + + - round + - increment + - decrement + - format '%(' + - add + - iscolor + - isnumber + - isurl + - isstring + - iskeyword + +- Keyframe blocks + + +Differences from less.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 +------------- + +- JavaScript evaluation + + +License +------- + +See the LICENSE file diff --git a/lesscpy/lessc/__init__.py b/lesscpy/lessc/__init__.py index 4fe32e1..5fa344c 100644 --- a/lesscpy/lessc/__init__.py +++ b/lesscpy/lessc/__init__.py @@ -1,4 +1,4 @@ """ - Main lesscss parse library. Contains lexer and parser, along with + Main lesscss parse library. Contains lexer and parser, along with utility classes -""" \ No newline at end of file +""" diff --git a/lesscpy/lessc/color.py b/lesscpy/lessc/color.py index 4be850d..c0da7fa 100644 --- a/lesscpy/lessc/color.py +++ b/lesscpy/lessc/color.py @@ -2,7 +2,7 @@ """ .. module:: lesscpy.lessc.color :synopsis: Lesscpy Color functions - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson @@ -11,7 +11,9 @@ import colorsys from . import utility from lesscpy.lib import colors + class Color(): + def process(self, expression): """ Process color expression args: @@ -25,11 +27,13 @@ class Color(): r = ['#'] for i in range(3): v = self.operate(c1[i], c2[i], o) - if v > 0xff: v = 0xff - if v < 0: v = 0 + if v > 0xff: + v = 0xff + if v < 0: + v = 0 r.append("%02x" % v) return ''.join(r) - + def operate(self, left, right, operation): """ Do operation on colors args: @@ -46,7 +50,7 @@ class Color(): '/': '__truediv__' }.get(operation) return getattr(left, operation)(right) - + def rgb(self, *args): """ Translate rgb(...) to color string raises: @@ -58,15 +62,15 @@ class Color(): return self.rgba(*args) elif len(args) == 3: try: - return self._rgbatohex(map(int, args)) + return self._rgbatohex(list(map(int, args))) except ValueError: - if all((a for a in args - if a[-1] == '%' + if all((a for a in args + if a[-1] == '%' and 100 >= int(a[:-1]) >= 0)): return self._rgbatohex([int(a[:-1]) * 255 / 100.0 for a in args]) raise ValueError('Illegal color values') - + def rgba(self, *args): """ Translate rgba(...) to color string raises: @@ -76,15 +80,15 @@ class Color(): """ if len(args) == 4: try: - return self._rgbatohex(map(int, args)) + return self._rgbatohex(list(map(int, args))) except ValueError: - if all((a for a in args - if a[-1] == '%' + if all((a for a in args + if a[-1] == '%' and 100 >= int(a[:-1]) >= 0)): return self._rgbatohex([int(a[:-1]) * 255 / 100.0 for a in args]) raise ValueError('Illegal color values') - + def hsl(self, *args): """ Translate hsl(...) to color string raises: @@ -96,13 +100,15 @@ class Color(): return self.hsla(*args) elif len(args) == 3: h, s, l = args - if type(l) == str: l = int(l.strip('%')) - if type(s) == str: s = int(s.strip('%')) + if isinstance(l, str): + l = int(l.strip('%')) + if isinstance(s, str): + s = int(s.strip('%')) rgb = colorsys.hls_to_rgb(int(h) / 360.0, l / 100.0, s / 100.0) color = (round(c * 255) for c in rgb) return self._rgbatohex(color) raise ValueError('Illegal color values') - + def hsla(self, *args): """ Translate hsla(...) to color string raises: @@ -112,14 +118,16 @@ class Color(): """ if len(args) == 4: h, s, l, a = args - if type(l) == str: l = int(l.strip('%')) - if type(s) == str: s = int(s.strip('%')) + if isinstance(l, str): + l = int(l.strip('%')) + if isinstance(s, str): + s = int(s.strip('%')) rgb = colorsys.hls_to_rgb(int(h) / 360.0, l / 100.0, s / 100.0) color = [float(round(c * 255)) for c in rgb] color.append(round(float(a[:-1]) / 100.0, 2)) return "rgba(%s,%s,%s,%s)" % tuple(color) raise ValueError('Illegal color values') - + def hue(self, color, *args): """ Return the hue value of a color args: @@ -133,7 +141,7 @@ class Color(): h, l, s = self._hextohls(color) return round(h * 360.0, 3) raise ValueError('Illegal color values') - + def saturation(self, color, *args): """ Return the saturation value of a color args: @@ -147,7 +155,7 @@ class Color(): h, l, s = self._hextohls(color) return s * 100.0 raise ValueError('Illegal color values') - + def lightness(self, color, *args): """ Return the lightness value of a color args: @@ -161,12 +169,12 @@ class Color(): h, l, s = self._hextohls(color) return l * 100.0 raise ValueError('Illegal color values') - + def opacity(self, *args): """ """ pass - + def lighten(self, color, diff, *args): """ Lighten a color args: @@ -178,7 +186,7 @@ class Color(): if color and diff: return self._ophsl(color, diff, 1, '__add__') raise ValueError('Illegal color values') - + def darken(self, color, diff, *args): """ Darken a color args: @@ -190,7 +198,7 @@ class Color(): if color and diff: return self._ophsl(color, diff, 1, '__sub__') raise ValueError('Illegal color values') - + def saturate(self, color, diff, *args): """ Saturate a color args: @@ -202,7 +210,7 @@ class Color(): if color and diff: return self._ophsl(color, diff, 2, '__add__') raise ValueError('Illegal color values') - + def desaturate(self, color, diff, *args): """ Desaturate a color args: @@ -214,11 +222,11 @@ class Color(): if color and diff: return self._ophsl(color, diff, 2, '__sub__') raise ValueError('Illegal color values') - + def _clamp(self, value): # Clamp value return min(1, max(0, value)) - + def grayscale(self, color, *args): """ Simply 100% desaturate. args: @@ -234,7 +242,7 @@ class Color(): """Wrapper for grayscale, other spelling """ return self.grayscale(color, *args) - + def spin(self, color, degree, *args): """ Spin color by degree. (Increase / decrease hue) args: @@ -246,7 +254,7 @@ class Color(): str """ if color and degree: - if type(degree) == str: + if isinstance(degree, str): degree = int(degree.strip('%')) h, l, s = self._hextohls(color) h = ((h * 360.0) + degree) % 360.0 @@ -255,29 +263,29 @@ class Color(): color = (round(c * 255) for c in rgb) return self._rgbatohex(color) raise ValueError('Illegal color values') - + def mix(self, color1, color2, weight=50, *args): """This algorithm factors in both the user-provided weight and the difference between the alpha values of the two colors to decide how to perform the weighted average of the two RGB values. - + It works by first normalizing both parameters to be within [-1, 1], where 1 indicates "only use color1", -1 indicates "only use color 0", and all values in between indicated a proportionately weighted average. - + Once we have the normalized variables w and a, we apply the formula (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color1. This formula has two especially nice properties: - + * When either w or a are -1 or 1, the combined weight is also that number (cases where w * a == -1 are undefined, and handled as a special case). - + * When a is 0, the combined weight is w, and vice versa - + Finally, the weight of color1 is renormalized to be within [0, 1] and the weight of color2 is given by 1 minus the weight of color1. - + Copyright (c) 2006-2009 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein http://sass-lang.com args: @@ -290,13 +298,13 @@ class Color(): str """ if color1 and color2: - if type(weight) == str: + if isinstance(weight, str): weight = int(weight.strip('%')) weight = ((weight / 100.0) * 2) - 1 rgb1 = self._hextorgb(color1) rgb2 = self._hextorgb(color2) alpha = 0 - w1 = (((weight if weight * alpha == -1 + w1 = (((weight if weight * alpha == -1 else weight + alpha) / (1 + weight * alpha)) + 1) w1 = w1 / 2.0 w2 = 1 - w1 @@ -307,7 +315,7 @@ class Color(): ] return self._rgbatohex(rgb) raise ValueError('Illegal color values') - + def fmt(self, color): """ Format CSS Hex color code. uppercase becomes lowercase, 3 digit codes expand to 6 digit. @@ -324,15 +332,16 @@ class Color(): color = ''.join([c * 2 for c in color]) return '#%s' % color raise ValueError('Cannot format non-color') - + def _rgbatohex(self, rgba): - return '#%s' % ''.join(["%02x" % v for v in - [0xff - if h > 0xff else - 0 if h < 0 else h + return '#%s' % ''.join(["%02x" % v for v in + [0xff + if h > 0xff else + 0 if h < 0 else h for h in rgba] ]) - def _hextorgb(self, hex): + + def _hextorgb(self, hex): if hex.lower() in colors.lessColors: hex = colors.lessColors[hex.lower()] hex = hex.strip() @@ -341,22 +350,19 @@ class Color(): 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)] + 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 _hextohls(self, hex): rgb = self._hextorgb(hex) return colorsys.rgb_to_hls(*[c / 255.0 for c in rgb]) - + def _ophsl(self, color, diff, idx, op): - if type(diff) == str: diff = int(diff.strip('%')) + if isinstance(diff, str): + diff = int(diff.strip('%')) hls = list(self._hextohls(color)) hls[idx] = self._clamp(getattr(hls[idx], op)(diff / 100.0)) rgb = colorsys.hls_to_rgb(*hls) color = (round(c * 255) for c in rgb) return self._rgbatohex(color) - - - - \ No newline at end of file diff --git a/lesscpy/lessc/formatter.py b/lesscpy/lessc/formatter.py index 3066a87..796b79e 100644 --- a/lesscpy/lessc/formatter.py +++ b/lesscpy/lessc/formatter.py @@ -2,21 +2,24 @@ """ .. module:: lesscpy.lessc.formatter :synopsis: CSS Formatter class. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ + + class Formatter(object): + def __init__(self, args): self.args = args - + def format(self, parse): """ """ if not parse.result: return '' - eb = '\n' + eb = '\n' if self.args.xminify: eb = '' self.args.minify = True @@ -36,9 +39,7 @@ class Formatter(object): 'ws': ' ', 'eb': eb }) - self.out = [u.fmt(self.items) - for u in parse.result + self.out = [u.fmt(self.items) + for u in parse.result if u] return ''.join(self.out).strip() - - \ No newline at end of file diff --git a/lesscpy/lessc/lexer.py b/lesscpy/lessc/lexer.py index 833e4a5..e03d86a 100644 --- a/lesscpy/lessc/lexer.py +++ b/lesscpy/lessc/lexer.py @@ -1,10 +1,10 @@ """ 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. @@ -15,11 +15,12 @@ import ply.lex as lex from lesscpy.lib import dom from lesscpy.lib import css + class LessLexer: states = ( - ('parn', 'inclusive'), + ('parn', 'inclusive'), ) - literals = ',<>{}=%!/*-+:;~&'; + literals = ',<>{}=%!/*-+:;~&' tokens = [ 'css_ident', 'css_dom', @@ -36,7 +37,7 @@ class LessLexer: 'css_vendor_hack', 'css_uri', 'css_ms_filter', - + 'less_variable', 'less_comment', 'less_string', @@ -44,31 +45,31 @@ class LessLexer: 'less_when', 'less_and', 'less_not', - + 't_ws', 't_popen', 't_pclose', ] reserved = { - '@media' : 'css_media', + '@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', - '@-ms-keyframes' : 'css_keyframes', - '@-o-keyframes' : 'css_keyframes', - + '@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', + '@-ms-keyframes': 'css_keyframes', + '@-o-keyframes': 'css_keyframes', + '@arguments': 'less_arguments', } tokens += list(set(reserved.values())) # Tokens with significant following whitespace significant_ws = [ - 'css_class', - 'css_id', + 'css_class', + 'css_id', 'css_dom', 'css_property', 'css_vendor_property', @@ -79,23 +80,23 @@ class LessLexer: '&', ] significant_ws += list(set(reserved.values())) - + def __init__(self): - self.build(reflags=re.UNICODE|re.IGNORECASE) + self.build(reflags=re.UNICODE | re.IGNORECASE) self.last = None - self.next = None + self.next_ = None self.pretok = True - + def t_css_filter(self, t): (r'\[[^\]]*\]' - '|(not|lang|nth-[a-z\-]+)\(.+\)' - '|and[ \t]\([^><\{]+\)') + '|(not|lang|nth-[a-z\-]+)\(.+\)' + '|and[ \t]\([^><\{]+\)') return t - + def t_css_ms_filter(self, t): r'progid:[^;]*' return t - + def t_css_ident(self, t): (r'([\-\.\#]?' '|@[@\-]?)' @@ -140,19 +141,19 @@ class LessLexer: t.type = 'css_vendor_property' t.value = t.value.strip() return t - + def t_less_variable(self, t): r'@\w+' return t - + def t_css_color(self, t): r'\#[0-9]([0-9a-f]{5}|[0-9a-f]{2})' 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_parn_css_uri(self, t): (r'data:[^\)]+' '|(([a-z]+://)?' @@ -162,7 +163,7 @@ class LessLexer: '(\#[a-z]+)?)' ')+') return t - + def t_parn_css_ident(self, t): (r'(([_a-z]' '|[\200-\377]' @@ -172,11 +173,11 @@ class LessLexer: '|\\\[0-9a-f]{1,6}' '|\\\[^\r\n\s0-9a-f])*)') return t - + def t_newline(self, t): r'[\n\r]+' t.lexer.lineno += t.value.count('\n') - + def t_css_comment(self, t): r'(/\*(.|\n|\r)*?\*/)' t.lexer.lineno += t.value.count('\n') @@ -185,12 +186,12 @@ class LessLexer: 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\f\v]+' t.value = ' ' @@ -200,37 +201,38 @@ class LessLexer: 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_string(self, t): (r'"([^"@]*@\{[^"\}]+\}[^"]*)+"' - '|\'([^\'@]*@\{[^\'\}]+\}[^\']*)+\'') + '|\'([^\'@]*@\{[^\'\}]+\}[^\']*)+\'') t.lexer.lineno += t.value.count('\n') return t - + def t_css_string(self, t): r'"[^"]*"|\'[^\']*\'' t.lexer.lineno += t.value.count('\n') return t - + # Error handling rule def t_error(self, t): - raise SyntaxError("Illegal character '%s' line %d" % (t.value[0], t.lexer.lineno)) + 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) - + self.lexer = lex.lex(module=self, **kwargs) + def file(self, filename): """ Lex file. @@ -238,14 +240,14 @@ class LessLexer: with open(filename) as f: self.lexer.input(f.read()) return self - + def input(self, filename): """ Wrapper for file """ with open(filename) as f: self.lexer.input(f.read()) - + def token(self): """ Token function. Contains 2 hacks: @@ -254,20 +256,21 @@ class LessLexer: 2. Strips out whitespace from nonsignificant locations to ease parsing. """ - if self.next: - t = self.next - self.next = None + if self.next_: + t = self.next_ + self.next_ = None return t while True: t = self.lexer.token() - if not t: return t + if not t: + return t if t.type == 't_ws' and ( - self.pretok or (self.last - and self.last.type not in self.significant_ws)): + self.pretok or (self.last + and self.last.type not in self.significant_ws)): continue self.pretok = False if t.type == '}' and self.last and self.last.type not in '{;}': - self.next = t + self.next_ = t tok = lex.LexToken() tok.type = ';' tok.value = ';' diff --git a/lesscpy/lessc/parser.py b/lesscpy/lessc/parser.py index 55488a1..d45c15f 100644 --- a/lesscpy/lessc/parser.py +++ b/lesscpy/lessc/parser.py @@ -1,43 +1,48 @@ -from __future__ import print_function # -*- coding: utf8 -*- """ .. module:: lesscpy.lessc.parser :synopsis: Lesscss parser. - + http://www.dabeaz.com/ply/ply.html http://www.w3.org/TR/CSS21/grammar.html#scanner http://lesscss.org/#docs - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ + +from __future__ import print_function + import os import sys import ply.yacc + from . import lexer from . import utility from .scope import Scope from .color import Color from lesscpy.plib import * + class LessParser(object): precedence = ( - ('left', '+', '-'), - ('left', '*', '/'), + ('left', '+', '-'), + ('left', '*', '/'), ) - def __init__(self, - lex_optimize=True, - yacc_optimize=True, - tabfile='yacctab', - yacc_debug=False, - scope=None, - outputdir='/tmp', - importlvl=0, - verbose=False - ): + + def __init__(self, + lex_optimize=True, + yacc_optimize=True, + tabfile='yacctab', + yacc_debug=False, + scope=None, + outputdir='/tmp', + importlvl=0, + verbose=False + ): """ Parser object - + Kwargs: lex_optimize (bool): Optimize lexer yacc_optimize (bool): Optimize parser @@ -48,19 +53,19 @@ class LessParser(object): importlvl (int): Import depth verbose (bool): Verbose mode """ - self.verbose = verbose - self.importlvl = importlvl - self.lex = lexer.LessLexer() + self.verbose = verbose + self.importlvl = importlvl + self.lex = lexer.LessLexer() if not tabfile: tabfile = 'yacctab' - + self.ignored = ('css_comment', 'less_comment', 'css_vendor_hack') - - self.tokens = [t for t in self.lex.tokens + + self.tokens = [t for t in self.lex.tokens if t not in self.ignored] self.parser = ply.yacc.yacc( - module=self, + module=self, start='tunit', debug=yacc_debug, optimize=yacc_optimize, @@ -71,22 +76,24 @@ class LessParser(object): self.stash = {} self.result = None self.target = None - + def parse(self, filename='', debuglevel=0): """ Parse file. kwargs: filename (str): File to parse debuglevel (int): Parser debuglevel """ - if self.verbose: print('Compiling target: %s' % filename, file=sys.stderr) + if self.verbose: + print('Compiling target: %s' % filename, file=sys.stderr) self.scope.push() self.target = filename - self.result = self.parser.parse(filename, lexer=self.lex, debug=debuglevel) + self.result = self.parser.parse( + filename, lexer=self.lex, debug=debuglevel) self.post_parse() - + def post_parse(self): """ Post parse cycle. nodejs version allows calls to mixins - not yet defined or known to the parser. We defer all calls + not yet defined or known to the parser. We defer all calls to mixins until after first cycle when all names are known. """ if self.result: @@ -97,35 +104,35 @@ class LessParser(object): except SyntaxError as e: self.handle_error(e, 0) self.result = list(utility.flatten(out)) - + def scopemap(self): """ Output scopemap. """ utility.debug_print(self.result) - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_tunit(self, p): """ tunit : unit_list """ p[0] = [u for u in p[1] if u] - + def p_unit_list(self, p): """ unit_list : unit_list unit | unit """ - if type(p[1]) is list: + if isinstance(p[1], list): if len(p) >= 3: - if type(p[2]) is list: + if isinstance(p[2], list): p[1].extend(p[2]) else: p[1].append(p[2]) else: - p[1] = [p[1]] + p[1] = [p[1]] p[0] = p[1] - + def p_unit(self, p): """ unit : statement | variable_decl @@ -135,39 +142,41 @@ class LessParser(object): | import_statement """ p[0] = p[1] - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# - +# + def p_statement_aux(self, p): """ statement : css_charset t_ws css_string ';' | css_namespace t_ws css_string ';' """ p[0] = Statement(list(p)[1:], p.lineno(1)) p[0].parse(None) - + def p_statement_namespace(self, p): """ statement : css_namespace t_ws word css_string ';' """ p[0] = Statement(list(p)[1:], p.lineno(1)) p[0].parse(None) - + def p_statement_import(self, p): """ import_statement : css_import t_ws css_string ';' | css_import t_ws css_string dom ';' """ if self.importlvl > 8: - raise ImportError('Recrusive import level too deep > 8 (circular import ?)') + raise ImportError( + 'Recrusive import level too deep > 8 (circular import ?)') ipath = utility.destring(p[3]) 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' + 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 = LessParser(importlvl=self.importlvl + 1, verbose=self.verbose, scope=self.scope) recurse.parse(filename=filename, debuglevel=0) p[0] = recurse.result @@ -181,10 +190,10 @@ class LessParser(object): p[0] = Statement(list(p)[1:], p.lineno(1)) p[0].parse(None) sys.stdout.flush() - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_block(self, p): """ block_decl : block_open declaration_list brace_close @@ -192,7 +201,7 @@ class LessParser(object): p[0] = Block(list(p)[1:-1], p.lineno(3)) self.scope.pop() self.scope.add_block(p[0]) - + def p_block_replace(self, p): """ block_decl : identifier ';' """ @@ -203,7 +212,7 @@ class LessParser(object): else: # fallback to mixin. Allow calls to mixins without parens p[0] = Deferred(p[1], None, p.lineno(2)) - + def p_block_open(self, p): """ block_open : identifier brace_open """ @@ -213,7 +222,7 @@ class LessParser(object): pass p[0] = p[1] self.scope.current = p[1] - + def p_font_face_open(self, p): """ block_open : css_font_face t_ws brace_open """ @@ -221,7 +230,7 @@ class LessParser(object): # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_mixin(self, p): """ mixin_decl : open_mixin declaration_list brace_close @@ -241,12 +250,12 @@ class LessParser(object): p[0].append(p[5]) else: p[0].append(None) - + def p_mixin_guard(self, p): """ mixin_guard : less_when mixin_guard_cond_list """ p[0] = p[2] - + def p_mixin_guard_cond_list_aux(self, p): """ mixin_guard_cond_list : mixin_guard_cond_list ',' mixin_guard_cond | mixin_guard_cond_list less_and mixin_guard_cond @@ -254,24 +263,24 @@ class LessParser(object): p[1].append(p[2]) p[1].append(p[3]) p[0] = p[1] - + def p_mixin_guard_cond_list(self, p): """ mixin_guard_cond_list : mixin_guard_cond """ p[0] = [p[1]] - + def p_mixin_guard_cond_rev(self, p): """ mixin_guard_cond : less_not t_popen argument mixin_guard_cmp argument t_pclose | less_not t_popen argument t_pclose """ p[0] = utility.reverse_guard(list(p)[3:-1]) - + def p_mixin_guard_cond(self, p): """ mixin_guard_cond : t_popen argument mixin_guard_cmp argument t_pclose | t_popen argument t_pclose """ p[0] = list(p)[2:-1] - + def p_mixin_guard_cmp(self, p): """ mixin_guard_cmp : '>' | '<' @@ -282,24 +291,24 @@ class LessParser(object): | '<' '>' """ p[0] = ''.join(list(p)[1:]) - + def p_call_mixin(self, p): """ call_mixin : identifier t_popen mixin_args_list t_pclose ';' """ p[1].parse(None) p[0] = Deferred(p[1], p[3], p.lineno(4)) - + def p_mixin_args_arguments(self, p): """ mixin_args_list : less_arguments """ p[0] = [p[1]] - + def p_mixin_args_list_aux(self, p): """ mixin_args_list : mixin_args_list ',' mixin_args """ p[1].extend([p[3]]) p[0] = p[1] - + def p_mixin_args_list(self, p): """ mixin_args_list : mixin_args """ @@ -316,31 +325,31 @@ class LessParser(object): | mixin_kwarg """ p[0] = [p[1]] - + def p_mixin_args_empty(self, p): """ mixin_args : empty """ p[0] = None - + def p_mixin_kwarg(self, p): """ mixin_kwarg : variable ':' mixin_kwarg_arg_list """ p[0] = Variable(list(p)[1:], p.lineno(2)) - + def p_margument_list_aux(self, p): """ mixin_kwarg_arg_list : mixin_kwarg_arg_list argument """ p[1].extend(list(p)[2:]) p[0] = p[1] - + def p_margument_list(self, p): """ mixin_kwarg_arg_list : argument """ p[0] = [p[1]] - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_declaration_list(self, p): """ declaration_list : declaration_list declaration @@ -350,7 +359,7 @@ class LessParser(object): if len(p) > 2: p[1].extend(p[2]) p[0] = p[1] - + def p_declaration(self, p): """ declaration : variable_decl | property_decl @@ -359,11 +368,11 @@ class LessParser(object): | call_mixin | import_statement """ - p[0] = p[1] if type(p[1]) is list else [p[1]] - + p[0] = p[1] if isinstance(p[1], list) else [p[1]] + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_variable_decl(self, p): """ variable_decl : variable ':' style_list ';' @@ -371,10 +380,10 @@ class LessParser(object): p[0] = Variable(list(p)[1:-1], p.lineno(4)) p[0].parse(self.scope) self.scope.add_variable(p[0]) - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_property_decl(self, p): """ property_decl : prop_open style_list ';' @@ -382,29 +391,29 @@ class LessParser(object): | prop_open empty ';' """ l = len(p) - p[0] = Property(list(p)[1:-1], p.lineno(l-1)) - + p[0] = Property(list(p)[1:-1], p.lineno(l - 1)) + def p_property_decl_arguments(self, p): """ property_decl : prop_open less_arguments ';' """ p[0] = Property([p[1], [p[2]]], p.lineno(3)) - + def p_prop_open_ie_hack(self, p): """ prop_open : '*' prop_open """ p[0] = (p[1][0], p[2][0]) - + def p_prop_open(self, p): """ prop_open : property ':' | vendor_property ':' | word ':' """ p[0] = (p[1][0], '') - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# - +# + def p_style_list_aux(self, p): """ style_list : style_list style | style_list ',' style @@ -412,12 +421,12 @@ class LessParser(object): """ p[1].extend(list(p)[2:]) p[0] = p[1] - + def p_style_list(self, p): """ style_list : style """ p[0] = [p[1]] - + def p_style(self, p): """ style : expression | css_string @@ -429,10 +438,10 @@ class LessParser(object): | css_ms_filter """ p[0] = p[1] - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_identifier(self, p): """ identifier : identifier_list @@ -440,7 +449,7 @@ class LessParser(object): | page filter """ p[0] = Identifier(p[1], 0) - + def p_identifier_istr(self, p): """ identifier : t_popen '~' istring t_pclose """ @@ -452,18 +461,18 @@ class LessParser(object): p[1].extend([p[2]]) p[1].extend(p[3]) p[0] = p[1] - + def p_identifier_list(self, p): """ identifier_list : identifier_group """ p[0] = p[1] - + def p_identifier_list_keyframe(self, p): """ identifier_list : css_keyframes t_ws css_ident | css_keyframes t_ws css_ident t_ws """ p[0] = list(p)[1:] - + def p_identifier_group_op(self, p): """ identifier_group : identifier_group child_selector ident_parts | identifier_group '+' ident_parts @@ -471,38 +480,40 @@ class LessParser(object): | identifier_group '*' """ p[1].extend([p[2]]) - if len(p) > 3: p[1].extend(p[3]) + if len(p) > 3: + p[1].extend(p[3]) p[0] = p[1] - + def p_identifier_group(self, p): """ identifier_group : ident_parts """ p[0] = p[1] - + def p_ident_parts_aux(self, p): """ ident_parts : ident_parts ident_part | ident_parts filter_group """ - if type(p[2]) is list: + if isinstance(p[2], list): p[1].extend(p[2]) - else: p[1].append(p[2]) + else: + p[1].append(p[2]) p[0] = p[1] - + def p_ident_parts(self, p): """ ident_parts : ident_part | selector | filter_group """ - if type(p[1]) is not list: + if not isinstance(p[1], list): p[1] = [p[1]] p[0] = p[1] - + def p_ident_media(self, p): """ ident_parts : css_media t_ws | css_media t_ws t_popen word ':' number t_pclose """ p[0] = list(p)[1:] - + def p_selector(self, p): """ selector : '*' | '+' @@ -510,7 +521,7 @@ class LessParser(object): | general_sibling_selector """ p[0] = p[1] - + def p_ident_part(self, p): """ ident_part : class | id @@ -519,22 +530,22 @@ class LessParser(object): | color """ p[0] = p[1] - + def p_ident_part_aux(self, p): """ ident_part : combinator vendor_property """ p[0] = [p[1], p[2]] - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_filter_group_aux(self, p): """ filter_group : filter_group filter """ p[1].extend(p[2]) p[0] = p[1] - + def p_filter_group(self, p): """ filter_group : filter """ @@ -552,11 +563,11 @@ class LessParser(object): | ':' ':' vendor_property """ p[0] = list(p)[1:] - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# - +# + def p_fcall(self, p): """ fcall : word t_popen argument_list t_pclose | property t_popen argument_list t_pclose @@ -566,28 +577,28 @@ class LessParser(object): | '~' css_string """ p[0] = Call(list(p)[1:], 0) - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_argument_list_empty(self, p): """ argument_list : empty """ p[0] = '' - + def p_argument_list_aux(self, p): """ argument_list : argument_list argument | argument_list ',' argument """ p[1].extend(list(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 @@ -599,10 +610,10 @@ class LessParser(object): | fcall """ p[0] = p[1] - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_expression_aux(self, p): """ expression : expression '+' expression @@ -612,160 +623,161 @@ class LessParser(object): | word '/' expression """ p[0] = Expression(list(p)[1:], 0) - + 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): + + def p_factor(self, p): """ factor : color | number | variable | css_dom """ p[0] = p[1] - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_interpolated_str(self, p): """ istring : less_string """ p[0] = String(p[1], p.lineno(1)) - + def p_variable_neg(self, p): """ variable : '-' variable """ - p[0] = ['-', p[2]] - + 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 | less_variable t_ws """ # p[0] = p[1] - p[0] = tuple(list(p)[1:]) + p[0] = tuple(list(p)[1:]) def p_color(self, p): """ color : css_color | css_color t_ws """ try: - p[0] = Color().fmt(p[1]) - if len(p) > 2: p[0] = [p[0], p[2]] + p[0] = Color().fmt(p[1]) + if len(p) > 2: + p[0] = [p[0], p[2]] except ValueError: - self.handle_error('Illegal color value `%s`' % p[1], p.lineno(1), 'W') + self.handle_error( + 'Illegal color value `%s`' % p[1], p.lineno(1), 'W') p[0] = p[1] - + def p_number(self, p): """ number : css_number | css_number t_ws - """ - p[0] = tuple(list(p)[1:]) - + """ + p[0] = tuple(list(p)[1:]) + def p_dom(self, p): """ dom : css_dom | css_dom t_ws """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_word(self, p): """ word : css_ident | css_ident t_ws """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_class(self, p): """ class : css_class | css_class t_ws """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_id(self, p): """ id : css_id | css_id t_ws """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_property(self, p): """ property : css_property | css_property t_ws """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_page(self, p): """ page : css_page | css_page t_ws """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_vendor_property(self, p): """ vendor_property : css_vendor_property | css_vendor_property t_ws """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_combinator(self, p): """ combinator : '&' t_ws | '&' """ - p[0] = tuple(list(p)[1:]) - - + p[0] = tuple(list(p)[1:]) + def p_child_selector(self, p): """ child_selector : '>' t_ws | '>' """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_general_sibling_selector(self, p): """ general_sibling_selector : '~' t_ws | '~' """ - p[0] = tuple(list(p)[1:]) - + p[0] = tuple(list(p)[1:]) + def p_scope_open(self, p): """ brace_open : '{' """ self.scope.push() p[0] = p[1] - + def p_scope_close(self, p): """ brace_close : '}' """ p[0] = p[1] - + def p_empty(self, p): 'empty :' pass - + # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# def p_error(self, t): """ Internal error handler args: - t (Lex token): Error token + t (Lex token): Error token """ - if t: - print("\x1b[31mE: %s line: %d, Syntax Error, token: `%s`, `%s`\x1b[0m" + if t: + print("\x1b[31mE: %s line: %d, Syntax Error, token: `%s`, `%s`\x1b[0m" % (self.target, t.lineno, t.type, t.value), file=sys.stderr) while True: t = self.lex.token() @@ -775,7 +787,7 @@ class LessParser(object): break self.parser.restart() return t - + def handle_error(self, e, line, t='E'): """ Custom error handler args: @@ -785,5 +797,5 @@ class LessParser(object): """ # print(e.trace()) color = '\x1b[31m' if t == 'E' else '\x1b[33m' - print("%s%s: line: %d: %s\n" % (color, t, line, e), end='\x1b[0m', file=sys.stderr) - + print("%s%s: line: %d: %s\n" % + (color, t, line, e), end='\x1b[0m', file=sys.stderr) diff --git a/lesscpy/lessc/scope.py b/lesscpy/lessc/scope.py index 04b4566..f674fa4 100644 --- a/lesscpy/lessc/scope.py +++ b/lesscpy/lessc/scope.py @@ -1,17 +1,20 @@ """ .. module:: lesscpy.lessc.scope :synopsis: Scope class. - - + + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ from . import utility + class Scope(list): + """ Scope class. A stack implementation. """ + def __init__(self, init=False): """Scope Args: @@ -19,39 +22,39 @@ class Scope(list): """ super(Scope, self).__init__() self._mixins = {} - if init: self.push() + if init: + self.push() self.deferred = False self.real = [] - + def push(self): """Push level on scope """ self.append({ - '__variables__' : {}, - '__blocks__': [], + '__variables__': {}, + '__blocks__': [], '__names__': [], '__current__': None }) - + @property def current(self): return self[-1]['__current__'] - + @current.setter def current(self, value): self[-1]['__current__'] = value - + @property def scopename(self): """Current scope name as list Returns: list """ - return [r['__current__'] - for r in self + return [r['__current__'] + for r in self if r['__current__']] - def add_block(self, block): """Add block element to scope Args: @@ -59,7 +62,7 @@ class Scope(list): """ self[-1]['__blocks__'].append(block) self[-1]['__names__'].append(block.raw()) - + def add_mixin(self, mixin): """Add mixin to scope Args: @@ -70,14 +73,14 @@ class Scope(list): self._mixins[raw].append(mixin) else: self._mixins[raw] = [mixin] - + def add_variable(self, variable): """Add variable to scope Args: variable (Variable): Variable object """ self[-1]['__variables__'][variable.name] = variable - + def variables(self, name): """Search for variable by name. Searches scope top down Args: @@ -85,7 +88,7 @@ class Scope(list): Returns: Variable object OR False """ - if type(name) is tuple: + if isinstance(name, tuple): name = name[0] i = len(self) while i >= 0: @@ -93,7 +96,7 @@ class Scope(list): if name in self[i]['__variables__']: return self[i]['__variables__'][name] return False - + def mixins(self, name): """ Search mixins for name. Allow '>' to be ignored. '.a .b()' == '.a > .b()' @@ -103,16 +106,17 @@ class Scope(list): Mixin object list OR False """ m = self._smixins(name) - if m: return m + if m: + return m return self._smixins(name.replace('?>?', ' ')) - + def _smixins(self, name): """Inner wrapper to search for mixins by name. """ - return (self._mixins[name] + return (self._mixins[name] if name in self._mixins else False) - + def blocks(self, name): """ Search for defined blocks recursively. @@ -123,9 +127,10 @@ class Scope(list): Block object OR False """ b = self._blocks(name) - if b: return b + if b: + return b return self._blocks(name.replace('?>?', ' ')) - + def _blocks(self, name): """Inner wrapper to search for blocks by name. """ @@ -142,9 +147,10 @@ class Scope(list): r = b.raw() if r and name.startswith(r): b = utility.blocksearch(b, name) - if b: return b + if b: + return b return False - + def update(self, scope, at=0): """Update scope. Add another scope to this one. Args: @@ -157,7 +163,7 @@ class Scope(list): self[at]['__variables__'].update(scope[at]['__variables__']) self[at]['__blocks__'].extend(scope[at]['__blocks__']) self[at]['__names__'].extend(scope[at]['__names__']) - + def swap(self, name): """ Swap variable name for variable value Args: @@ -167,11 +173,10 @@ class Scope(list): """ if name.startswith('@@'): var = self.variables(name[1:]) - if var is False: + if var is False: raise SyntaxError('Unknown variable %s' % name) name = '@' + utility.destring(var.value[0]) var = self.variables(name) - if var is False: + if var is False: raise SyntaxError('Unknown variable %s' % name) return var.value - diff --git a/lesscpy/lessc/utility.py b/lesscpy/lessc/utility.py index 4d15bfb..33977a4 100644 --- a/lesscpy/lessc/utility.py +++ b/lesscpy/lessc/utility.py @@ -2,19 +2,23 @@ """ .. module:: lesscpy.lessc.utility :synopsis: various utility functions - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ + +from __future__ import print_function + import collections import re + def flatten(lst): """Flatten list. Args: lst (list): List to flatten - Returns: + Returns: generator """ for elm in lst: @@ -23,7 +27,8 @@ def flatten(lst): yield sub else: yield elm - + + def pairwise(lst): """ yield item i and item i+1 in lst. e.g. (lst[0], lst[1]), (lst[1], lst[2]), ..., (lst[-1], None) @@ -32,29 +37,31 @@ def pairwise(lst): Returns: list """ - if not lst: + if not lst: return length = len(lst) - for i in range(length-1): - yield lst[i], lst[i+1] + for i in range(length - 1): + yield lst[i], lst[i + 1] yield lst[-1], None - + + def rename(blocks, scope, stype): - """ Rename all sub-blocks moved under another + """ Rename all sub-blocks moved under another block. (mixins) Args: lst (list): block list scope (object): Scope object """ for p in blocks: - if type(p) is stype: + if isinstance(p, stype): p.tokens[0].parse(scope) - if p.tokens[1]: + if p.tokens[1]: scope.push() scope.current = p.tokens[0] rename(p.tokens[1], scope, stype) scope.pop() - + + def blocksearch(block, name): """ Recursive search for name in block (inner blocks) Args: @@ -64,13 +71,15 @@ def blocksearch(block, name): """ if hasattr(block, 'tokens'): for b in block.tokens[1]: - b = (b if hasattr(b, 'raw') and b.raw() == name + b = (b if hasattr(b, 'raw') and b.raw() == name else blocksearch(b, name)) - if b: return b + if b: + return b return False + def reverse_guard(lst): - """ Reverse guard expression. not + """ Reverse guard expression. not (@a > 5) -> (@a <= 5) Args: lst (list): Expression @@ -87,6 +96,7 @@ def reverse_guard(lst): } return [rev[l] if l in rev else l for l in lst] + def debug_print(lst, lvl=0): """ Print scope tree args: @@ -99,8 +109,9 @@ def debug_print(lst, lvl=0): for p in lst: debug_print(p, lvl) elif hasattr(lst, 'tokens'): - print(pad, t) - debug_print(list(flatten(lst.tokens)), lvl+1) + print(pad, t) + debug_print(list(flatten(lst.tokens)), lvl + 1) + def destring(value): """ Strip quotes from string @@ -111,6 +122,7 @@ def destring(value): """ return value.strip('"\'') + def analyze_number(var, err=''): """ Analyse number for type and split from unit 1px -> (q, 'px') @@ -124,7 +136,7 @@ def analyze_number(var, err=''): tuple """ n, u = split_unit(var) - if type(var) is not str: + if not isinstance(var, str): return (var, u) if is_color(var): return (var, 'color') @@ -136,6 +148,7 @@ def analyze_number(var, err=''): raise SyntaxError('%s ´%s´' % (err, var)) return (n, u) + def with_unit(number, unit=None): """ Return number with unit args: @@ -144,17 +157,18 @@ def with_unit(number, unit=None): returns: str """ - if type(number) is tuple: + if isinstance(number, tuple): number, unit = number - if number == 0: + if number == 0: return '0' if unit: number = str(number) if number.startswith('.'): number = '0' + number return "%s%s" % (number, unit) - return number if type(number) is str else str(number) - + return number if isinstance(number, str) else str(number) + + def is_color(value): """ Is string CSS color args: @@ -162,7 +176,7 @@ def is_color(value): returns: bool """ - if not value or type(value) is not str: + if not value or not isinstance(value, str): return False if value[0] == '#' and len(value) in [4, 5, 7, 9]: try: @@ -171,7 +185,8 @@ def is_color(value): except ValueError: pass return False - + + def is_variable(value): """ Check if string is LESS variable args: @@ -179,13 +194,14 @@ def is_variable(value): returns: bool """ - if type(value) is str: + if isinstance(value, str): return (value.startswith('@') or value.startswith('-@')) - elif type(value) is tuple: + elif isinstance(value, tuple): value = ''.join(value) return (value.startswith('@') or value.startswith('-@')) return False + def is_int(value): """ Is value integer args: @@ -200,6 +216,7 @@ def is_int(value): pass return False + def is_float(value): """ Is value float args: @@ -215,6 +232,7 @@ def is_float(value): pass return False + def split_unit(value): """ Split a number from its unit 1px -> (q, 'px') @@ -224,7 +242,4 @@ def split_unit(value): tuple """ r = re.search('^(\-?[\d\.]+)(.*)$', str(value)) - return r.groups() if r else ('','') - - - \ No newline at end of file + return r.groups() if r else ('', '') diff --git a/lesscpy/lib/colors.py b/lesscpy/lib/colors.py index a087a6b..4266e07 100644 --- a/lesscpy/lib/colors.py +++ b/lesscpy/lib/colors.py @@ -1,151 +1,151 @@ """ """ lessColors = { - 'aliceblue':'#f0f8ff', - 'antiquewhite':'#faebd7', - 'aqua':'#00ffff', - 'aquamarine':'#7fffd4', - 'azure':'#f0ffff', - 'beige':'#f5f5dc', - 'bisque':'#ffe4c4', - 'black':'#000000', - 'blanchedalmond':'#ffebcd', - 'blue':'#0000ff', - 'blueviolet':'#8a2be2', - 'brown':'#a52a2a', - 'burlywood':'#deb887', - 'cadetblue':'#5f9ea0', - 'chartreuse':'#7fff00', - 'chocolate':'#d2691e', - 'coral':'#ff7f50', - 'cornflowerblue':'#6495ed', - 'cornsilk':'#fff8dc', - 'crimson':'#dc143c', - 'cyan':'#00ffff', - 'darkblue':'#00008b', - 'darkcyan':'#008b8b', - 'darkgoldenrod':'#b8860b', - 'darkgray':'#a9a9a9', - 'darkgrey':'#a9a9a9', - 'darkgreen':'#006400', - 'darkkhaki':'#bdb76b', - 'darkmagenta':'#8b008b', - 'darkolivegreen':'#556b2f', - 'darkorange':'#ff8c00', - 'darkorchid':'#9932cc', - 'darkred':'#8b0000', - 'darksalmon':'#e9967a', - 'darkseagreen':'#8fbc8f', - 'darkslateblue':'#483d8b', - 'darkslategray':'#2f4f4f', - 'darkslategrey':'#2f4f4f', - 'darkturquoise':'#00ced1', - 'darkviolet':'#9400d3', - 'deeppink':'#ff1493', - 'deepskyblue':'#00bfff', - 'dimgray':'#696969', - 'dimgrey':'#696969', - 'dodgerblue':'#1e90ff', - 'firebrick':'#b22222', - 'floralwhite':'#fffaf0', - 'forestgreen':'#228b22', - 'fuchsia':'#ff00ff', - 'gainsboro':'#dcdcdc', - 'ghostwhite':'#f8f8ff', - 'gold':'#ffd700', - 'goldenrod':'#daa520', - 'gray':'#808080', - 'grey':'#808080', - 'green':'#008000', - 'greenyellow':'#adff2f', - 'honeydew':'#f0fff0', - 'hotpink':'#ff69b4', - 'indianred':'#cd5c5c', - 'indigo':'#4b0082', - 'ivory':'#fffff0', - 'khaki':'#f0e68c', - 'lavender':'#e6e6fa', - 'lavenderblush':'#fff0f5', - 'lawngreen':'#7cfc00', - 'lemonchiffon':'#fffacd', - 'lightblue':'#add8e6', - 'lightcoral':'#f08080', - 'lightcyan':'#e0ffff', - 'lightgoldenrodyellow':'#fafad2', - 'lightgray':'#d3d3d3', - 'lightgrey':'#d3d3d3', - 'lightgreen':'#90ee90', - 'lightpink':'#ffb6c1', - 'lightsalmon':'#ffa07a', - 'lightseagreen':'#20b2aa', - 'lightskyblue':'#87cefa', - 'lightslategray':'#778899', - 'lightslategrey':'#778899', - 'lightsteelblue':'#b0c4de', - 'lightyellow':'#ffffe0', - 'lime':'#00ff00', - 'limegreen':'#32cd32', - 'linen':'#faf0e6', - 'magenta':'#ff00ff', - 'maroon':'#800000', - 'mediumaquamarine':'#66cdaa', - 'mediumblue':'#0000cd', - 'mediumorchid':'#ba55d3', - 'mediumpurple':'#9370d8', - 'mediumseagreen':'#3cb371', - 'mediumslateblue':'#7b68ee', - 'mediumspringgreen':'#00fa9a', - 'mediumturquoise':'#48d1cc', - 'mediumvioletred':'#c71585', - 'midnightblue':'#191970', - 'mintcream':'#f5fffa', - 'mistyrose':'#ffe4e1', - 'moccasin':'#ffe4b5', - 'navajowhite':'#ffdead', - 'navy':'#000080', - 'oldlace':'#fdf5e6', - 'olive':'#808000', - 'olivedrab':'#6b8e23', - 'orange':'#ffa500', - 'orangered':'#ff4500', - 'orchid':'#da70d6', - 'palegoldenrod':'#eee8aa', - 'palegreen':'#98fb98', - 'paleturquoise':'#afeeee', - 'palevioletred':'#d87093', - 'papayawhip':'#ffefd5', - 'peachpuff':'#ffdab9', - 'peru':'#cd853f', - 'pink':'#ffc0cb', - 'plum':'#dda0dd', - 'powderblue':'#b0e0e6', - 'purple':'#800080', - 'red':'#ff0000', - 'rosybrown':'#bc8f8f', - 'royalblue':'#4169e1', - 'saddlebrown':'#8b4513', - 'salmon':'#fa8072', - 'sandybrown':'#f4a460', - 'seagreen':'#2e8b57', - 'seashell':'#fff5ee', - 'sienna':'#a0522d', - 'silver':'#c0c0c0', - 'skyblue':'#87ceeb', - 'slateblue':'#6a5acd', - 'slategray':'#708090', - 'slategrey':'#708090', - 'snow':'#fffafa', - 'springgreen':'#00ff7f', - 'steelblue':'#4682b4', - 'tan':'#d2b48c', - 'teal':'#008080', - 'thistle':'#d8bfd8', - 'tomato':'#ff6347', - 'turquoise':'#40e0d0', - 'violet':'#ee82ee', - 'wheat':'#f5deb3', - 'white':'#ffffff', - 'whitesmoke':'#f5f5f5', - 'yellow':'#ffff00', - 'yellowgreen':'#9acd32' -} \ No newline at end of file + 'aliceblue': '#f0f8ff', + 'antiquewhite': '#faebd7', + 'aqua': '#00ffff', + 'aquamarine': '#7fffd4', + 'azure': '#f0ffff', + 'beige': '#f5f5dc', + 'bisque': '#ffe4c4', + 'black': '#000000', + 'blanchedalmond': '#ffebcd', + 'blue': '#0000ff', + 'blueviolet': '#8a2be2', + 'brown': '#a52a2a', + 'burlywood': '#deb887', + 'cadetblue': '#5f9ea0', + 'chartreuse': '#7fff00', + 'chocolate': '#d2691e', + 'coral': '#ff7f50', + 'cornflowerblue': '#6495ed', + 'cornsilk': '#fff8dc', + 'crimson': '#dc143c', + 'cyan': '#00ffff', + 'darkblue': '#00008b', + 'darkcyan': '#008b8b', + 'darkgoldenrod': '#b8860b', + 'darkgray': '#a9a9a9', + 'darkgrey': '#a9a9a9', + 'darkgreen': '#006400', + 'darkkhaki': '#bdb76b', + 'darkmagenta': '#8b008b', + 'darkolivegreen': '#556b2f', + 'darkorange': '#ff8c00', + 'darkorchid': '#9932cc', + 'darkred': '#8b0000', + 'darksalmon': '#e9967a', + 'darkseagreen': '#8fbc8f', + 'darkslateblue': '#483d8b', + 'darkslategray': '#2f4f4f', + 'darkslategrey': '#2f4f4f', + 'darkturquoise': '#00ced1', + 'darkviolet': '#9400d3', + 'deeppink': '#ff1493', + 'deepskyblue': '#00bfff', + 'dimgray': '#696969', + 'dimgrey': '#696969', + 'dodgerblue': '#1e90ff', + 'firebrick': '#b22222', + 'floralwhite': '#fffaf0', + 'forestgreen': '#228b22', + 'fuchsia': '#ff00ff', + 'gainsboro': '#dcdcdc', + 'ghostwhite': '#f8f8ff', + 'gold': '#ffd700', + 'goldenrod': '#daa520', + 'gray': '#808080', + 'grey': '#808080', + 'green': '#008000', + 'greenyellow': '#adff2f', + 'honeydew': '#f0fff0', + 'hotpink': '#ff69b4', + 'indianred': '#cd5c5c', + 'indigo': '#4b0082', + 'ivory': '#fffff0', + 'khaki': '#f0e68c', + 'lavender': '#e6e6fa', + 'lavenderblush': '#fff0f5', + 'lawngreen': '#7cfc00', + 'lemonchiffon': '#fffacd', + 'lightblue': '#add8e6', + 'lightcoral': '#f08080', + 'lightcyan': '#e0ffff', + 'lightgoldenrodyellow': '#fafad2', + 'lightgray': '#d3d3d3', + 'lightgrey': '#d3d3d3', + 'lightgreen': '#90ee90', + 'lightpink': '#ffb6c1', + 'lightsalmon': '#ffa07a', + 'lightseagreen': '#20b2aa', + 'lightskyblue': '#87cefa', + 'lightslategray': '#778899', + 'lightslategrey': '#778899', + 'lightsteelblue': '#b0c4de', + 'lightyellow': '#ffffe0', + 'lime': '#00ff00', + 'limegreen': '#32cd32', + 'linen': '#faf0e6', + 'magenta': '#ff00ff', + 'maroon': '#800000', + 'mediumaquamarine': '#66cdaa', + 'mediumblue': '#0000cd', + 'mediumorchid': '#ba55d3', + 'mediumpurple': '#9370d8', + 'mediumseagreen': '#3cb371', + 'mediumslateblue': '#7b68ee', + 'mediumspringgreen': '#00fa9a', + 'mediumturquoise': '#48d1cc', + 'mediumvioletred': '#c71585', + 'midnightblue': '#191970', + 'mintcream': '#f5fffa', + 'mistyrose': '#ffe4e1', + 'moccasin': '#ffe4b5', + 'navajowhite': '#ffdead', + 'navy': '#000080', + 'oldlace': '#fdf5e6', + 'olive': '#808000', + 'olivedrab': '#6b8e23', + 'orange': '#ffa500', + 'orangered': '#ff4500', + 'orchid': '#da70d6', + 'palegoldenrod': '#eee8aa', + 'palegreen': '#98fb98', + 'paleturquoise': '#afeeee', + 'palevioletred': '#d87093', + 'papayawhip': '#ffefd5', + 'peachpuff': '#ffdab9', + 'peru': '#cd853f', + 'pink': '#ffc0cb', + 'plum': '#dda0dd', + 'powderblue': '#b0e0e6', + 'purple': '#800080', + 'red': '#ff0000', + 'rosybrown': '#bc8f8f', + 'royalblue': '#4169e1', + 'saddlebrown': '#8b4513', + 'salmon': '#fa8072', + 'sandybrown': '#f4a460', + 'seagreen': '#2e8b57', + 'seashell': '#fff5ee', + 'sienna': '#a0522d', + 'silver': '#c0c0c0', + 'skyblue': '#87ceeb', + 'slateblue': '#6a5acd', + 'slategray': '#708090', + 'slategrey': '#708090', + 'snow': '#fffafa', + 'springgreen': '#00ff7f', + 'steelblue': '#4682b4', + 'tan': '#d2b48c', + 'teal': '#008080', + 'thistle': '#d8bfd8', + 'tomato': '#ff6347', + 'turquoise': '#40e0d0', + 'violet': '#ee82ee', + 'wheat': '#f5deb3', + 'white': '#ffffff', + 'whitesmoke': '#f5f5f5', + 'yellow': '#ffff00', + 'yellowgreen': '#9acd32' +} diff --git a/lesscpy/lib/css.py b/lesscpy/lib/css.py index 6938f56..cf95d07 100644 --- a/lesscpy/lib/css.py +++ b/lesscpy/lib/css.py @@ -1,6 +1,6 @@ """ CSS syntax names. - + Copyright (c) See LICENSE for details. @@ -123,51 +123,51 @@ css2 = [ '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', + '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', + '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', @@ -205,7 +205,7 @@ css3 = [ 'line-stacking-ruby', 'line-stacking-shift', 'line-stacking-strategy', -# 'mark', + # 'mark', 'mark-after', 'mark-before', 'marks', diff --git a/lesscpy/lib/dom.py b/lesscpy/lib/dom.py index 3ad30e6..2569d84 100644 --- a/lesscpy/lib/dom.py +++ b/lesscpy/lib/dom.py @@ -1,6 +1,6 @@ """ HTML DOM names - + Copyright (c) See LICENSE for details. @@ -58,7 +58,7 @@ html4 = [ 'label', 'legend', 'li', -# 'link', + # 'link', 'map', 'mark', 'menu', @@ -97,7 +97,7 @@ html4 = [ 'u', 'ul', 'var', - + 'print', 'screen', 'all', @@ -127,7 +127,7 @@ html5 = [ 'nav', 'output', 'progress', - ' progress-bar-stripes', + ' progress-bar-stripes', 'rp', 'rt', 'ruby', @@ -141,4 +141,4 @@ html5 = [ 'wbr', ] html = html4 -html.extend(html5) \ No newline at end of file +html.extend(html5) diff --git a/lesscpy/plib/__init__.py b/lesscpy/plib/__init__.py index 4b22cd7..fe6d5fe 100644 --- a/lesscpy/plib/__init__.py +++ b/lesscpy/plib/__init__.py @@ -2,23 +2,23 @@ """ .. module:: lesscpy.plib :synopsis: Parse Nodes for Lesscpy - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ __all__ = [ - 'Block', - 'Call', - 'Deferred', - 'Expression', - 'Identifier', - 'Mixin', - 'Node', - 'Property', - 'Statement', - 'String', - 'Variable' + 'Block', + 'Call', + 'Deferred', + 'Expression', + 'Identifier', + 'Mixin', + 'Node', + 'Property', + 'Statement', + 'String', + 'Variable' ] from .block import Block from .call import Call @@ -30,4 +30,4 @@ from .node import Node from .property import Property from .statement import Statement from .string import String -from .variable import Variable \ No newline at end of file +from .variable import Variable diff --git a/lesscpy/plib/block.py b/lesscpy/plib/block.py index 653cf06..9b0b916 100644 --- a/lesscpy/plib/block.py +++ b/lesscpy/plib/block.py @@ -2,7 +2,7 @@ """ .. module:: lesscpy.plib.block :synopsis: Block parse node. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson @@ -11,7 +11,9 @@ import re from .node import Node from lesscpy.lessc import utility + class Block(Node): + """ Block node. Represents one parse-block. Can contain property nodes or other block nodes. identifier { @@ -19,6 +21,7 @@ class Block(Node): inner blocks } """ + def parse(self, scope): """Parse block node. args: @@ -35,14 +38,15 @@ class Block(Node): scope.real.append(self.name) if not self.name.parsed: self.name.parse(scope) - if not inner: inner = [] + if not inner: + inner = [] inner = list(utility.flatten([p.parse(scope) for p in inner if p])) - self.parsed = [p for p in inner if p and type(p) is not Block] - self.inner = [p for p in inner if p and type(p) is Block] + self.parsed = [p for p in inner if p and not isinstance(p, Block)] + self.inner = [p for p in inner if p and isinstance(p, Block)] scope.real.pop() scope.pop() return self - + def raw(self, clean=False): """Raw block name args: @@ -54,7 +58,7 @@ class Block(Node): return self.tokens[0].raw(clean) except (AttributeError, TypeError): pass - + def fmt(self, fills): """Format block (CSS) args: @@ -72,9 +76,9 @@ class Block(Node): }) out.append(f % fills) if hasattr(self, 'inner'): - if self.name.subparse: # @media + if self.name.subparse: # @media inner = ''.join([p.fmt(fills) for p in self.inner]) - inner = inner.replace(fills['nl'], + inner = inner.replace(fills['nl'], fills['nl'] + fills['tab']).rstrip(fills['tab']) if not fills['nl']: inner = inner.strip() @@ -86,7 +90,7 @@ class Block(Node): else: out.append(''.join([p.fmt(fills) for p in self.inner])) return ''.join(out) - + def copy(self): """ Return a full copy of self returns: Block object @@ -98,12 +102,12 @@ class Block(Node): if name: name = name.copy() return Block([name, inner], 0) - + def copy_inner(self, scope): - """Copy block contents (properties, inner blocks). + """Copy block contents (properties, inner blocks). Renames inner block from current scope. Used for mixins. - args: + args: scope (Scope): Current scope returns: list (block contents) diff --git a/lesscpy/plib/call.py b/lesscpy/plib/call.py index e6485ad..0fb4137 100644 --- a/lesscpy/plib/call.py +++ b/lesscpy/plib/call.py @@ -2,12 +2,13 @@ """ .. module:: lesscpy.plib.call :synopsis: Call parse node - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ -import re, math +import re +import math try: from urllib.parse import quote as urlquote, urlparse except ImportError: @@ -17,7 +18,9 @@ import lesscpy.lessc.utility as utility import lesscpy.lessc.color as Color from lesscpy.lib.colors import lessColors + class Call(Node): + """Call node. Node represents a function call. All builtin none-color functions are in this node. This node attempts calls on built-ins and lets non-builtins @@ -25,7 +28,7 @@ class Call(Node): increment(3px) --> 4px unknown(3px) --> unknown(3px) """ - + def parse(self, scope): """Parse Node within scope. the functions ~( and e( map to self.escape @@ -41,14 +44,14 @@ class Call(Node): elif name in ('~', 'e'): name = 'escape' color = Color.Color() - args = [t for t in parsed - if type(t) is not str or t not in '(),'] + args = [t for t in parsed + if not isinstance(t, str) or t not in '(),'] if hasattr(self, name): try: return getattr(self, name)(*args) except ValueError: pass - + if hasattr(color, name): try: result = getattr(color, name)(*args) @@ -59,7 +62,7 @@ class Call(Node): except ValueError: pass return name + ''.join([p for p in parsed]) - + def escape(self, string, *args): """Less Escape. args: @@ -68,7 +71,7 @@ class Call(Node): str """ return utility.destring(string.strip('~')) - + def sformat(self, string, *args): """ String format. args: @@ -85,15 +88,15 @@ class Call(Node): i = 0 for n in m: v = { - '%A' : urlquote, - '%s' : utility.destring, + '%A': urlquote, + '%s': utility.destring, }.get(n, str)(args[i]) items.append(v) i += 1 format = format.replace('%A', '%s') format = format.replace('%d', '%s') return format % tuple(items) - + def isnumber(self, string, *args): """Is number args: @@ -106,7 +109,7 @@ class Call(Node): except SyntaxError as e: return False return True - + def iscolor(self, string, *args): """Is color args: @@ -115,7 +118,7 @@ class Call(Node): bool """ return (string in lessColors) - + def isurl(self, string, *args): """Is url args: @@ -126,13 +129,15 @@ class Call(Node): arg = utility.destring(string) regex = re.compile(r'^(?:http|ftp)s?://' # http:// or https:// r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+' - r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain... - r'localhost|' #localhost... + r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain... + # localhost... + r'localhost|' r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip - r'(?::\d+)?' # optional port + # optional port + r'(?::\d+)?' r'(?:/?|[/?]\S+)$', re.IGNORECASE) return regex.match(arg) - + def isstring(self, string, *args): """Is string args: @@ -142,7 +147,7 @@ class Call(Node): """ regex = re.compile(r'\'[^\']*\'|"[^"]*"') return regex.match(string) - + def iskeyword(self, string, *args): """Is less keyword args: @@ -151,7 +156,7 @@ class Call(Node): bool """ return (string in ('when', 'and', 'not')) - + def increment(self, value, *args): """ Increment function args: @@ -160,8 +165,8 @@ class Call(Node): str """ n, u = utility.analyze_number(value) - return utility.with_unit(n+1, u) - + return utility.with_unit(n + 1, u) + def decrement(self, value, *args): """ Decrement function args: @@ -170,8 +175,8 @@ class Call(Node): str """ n, u = utility.analyze_number(value) - return utility.with_unit(n-1, u) - + return utility.with_unit(n - 1, u) + def add(self, *args): """ Add integers args: @@ -179,9 +184,10 @@ class Call(Node): returns: str """ - if(len(args) <= 1): return 0 + if(len(args) <= 1): + return 0 return sum([int(v) for v in args]) - + def round(self, value, *args): """ Round number args: @@ -191,7 +197,7 @@ class Call(Node): """ n, u = utility.analyze_number(value) return utility.with_unit(round(float(n)), u) - + def ceil(self, value, *args): """ Ceil number args: @@ -201,7 +207,7 @@ class Call(Node): """ n, u = utility.analyze_number(value) return utility.with_unit(math.ceil(n), u) - + def floor(self, value, *args): """ Floor number args: @@ -211,7 +217,7 @@ class Call(Node): """ n, u = utility.analyze_number(value) return utility.with_unit(math.floor(n), u) - + def percentage(self, value, *args): """ Return percentage value args: diff --git a/lesscpy/plib/deferred.py b/lesscpy/plib/deferred.py index 52203d0..dfdf58d 100644 --- a/lesscpy/plib/deferred.py +++ b/lesscpy/plib/deferred.py @@ -2,7 +2,7 @@ """ .. module:: lesscpy.plib.deferred :synopsis: Deferred mixin call. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson @@ -10,11 +10,13 @@ from .node import Node from lesscpy.lessc import utility + class Deferred(Node): + def __init__(self, mixin, args, lineno=0): """This node represents mixin calls. The calls - to these mixins are deferred until the second - parse cycle. lessc.js allows calls to mixins not + to these mixins are deferred until the second + parse cycle. lessc.js allows calls to mixins not yet defined or known. args: mixin (Mixin): Mixin object @@ -22,18 +24,18 @@ class Deferred(Node): """ self.tokens = [mixin, args] self.lineno = lineno - + def parse(self, scope, error=False): """ Parse function. We search for mixins first within current scope then fallback to global scope. The special scope.deferred - is used when local scope mixins are called - within parent mixins. + is used when local scope mixins are called + within parent mixins. If nothing is found we fallback to block-mixin as lessc.js allows calls to blocks and mixins to be inter-changable. - clx: This method is a HACK that stems from - poor design elsewhere. I will fix it + clx: This method is a HACK that stems from + poor design elsewhere. I will fix it when I have more time. args: scope (Scope): Current scope @@ -44,11 +46,11 @@ class Deferred(Node): ident, args = self.tokens ident.parse(scope) mixins = scope.mixins(ident.raw()) - + if not mixins: ident.parse(None) mixins = scope.mixins(ident.raw()) - + if not mixins: if scope.deferred: store = [t for t in scope.deferred.parsed[-1]] @@ -61,7 +63,7 @@ class Deferred(Node): break scope.deferred.parsed[-1].pop() scope.deferred.parsed[-1] = store - + if not mixins: # Fallback to blocks block = scope.blocks(ident.raw()) @@ -72,34 +74,34 @@ class Deferred(Node): scope.current = scope.real[-1] if scope.real else None res = block.copy_inner(scope) scope.current = None - + if mixins: for mixin in mixins: scope.current = scope.real[-1] if scope.real else None res = mixin.call(scope, args) - if res: + if res: # Add variables to scope to support # closures [scope.add_variable(v) for v in mixin.vars] scope.deferred = ident break - + if res: - store = [t for t in scope.deferred.parsed[-1]] if scope.deferred else False + store = [t for t in scope.deferred.parsed[ + -1]] if scope.deferred else False res = [p.parse(scope) for p in res if p] - while(any(t for t in res if type(t) is Deferred)): + while(any(t for t in res if isinstance(t, Deferred))): res = [p.parse(scope) for p in res if p] - if store: scope.deferred.parsed[-1] = store - + if store: + scope.deferred.parsed[-1] = store + if error and not res: raise SyntaxError('NameError `%s`' % ident.raw(True)) return res - + def copy(self): """ Returns self (used when Block objects are copy'd) returns: self """ return self - - diff --git a/lesscpy/plib/expression.py b/lesscpy/plib/expression.py index 28cf08a..6bd27ce 100644 --- a/lesscpy/plib/expression.py +++ b/lesscpy/plib/expression.py @@ -2,7 +2,7 @@ """ .. module:: lesscpy.plib.expression :synopsis: Expression node. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson @@ -12,11 +12,13 @@ from .node import Node from lesscpy.lessc import utility from lesscpy.lessc import color + class Expression(Node): + """Expression node. Parses all expression except color expressions, (handled in the color class) """ - + def parse(self, scope): """ Parse Node args: @@ -29,9 +31,9 @@ class Expression(Node): assert(len(self.tokens) == 3) expr = self.process(self.tokens, scope) expr = [self.neg(t, scope) for t in expr] - A, O, B = [e[0] - if type(e) is tuple - else e + A, O, B = [e[0] + if isinstance(e, tuple) + else e for e in expr if str(e).strip()] try: @@ -44,10 +46,10 @@ class Expression(Node): if ua == 'color' or ub == 'color': return color.Color().process((A, O, B)) out = self.operate(a, b, O) - if type(out) is bool: + if isinstance(out, bool): return out return self.with_units(out, ua, ub) - + def neg(self, value, scope): """Apply negativity. args: @@ -58,17 +60,17 @@ class Expression(Node): returns: str """ - if value and type(value) is list and value[0] == '-': + if value and isinstance(value, list) and value[0] == '-': val = value[1] if len(value) > 1 and hasattr(value[1], 'parse'): val = value[1].parse(scope) - if type(val) is str: + if isinstance(val, str): return '-' + val return -val return value - + def with_units(self, val, ua, ub): - """Return value with unit. + """Return value with unit. args: val (mixed): result ua (str): 1st unit @@ -78,13 +80,14 @@ class Expression(Node): returns: str """ - if not val: return str(val) + if not val: + return str(val) if ua or ub: if ua and ub: if ua == ub: return str(val) + ua else: - # Nodejs version does not seem to mind mismatched + # Nodejs version does not seem to mind mismatched # units within expressions. So we choose the first # as they do # raise SyntaxError("Error in expression %s != %s" % (ua, ub)) @@ -94,7 +97,7 @@ class Expression(Node): elif ub: return str(val) + ub return str(val) - + def operate(self, vala, valb, oper): """Perform operation args: @@ -130,11 +133,11 @@ class Expression(Node): except ValueError: pass return ret - + def py2op(self, vala, operation, valb): """ Python2 operators """ - if operation == '__lt__': + if operation == '__lt__': ret = (vala < valb) elif operation == '__gt__': ret = (vala > valb) @@ -149,7 +152,7 @@ class Expression(Node): else: ret = getattr(vala, operation)(valb) return ret - + def expression(self): """Return str representation of expression returns: diff --git a/lesscpy/plib/identifier.py b/lesscpy/plib/identifier.py index 3ed0015..c09c26f 100644 --- a/lesscpy/plib/identifier.py +++ b/lesscpy/plib/identifier.py @@ -2,7 +2,7 @@ """ .. module:: lesscpy.plib.identifier :synopsis: Identifier node. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson @@ -10,10 +10,13 @@ import re from .node import Node from lesscpy.lessc import utility + + class Identifier(Node): + """Identifier node. Represents block identifier. """ - + def parse(self, scope): """Parse node. Block identifiers are stored as strings with spaces replaced with ? @@ -24,15 +27,15 @@ class Identifier(Node): returns: self """ - names = [] - name = [] - self._subp = ( - '@media', '@keyframes', + names = [] + name = [] + self._subp = ( + '@media', '@keyframes', '@-moz-keyframes', '@-webkit-keyframes', '@-ms-keyframes' ) if self.tokens and hasattr(self.tokens, 'parse'): - self.tokens = list(utility.flatten([id.split() + [','] + self.tokens = list(utility.flatten([id.split() + [','] for id in self.tokens.parse(scope).split(',')])) self.tokens.pop() if self.tokens and self.tokens[0] in self._subp: @@ -54,11 +57,11 @@ class Identifier(Node): name.append(n) names.append(name) parsed = self.root(scope, names) if scope else names - self.parsed = [[i for i, j in utility.pairwise(part) - if i != ' ' or (j and '?' not in j)] + self.parsed = [[i for i, j in utility.pairwise(part) + if i != ' ' or (j and '?' not in j)] for part in parsed] return self - + def root(self, scope, names): """Find root of identifier, from scope args: @@ -68,16 +71,16 @@ class Identifier(Node): list """ parent = scope.scopename - if parent: + if parent: parent = parent[-1] if parent.parsed: - return [self._pscn(part, n) + return [self._pscn(part, n) if part and part[0] not in self._subp else n for part in parent.parsed for n in names] return names - + def _pscn(self, parent, name): """ """ @@ -96,28 +99,28 @@ class Identifier(Node): parsed.append(' ') parsed.extend(name) return parsed - + def raw(self, clean=False): """Raw identifier. - args: + args: clean (bool): clean name returns: str """ - if clean: return ''.join(''.join(p) for p in self.parsed).replace('?', ' ') + if clean: + return ''.join(''.join(p) for p in self.parsed).replace('?', ' ') return '%'.join('%'.join(p) for p in self.parsed).strip().strip('%') - + def copy(self): """ Return copy of self Returns: Identifier object """ - tokens = ([t for t in self.tokens] - if type(self.tokens) is list + tokens = ([t for t in self.tokens] + if isinstance(self.tokens, list) else self.tokens) return Identifier(tokens, 0) - - + def fmt(self, fills): """Format identifier args: @@ -125,11 +128,9 @@ class Identifier(Node): returns: str (CSS) """ - name = ',$$'.join(''.join(p).strip() + name = ',$$'.join(''.join(p).strip() for p in self.parsed) name = re.sub('\?(.)\?', '%(ws)s\\1%(ws)s', name) % fills - return (name.replace('$$', fills['nl']) - if len(name) > 85 + return (name.replace('$$', fills['nl']) + if len(name) > 85 else name.replace('$$', fills['ws'])).replace(' ', ' ') - - diff --git a/lesscpy/plib/mixin.py b/lesscpy/plib/mixin.py index 0b37fa9..75ead7e 100644 --- a/lesscpy/plib/mixin.py +++ b/lesscpy/plib/mixin.py @@ -2,22 +2,26 @@ """ .. module:: lesscpy.plib.mixin :synopsis: Mixin node. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ -import sys, copy, itertools +import sys +import copy +import itertools from .node import Node from .block import Block from .expression import Expression from .variable import Variable from lesscpy.lessc import utility + class Mixin(Node): + """ Mixin Node. Represents callable mixin types. """ - + def parse(self, scope): """Parse node args: @@ -30,18 +34,18 @@ class Mixin(Node): self.name, args, self.guards = self.tokens[0] self.args = [a for a in utility.flatten(args) if a] self.body = Block([None, self.tokens[1]], 0) - self.vars = list(utility.flatten([list(v.values()) - for v in [s['__variables__'] + self.vars = list(utility.flatten([list(v.values()) + for v in [s['__variables__'] for s in scope]])) return self - + def raw(self): """Raw mixin name returns: str """ return self.name.raw() - + def parse_args(self, args, scope): """Parse arguments to mixin. Add them to scope as variables. Sets upp special variable @arguments @@ -52,22 +56,24 @@ class Mixin(Node): raises: SyntaxError """ - arguments = zip(args, [' '] * len(args)) if args and args[0] else None - zl = itertools.zip_longest if sys.version_info[0] == 3 else itertools.izip_longest + arguments = list(zip(args, [' '] * len(args))) if args and args[0] else None + zl = itertools.zip_longest if sys.version_info[ + 0] == 3 else itertools.izip_longest if self.args: parsed = [v if hasattr(v, 'parse') else v for v in copy.copy(self.args)] - args = args if type(args) is list else [args] - vars = [self._parse_arg(var, arg, scope) + args = args if isinstance(args, list) else [args] + vars = [self._parse_arg(var, arg, scope) for arg, var in zl([a for a in args], parsed)] - for var in vars: - if var: var.parse(scope) + for var in vars: + if var: + var.parse(scope) if not arguments: arguments = [v.value for v in vars if v] if not arguments: arguments = '' Variable(['@arguments', None, arguments]).parse(scope) - + def _parse_arg(self, var, arg, scope): """ Parse a single argument to mixin. args: @@ -77,24 +83,26 @@ class Mixin(Node): returns: Variable object or None """ - if type(var) is Variable: + if isinstance(var, Variable): # kwarg if arg: if utility.is_variable(arg[0]): tmp = scope.variables(arg[0]) - if not tmp: return None + if not tmp: + return None val = tmp.value else: val = arg var = Variable(var.tokens[:-1] + [val]) else: - #arg + # arg if utility.is_variable(var): if arg is None: raise SyntaxError('Missing argument to mixin') elif utility.is_variable(arg[0]): tmp = scope.variables(arg[0]) - if not tmp: return None + if not tmp: + return None val = tmp.value else: val = arg @@ -102,7 +110,7 @@ class Mixin(Node): else: return None return var - + def parse_guards(self, scope): """Parse guards on mixin. args: @@ -115,16 +123,17 @@ class Mixin(Node): if self.guards: cor = True if ',' in self.guards else False for g in self.guards: - if type(g) is list: - res = (g[0].parse(scope) - if len(g) == 1 + if isinstance(g, list): + res = (g[0].parse(scope) + if len(g) == 1 else Expression(g).parse(scope)) if cor: - if res: return True + if res: + return True elif not res: return False return True - + def call(self, scope, args=[]): """Call mixin. Parses a copy of the mixins body in the current scope and returns it. @@ -138,8 +147,8 @@ class Mixin(Node): """ ret = False if args: - args = [[a.parse(scope) - if type(a) is Expression + args = [[a.parse(scope) + if isinstance(a, Expression) else a for a in arg] if arg else arg for arg in args] @@ -151,5 +160,6 @@ class Mixin(Node): if self.parse_guards(scope): body = self.body.copy() ret = body.tokens[1] - if ret: utility.rename(ret, scope, Block) + if ret: + utility.rename(ret, scope, Block) return ret diff --git a/lesscpy/plib/node.py b/lesscpy/plib/node.py index bda0d6b..b85f057 100644 --- a/lesscpy/plib/node.py +++ b/lesscpy/plib/node.py @@ -2,14 +2,16 @@ """ .. module:: lesscpy.plib.node :synopsis: Base Node - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ from lesscpy.lessc import utility + class Node(object): + def __init__(self, tokens, lineno=0): """ Base Node args: @@ -19,7 +21,7 @@ class Node(object): self.tokens = tokens self.lineno = lineno self.parsed = False - + def parse(self, scope): """ Base parse function args: @@ -28,7 +30,7 @@ class Node(object): self """ return self - + def process(self, tokens, scope): """ Process tokenslist, flattening and parsing it args: @@ -41,17 +43,18 @@ class Node(object): tokens = list(utility.flatten(tokens)) done = True if any(t for t in tokens if hasattr(t, 'parse')): - tokens = [t.parse(scope) - if hasattr(t, 'parse') + tokens = [t.parse(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_variables(tokens, scope) done = False - if done: break + if done: + break return tokens - + def replace_variables(self, tokens, scope): """ Replace variables in tokenlist args: @@ -62,7 +65,7 @@ class Node(object): """ return [scope.swap(t) if utility.is_variable(t) - else t + else t for t in tokens] def fmt(self, fills): diff --git a/lesscpy/plib/property.py b/lesscpy/plib/property.py index 757f02a..20be2a4 100644 --- a/lesscpy/plib/property.py +++ b/lesscpy/plib/property.py @@ -2,7 +2,7 @@ """ .. module:: lesscpy.plib.property :synopsis: Property node. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson @@ -11,10 +11,12 @@ import re from .node import Node from lesscpy.lessc import utility + class Property(Node): + """Represents CSS property declaration. """ - + def parse(self, scope): """Parse node args: @@ -37,17 +39,17 @@ class Property(Node): style = self.preprocess(style) self.parsed = self.process(style, scope) return self - + def preprocess(self, style): """Hackish preprocessing from font shorthand tags. Skips expression parse on certain tags. - args: + args: style (list): . returns: list """ if self.property == 'font': - style = [''.join(u.expression()) + style = [''.join(u.expression()) if hasattr(u, 'expression') else u for u in style] @@ -57,7 +59,7 @@ class Property(Node): else u for u in style] return style - + def fmt(self, fills): """ Format node args: @@ -70,10 +72,10 @@ class Property(Node): if fills['nl']: self.parsed = [',%s' % fills['ws'] if p == ',' - else p + else p for p in self.parsed] - style = ''.join([p.fmt(fills) - if hasattr(p, 'fmt') + style = ''.join([p.fmt(fills) + if hasattr(p, 'fmt') else str(p) for p in self.parsed]) # IE cannot handle no space after url() @@ -84,7 +86,7 @@ class Property(Node): 'important': imp }) return f % fills - + def copy(self): """ Return a full copy of self Returns: diff --git a/lesscpy/plib/statement.py b/lesscpy/plib/statement.py index 507d148..dcb67e9 100644 --- a/lesscpy/plib/statement.py +++ b/lesscpy/plib/statement.py @@ -2,7 +2,7 @@ """ .. module:: lesscpy.plib.statement :synopsis: Statement node. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson @@ -10,10 +10,12 @@ from .node import Node from lesscpy.lessc import utility + class Statement(Node): + """Represents CSS statement (@import, @charset...) """ - + def parse(self, scope): """Parse node args: @@ -29,7 +31,7 @@ class Statement(Node): # Media @import self.parsed.insert(3, ' ') return self - + def fmt(self, fills): """ Format node args: @@ -38,4 +40,3 @@ class Statement(Node): str """ return ''.join(self.parsed) + fills['eb'] - \ No newline at end of file diff --git a/lesscpy/plib/string.py b/lesscpy/plib/string.py index d0c9de8..7037712 100644 --- a/lesscpy/plib/string.py +++ b/lesscpy/plib/string.py @@ -2,7 +2,7 @@ """ .. module:: lesscpy.plib.string :synopsis: Less interpolated string node. - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson @@ -11,7 +11,9 @@ import re from .node import Node from lesscpy.lessc import utility + class String(Node): + def parse(self, scope): """Parse node args: @@ -23,7 +25,7 @@ class String(Node): """ self.scope = scope return re.sub(r'@\{([^\}]+)\}', lambda m: self.swap(m.group(1)), self.tokens) - + def swap(self, var): """ Replace variable args: @@ -36,4 +38,3 @@ class String(Node): var = self.scope.swap('@' + var) var = ''.join(utility.flatten(var)) return var.strip("\"'") - \ No newline at end of file diff --git a/lesscpy/plib/variable.py b/lesscpy/plib/variable.py index 34dfd3a..b5ac11b 100644 --- a/lesscpy/plib/variable.py +++ b/lesscpy/plib/variable.py @@ -2,14 +2,16 @@ """ .. module:: lesscpy.plib.variable :synopsis: Variable declaration - + Copyright (c) See LICENSE for details. .. moduleauthor:: Johann T. Mariusson """ from .node import Node + class Variable(Node): + def parse(self, scope): """ Parse function args: @@ -18,21 +20,20 @@ class Variable(Node): self """ self.name, _, self.value = self.tokens - if type(self.name) is tuple: + if isinstance(self.name, tuple): if len(self.name) > 1: self.name, pad = self.name self.value.append(pad) else: self.name = self.name[0] scope.add_variable(self) - + def copy(self): """ Return a copy of self Returns: Variable object """ return Variable([t for t in self.tokens]) - + def fmt(self, fills): return '' - diff --git a/lesscpy/scripts/compiler.py b/lesscpy/scripts/compiler.py index 8ad328c..f6b92c7 100644 --- a/lesscpy/scripts/compiler.py +++ b/lesscpy/scripts/compiler.py @@ -1,20 +1,23 @@ -from __future__ import print_function # -*- coding: utf8 -*- """ .. module:: lesscpy.scripts.compiler CSS/LESSCSS run script http://lesscss.org/#docs - + Copyright (c) See LICENSE for details .. moduleauthor:: Johann T. Mariusson """ + +from __future__ import print_function + import os import sys import glob import copy import argparse + sys.path.append(os.path.abspath(os.path.dirname(__file__))) from lesscpy.lessc import parser from lesscpy.lessc import lexer @@ -22,6 +25,7 @@ from lesscpy.lessc import formatter VERSION_STR = 'Lesscpy compiler 0.9h' + def ldirectory(inpath, outpath, args, scope): """Compile all *.less files in directory Args: @@ -44,10 +48,10 @@ def ldirectory(inpath, outpath, args, scope): for lf in less: outf = os.path.splitext(os.path.basename(lf)) minx = '.min' if args.min_ending else '' - outf = "%s/%s%s.css" % (outpath, outf[0], minx) + outf = "%s/%s%s.css" % (outpath, outf[0], minx) if not args.force and os.path.exists(outf): recompile = os.path.getmtime(outf) < os.path.getmtime(lf) - else: + else: recompile = True if recompile: print('%s -> %s' % (lf, outf)) @@ -62,71 +66,78 @@ def ldirectory(inpath, outpath, args, scope): if not args.dry_run: with open(outf, 'w') as outfile: outfile.write(css) - elif args.verbose: print('skipping %s, not modified' % lf, file=sys.stderr) + elif args.verbose: + print('skipping %s, not modified' % lf, file=sys.stderr) sys.stdout.flush() if args.recurse: - [ldirectory(os.path.join(inpath, name), os.path.join(outpath, name), args, scope) - for name in os.listdir(inpath) + [ldirectory(os.path.join(inpath, name), os.path.join(outpath, name), args, scope) + for name in os.listdir(inpath) if os.path.isdir(os.path.join(inpath, name)) and not name.startswith('.') and not name == outpath] # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -# +# + + def run(): """Run compiler """ - aparse = argparse.ArgumentParser(description='LessCss Compiler', - epilog='<< jtm@robot.is @_o >>', + aparse = argparse.ArgumentParser(description='LessCss Compiler', + epilog='<< jtm@robot.is @_o >>', version=VERSION_STR) aparse.add_argument('-I', '--include', action="store", type=str, help="Included less-files (comma separated)") - aparse.add_argument('-V', '--verbose', action="store_true", + aparse.add_argument('-V', '--verbose', action="store_true", default=False, help="Verbose mode") fgroup = aparse.add_argument_group('Formatting options') - fgroup.add_argument('-x', '--minify', action="store_true", + fgroup.add_argument('-x', '--minify', action="store_true", default=False, help="Minify output") - fgroup.add_argument('-X', '--xminify', action="store_true", + fgroup.add_argument('-X', '--xminify', action="store_true", default=False, help="Minify output, no end of block newlines") fgroup.add_argument('-t', '--tabs', help="Use tabs", action="store_true") - fgroup.add_argument('-s', '--spaces', help="Number of startline spaces (default 2)", default=2) - dgroup = aparse.add_argument_group('Directory options', + fgroup.add_argument( + '-s', '--spaces', help="Number of startline spaces (default 2)", default=2) + dgroup = aparse.add_argument_group('Directory options', 'Compiles all *.less files in directory that ' 'have a newer timestamp than it\'s css file.') dgroup.add_argument('-o', '--out', action="store", help="Output directory") - dgroup.add_argument('-r', '--recurse', action="store_true", help="Recursive into subdirectorys") - dgroup.add_argument('-f', '--force', action="store_true", help="Force recompile on all files") - dgroup.add_argument('-m', '--min-ending', action="store_true", + dgroup.add_argument( + '-r', '--recurse', action="store_true", help="Recursive into subdirectorys") + dgroup.add_argument( + '-f', '--force', action="store_true", help="Force recompile on all files") + dgroup.add_argument('-m', '--min-ending', action="store_true", default=False, help="Add '.min' into output filename. eg, name.min.css") - dgroup.add_argument('-D', '--dry-run', action="store_true", + dgroup.add_argument('-D', '--dry-run', action="store_true", default=False, help="Dry run, do not write files") group = aparse.add_argument_group('Debugging') - group.add_argument('-g', '--debug', action="store_true", - default=False, help="Debugging information") - group.add_argument('-S', '--scopemap', action="store_true", - default=False, help="Scopemap") - 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") + group.add_argument('-g', '--debug', action="store_true", + default=False, help="Debugging information") + group.add_argument('-S', '--scopemap', action="store_true", + default=False, help="Scopemap") + 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', help="less file or directory") args = aparse.parse_args() try: # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # + # if args.lex_only: lex = lexer.LessLexer() ll = lex.file(args.target) while True: tok = ll.token() - if not tok: break + if not tok: + break print(tok) print('EOF') sys.exit() # # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - # + # yacctab = 'yacctab' if args.debug else None scope = None if args.include: @@ -150,9 +161,9 @@ def run(): if not os.path.exists(args.target): sys.exit("Target not found '%s' ..." % args.target) if os.path.isdir(args.target): - ldirectory(args.target, args.out, args, scope) + ldirectory(args.target, args.out, args, scope) if args.dry_run: - print('Dry run, nothing done.', file=sys.stderr) + print('Dry run, nothing done.', file=sys.stderr) else: p = parser.LessParser(yacc_debug=(args.debug), lex_optimize=True, @@ -168,4 +179,3 @@ def run(): print(out) except (KeyboardInterrupt, SystemExit, IOError): sys.exit('\nAborting...') - diff --git a/lesscpy/test/__main__.py b/lesscpy/test/__main__.py index 67d806d..1dde8d7 100644 --- a/lesscpy/test/__main__.py +++ b/lesscpy/test/__main__.py @@ -7,6 +7,7 @@ import re import bootstrap + def find(): svn = re.compile('\.svn') test = re.compile('test.+\.py$') @@ -22,4 +23,3 @@ def find(): if __name__ == '__main__': unittest.main(defaultTest='find') - \ No newline at end of file diff --git a/lesscpy/test/bootstrap.py b/lesscpy/test/bootstrap.py index b186c18..ae5a807 100644 --- a/lesscpy/test/bootstrap.py +++ b/lesscpy/test/bootstrap.py @@ -10,4 +10,4 @@ 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) \ No newline at end of file + path = os.path.dirname(path) diff --git a/lesscpy/test/testcolor.py b/lesscpy/test/testcolor.py index 7b10e60..a0b959f 100644 --- a/lesscpy/test/testcolor.py +++ b/lesscpy/test/testcolor.py @@ -8,49 +8,50 @@ from lesscpy.lessc import color class TestLessColor(unittest.TestCase): + def setUp(self): self.color = color.Color() - + def test_rgb(self): test = self.color.rgb for r, g, b, v in [ - (255,255,255,'#ffffff'), - (100,100,100,'#646464'), - (0,0,0,'#000000'), - ('70%','70%','70%', '#b2b2b2'), - ('1%','1%','1%', '#020202'), - ('100%','100%','100%', '#ffffff'), - ('0%','0%','0%', '#000000'), + (255, 255, 255, '#ffffff'), + (100, 100, 100, '#646464'), + (0, 0, 0, '#000000'), + ('70%', '70%', '70%', '#b2b2b2'), + ('1%', '1%', '1%', '#020202'), + ('100%', '100%', '100%', '#ffffff'), + ('0%', '0%', '0%', '#000000'), ]: self.assertEqual(test(r, g, b), v) for args in [ - (255,255,256), - (0,-1,0), + (255, 255, 256), + (0, -1, 0), ('100%', '100%', 200), ('100%', '100%', '200%'), ]: - self.assertRaises(ValueError, test, args) - + self.assertRaises(ValueError, test, args) + def test_rgba(self): test = self.color.rgba for r, g, b, a, v in [ - (255,255,255,255,'#ffffffff'), - (100,100,100,100,'#64646464'), - (0,0,0,0,'#00000000'), - ('70%','70%','70%', '70%', '#b2b2b2b2'), - ('1%','1%','1%', '1%', '#02020202'), - ('100%','100%','100%','100%', '#ffffffff'), - ('0%','0%','0%','0%', '#00000000'), + (255, 255, 255, 255, '#ffffffff'), + (100, 100, 100, 100, '#64646464'), + (0, 0, 0, 0, '#00000000'), + ('70%', '70%', '70%', '70%', '#b2b2b2b2'), + ('1%', '1%', '1%', '1%', '#02020202'), + ('100%', '100%', '100%', '100%', '#ffffffff'), + ('0%', '0%', '0%', '0%', '#00000000'), ]: self.assertEqual(test(r, g, b, a), v) for args in [ - (255,255,255,256), - (0,0,0,-1), + (255, 255, 255, 256), + (0, 0, 0, -1), ('100%', '100%', '100%', 200), ('100%', '100%', '100%', '200%'), ]: - self.assertRaises(ValueError, test, args) - + self.assertRaises(ValueError, test, args) + def test_hsl(self): """ """ @@ -63,7 +64,7 @@ class TestLessColor(unittest.TestCase): (100, '0%', '0%', '#000000'), ]: self.assertEqual(test(h, s, l), v) - + def test_hsla(self): test = self.color.hsla for h, s, l, a, v in [ @@ -74,7 +75,7 @@ class TestLessColor(unittest.TestCase): (31, '100%', '4%', '100%', 'rgba(20.0,11.0,0.0,1.0)'), ]: self.assertEqual(test(h, s, l, a), v) - + def test_fmt(self): test = self.color.fmt self.assertEqual(test('#000'), '#000000') @@ -88,7 +89,7 @@ class TestLessColor(unittest.TestCase): self.assertRaises(ValueError, test, None) self.assertRaises(ValueError, test, 'aabbcc') self.assertRaises(ValueError, test, '#4aabbcc') - + def test_saturate(self): test = self.color.saturate for c, p, v in [ @@ -108,10 +109,10 @@ class TestLessColor(unittest.TestCase): ('#29332f', '40%', '#174533'), ('#29332f', '60%', '#0d4f35'), ('#29332f', '100%', '#005c37'), - + ]: self.assertEqual(test(c, p), v, v) - + def test_desaturate(self): test = self.color.desaturate for c, p, v in [ @@ -131,10 +132,10 @@ class TestLessColor(unittest.TestCase): ('#29332f', '40%', '#2e2e2e'), ('#29332f', '60%', '#2e2e2e'), ('#29332f', '100%', '#2e2e2e'), - + ]: self.assertEqual(test(c, p), v, v) - + def test_spin(self): test = self.color.spin for c, p, v in [ @@ -154,9 +155,9 @@ class TestLessColor(unittest.TestCase): ('#29332f', '40%', '#293033'), ('#29332f', '60%', '#292d33'), ('#29332f', '100%', '#2c2933'), - + ]: self.assertEqual(test(c, p), v, v) - + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/lesscpy/test/testexpression.py b/lesscpy/test/testexpression.py index af5d48b..29ad58a 100644 --- a/lesscpy/test/testexpression.py +++ b/lesscpy/test/testexpression.py @@ -3,7 +3,9 @@ if __name__ == '__main__': import bootstrap from lesscpy.plib.expression import Expression -class TestExpression(unittest.TestCase): + +class TestExpression(unittest.TestCase): + def test_basic(self): for test in [ ['0', '+', '0', '0'], @@ -18,10 +20,10 @@ class TestExpression(unittest.TestCase): ['2.0px', '+', '2', '4px'], [('2px', ' '), '+', '2.0', '4px'], ['2.0px', '+', '2.0', '4px'], - ]: + ]: e = Expression(test[:3]) self.assertEqual(test[3], e.parse(None), str(test)) - + def test_neg(self): for test in [ ['-0', '+', '0', '0'], @@ -42,7 +44,7 @@ class TestExpression(unittest.TestCase): ]: e = Expression(test[:3]) self.assertEqual(test[3], e.parse(None), str(test)) - + def testop(self): for test in [ ['0', '=', '0', True], @@ -55,7 +57,7 @@ class TestExpression(unittest.TestCase): ]: e = Expression(test[:3]) self.assertEqual(test[3], e.parse(None), test) - + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/lesscpy/test/testidentifier.py b/lesscpy/test/testidentifier.py index e26cab9..475d43e 100644 --- a/lesscpy/test/testidentifier.py +++ b/lesscpy/test/testidentifier.py @@ -4,7 +4,9 @@ if __name__ == '__main__': from lesscpy.lessc.scope import Scope from lesscpy.plib.identifier import Identifier -class TestIdentifier(unittest.TestCase): + +class TestIdentifier(unittest.TestCase): + def test_basic(self): fl = {'ws': ' '} for i in [ @@ -19,7 +21,7 @@ class TestIdentifier(unittest.TestCase): t, r = i id = Identifier(t, 0) self.assertEqual(id.parse(None).fmt(fl), r, i) - + def test_scope(self): fl = {'ws': ' '} sc = Scope() @@ -36,7 +38,7 @@ class TestIdentifier(unittest.TestCase): t, r = i id = Identifier(t, 0) self.assertEqual(id.parse(sc).fmt(fl), r, i) - + def test_combinators(self): fl = {'ws': ' '} sc = Scope() @@ -46,8 +48,9 @@ class TestIdentifier(unittest.TestCase): (['&', '.scope', ' ', 'a'], '.current.scope a'), (['.scope', '&', ' ', 'a'], '.scope.current a'), (['.scope', ' ', 'a', '&'], '.scope a.current'), - (['&', '>' ,'.scope', ' ', 'a'], '.current > .scope a'), - (['.span', '&', '.scope', ' ', 'a', '&'], '.span.current.scope a.current'), + (['&', '>', '.scope', ' ', 'a'], '.current > .scope a'), + (['.span', '&', '.scope', ' ', 'a', '&'], + '.span.current.scope a.current'), ]: t, r = i id = Identifier(t, 0) @@ -56,17 +59,17 @@ class TestIdentifier(unittest.TestCase): sc.current = Identifier(['&', '.next'], 0).parse(sc) id = Identifier(['&', '.top'], 0) self.assertEqual(id.parse(sc).fmt(fl), '.current.next.top') - + def test_groups(self): fl = {'ws': ' '} sc = Scope() sc.push() - sc.current = Identifier(['.a', ',', '.b'], 0).parse(sc) + sc.current = Identifier(['.a', ',', '.b'], 0).parse(sc) for i in [ (['&', '.scope', ' ', 'a'], '.a.scope a, .b.scope a'), (['.scope', '&', ' ', 'a'], '.scope.a a, .scope.b a'), (['.scope', ' ', 'a', '&'], '.scope a.a, .scope a.b'), - (['>' ,'&', '.scope', ' ', 'a'], ' > .a.scope a, > .b.scope a'), + (['>', '&', '.scope', ' ', 'a'], ' > .a.scope a, > .b.scope a'), ]: t, r = i id = Identifier(t, 0) @@ -76,18 +79,19 @@ class TestIdentifier(unittest.TestCase): sc.current = Identifier(['.c', ',', '.d'], 0).parse(sc) id = Identifier(['.deep'], 0) self.assertEqual(id.parse(sc).fmt(fl), '.a .next .c .deep, ' - '.a .next .d .deep, ' - '.b .next .c .deep, ' - '.b .next .d .deep') + '.a .next .d .deep, ' + '.b .next .c .deep, ' + '.b .next .d .deep') self.assertEqual(id.raw(), '.a% %.next% %.c% %.deep%.a%' ' %.next% %.d% %.deep%.b% %.next%' ' %.c% %.deep%.b% %.next% %.d% %.deep') - + def test_media(self): fl = {'ws': ' '} sc = Scope() sc.push() - sc.current = Identifier(['@media', ' ', 'screen', ',', 'projection'], 0).parse(sc) + sc.current = Identifier( + ['@media', ' ', 'screen', ',', 'projection'], 0).parse(sc) self.assertEqual(sc.current.fmt(fl), '@media screen,projection') for i in [ (['html'], 'html'), @@ -95,7 +99,6 @@ class TestIdentifier(unittest.TestCase): t, r = i id = Identifier(t, 0) self.assertEqual(id.parse(sc).fmt(fl), r, i) - + if __name__ == '__main__': unittest.main() - \ No newline at end of file diff --git a/lesscpy/test/testissues.py b/lesscpy/test/testissues.py index 3bc2b42..4956991 100644 --- a/lesscpy/test/testissues.py +++ b/lesscpy/test/testissues.py @@ -9,16 +9,20 @@ import bootstrap from lesscpy.lessc import parser from lesscpy.lessc import formatter + class TestCase(unittest.TestCase): pass + class Opt(object): + def __init__(self): self.minify = False self.xminify = False self.tabs = True -def create_test (args): + +def create_test(args): def do_test_expected(self): lessf, cssf, minf = args if os.path.exists(cssf): @@ -31,18 +35,22 @@ def create_test (args): with open(cssf) as cssf: for line in cssf.readlines(): if i >= pl: - self.fail("%s: result has less lines (%d < %d)" % (cssf, i, pl)) + self.fail( + "%s: result has less lines (%d < %d)" % (cssf, i, pl)) line = line.rstrip() - if not line: continue - self.assertEqual(line, pout[i], '%s: Line %d' % (cssf, i+1)) + if not line: + continue + self.assertEqual( + line, pout[i], '%s: Line %d' % (cssf, i + 1)) i += 1 if pl > i and i: - self.fail("%s: result has more lines (%d > %d)" % (cssf, i, pl)) + self.fail( + "%s: result has more lines (%d > %d)" % (cssf, i, pl)) else: self.fail("%s not found..." % cssf) return do_test_expected -LESS = glob.glob( os.path.join('less/issues', '*.less')) +LESS = glob.glob(os.path.join('less/issues', '*.less')) for less in LESS: lessf = less.split('.')[0].split('/')[-1] css = 'css/issues/' + lessf + '.css' @@ -51,5 +59,5 @@ for less in LESS: 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 +if __name__ == "__main__": + unittest.main() diff --git a/lesscpy/test/testless.py b/lesscpy/test/testless.py index 14c61f5..bed0815 100644 --- a/lesscpy/test/testless.py +++ b/lesscpy/test/testless.py @@ -9,16 +9,20 @@ import bootstrap from lesscpy.lessc import parser from lesscpy.lessc import formatter + class TestCase(unittest.TestCase): pass + class Opt(object): + def __init__(self): self.minify = False self.xminify = False self.tabs = True -def create_test (args): + +def create_test(args): def do_test_expected(self): lessf, cssf, minf = args if os.path.exists(cssf): @@ -31,13 +35,17 @@ def create_test (args): with open(cssf) as cssf: for line in cssf.readlines(): if i >= pl: - self.fail("%s: result has less lines (%d < %d)" % (cssf, i, pl)) + self.fail( + "%s: result has less lines (%d < %d)" % (cssf, i, pl)) line = line.rstrip() - if not line: continue - self.assertEqual(line, pout[i], '%s: Line %d' % (cssf, i+1)) + if not line: + continue + self.assertEqual( + line, pout[i], '%s: Line %d' % (cssf, i + 1)) i += 1 if pl > i and i: - self.fail("%s: result has more lines (%d > %d)" % (cssf, i, pl)) + self.fail( + "%s: result has more lines (%d > %d)" % (cssf, i, pl)) else: self.fail("%s not found..." % cssf) if os.path.exists(minf): @@ -52,16 +60,19 @@ def create_test (args): with open(minf) as cssf: for line in cssf.readlines(): if i >= ml: - self.fail("%s: result has less lines (%d < %d)" % (minf, i, ml)) - self.assertEqual(line.rstrip(), mout[i], '%s: Line %d' % (minf, i+1)) + self.fail( + "%s: result has less lines (%d < %d)" % (minf, i, ml)) + self.assertEqual( + line.rstrip(), mout[i], '%s: Line %d' % (minf, i + 1)) i += 1 if ml > i and i: - self.fail("%s: result has more lines (%d > %d)" % (minf, i, ml)) + self.fail( + "%s: result has more lines (%d > %d)" % (minf, i, ml)) else: self.fail("%s not found..." % minf) return do_test_expected -LESS = glob.glob( os.path.join('less/', '*.less')) +LESS = glob.glob(os.path.join('less/', '*.less')) for less in LESS: lessf = less.split('.')[0].split('/')[-1] css = 'css/' + lessf + '.css' @@ -70,5 +81,5 @@ for less in LESS: test_method.__name__ = 'test_%s' % less.replace('./-', '_') setattr(TestCase, test_method.__name__, test_method) -if __name__=="__main__": +if __name__ == "__main__": unittest.main() diff --git a/lesscpy/test/testscope.py b/lesscpy/test/testscope.py index 1e3fd83..7e7965e 100644 --- a/lesscpy/test/testscope.py +++ b/lesscpy/test/testscope.py @@ -4,8 +4,9 @@ if __name__ == '__main__': from lesscpy.lessc.scope import Scope from lesscpy.plib.identifier import Identifier -class TestIdentifier(unittest.TestCase): + +class TestIdentifier(unittest.TestCase): pass if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/lesscpy/test/testutility.py b/lesscpy/test/testutility.py index bb41b0c..3bf8ea1 100644 --- a/lesscpy/test/testutility.py +++ b/lesscpy/test/testutility.py @@ -3,7 +3,9 @@ if __name__ == '__main__': import bootstrap import lesscpy.lessc.utility as utility -class TestUtility(unittest.TestCase): + +class TestUtility(unittest.TestCase): + def testanalyze(self): test = utility.analyze_number self.assertEqual((0, ''), test('0')) @@ -23,7 +25,7 @@ class TestUtility(unittest.TestCase): self.assertRaises(SyntaxError, test, 'gg') self.assertRaises(SyntaxError, test, '-o') self.assertRaises(SyntaxError, test, '') - + def testsplit_unit(self): test = utility.split_unit self.assertEqual(('', ''), test(None)) @@ -33,7 +35,7 @@ class TestUtility(unittest.TestCase): self.assertEqual(('1', ''), test('1')) self.assertEqual(('1', 'px'), test('1px')) self.assertEqual(('-1', 'px'), test('-1px')) - + def testis_int(self): test = utility.is_int self.assertTrue(test(1)) @@ -43,7 +45,7 @@ class TestUtility(unittest.TestCase): self.assertFalse(test(False)) self.assertFalse(test(None)) self.assertFalse(test(0.0)) - + def testis_float(self): test = utility.is_float self.assertFalse(test(1)) @@ -54,7 +56,7 @@ class TestUtility(unittest.TestCase): self.assertTrue(test(-0.0)) self.assertTrue(test('77.0565')) self.assertTrue(test('-0.0')) - + def testis_color(self): test = utility.is_color self.assertTrue(test('#123')) @@ -69,7 +71,7 @@ class TestUtility(unittest.TestCase): self.assertFalse(test('.925')) self.assertFalse(test(False)) self.assertFalse(test([])) - + def testis_variable(self): test = utility.is_variable self.assertTrue(test('@var')) @@ -78,7 +80,7 @@ class TestUtility(unittest.TestCase): self.assertFalse(test('')) self.assertFalse(test(False)) self.assertFalse(test([])) - + def testwith_unit(self): test = utility.with_unit self.assertEqual('1px', test((1, 'px'))) @@ -89,8 +91,7 @@ class TestUtility(unittest.TestCase): self.assertEqual('1', test(1)) self.assertEqual('1', test(1, None)) self.assertEqual('1', test(1,)) - - - + + if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..90412f0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ply diff --git a/setup.py b/setup.py index 6a33f96..6bce3ba 100755 --- a/setup.py +++ b/setup.py @@ -25,5 +25,5 @@ setup( 'lesscpy/test/less/*.less', 'lesscpy/test/less/issues/*.less',]}, license=open('LICENSE').read(), - long_description=open('README').read(), -) \ No newline at end of file + long_description=open('README.rst').read(), +) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..e1744d8 --- /dev/null +++ b/tox.ini @@ -0,0 +1,10 @@ +[tox] +envlist = py26,py27,py33,pep8 + +[testenv] +deps = -r{toxinidir}/requirements.txt +commands = python lesscpy/test/__main__.py + +[testenv:pep8] +deps = pep8 +commands = pep8 --repeat --show-source --ignore=E501 --exclude=.venv,.tox,dist,doc lesscpy