Mixin guards

This commit is contained in:
jtm 2012-03-25 13:25:05 +00:00
parent 26ffa97770
commit b15674d56b
9 changed files with 401 additions and 18 deletions

10
README
View File

@ -76,12 +76,14 @@ Supported features
* Mixins
* mixins (Nested)
* mixins (Nested (Calls))
* Guard expressions
* Parametered mixins (class)
* Parametered mixins (id)
* @arguments
* Nesting
* Escapes ~/e()
* Expressions
* Keyframe blocks
* Color functions:
** lighten
** darken
@ -98,6 +100,12 @@ Supported features
** decrement
** format '%('
** add
** iscolor
** isnumber
** isurl
** isstring
** iskeyword
* Keyframe blocks
Differences from lessc.js
=========================
@ -107,10 +115,8 @@ Differences from lessc.js
Not supported (yet)
===================
* Keyframe blocks
* mixins (closures)
* Pattern-matching
* Guard expressions
* JavaScript evaluation
License

View File

@ -190,12 +190,13 @@ class LessParser(object):
# fallback to mixin. Allow calls to mixins without parens
mixin = self.scope.mixins(m.raw())
if mixin:
res = None
for m in mixin:
try:
res = m.call(self.scope)
except SyntaxError as e:
self.handle_error(e, p.lineno(2))
if m: break
if res: break
p[0] = res
else:
self.handle_error('Call unknown block `%s`' % m.raw(True), p.lineno(2))
@ -244,11 +245,6 @@ class LessParser(object):
"""
p[0] = p[2]
def p_mixin_guard_not(self, p):
""" mixin_guard : less_when less_not mixin_guard_cond_list
"""
p[0] = p[3]
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
@ -261,6 +257,12 @@ class LessParser(object):
""" 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
@ -284,6 +286,7 @@ class LessParser(object):
"""
p[1].parse(None)
mixin = self.scope.mixins(p[1].raw())
res = None
if mixin:
for m in mixin:
try:
@ -302,8 +305,11 @@ class LessParser(object):
else:
if self.scope.in_mixin:
res = Deferred(p[1], p[3])
if not res:
self.handle_error('Call unknown mixin `%s`' % p[1].raw(True), p.lineno(2))
if res is False:
args = ''.join([''.join(a) for a in p[3]]) if p[3] else ''
self.handle_error('Call unknown mixin `%s(%s)`' %
(p[1].raw(True), args),
p.lineno(2))
p[0] = res
def p_mixin_args_arguments(self, p):

View File

@ -53,6 +53,19 @@ def blocksearch(block, name):
if b: return b
return False
def reverse_guard(ll):
"""
"""
rev = {
'<': '>',
'>': '<',
'=': '!=',
'!=': '=',
'>=': '<=',
'<=': '>='
}
return [rev[l] if l in rev else l for l in ll]
def debug_print(ll, lvl=0):
"""
"""

151
lesscpy/lib/colors.py Normal file
View File

@ -0,0 +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'
}

View File

@ -2,10 +2,11 @@
Calls to builtin functions.
"""
import re, math
from urllib.parse import quote as urlquote
from urllib.parse import quote as urlquote, urlparse
from .node import Node
import lesscpy.lessc.utility as utility
import lesscpy.lessc.color as Color
from lesscpy.lib.colors import lessColors
class Call(Node):
def parse(self, scope):
@ -64,6 +65,53 @@ class Call(Node):
format = format.replace('%A', '%s')
return format % tuple(items)
def isnumber(self, *args):
"""
"""
if(len(args) > 1):
raise SyntaxError('Wrong number of arguments')
try:
n, u = utility.analyze_number(args[0])
except SyntaxError as e:
return False
return True
def iscolor(self, *args):
"""
"""
if(len(args) > 1):
raise SyntaxError('Wrong number of arguments')
return (args[0] in lessColors)
def isurl(self, *args):
"""
"""
if(len(args) > 1):
raise SyntaxError('Wrong number of arguments')
arg = utility.destring(args[0])
regex = re.compile(r'^(?:http|ftp)s?://' # http:// or https://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
r'localhost|' #localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
return regex.match(arg)
def isstring(self, *args):
"""
"""
if(len(args) > 1):
raise SyntaxError('Wrong number of arguments')
regex = re.compile(r'\'[^\']*\'|"[^"]*"')
return regex.match(args[0])
def iskeyword(self, *args):
"""
"""
if(len(args) > 1):
raise SyntaxError('Wrong number of arguments')
return (args[0] in ('when', 'and', 'not'))
def increment(self, *args):
""" Increment function
@param Mixed: value

View File

@ -37,7 +37,7 @@ class Mixin(Node):
for var in vars:
if var: scope.add_variable(var)
if not arguments:
arguments = [v.value for v in vars]
arguments = [v.value for v in vars if v]
if arguments:
arguments = [' ' if a == ',' else a for a in arguments]
else:
@ -73,29 +73,37 @@ class Mixin(Node):
return None
return var
def parse_guards(self, scope, args):
def parse_guards(self, scope):
"""
Parse guards on mixin.
"""
if self.guards:
cor = True if ',' in self.guards else False
for g in self.guards:
if type(g) is list:
e = Expression(g)
if not e.parse(scope):
res = (g[0].parse(scope)
if len(g) == 1
else Expression(g).parse(scope))
if cor:
if res: return True
elif not res:
return False
return True
def call(self, scope, args=None):
"""
Call mixin. Parses a copy of the mixins body
in the current scope and returns it.
"""
self.parse_args(args, scope)
if self.parse_guards(scope, args):
try:
self.parse_args(args, scope)
except SyntaxError:
return False
if self.parse_guards(scope):
body = copy.deepcopy(self.body)
scope.update([self.scope], -1)
body.parse(scope)
r = list(utility.flatten([body.parsed, body.inner]))
utility.rename(r, scope)
return r
return False

View File

@ -0,0 +1,33 @@
.a {
width: 1px;
height: -1px;
padding: 0;
padding: 0;
padding: 0;
}
.b {
margin: 11;
margin: -11;
}
.c {
height: 5px;
width: 5px;
}
.d {
src: 'mobile';
padding: 1px;
}
.e {
width: 9px;
color: yellow;
src: 'http://www.lesscss.org/#-parametric-mixins';
filter: 'wtf';
ignore: when;
}
.f {
width: 9px;
height: -9px;
}
.a .span5 {
width: 5px;
}

View File

@ -0,0 +1,7 @@
.a{width:1px;height:-1px;padding:0;padding:0;padding:0;}
.b{margin:11;margin:-11;}
.c{height:5px;width:5px;}
.d{src:'mobile';padding:1px;}
.e{width:9px;color:yellow;src:'http://www.lesscss.org/#-parametric-mixins';filter:'wtf';ignore:when;}
.f{width:9px;height:-9px;}
.a .span5{width:5px;}

View File

@ -0,0 +1,111 @@
/*
Guards
*/
.inner(@index) {
width: @index * 1px;
}
.sign (@index) when (@index > 0) {
.inner(@index);
}
.sign (@index) when (@index < 0) {
height: @index;
}
.sign (@index: 0) when (@index = 0) {
padding: @index;
}
.a {
.sign(1px);
.sign(-1px);
.sign(0);
// No arg
.sign();
.sign;
}
// condition list
.mixin1 (@a) when (@a > 10), (@a < -10) {
margin: @a;
}
.b{
.mixin1(11);
.mixin1(-11);
}
.max (@a, @b) when (@a > @b) {
width: @a
}
.max (@a, @b) when (@a < @b) {
height: @b
}
.c {
.max(3px, 5px);
.max(5px, 3px);
}
@media1: mobile;
.mmixin (@a) when (@media1 = mobile) {
src: 'mobile';
padding: @a;
}
.mmixin (@a) when (@media1 = desktop) {
src: 'desktop';
padding: @a * 3px;
}
.d {
.mmixin(1px);
}
/*
isType functions
*/
.mixtype (@a, @b: 0) when (isnumber(@b)) {
width: @b;
}
.mixtype (@a, @b: black) when (iscolor(@b)) {
color: @b;
}
.mixtype (@a, @b: 'http://www.lesscss.org') when (isurl(@b)) {
src: @b;
}
.mixtype (@a, @b: 'somestr') when (isstring(@b)) {
filter: @b;
}
.mixtype (@a, @b: 'somestr') when (iskeyword(@b)) {
ignore: @b;
}
.e {
.mixtype (6px, 9px);
.mixtype (6px, yellow);
.mixtype (6px, 'http://www.lesscss.org/#-parametric-mixins');
.mixtype (6px, 'wtf');
.mixtype (6px, when);
}
/*
Mixed conditions
*/
.mixcond (@a) when (isnumber(@a)) and (@a > 0) {
width: @a;
}
/*
.mixcond (@a) when (isnumber(@a)) and not (@a != 0) {
margin: @a;
}
*/
.mixcond (@b) when not (@b > 0) {
height: @b;
}
.f {
.mixcond(9px);
// .mixcond(0);
.mixcond(-9px);
}
/*
Bootstrap example
*/
.span(@index) {
width: @index * 1px;
}
.spanX (@index) when (@index > 0) {
(~".span@{index}") {
.span(@index);
}
}
.a {
.spanX(5);
}