1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ parser.out
|
||||
build
|
||||
dist
|
||||
*.pyc
|
||||
MANIFEST
|
||||
|
||||
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@@ -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
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
include LICENSE
|
||||
include README
|
||||
include README.rst
|
||||
include requirements.txt
|
||||
include tox.ini
|
||||
|
||||
136
README
136
README
@@ -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
|
||||
|
||||
<jtm@robot.is>
|
||||
150
README.rst
Normal file
150
README.rst
Normal file
@@ -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
|
||||
@@ -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
|
||||
"""
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
.. module:: lesscpy.lessc.color
|
||||
:synopsis: Lesscpy Color functions
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,21 +2,24 @@
|
||||
"""
|
||||
.. module:: lesscpy.lessc.formatter
|
||||
:synopsis: CSS Formatter class.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
"""
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@@ -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.
|
||||
<jtm@robot.is>
|
||||
@@ -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 = ';'
|
||||
|
||||
@@ -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 <jtm@robot.is>
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
"""
|
||||
.. module:: lesscpy.lessc.scope
|
||||
:synopsis: Scope class.
|
||||
|
||||
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
"""
|
||||
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
|
||||
|
||||
|
||||
@@ -2,19 +2,23 @@
|
||||
"""
|
||||
.. module:: lesscpy.lessc.utility
|
||||
:synopsis: various utility functions
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
"""
|
||||
|
||||
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 ('','')
|
||||
|
||||
|
||||
|
||||
return r.groups() if r else ('', '')
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
'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'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
CSS syntax names.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
<jtm@robot.is>
|
||||
@@ -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',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""
|
||||
HTML DOM names
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
<jtm@robot.is>
|
||||
@@ -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)
|
||||
html.extend(html5)
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib
|
||||
:synopsis: Parse Nodes for Lesscpy
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
"""
|
||||
__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
|
||||
from .variable import Variable
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.block
|
||||
:synopsis: Block parse node.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
@@ -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)
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.call
|
||||
:synopsis: Call parse node
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
"""
|
||||
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:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.deferred
|
||||
:synopsis: Deferred mixin call.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.expression
|
||||
:synopsis: Expression node.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
@@ -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:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.identifier
|
||||
:synopsis: Identifier node.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
@@ -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(' ', ' ')
|
||||
|
||||
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.mixin
|
||||
:synopsis: Mixin node.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.node
|
||||
:synopsis: Base Node
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
"""
|
||||
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):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.property
|
||||
:synopsis: Property node.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
@@ -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:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.statement
|
||||
:synopsis: Statement node.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
@@ -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']
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.string
|
||||
:synopsis: Less interpolated string node.
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
@@ -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("\"'")
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
"""
|
||||
.. module:: lesscpy.plib.variable
|
||||
:synopsis: Variable declaration
|
||||
|
||||
|
||||
Copyright (c)
|
||||
See LICENSE for details.
|
||||
.. moduleauthor:: Johann T. Mariusson <jtm@robot.is>
|
||||
"""
|
||||
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 ''
|
||||
|
||||
|
||||
@@ -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 <jtm@robot.is>
|
||||
"""
|
||||
|
||||
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...')
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
path = os.path.dirname(path)
|
||||
|
||||
@@ -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()
|
||||
unittest.main()
|
||||
|
||||
@@ -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()
|
||||
unittest.main()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
unittest.main()
|
||||
|
||||
@@ -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()
|
||||
unittest.main()
|
||||
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
ply
|
||||
4
setup.py
4
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(),
|
||||
)
|
||||
long_description=open('README.rst').read(),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user