initial commit

This commit is contained in:
robotis
2012-01-28 14:52:09 +00:00
commit f4ba41cc1d
115 changed files with 5286 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
__pycache__
.project
.pydevproject
parser.out
.settings
build

20
LICENSE Normal file
View File

@@ -0,0 +1,20 @@
Copyright (c) 2012 Jóhann T Maríusson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

107
README Normal file
View File

@@ -0,0 +1,107 @@
*
* LESSCPY *
*
python LessCss Compiler.
v0.6
A compiler written in python 3 for the lesscss language. For those of us not willing/able to
have node.js installed in our environment. Not all features of lesscss are supported (yet).
Some features wil probably never be supported (JavaScript evaluation). This program uses PLY
(Python Lex-Yacc) to tokenize/parse the input.
This is an early version, so you are likly to find bugs.
For more information on lesscss:
* http://lesscss.org/
* https://github.com/cloudhead/less.js
Requirements
============
* python 3+
* ply (Python Lex-Yacc) python 3 version
For more information on ply:
* http://www.dabeaz.com/ply/
Installation
============
python3 setup.py install
or simply place the package into your python path.
Compiler script Usage
=====================
usage: lesscpy [-h] [-I INCLUDE] [-x] [-X] [-m] [-D] [-v] [-o OUT] [-S] [-V]
[-L] [-N]
target
positional arguments:
target
optional arguments:
-h, --help show this help message and exit
-I INCLUDE, --include INCLUDE
Included less-files (comma separated)
-x, --minify Minify output
-X, --xminify Minify output, no end of block newlines
-m, --min-ending Add '.min' into output filename. eg, name.min.css
-D, --dry-run Dry run, do not write files
-v, --verbose Verbose mode
-o OUT, --out OUT Output directory
Debugging:
-S, --scopemap Scopemap
-V, --debug Debug mode
-L, --lex-only Run lexer on target
-N, --no-css No css output
Supported features
==================
* Variables
* String interpolation
* Mixins
* Parametered mixins (class)
* @arguments
* Nesting
* Escapes ~/e()
* Expressions
* Color functions:
** lighten
** darken
** saturate
** desaturate
** spin
** hue
** saturation
** lightness
* Other functions:
** round
** increment
** decrement
** format '%('
** add
Differences from lessc.js
=========================
* All MS filters and other strange vendor constructs must be escaped
* All colors are auto-formatted to #nnnnnn. eg, #f7e923
* Does not preserve css comments
Not supported (yet)
===================
* Keyframe blocks
* Parametered mixins (id)
* mixins (closures)
* mixins (Nested)
* Pattern-matching
* Guard expressions
* JavaScript evaluation
License
=======
See the LICENSE file
<jtm@robot.is>

19
bin/lesscpy Executable file
View File

@@ -0,0 +1,19 @@
#! /usr/bin/python3
"""
CSS/LESSCSS run script
http://lesscss.org/#docs
<jtm@robot.is>
"""
import sys, os
path = os.path.abspath(sys.argv[0])
while os.path.dirname(path) != path:
if os.path.exists(os.path.join(path, 'lesscpy', '__init__.py')):
sys.path.insert(0, path)
break
path = os.path.dirname(path)
from lesscpy.scripts import compiler
compiler.run()

0
lesscpy/__init__.py Normal file
View File

154
lesscpy/less.ast Normal file
View File

@@ -0,0 +1,154 @@
unit : statement_list
statement_list : statement_list statement
| statement
statement : block_decl
| variable_decl
| mixin_decl
| css_charset css_string ';'
| css_namespace css_string ';'
| css_namespace css_ident css_string ';'
| css_import css_string ';'
mixin_decl : block_open_mixin declaration_list brace_close
block_decl : block_open declaration_list brace_close
| block_open brace_close
block_open_mixin : css_class t_popen block_mixin_args t_pclose brace_open
| css_class t_popen less_arguments t_pclose brace_open
| css_class t_popen t_pclose brace_open
block_mixin_args : block_mixin_args ',' block_mixin_arg
| block_mixin_arg
block_mixin_arg : less_variable ':' block_mixin_factor
| block_mixin_factor
| less_variable
block_mixin_factor : css_number
| css_color
| css_ident
| css_string
block_open : identifier_list brace_open
mixin : identifier_list ';'
identifier_list : identifier_group
| identifier_page
| css_font_face
identifier_page : identifier_page dom_filter
| css_page
identifier_group : identifier_group ',' identifier
| identifier_group '+' identifier
| identifier_group identifier
| identifier
| css_media
identifier : css_dom
| css_id
| css_class
| dom_filter
| css_color
| less_combine
| '*'
| '>'
declaration_list : declaration_list declaration
| declaration
| property_decl
| block_decl
| variable_decl
variable_decl : less_variable ':' style_list ';'
property_decl : identifier_list t_popen argument_list t_pclose ';'
| identifier_list t_popen t_pclose ';'
| property ':' style_list ';'
| property ':' style_list
| property ':' ';'
| mixin
property : css_property
| css_vendor_property
| css_ident
style_list : style_group
| less_arguments
style_group : style_group ',' style
| style_group style
| style
style : expression
| css_important
| css_string
| istring
| css_vendor_property
| css_property
| css_ident
| '~' istring
| '~' css_string
dom_filter : css_dom filter_group
| css_id filter_group
| css_class filter_group
| less_combine filter_group
filter_group : filter filter
| filter
filter : css_filter
| ':' css_ident
| ':' css_filter
| ':' ':' css_ident
expression : expression '+' expression
| expression '-' expression
| expression '*' expression
| expression '/' expression
| '-' t_popen expression t_pclose
| t_popen expression t_pclose
| factor
factor : color
| number
| variable
| css_dom
| fcall
fcall : css_ident t_popen argument_list t_pclose
| css_property t_popen argument_list t_pclose
| css_vendor_property t_popen argument_list t_pclose
| less_open_format argument_list t_pclose
argument_list : argument_list ',' argument
| argument_list argument
| argument
argument : expression
| css_string
| istring
| css_ident
| css_id
| css_uri
| '='
istring : less_string
variable : '-' variable
| t_popen variable t_pclose
| less_variable
color : css_color
number : css_number
| css_number_unit
brace_open : '{'
brace_close : '}'

View File

217
lesscpy/lessc/color.py Normal file
View File

@@ -0,0 +1,217 @@
"""
LESSCPY Color functions
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import colorsys
class LessColor():
def process(self, expression):
""" Process color expression
@param tuple: color expression
@return: string
"""
a, o, b = expression
c1 = self.hex_to_rgb(a)
c2 = self.hex_to_rgb(b)
r = ['#']
for i in range(3):
v = self.operate(c1[i], c2[i], o)
if v > 0xff: v = 0xff
if v < 0: v = 0
r.append("%02x" % v)
return ''.join(r)
def operate(self, a, b, o):
""" Do operation on colors
@param string: color
@param string: color
@param string: operator
"""
operation = {
'+': '__add__',
'-': '__sub__',
'*': '__mul__',
'/': '__truediv__'
}.get(o)
v = getattr(a, operation)(b)
return v
def rgb(self, r, g, b):
""" RGB color function
@param str: RED channel
@param str: GREEN channel
@param str: BLUE channel
@return: str
"""
return self.__str_rgb((int(r), int(g), int(b)))
def hex_to_rgb(self, hex, *args):
""" Convert HEX color code to rgb
@param str: hex code
@return tuple
"""
hex = hex.strip()
if hex[0] == '#':
hex = hex.strip('#').strip(';')
if len(hex) == 3:
hex = [c * 2 for c in hex]
else:
hex = [hex[i:i+2] for i in range(0, len(hex), 2)]
return tuple(int(c, 16) for c in hex)
return [int(hex, 16)] * 3
def hex_to_hls(self, hex, *args):
""" Convert Hex color code to hls
@param str: hex code
@return tuple
"""
rgb = self.hex_to_rgb(hex)
return colorsys.rgb_to_hls(*[c / 255.0 for c in rgb])
def hls(self, h, l, s, *args):
"""
Create HEX color value from hls
@param h: hue (0 <= h <= 360)
@param l: lightness (0 <= l <= 100)
@param s: saturation (0 <= l <= 100)
"""
if type(l) == str: l = int(l.strip('%'))
if type(s) == str: s = int(s.strip('%'))
rgb = colorsys.hls_to_rgb(int(h) / 360, l / 100, s / 100)
return self.__format_rgb(rgb)
def hsl(self, h, s, l, *args):
"""
Wrapper for hls
"""
return self.hls(h, l, s)
def hue(self, color, *args):
"""
Returns the hue channel of color
@param str: hex code
@return int
"""
h, l, s = self.hex_to_hls(color)
return round(h * 360, 3)
def saturation(self, color, *args):
"""
Returns the saturation channel of color
@param str: hex code
@return int
"""
h, l, s = self.hex_to_hls(color)
return round(s * 100)
def lightness(self, color, *args):
"""
Returns the lightness channel of color
@param str: hex code
@return int
"""
h, l, s = self.hex_to_hls(color)
return round(l * 100)
def saturate(self, color, p, *args):
"""
"""
if type(p) == str: p = int(p.strip('%'))
h, l, s = self.hex_to_hls(color)
rgb = colorsys.hls_to_rgb(h, l, s + (p / 100))
return self.__format_rgb(rgb)
def desaturate(self, color, p, *args):
"""
"""
if type(p) == str: p = int(p.strip('%'))
h, l, s = self.hex_to_hls(color)
rgb = colorsys.hls_to_rgb(h, l, s - (p / 100))
return self.__format_rgb(rgb)
def lighten(self, color, p, *args):
"""
"""
if type(p) == str: p = int(p.strip('%'))
h, l, s = self.hex_to_hls(color)
rgb = colorsys.hls_to_rgb(h, l + (p / 100), s)
return self.__format_rgb(rgb)
def darken(self, color, p, *args):
"""
"""
if type(p) == str: p = int(p.strip('%'))
h, l, s = self.hex_to_hls(color)
rgb = colorsys.hls_to_rgb(h, l - (p / 100), s)
return self.__format_rgb(rgb)
def greyscale(self, color, *args):
"""
"""
return self.desaturate(color, 100)
def fadein(self, color, pc, *args):
"""
"""
pass
def fadeout(self, color, pc, *args):
"""
"""
pass
def fade(self):
"""
"""
pass
def spin(self, color, deg):
"""
"""
if type(deg) == str: deg = int(deg.strip('%'))
h, l, s = self.hex_to_hls(color)
h = ((h * 360) + deg) % 360
h = 360 + h if h < 0 else h
rgb = colorsys.hls_to_rgb(h / 360, l, s)
return self.__format_rgb(rgb)
def mix(self, color1, color2, weight):
"""
"""
pass
def format(self, color):
"""
Format CSS Hex color code.
uppercase becomes lowercase, 3 digit codes expand to 6 digit.
@param string: color
"""
if type(color) == str and color[0] == '#':
color = color.lower().strip().strip('#').strip(';')
if len(color) == 3:
color = ''.join([c * 2 for c in color])
return '#%s' % color
raise ValueError('Cannot format non-color')
def __format_rgb(self, rgb):
""" Format RGB tuple to string
@param tuple: RGB tuple
@return: string
"""
color = (round(c * 255) for c in rgb)
return self.__str_rgb(color)
def __str_rgb(self, rgb):
"""
"""
return '#%s' % ''.join(["%02x" % v for v in
[0xff
if h > 0xff else
0 if h < 0 else h
for h in rgb]
])

View File

@@ -0,0 +1,87 @@
"""
CSS Formatter class.
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
class Formatter(object):
def format(self, parse, minify=False, xminify=False):
""" Format css output from parser
@param Parse-result object: Parse-result object
@param bool: Minify flag
@param bool: Skip end of block newlines
@return: string
"""
eb = '\n'
if xminify:
eb = ''
minify = True
self.items = {}
if minify:
self.items.update({
'nl': '',
'tab': '',
'ws': '',
'endblock': eb
})
else:
self.items.update({
'nl': '\n',
'tab': '\t',
'ws': ' ',
'endblock': eb
})
self.out = []
if parse.result:
for u in parse.result:
self.out.extend(self.fprint(u))
return ''.join(self.out)
def fprint(self, node):
""" Format node.
@param Node object: Node object
"""
out = []
if not node: return out
if 'proplist' in node.parsed:
node.parsed['proplist'] = ''.join([self.sprintf(p.format, p.parsed)
for p in node.parsed['proplist']
if p])
if node.parsed['proplist']:
out.append(self.sprintf(node.format, node.parsed))
else:
out.append(self.sprintf(node.format, node.parsed))
if 'inner' in node.parsed:
if node._blocktype:
out.append(self.fblockinner(node))
else:
for iu in node.parsed['inner']:
out.extend(self.fprint(iu))
return out
def fblockinner(self, node):
""" Format inner block type
@param Node: node
@return: str
"""
sub = []
for iu in node.parsed['inner']:
sub.extend(self.fprint(iu))
sub = ''.join(sub)
if sub:
if self.items['tab']:
sub = '\t'+''.join(sub)
sub = sub.replace('\n', '\n\t').rstrip('\t')
node.parsed['proplist'] = sub
return self.sprintf(node.format, node.parsed)
return ''
def sprintf(self, frm, items):
""" Perform format action
@param string: Format string
@param dict: format items
@return: string
"""
items.update(self.items)
return frm % items

202
lesscpy/lessc/lexer.py Normal file
View File

@@ -0,0 +1,202 @@
"""
Lexer for LESSCSS.
http://www.dabeaz.com/ply/ply.html
http://www.w3.org/TR/CSS21/grammar.html#scanner
http://lesscss.org/#docs
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import re
import ply.lex as lex
from lesscpy.lib import dom
from lesscpy.lib import css
class LessLexer:
states = (
('parn', 'inclusive'),
)
literals = ',{}>=%!/*-+:;()~';
tokens = [
'css_ident',
'css_dom',
'css_class',
'css_id',
'css_property',
'css_vendor_property',
'css_comment',
'css_string',
'css_color',
'css_filter',
'css_number',
'css_number_unit',
'css_important',
'css_vendor_hack',
'css_uri',
'less_variable',
'less_comment',
'less_string',
'less_open_format',
'less_combine',
't_ws',
't_popen',
't_pclose',
]
reserved = {
'@media' : 'css_media',
'@page': 'css_page',
'@import' : 'css_import',
'@charset' : 'css_charset',
'@font-face' : 'css_font_face',
'@namespace' : 'css_namespace',
'@keyframes' : 'css_keyframes',
'@-moz-keyframes' : 'css_keyframes',
'@-webkit-keyframes' : 'css_keyframes',
'@arguments': 'less_arguments',
}
tokens = tokens + list(set(reserved.values()))
def __init__(self):
self.build(reflags=re.UNICODE|re.IGNORECASE)
def t_css_filter(self, t):
(r'\[[^\]]*\]'
'|(not|lang|nth-[a-z\-]+)\(.+\)'
'|and[ \t]\(.+\)')
return t
def t_css_ident(self, t):
(r'([\-\.\#]?'
'|@[@\-]?)'
'([_a-z]'
'|[\200-\377]'
'|\\\[0-9a-f]{1,6}([ \t\f])?'
'|\\\[^\s\r\n0-9a-f])'
'([_a-z0-9\-]|[\200-\377]'
'|\\\[0-9a-f]{1,6}([ \t\f])?'
'|\\\[^\s\r\n0-9a-f])*[ \t]?')
v = t.value.strip()
c = v[0]
if c == '.':
t.type = 'css_class'
elif c == '#':
t.type = 'css_id'
try:
int(v[1:], 16)
t.type = 'css_color'
except ValueError:
pass
elif v in css.propertys:
t.type = 'css_property'
t.value = t.value.strip()
elif v.lower() in dom.html:
t.type = 'css_dom'
t.value = t.value
elif c == '@':
if v in LessLexer.reserved:
t.type = LessLexer.reserved[v]
else:
t.type = 'less_variable'
elif c == '-':
t.type = 'css_vendor_property'
return t
def t_css_color(self, t):
r'\#[0-9]([0-9a-f]{5}|[0-9a-f]{2})'
return t
def t_parn_css_uri(self, t):
(r'data:[^\)]+'
'|(([a-z]+://)?'
'('
'([\.\w:]+[\\/])+'
'|([a-z][\w\.\-]+(\.[a-z0-9]+))'
'(\#[a-z]+)?)'
')+')
return t
def t_parn_css_ident(self, t):
(r'(([_a-z]'
'|[\200-\377]'
'|\\\[0-9a-f]{1,6}([ \t\f])?'
'|\\\[^\r\n\s0-9a-f])'
'([_a-z0-9\-]|[\200-\377]'
'|\\\[0-9a-f]{1,6}([ \t\f])?'
'|\\\[^\r\n\s0-9a-f])*)')
return t
def t_css_number(self, t):
r'-?(\d*\.\d+|\d+)(s|%|in|ex|[ecm]m|p[txc]|deg|g?rad|ms?|k?hz)?'
return t
def t_newline(self, t):
r'\n+'
t.lexer.lineno += len(t.value)
def t_css_comment(self, t):
r'(/\*(.|\n)*?\*/)'
t.lexer.lineno += t.value.count('\n')
pass
def t_less_comment(self, t):
r'//.*'
pass
def t_css_important(self, t):
r'!\s*important'
t.value = '!important'
return t
def t_t_ws(self, t):
r'[ \t]+'
pass
def t_t_popen(self, t):
r'\('
t.lexer.push_state('parn')
return t
def t_less_open_format(self, t):
r'%\('
t.lexer.push_state('parn')
return t
def t_t_pclose(self, t):
r'\)'
t.lexer.pop_state()
return t
def t_less_combine(self, t):
r'&[ \t]?'
return t
t_less_string = (r'"([^"@]*@\{[^"\}]+\}[^"]*)+"'
'|\'([^\'@]*@\{[^\'\}]+\}[^\']*)+\'')
t_css_string = r'"[^"]*"|\'[^\']*\''
# Error handling rule
def t_error(self, t):
raise SyntaxError("Illegal character '%s' line %d" % (t.value[0], t.lexer.lineno))
t.lexer.skip(1)
# Build the lexer
def build(self, **kwargs):
self.lexer = lex.lex(module=self, **kwargs)
def file(self, filename):
with open(filename) as f:
self.lexer.input(f.read())
return self.lexer
def input(self, filename):
with open(filename) as f:
self.lexer.input(f.read())
def token(self):
return self.lexer.token()

691
lesscpy/lessc/parser.py Normal file
View File

@@ -0,0 +1,691 @@
"""
LESSCSS Parser.
http://www.dabeaz.com/ply/ply.html
http://www.w3.org/TR/CSS21/grammar.html#scanner
http://lesscss.org/#docs
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import os
import ply.yacc
from . import lexer
from . import utility
from .color import LessColor
from lesscpy.plib import *
class LessParser(object):
precedence = (
('left', '+', '-'),
('left', '*', '/'),
)
def __init__(self,
lex_optimize=True,
yacc_optimize=True,
yacctab='yacctab',
yacc_debug=False,
scope=None,
outputdir='/tmp',
importlvl=0):
""" Parser object
@param bool: Optimized lexer
@param bool: Optimized parser
@param string: Yacc tables file
@param bool: Debug mode
@param dict: Included scope
"""
self.importlvl = importlvl
self.lex = lexer.LessLexer()
if not yacctab:
yacctab = 'yacctab'
self.ignored = ('t_ws', 'css_comment', 'less_comment',
'css_vendor_hack', 'css_keyframes')
self.tokens = [t for t in self.lex.tokens
if t not in self.ignored]
self.parser = ply.yacc.yacc(
module=self,
start='unit',
debug=yacc_debug,
optimize=yacc_optimize,
tabmodule=yacctab,
outputdir=outputdir
)
self.scope = scope if scope else []
self.stash = {}
self.result = None
self.target = None
def parse(self, filename='', debuglevel=0):
""" Parse file.
@param string: Filename
@param int: Debuglevel
"""
self._create_scope()
self.target = filename
self.result = self.parser.parse(filename, lexer=self.lex, debug=debuglevel)
def scopemap(self):
""" Output scopemap.
"""
if self.result:
utility.print_recursive(self.result)
def p_unit(self, p):
""" unit : statement_list
"""
p[0] = p[1]
def p_statement_list_aux(self, p):
""" statement_list : statement_list statement
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_statement_list(self, p):
""" statement_list : statement
"""
p[0] = [p[1]]
def p_statement(self, p):
""" statement : block_decl
| variable_decl
| mixin_decl
"""
p[0] = p[1]
def p_statement_aux(self, p):
""" statement : css_charset css_string ';'
| css_namespace css_string ';'
"""
p[0] = Statement(p)
p[0].parse(None)
def p_statement_namespace(self, p):
""" statement : css_namespace css_ident css_string ';'
"""
p[0] = Statement(p)
p[0].parse(None)
def p_statement_import(self, p):
""" statement : css_import css_string ';'
"""
if self.importlvl > 8:
raise ImportError('Recrusive import level too deep > 8 (circular import ?)')
ipath = utility.destring(p[2])
fn, fe = os.path.splitext(ipath)
if not fe or fe.lower() == '.less':
try:
cpath = os.path.dirname(os.path.abspath(self.target))
if not fe: ipath += '.less'
filename = "%s%s%s" % (cpath, os.sep, ipath)
if os.path.exists(filename):
recurse = LessParser(importlvl=self.importlvl+1)
recurse.parse(filename=filename, debuglevel=0)
self.update_scope(recurse.scope)
else:
err = "Cannot import '%s', file not found" % filename
self.handle_error(err, p, 'W')
p[0] = None
except ImportError as e:
self.handle_error(e, p)
else:
p[0] = Statement(p)
p[0].parse(None)
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_mixin_decl(self, p):
""" mixin_decl : block_open_mixin declaration_list brace_close
"""
try:
mixin = Mixin(p)
mixin.parse(self.scope, self.stash)
self.scope[-1]['__mixins__'][mixin.name.strip()] = mixin
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
def p_block_decl(self, p):
""" block_decl : block_open declaration_list brace_close
"""
try:
block = Block(p)
block.parse(self.scope)
self.scope[-1]['__blocks__'].append(block)
p[0] = block
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
def p_block_empty(self, p):
""" block_decl : block_open brace_close
"""
p[0] = None
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_block_open_mixin(self, p):
""" block_open_mixin : css_class t_popen block_mixin_args t_pclose brace_open
"""
self.scope[-1]['current'] = '__mixin__'
p[0] = list(p)[1:5]
def p_block_open_mixin_aux(self, p):
""" block_open_mixin : css_class t_popen less_arguments t_pclose brace_open
"""
self.scope[-1]['current'] = '__mixin__'
p[0] = list(p)[1:5]
def p_block_open_mixin_empty(self, p):
""" block_open_mixin : css_class t_popen t_pclose brace_open
"""
self.scope[-1]['current'] = '__mixin__'
p[0] = [p[1]]
def p_block_mixin_args_aux(self, p):
""" block_mixin_args : block_mixin_args ',' block_mixin_arg
"""
p[1].extend([p[2], p[3]])
p[0] = p[1]
def p_block_mixin_args(self, p):
""" block_mixin_args : block_mixin_arg
"""
p[0] = [p[1]]
def p_block_mixin_arg_def(self, p):
""" block_mixin_arg : less_variable ':' block_mixin_factor
"""
p[0] = list(p)[1:4]
def p_block_mixin_arg(self, p):
""" block_mixin_arg : block_mixin_factor
| less_variable
"""
p[0] = p[1]
def p_block_mixin_factor(self, p):
""" block_mixin_factor : css_number
| css_color
| css_ident
| css_string
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_block_open(self, p):
""" block_open : identifier_list brace_open
"""
name = ["%s " % t
if t in '>+'
else t
for t in utility.flatten(p[1])]
self.scope[-1]['current'] = ''.join(name).strip()
p[0] = p[1]
def p_identifier_list_mixin(self, p):
""" mixin : identifier_list ';'
"""
p[0] = p[1]
def p_identifier_list(self, p):
""" identifier_list : identifier_group
| identifier_page
| css_font_face
"""
if type(p[1]) is list:
p[0] = p[1]
else:
p[0] = [p[1]]
def p_identifier_page_aux(self, p):
""" identifier_page : identifier_page dom_filter
"""
p[1].extend(p[2])
p[0] = p[1]
def p_identifier_page(self, p):
""" identifier_page : css_page
"""
p[0] = [p[1]]
def p_identifier_group_op(self, p):
""" identifier_group : identifier_group ',' identifier
| identifier_group '+' identifier
"""
p[1].extend([p[2], p[3]])
p[0] = p[1]
def p_identifier_group_aux(self, p):
""" identifier_group : identifier_group identifier
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_identifier_group(self, p):
""" identifier_group : identifier
"""
p[0] = [p[1]]
def p_identifier_group_media(self, p):
""" identifier_group : css_media
"""
p[0] = [p[1]]
def p_identifier(self, p):
""" identifier : css_dom
| css_id
| css_class
| dom_filter
| css_color
| less_combine
| '*'
| '>'
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_declaration_list_aux(self, p):
""" declaration_list : declaration_list declaration
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_declaration_list(self, p):
""" declaration_list : declaration
"""
p[0] = [p[1]]
def p_declaration(self, p):
""" declaration : property_decl
"""
p[0] = p[1]
def p_declaration_block(self, p):
""" declaration : block_decl
| variable_decl
"""
p[0] = p[1]
def p_variable_decl(self, p):
""" variable_decl : less_variable ':' style_list ';'
"""
current = self.scope[-1]
try:
v = Variable(p)
v.parse(self.scope)
if 'current' in current and current['current'] == '__mixin__':
self.stash[v.name()] = v
else:
current[v.name()] = v
except SyntaxError as e:
print(e)
p[0] = None
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_property_mixin_call(self, p):
""" property_decl : identifier_list t_popen argument_list t_pclose ';'
"""
n = p[1][0]
if n in self.scope[0]['__mixins__']:
if self.scope[-1]['current'] != '__mixin__':
try:
p[0] = self.scope[0]['__mixins__'][n].call(p[3], self.scope)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
else:
p[0] = self.scope[0]['__mixins__'][n]
else:
self.handle_error('Mixin not found in scope: ´%s´' % n, p)
p[0] = None
def p_property_mixin_call_empty(self, p):
""" property_decl : identifier_list t_popen t_pclose ';'
"""
n = p[1][0]
if n in self.scope[0]['__mixins__']:
try:
p[0] = self.scope[0]['__mixins__'][n].call(None)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
else:
p[0] = None
def p_property_mixin(self, p):
""" property_decl : mixin
"""
m = ''.join([u.strip() for u in p[1]])
l = utility.block_search(m, self.scope)
if l:
p[0] = [b.parsed['proplist'] for b in l]
elif m in self.scope[0]['__mixins__']:
try:
p[0] = self.scope[0]['__mixins__'][m].call(None)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
else:
p[0] = []
def p_property_decl(self, p):
""" property_decl : property ':' style_list ';'
| property ':' style_list
"""
p[0] = Property(p)
if self.scope[-1]['current'] != '__mixin__':
try:
p[0].parse(self.scope)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
def p_prop_decl_bad(self, p):
""" property_decl : property ':' ';'
"""
p[0] = None
def p_property(self, p):
""" property : css_property
| css_vendor_property
| css_ident
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_style_list(self, p):
""" style_list : style_group
"""
p[0] = p[1]
def p_less_style_list(self, p):
""" style_list : less_arguments
"""
p[0] = p[1]
def p_style_group_sep(self, p):
""" style_group : style_group ',' style
"""
p[1].extend([p[2], p[3]])
p[0] = p[1]
def p_style_group_aux(self, p):
""" style_group : style_group style
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_style_group(self, p):
""" style_group : style
"""
p[0] = [p[1]]
def p_style(self, p):
""" style : expression
| css_important
| css_string
| istring
| css_vendor_property
| css_property
| css_ident
"""
p[0] = p[1]
def p_style_escape(self, p):
""" style : '~' istring
| '~' css_string
"""
try:
p[0] = Call(p)#.parse(self.scope)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_dom_filter(self, p):
""" dom_filter : css_dom filter_group
| css_id filter_group
| css_class filter_group
| less_combine filter_group
"""
p[0] = [p[1], p[2]]
def p_filter_group_aux(self, p):
""" filter_group : filter filter
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_filter_group(self, p):
""" filter_group : filter
"""
p[0] = [p[1]]
def p_filter(self, p):
""" filter : css_filter
| ':' css_ident
| ':' css_filter
| ':' ':' css_ident
"""
p[0] = list(p)[1:]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_expression_aux(self, p):
'''expression : expression '+' expression
| expression '-' expression
| expression '*' expression
| expression '/' expression
'''
try:
p[0] = Expression(p)
except SyntaxError as e:
print(e)
p[0] = None
def p_expression_p_neg(self, p):
""" expression : '-' t_popen expression t_pclose
"""
p[0] = [p[1], p[3]]
def p_expression_p(self, p):
""" expression : t_popen expression t_pclose
"""
p[0] = p[2]
def p_expression(self, p):
""" expression : factor
"""
p[0] = p[1]
def p_factor(self, p):
"""factor : color
| number
| variable
| css_dom
| fcall
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_fcall(self, p):
""" fcall : css_ident t_popen argument_list t_pclose
| css_property t_popen argument_list t_pclose
| css_vendor_property t_popen argument_list t_pclose
"""
p[0] = Call(p)
if self.scope[-1]['current'] != '__mixin__':
try:
p[0] = p[0].parse(self.scope)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
def p_fcall_format(self, p):
""" fcall : less_open_format argument_list t_pclose
"""
try:
p[0] = Call(p).parse(self.scope)
except SyntaxError as e:
self.handle_error(e, p)
p[0] = None
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_argument_list_aux_1(self, p):
""" argument_list : argument_list ',' argument
"""
p[1].extend([p[2], p[3]])
p[0] = p[1]
def p_argument_list_aux(self, p):
""" argument_list : argument_list argument
"""
p[1].extend([p[2]])
p[0] = p[1]
def p_argument_list(self, p):
""" argument_list : argument
"""
p[0] = [p[1]]
def p_argument(self, p):
""" argument : expression
| css_string
| istring
| css_ident
| css_id
| css_uri
| '='
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_interpolated_str(self, p):
""" istring : less_string
"""
try:
p[0] = String(p)#.parse(self.scope)
except SyntaxError as e:
self.handle_error(e, p, 'W')
p[0] = p[1]
def p_variable_neg(self, p):
""" variable : '-' variable
"""
p[0] = '-' + p[2]
def p_variable_strange(self, p):
""" variable : t_popen variable t_pclose
"""
p[0] = p[2]
def p_variable(self, p):
""" variable : less_variable
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_color(self, p):
""" color : css_color
"""
p[0] = LessColor().format(p[1])
def p_number(self, p):
""" number : css_number
| css_number_unit
"""
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def p_scope_open(self, p):
""" brace_open : '{'
"""
self._create_scope()
p[0] = p[1]
def p_scope_close(self, p):
""" brace_close : '}'
"""
self.scope.pop()
p[0] = p[1]
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def _create_scope(self):
""" Create a scope.
"""
self.scope.append({'__blocks__': [], '__mixins__': {}})
def update_scope(self, scope):
"""
"""
blocks = [b for b in self.scope[0]['__blocks__']]
blocks.extend([b for b in scope[0]['__blocks__']])
self.scope[0].update(scope[0])
self.scope[0]['__blocks__'] = blocks
def p_error(self, t):
""" Internal error handler
@param Lex token: Error token
"""
if t: print("E: line: %d, Syntax Error, token: `%s`, `%s`"
% (t.lineno, t.type, t.value))
while True:
t = self.lex.token()
if not t or t.value == '}':
if len(self.scope) > 1:
self.scope.pop()
break
self.parser.restart()
return t
def handle_error(self, e, p, t='E'):
""" Custom error handler
@param Exception: Exception
@param Parser token: Parser token
@param string: Error level
"""
l = [n for n in [p.lineno(i) for i in range(len(p))] if n]
l = l[0] if l else -1
print("%s: line: %d: " % (t, l), end='')
print(e)

154
lesscpy/lessc/utility.py Normal file
View File

@@ -0,0 +1,154 @@
"""
Utility functions
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import collections
def flatten(l):
"""
Flatten list.
@param l: list
@return: generator
"""
for el in l:
if isinstance(el, collections.Iterable) and not isinstance(el, str):
for sub in flatten(el):
yield sub
else:
yield el
def block_search(name, scope):
""" Search scope blocks for name
@param str: name
@param list: scope
@return: list
"""
def search(m, l):
"""
"""
prop = []
for b in l:
n = b._cident
if m == n:
prop.append(b)
elif m in b._parts:
prop.append(b)
elif m.startswith(n):
r = search(m, b.parsed['inner'])
if r: prop.extend(r)
return prop
l = len(scope)
i = 1
prop = []
while (l-i) >= 0:
if scope[-i]['__blocks__']:
b = search(name, scope[-i]['__blocks__'])
if b: prop.extend(b)
i += 1
# HACK
if '>' in name:
name = ''.join([c for c in name if c != '>'])
i = 0
while (l-i) >= 0:
if scope[-i]['__blocks__']:
b = search(name, scope[-i]['__blocks__'])
if b: prop.extend(b)
i += 1
return prop
def destring(v):
""" Strip quotes
@param string: value
@return: string
"""
return v.strip('"\'')
def analyze_number(var, err=''):
""" Analyse number for type and split from unit
@param str: value
@return: tuple (number, unit)
"""
u = None
if type(var) is not str:
return (var, u)
if is_color(var):
return (var, 'color')
var = var.strip()
n = var
if not '.' in var:
try:
n = int(var)
return (n, u)
except (ValueError, TypeError):
pass
try:
n = float(var)
except (ValueError, TypeError):
try:
n = ''.join([c for c in var if c in '0123456789.-'])
n = float(n) if '.' in n else int(n)
u = ''.join([c for c in var if c not in '0123456789.-'])
except ValueError:
raise SyntaxError('%s ´%s´' % (err, var))
return (n, u)
def with_unit(n, u):
""" Return number with unit
@param int/float: value
@param str: unit
@return: mixed
"""
if n == 0: return 0
if u:
return "%s%s" % (str(n), u)
return n
def is_color(v):
""" Is CSS color
@param mixed: value
@return: bool
"""
if not v or type(v) is not str:
return False
l = len(v)
if l == 4 or l == 7:
try:
int(v[1:], 16)
return True
except Exception:
return False
def is_variable(v):
""" Check if string is LESS variable
@param string: check
@return: bool
"""
if type(v) is str:
return (v.startswith('@') or v.startswith('-@'))
return False
def print_recursive(ll, lvl=0):
""" Scopemap printer
@param list: parser result
@param int: depth
"""
if not ll: return
pad = ''.join(['. '] * lvl)
if type(ll) is list:
ll = flatten(ll)
for l in ll:
t = type(l)
if t == str:
print(pad + l)
else:
print_recursive(l, lvl+1)
elif hasattr(ll, '_p'):
print(pad + str(type(ll)))
print_recursive(ll._p, lvl+1)
else:
print(pad + ll)

0
lesscpy/lib/__init__.py Normal file
View File

293
lesscpy/lib/css.py Normal file
View File

@@ -0,0 +1,293 @@
"""
CSS syntax names.
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
css2 = [
'azimuth',
'background-attachment',
'background-color',
'background-image',
'background-position',
'background-repeat',
'background',
'border-collapse',
'border-color',
'border-spacing',
'border-style',
'border-top',
'border-right',
'border-bottom',
'border-left',
'border-top-color',
'border-right-color',
'border-bottom-color',
'border-left-color',
'border-top-style',
'border-right-style',
'border-bottom-style',
'border-left-style',
'border-top-width',
'border-right-width',
'border-bottom-width',
'border-left-width',
'border-width',
'border',
'bottom',
'caption-side',
'clear',
'clip',
'color',
'content',
'counter-increment',
'counter-reset',
'cue-after',
'cue-before',
'cue',
'cursor',
'direction',
'display',
'elevation',
'empty-cells',
'float',
'font-family',
'font-size',
'font-style',
'font-variant',
'font-weight',
'font',
'height',
'left',
'letter-spacing',
'line-height',
'list-style-image',
'list-style-position',
'list-style-type',
'list-style',
'margin-right',
'margin-left',
'margin-top',
'margin-bottom',
'margin',
'max-height',
'max-width',
'min-height',
'min-width',
'orphans',
'outline-color',
'outline-style',
'outline-width',
'outline',
'overflow',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'padding',
'page-break-after',
'page-break-before',
'page-break-inside',
'pause-after',
'pause-before',
'pause',
'pitch-range',
'pitch',
'play-during',
'position',
'quotes',
'richness',
'right',
'speak-header',
'speak-numeral',
'speak-punctuation',
'speak',
'speech-rate',
'stress',
'table-layout',
'text-align',
'text-decoration',
'text-indent',
'text-transform',
'top',
'unicode-bidi',
'vertical-align',
'visibility',
'voice-family',
'volume',
'white-space',
'widows',
'width',
'word-spacing',
'z-index',
]
css3 = [
'alignment-adjust',
'alignment-baseline',
'animation',
'animation-delay',
'animation-direction',
'animation-duration',
'animation-iteration-count',
'animation-name',
'animation-play-state',
'animation-timing-function',
'appearance',
'backface-visibility',
'background-clip',
'background-origin',
'background-size',
'baseline-shift',
'bookmark-label',
'bookmark-level',
'bookmark-target',
'border-bottom-left-radius',
'border-bottom-right-radius',
'border-image',
'border-image-outset',
'border-image-repeat',
'border-image-slice',
'border-image-source',
'border-image-width',
'border-radius',
'border-top-left-radius',
'border-top-right-radius',
'box-align',
'box-decoration-break',
'box-direction',
'box-flex',
'box-flex-group',
'box-lines',
'box-ordinal-group',
'box-orient',
'box-pack',
'box-shadow',
'box-sizing',
'color-profile',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-color',
'column-rule-style',
'column-rule-width',
'column-span',
'column-width',
'columns',
'crop',
'dominant-baseline',
'drop-initial-after-adjust',
'drop-initial-after-align',
'drop-initial-before-adjust',
'drop-initial-before-align',
'drop-initial-size',
'drop-initial-value',
'fit',
'fit-position',
'float-offset',
'font-size-adjust',
'font-stretch',
'grid-columns',
'grid-rows',
'hanging-punctuation',
'hyphenate-after',
'hyphenate-before',
'hyphenate-character',
'hyphenate-lines',
'hyphenate-resource',
'hyphens',
'icon',
'image-orientation',
'image-resolution',
'inline-box-align',
'line-stacking',
'line-stacking-ruby',
'line-stacking-shift',
'line-stacking-strategy',
'mark',
'mark-after',
'mark-before',
'marks',
'marquee-direction',
'marquee-play-count',
'marquee-speed',
'marquee-style',
'move-to',
'nav-down',
'nav-index',
'nav-left',
'nav-right',
'nav-up',
'opacity',
'outline-offset',
'overflow-style',
'overflow-x',
'overflow-y',
'page',
'page-policy',
'perspective',
'perspective-origin',
'phonemes',
'punctuation-trim',
'rendering-intent',
'resize',
'rest',
'rest-after',
'rest-before',
'rotation',
'rotation-point',
'ruby-align',
'ruby-overhang',
'ruby-position',
'ruby-span',
'size',
'string-set',
'target',
'target-name',
'target-new',
'target-position',
'text-align-last',
'text-height',
'text-justify',
'text-outline',
'text-overflow',
'text-shadow',
'text-wrap',
'transform',
'transform-origin',
'transform-style',
'transition',
'transition-delay',
'transition-duration',
'transition-property',
'transition-timing-function',
'voice-balance',
'voice-duration',
'voice-pitch',
'voice-pitch-range',
'voice-rate',
'voice-stress',
'voice-volume',
'word-break',
'word-wrap'
]
vendor_prefix = [
'-ms-',
'-moz-',
'-o-',
'-atsc-',
'-wap-',
'-webkit-',
'-khtml-'
'-xv-',
'mso-',
]
vendor_ugly = [
'filter',
'accelerator',
'behavior',
'filter',
'zoom',
]
propertys = css2 + css3 + vendor_ugly

142
lesscpy/lib/dom.py Normal file
View File

@@ -0,0 +1,142 @@
"""
HTML DOM names
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
html4 = [
'a',
'abbr',
'acronym',
'address',
'applet',
'area',
'b',
'base',
'basefont',
'bdo',
'big',
'blockquote',
'body',
'br',
'button',
'caption',
'center',
'cite',
'code',
'col',
'colgroup',
'dd',
'del',
'dfn',
'dir',
'div',
'dl',
'dt',
'em',
'fieldset',
'font',
'form',
'frame',
'frameset',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'hr',
'html',
'i',
'iframe',
'img',
'input',
'ins',
'kbd',
'label',
'legend',
'li',
# 'link',
'map',
'mark',
'menu',
'meta',
'noframes',
'noscript',
'object',
'ol',
'optgroup',
'option',
'p',
'param',
'pre',
'q',
's',
'samp',
'script',
'select',
'small',
'span',
'strike',
'strong',
'style',
'sub',
'sup',
'table',
'tbody',
'td',
'textarea',
'tfoot',
'th',
'thead',
'title',
'tr',
'tt',
'u',
'ul',
'var',
'print',
'screen',
'all',
'projection',
# 'focus',
# 'hover',
]
html5 = [
'article',
'aside',
'audio',
'bdi',
'canvas',
'command',
'datalist',
'details',
'embed',
'figcaption',
'figure',
'footer',
'header',
'hgroup',
'keygen',
'mark',
'meter',
'nav',
'output',
'progress',
'rp',
'rt',
'ruby',
'section',
'source',
'summary',
'time',
'track',
'video',
'wbr',
]
html = html4
html.extend(html5)

18
lesscpy/plib/__init__.py Normal file
View File

@@ -0,0 +1,18 @@
__all__ = [
'Block',
'Call',
'Expression',
'Mixin',
'Property',
'Statement',
'String',
'Variable'
]
from .block import Block
from .call import Call
from .expression import Expression
from .mixin import Mixin
from .property import Property
from .statement import Statement
from .string import String
from .variable import Variable

81
lesscpy/plib/block.py Normal file
View File

@@ -0,0 +1,81 @@
"""
Block node.
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
from collections import OrderedDict
import lesscpy.lessc.utility as utility
from .process import Process
class Block(Process):
format = "%(identifier)s%(ws)s{%(nl)s%(proplist)s}%(endblock)s"
def parse(self, scope):
""" Parse Node
@param list: current scope
"""
self._blocktype = None
self.scope = scope
self._proplist()
self.parsed['inner'] = [p for p in self._p[2]
if type(p) is type(self)]
self._pname()
if self._name.startswith('@media'):
self._blocktype = 'inner'
self.parsed['identifier'] = self._ident.strip()
return self
def merge(self, block):
"""
"""
assert(type(block) is Block)
self.parsed['proplist'].extend(block.parsed['proplist'])
def _pname(self):
""" Parse block name and identifier
"""
name = ["%s " % t
if t in '>+'
else t
for t in utility.flatten(self._p[1])]
self._name = ''.join(name)
self._ident = self._fullname(name)
self._cident = self._ident.replace(' ', '')
def _fullname(self, name):
"""
"""
parent = list(utility.flatten([s['current']
for s in self.scope
if 'current' in s]))
if parent:
parent.reverse()
if parent[-1].startswith('@media'):
parent.pop()
names = [p.strip()
for p in self._name.split(',')]
self._parts = names
for p in parent:
parts = p.split(',')
names = [n.replace('&', p)
if '&' in n
else
"%s %s" % (p.strip(), n)
for n in names
for p in parts]
return ', '.join(names) if len(names) < 6 else ',\n'.join(names)
def _proplist(self):
""" Reduce property list to remove redundant styles.
Multiple porperties of the same type overwrite,
so we can safely take only the last.
"""
proplist = [p for p in utility.flatten(self._p[2])
if p and type(p) != type(self)]
store = OrderedDict()
for p in proplist:
store[p.parsed['property']] = p
self.parsed['proplist'] = store.values()

101
lesscpy/plib/call.py Normal file
View File

@@ -0,0 +1,101 @@
"""
Call Node.
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import re
from urllib.parse import quote as urlquote
from .process import Process
from lesscpy.lessc.color import LessColor
from .expression import Expression
import lesscpy.lessc.utility as utility
class Call(Process):
def parse(self, scope):
""" Parse Node
@param list: current scope
"""
self.scope = scope
call = list(utility.flatten(self._p[1:]))
name = call[0]
if name == '%(':
name = 'sformat'
elif name == '~':
name = 'e'
color = LessColor()
call = self.process_tokens(call[1:])
args = [t for t in call
if type(t) is not str or t not in '(),']
if hasattr(self, name):
try:
return getattr(self, name)(*args)
except ValueError:
pass
if hasattr(color, name):
try:
return getattr(color, name)(*args)
except ValueError:
pass
call = ' '.join([str(t) for t in call[1:-1]]).replace(' = ', '=')
return ["%s(%s)" % (name, call)]
def e(self, string):
""" Less Escape.
@param string: value
@return string
"""
return utility.destring(string.strip('~'))
def sformat(self, *args):
""" String format
@param list: values
@return string
"""
format = args[0]
items = []
m = re.findall('(%[asdA])', format)
i = 1
for n in m:
v = {
'%d' : int,
'%A' : urlquote,
'%s' : utility.destring,
}.get(n, str)(args[i])
items.append(v)
i += 1
format = format.replace('%A', '%s')
return format % tuple(items)
def increment(self, v):
""" Increment function
@param Mixed: value
@return: incremented value
"""
n, u = utility.analyze_number(v)
return utility.with_unit(n+1, u)
def decrement(self, v):
""" Decrement function
@param Mixed: value
@return: incremented value
"""
n, u = utility.analyze_number(v)
return utility.with_unit(n-1, u)
def add(self, *args):
""" Add integers
@param list: values
@return: int
"""
return sum([int(v) for v in args])
def round(self, v):
""" Round number
@param Mixed: value
@return: rounded value
"""
n, u = utility.analyze_number(v)
return utility.with_unit(round(float(n)), u)

100
lesscpy/plib/expression.py Normal file
View File

@@ -0,0 +1,100 @@
"""
Expression Node.
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import lesscpy.lessc.utility as utility
from .process import Process
from lesscpy.lessc import color
class Expression(Process):
def parse(self, scope):
""" Parse Node
@param list: current scope
"""
self.scope = scope
expr = [self._p[1], self._p[2], self._p[3]]
while True:
done = True
expr = [self.neg(t) for t in expr]
if any(t for t in expr if hasattr(t, 'parse')):
expr = [t.parse(scope) if hasattr(t, 'parse')
else t
for t in expr]
done = False
if any(t for t in expr if utility.is_variable(t)):
expr = self.replace_vars(expr)
done = False
expr = list(utility.flatten(expr))
if done: break
p = self.process(expr)
return p
def neg(self, t):
"""
"""
if t and type(t) is list and t[0] == '-':
v = t[1]
if len(t) > 1 and hasattr(t[1], 'parse'):
v = t[1].parse(self.scope)
if type(v) is str:
return '-' + v
return -v
return t
def process(self, expression):
"""
"""
assert(len(expression) == 3)
A, O, B = expression
a, ua = utility.analyze_number(A, 'Illegal element in expression')
b, ub = utility.analyze_number(B, 'Illegal element in expression')
if(a is False or b is False):
return self.expression()
if ua == 'color' or ub == 'color':
return color.LessColor().process(expression)
out = self.operate(a, b, O)
if type(a) is int and type(b) is int:
out = int(out)
return self.units(out, ua, ub)
def units(self, v, ua, ub):
"""
"""
if v == 0: return v;
if ua and ub:
if ua == ub:
return str(v) + ua
else:
raise SyntaxError("Error in expression %s != %s" % (ua, ub))
elif ua:
return str(v) + ua
elif ub:
return str(v) + ub
return v
def operate(self, a, b, o):
"""
"""
# print("´%s´ ´%s´ ´%s´" % (a, b, o))
operation = {
'+': '__add__',
'-': '__sub__',
'*': '__mul__',
'/': '__truediv__'
}.get(o)
v = getattr(a, operation)(b)
if v is NotImplemented:
v = getattr(b, operation)(a)
return v
def expression(self):
"""
"""
return [self._p[1], self._p[2], self._p[3]]

97
lesscpy/plib/mixin.py Normal file
View File

@@ -0,0 +1,97 @@
"""
Parametered Mixin Node.
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
from collections import deque
import lesscpy.lessc.utility as utility
import copy
from .process import Process
from .node import Node
class Mixin(Process):
""" Mixin Node.
"""
def parse(self, scope, stash=None):
""" Parse Node
@param list: current scope
"""
self.stash = stash
self.name = self._p[1][0]
if len(self._p[1]) > 1:
if type(self._p[1][2]) is list:
self.argv = [[u[0], u[2]] if type(u) is list
else [u, None]
for u in self._p[1][2]
if u and u != ',']
else:
self.argv = self._p[1][2]
self.argc = len(self.argv)
else:
self.argv = []
self.argc = 0
self.prop = self._p[2]
def call(self, args, scope=[{}]):
""" Call mixin function.
@param list: Arguments passed to mixin
@return: Property list
"""
if self.argv == '@arguments':
return [self.replace_arguments(copy.copy(p), args).parse(self.scope)
for p in self.prop
if p]
self.scope = scope
self.scope[0].update(self.stash)
prop = [copy.copy(p) for p in self.prop if p]
prop = utility.flatten([p.call(args, self.scope)
if type(p) is Mixin else p
for p in prop])
self._process_args(args)
return [p.parse(self.scope) for p in prop]
def _process_args(self, args):
""" Process arguments to mixin call.
Handle the @arguments variable
@param list: arguments
"""
variables = []
if args:
args = [a for a in self.process_tokens(args)
if a != ',']
else:
args = []
args = deque(args)
for v in self.argv:
if args:
u = args.popleft()
variables.append((v[0], u))
else:
variables.append(v)
for v in variables:
self._create_variable(v)
# Special @arguments
arguments = [v[1] for v in variables]
arguments.extend(args)
self._create_variable(('@arguments', [arguments]))
def _create_variable(self, l):
""" Create new variable in scope, from list or index
@param tuple: name/value pair
"""
assert(len(l) == 2)
var = Node()
var._name, var._value = l
self.scope[0][var._name] = var
def replace_arguments(self, p, args):
""" Replace the special @arguments variable
@param Property object: Property object
@param list: Replacement list
@return: Property object
"""
p._p[3] = args
return p

18
lesscpy/plib/node.py Normal file
View File

@@ -0,0 +1,18 @@
"""
Parser node base class.
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
class Node(object):
def name(self):
""" @return: node name"""
return self._name
def value(self):
""" @return: node value"""
return self._value
def parse(self, scope):
raise NotImplementedError()

84
lesscpy/plib/process.py Normal file
View File

@@ -0,0 +1,84 @@
"""
Base process Node
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import lesscpy.lessc.utility as utility
from .node import Node
class Process(Node):
def __init__(self, p):
self._p = list(p)
self.lines = [j for j in [p.lineno(i)
for i in range(len(self._p))]
if j]
self.scope = None
self.parsed = {}
def process_tokens(self, tokens):
"""
"""
while True:
done = True
if any(t for t in tokens if hasattr(t, 'parse')):
tokens = [t.parse(self.scope) if hasattr(t, 'parse')
else t
for t in tokens]
done = False
if any(t for t in tokens if utility.is_variable(t)):
tokens = self.replace_vars(tokens)
done = False
tokens = list(utility.flatten(tokens))
if done: break
return tokens
def replace_vars(self, tokens):
"""
Replace variables in tokenlist
"""
return [self.swap(t)
if utility.is_variable(t)
else t
for t in tokens]
def swap(self, var):
"""
Swap single variable
"""
if not self.scope:
raise SyntaxError("Unknown variable ´%s´" % var)
pad = ''
pre = ''
if var.endswith(' '):
var = var.strip()
pad = ' '
if var.startswith('-'):
var = ''.join(var[1:])
pre = '-'
r = var.startswith('@@')
t = ''.join(var[1:]) if r else var
i = len(self.scope)
while i >=0:
i -= 1
if t in self.scope[i]:
f = self.scope[i][t].value()
if r:
return self.swap("%s@%s%s" % (pre, f[0].strip('"\''), pad))
return self.ftok(f, pre, pad)
raise SyntaxError("Unknown variable ´%s´" % var)
def ftok(self, t, pre, pad):
"""
"""
try:
r = ''.join(t)
except TypeError:
r = t[0] if type(t) is list else str(t)
if pad and type(r) is str:
r += pad
if pre and type(r) is str:
r = pre + r
return r

40
lesscpy/plib/property.py Normal file
View File

@@ -0,0 +1,40 @@
"""
Property Node
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import lesscpy.lessc.utility as utility
from .process import Process
class Property(Process):
format = "%(tab)s%(property)s:%(ws)s%(style)s;%(nl)s"
def parse(self, scope):
""" Parse Node
@param list: current scope
"""
self.scope = scope
self.parsed = {}
tokens = list(utility.flatten([self._p[1], self._p[3]]))
self.parsed['property'] = tokens[0]
style = self.preprocess(tokens[1:])
style = self.process_tokens(style)
style = ' '.join([str(t).strip() for t in style
if t is not None]).replace(' , ', ', ')
self.parsed['style'] = style
return self
def preprocess(self, style):
""" Preprocess for annoying shorthand CSS declarations
@param list: style
@return: list
"""
if self.parsed['property'] == 'font':
style = [''.join(u.expression())
if hasattr(u, 'expression')
else u
for u in style]
return style

20
lesscpy/plib/statement.py Normal file
View File

@@ -0,0 +1,20 @@
"""
Statement Node
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
from .process import Process
class Statement(Process):
format = "%(identifier)s%(ws)s%(value)s;%(nl)s"
def parse(self, scope):
""" Parse Node
@param list: current scope
"""
self._ident = self._p[1].strip()
self.parsed['identifier'] = self._ident
self.parsed['value'] = self._p[2]

27
lesscpy/plib/string.py Normal file
View File

@@ -0,0 +1,27 @@
"""
String Node
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
import re
from .process import Process
class String(Process):
def parse(self, scope):
""" Parse Node
@param list: current scope
@return: string
"""
self.scope = scope
return re.sub(r'@\{([^\}]+)\}', lambda m: self.format(m.group(1)), self._p[1])
def format(self, var):
""" Format variable for replacement
@param string: var
@return: string
"""
var = '@' + var
var = self.swap(var)
return var.strip("\"'")

18
lesscpy/plib/variable.py Normal file
View File

@@ -0,0 +1,18 @@
"""
Variable Node
Copyright (c)
See LICENSE for details.
<jtm@robot.is>
"""
from .process import Process
class Variable(Process):
def name(self):
return self._p[1]
def value(self):
return self._p[3]
def parse(self, scope):
return ''

View File

127
lesscpy/scripts/compiler.py Normal file
View File

@@ -0,0 +1,127 @@
"""
CSS/LESSCSS run script
http://lesscss.org/#docs
Copyright (c)
See LICENSE for details
<jtm@robot.is>
"""
import os
import sys
import glob
import argparse
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
from lesscpy.lessc import parser
from lesscpy.lessc import lexer
from lesscpy.lessc import formatter
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
def run():
aparse = argparse.ArgumentParser(description='LessCss Compiler', epilog='<< jtm@robot.is @_o >>')
aparse.add_argument('-I', '--include', action="store", type=str,
help="Included less-files (comma separated)")
aparse.add_argument('-x', '--minify', action="store_true",
default=False, help="Minify output")
aparse.add_argument('-X', '--xminify', action="store_true",
default=False, help="Minify output, no end of block newlines")
aparse.add_argument('-m', '--min-ending', action="store_true",
default=False, help="Add '.min' into output filename. eg, name.min.css")
aparse.add_argument('-D', '--dry-run', action="store_true",
default=False, help="Dry run, do not write files")
aparse.add_argument('-v', '--verbose', action="store_true",
default=False, help="Verbose mode")
aparse.add_argument('-o', '--out', action="store", help="Output directory")
group = aparse.add_argument_group('Debugging')
group.add_argument('-S', '--scopemap', action="store_true",
default=False, help="Scopemap")
group.add_argument('-V', '--debug', action="store_true",
default=False, help="Debug mode")
group.add_argument('-L', '--lex-only', action="store_true",
default=False, help="Run lexer on target")
group.add_argument('-N', '--no-css', action="store_true",
default=False, help="No css output")
aparse.add_argument('target')
args = aparse.parse_args()
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
if args.lex_only:
lex = lexer.LessLexer()
ll = lex.file(args.target)
while True:
tok = ll.token()
if not tok: break
print(tok)
print('EOF')
sys.exit()
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
yacctab = 'yacctab' if args.debug else None
scope = None
if args.include:
for u in args.include.split(','):
if args.debug: print("compiling include: %s" % u)
p = parser.LessParser(
yacc_debug=False,
lex_optimize=True,
yacc_optimize=True,
yacctab=yacctab)
p.parse(filename=u, debuglevel=0)
if not scope:
scope = p.scope
else:
scope[0].update(p.scope[0])
else:
scope = None
p = None
f = formatter.Formatter()
if not os.path.exists(args.target):
sys.exit("Target not found '%s' ..." % args.target)
if os.path.isdir(args.target):
if not args.out:
sys.exit("Compile directory option needs -o ...")
elif os.path.isdir(args.out) and not os.listdir(args.out) == []:
sys.exit("Output directory not empty...")
else:
if not os.path.isdir(args.out):
if args.verbose:
print("Creating '%s'" % args.out)
if not args.dry_run:
os.mkdir(args.out)
less = glob.glob(os.path.join(args.target, '*.less'))
for lf in less:
outf = os.path.splitext(os.path.basename(lf))
min = '.min' if args.min_ending else ''
outf = "%s/%s%s.css" % (args.out, outf[0], min)
if args.verbose: print("%s -> %s" % (lf, outf))
p = parser.LessParser(yacc_debug=False,
lex_optimize=True,
yacc_optimize=True,
scope=scope,
yacctab=yacctab)
p.parse(filename=lf, debuglevel=0)
css = f.format(p, args.minify, args.xminify)
if not args.dry_run:
with open(outf, 'w') as outfile:
outfile.write(css)
if args.dry_run:
print('Dry run, nothing done.')
else:
if args.verbose: print("compiling target: %s" % args.target)
p = parser.LessParser(yacc_debug=(args.debug),
lex_optimize=True,
yacc_optimize=(not args.debug),
scope=scope)
p.parse(filename=args.target, debuglevel=0)
if args.scopemap:
args.no_css = True
p.scopemap()
if not args.no_css and p:
out = f.format(p, args.minify, args.xminify)
print(out)

0
lesscpy/test/__init__.py Normal file
View File

33
lesscpy/test/__main__.py Normal file
View File

@@ -0,0 +1,33 @@
"""
LessCss tests
"""
import unittest
import sys, os
import re
here = os.path.dirname(__file__)
path = os.path.abspath(here)
while os.path.dirname(path) != path:
if os.path.exists(os.path.join(path, 'lesscpy', '__init__.py')):
sys.path.insert(0, path)
break
path = os.path.dirname(path)
def find():
svn = re.compile('\.svn')
test = re.compile('test.+\.py$')
skip = re.compile('testissues.*')
alltests = unittest.TestSuite()
for path, dirs, files in os.walk(here):
if svn.search(path):
continue
for f in files:
if skip.search(f):
continue
if test.search(f):
module = __import__(f.split('.')[0])
alltests.addTest(unittest.findTestCases(module))
return alltests
if __name__ == '__main__':
unittest.main(defaultTest='find')

View File

@@ -0,0 +1,13 @@
.color_functions {
lighten: #ffcccc;
darken: #330000;
saturate: #203c31;
desaturate: #29332f;
greyscale: #4e0e27;
spin-p: #bf6b40;
spin-n: #bf4055;
hue: 100.0;
saturation: 12;
lightness: 95;
}

View File

@@ -0,0 +1 @@
.color_functions{lighten:#ffcccc;darken:#330000;saturate:#203c31;desaturate:#29332f;greyscale:#4e0e27;spin-p:#bf6b40;spin-n:#bf4055;hue:100.0;saturation:12;lightness:95;}

View File

@@ -0,0 +1,48 @@
#yelow #short {
color: #ffeeaa;
}
#yelow #long {
color: #ffeeaa;
}
#yelow #rgba {
color: rgba(255, 238, 170, 0.1);
}
#yelow #argb {
color: argb(rgba(255, 238, 170, 0.1));
}
#blue #short {
color: #0000ff;
}
#blue #long {
color: #0000ff;
}
#blue #rgba {
color: rgba(0, 0, 255, 0.1);
}
#blue #argb {
color: argb(rgba(0, 0, 255, 0.1));
}
#alpha #hsla {
color: hsla(11, 20%, 20%, 0.6);
}
#overflow .a {
color: #000000;
}
#overflow .b {
color: #ffffff;
}
#overflow .c {
color: #ffffff;
}
#overflow .d {
color: #00ff00;
}
#grey {
color: #c8c8c8;
}
#808080 {
color: #808080;
}
#00ff00 {
color: #00ff00;
}

16
lesscpy/test/css/colors.min.css vendored Normal file
View File

@@ -0,0 +1,16 @@
#yelow #short{color:#ffeeaa;}
#yelow #long{color:#ffeeaa;}
#yelow #rgba{color:rgba(255, 238, 170, 0.1);}
#yelow #argb{color:argb(rgba(255, 238, 170, 0.1));}
#blue #short{color:#0000ff;}
#blue #long{color:#0000ff;}
#blue #rgba{color:rgba(0, 0, 255, 0.1);}
#blue #argb{color:argb(rgba(0, 0, 255, 0.1));}
#alpha #hsla{color:hsla(11, 20%, 20%, 0.6);}
#overflow .a{color:#000000;}
#overflow .b{color:#ffffff;}
#overflow .c{color:#ffffff;}
#overflow .d{color:#00ff00;}
#grey{color:#c8c8c8;}
#808080{color:#808080;}
#00ff00{color:#00ff00;}

View File

@@ -0,0 +1,17 @@
#comments {
color: red;
background-color: orange;
font-size: 12px;
content: "content";
border: 1px solid black;
padding: 0;
margin: 2em;
}
.selector, .lots, .comments {
color: grey, orange;
-webkit-border-radius: 2px;
-moz-border-radius: 8px;
}
#last {
color: blue;
}

3
lesscpy/test/css/comments.min.css vendored Normal file
View File

@@ -0,0 +1,3 @@
#comments{color:red;background-color:orange;font-size:12px;content:"content";border:1px solid black;padding:0;margin:2em;}
.selector, .lots, .comments{color:grey, orange;-webkit-border-radius:2px;-moz-border-radius:8px;}
#last{color:blue;}

View File

@@ -0,0 +1,46 @@
.comma-delimited {
background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
text-shadow: -1px -1px 1px red, 6px 5px 5px yellow;
-moz-box-shadow: 0pt 0pt 2px rgba(255, 255, 255, 0.4) inset, 0pt 4px 6px rgba(255, 255, 255, 0.4) inset;
}
@font-face {
font-family: Headline;
src: local(Futura-Medium), url(fonts.svg#MyGeometricModern) format("svg");
}
.other {
-moz-transform: translate(0, 11em) rotate(-90deg);
}
p:not([class*="lead"]) {
color: black;
}
input[type="text"].class#id[attr=32]:not(1) {
color: white;
}
div#id.class[a=1][b=2].class:not(1) {
color: white;
}
ul.comma > li:not(:only-child)::after {
color: white;
}
ol.comma > li:nth-last-child(2)::after {
color: white;
}
li:nth-child(4n+1), li:nth-child(-5n), li:nth-child(-n+2) {
color: white;
}
a[href^="http://"] {
color: black;
}
a[href$="http://"] {
color: black;
}
form[data-disabled] {
color: black;
}
p::before {
color: black;
}
#issue322 {
-webkit-animation: anim2 7s infinite ease-in-out;
}

14
lesscpy/test/css/css-3.min.css vendored Normal file
View File

@@ -0,0 +1,14 @@
.comma-delimited{background:url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);text-shadow:-1px -1px 1px red, 6px 5px 5px yellow;-moz-box-shadow:0pt 0pt 2px rgba(255, 255, 255, 0.4) inset, 0pt 4px 6px rgba(255, 255, 255, 0.4) inset;}
@font-face{font-family:Headline;src:local(Futura-Medium), url(fonts.svg#MyGeometricModern) format("svg");}
.other{-moz-transform:translate(0, 11em) rotate(-90deg);}
p:not([class*="lead"]){color:black;}
input[type="text"].class#id[attr=32]:not(1){color:white;}
div#id.class[a=1][b=2].class:not(1){color:white;}
ul.comma > li:not(:only-child)::after{color:white;}
ol.comma > li:nth-last-child(2)::after{color:white;}
li:nth-child(4n+1), li:nth-child(-5n), li:nth-child(-n+2){color:white;}
a[href^="http://"]{color:black;}
a[href$="http://"]{color:black;}
form[data-disabled]{color:black;}
p::before{color:black;}
#issue322{-webkit-animation:anim2 7s infinite ease-in-out;}

View File

@@ -0,0 +1,16 @@
.escape\|random\|char {
color: red;
}
.mixin\!tUp {
font-weight: bold;
}
.\34 04 {
background: red;
}
.\34 04 strong {
color: fuchsia;
font-weight: bold;
}
.trailingTest\+ {
color: red;
}

5
lesscpy/test/css/css-escapes.min.css vendored Normal file
View File

@@ -0,0 +1,5 @@
.escape\|random\|char{color:red;}
.mixin\!tUp{font-weight:bold;}
.\34 04{background:red;}
.\34 04 strong{color:fuchsia;font-weight:bold;}
.trailingTest\+{color:red;}

89
lesscpy/test/css/css.css Normal file
View File

@@ -0,0 +1,89 @@
@charset "utf-8";
div {
color: black;
}
div {
width: 99%;
}
* {
min-width: 45em;
}
h1, h2 > a > p, h3 {
color: none;
}
div.class {
color: blue;
}
div#id {
color: green;
}
.class#id {
color: purple;
}
.one.two.three {
color: grey;
}
.one .two .three {
color: grey;
}
@media print {
font-size: 3em;
}
@media screen {
font-size: 10px;
}
@font-face {
font-family: 'Garamond Pro';
src: url("/fonts/garamond-pro.ttf");
}
a:hover, a:link {
color: #999999;
}
p, p:first-child {
text-transform: none;
}
q:lang(no) {
quotes: none;
}
p + h1 {
font-size: 2.2em;
}
#shorthands {
border: 1px solid #000000;
font: 12px/16px Arial;
margin: 1px 0;
padding: 0 auto;
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#shorthands {
font: 100%/16px Arial;
}
#more-shorthands {
margin: 0;
padding: 1px 0 2px 0;
font: normal small/20px 'Trebuchet MS', Verdana, sans-serif;
}
.misc {
-moz-border-radius: 2px;
display: -moz-inline-stack;
width: .1em;
background-color: #009998;
background-image: url(images/image.jpg);
background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue));
filter: alpha(opacity=100);
}
#important {
color: red !important;
width: 100% !important;
height: 20px !important;
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
}
#svg-data-uri {
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}

27
lesscpy/test/css/css.min.css vendored Normal file
View File

@@ -0,0 +1,27 @@
@charset"utf-8";div{color:black;}
div{width:99%;}
*{min-width:45em;}
h1, h2 > a > p, h3{color:none;}
div.class{color:blue;}
div#id{color:green;}
.class#id{color:purple;}
.one.two.three{color:grey;}
.one .two .three{color:grey;}
@media print{font-size:3em;}
@media screen{font-size:10px;}
@font-face{font-family:'Garamond Pro';src:url("/fonts/garamond-pro.ttf");}
a:hover, a:link{color:#999999;}
p, p:first-child{text-transform:none;}
q:lang(no){quotes:none;}
p + h1{font-size:2.2em;}
#shorthands{border:1px solid #000000;font:12px/16px Arial;margin:1px 0;padding:0 auto;background:url("http://www.lesscss.org/spec.html") no-repeat 0 4px;}
#shorthands{font:100%/16px Arial;}
#more-shorthands{margin:0;padding:1px 0 2px 0;font:normal small/20px 'Trebuchet MS', Verdana, sans-serif;}
.misc{-moz-border-radius:2px;display:-moz-inline-stack;width:.1em;background-color:#009998;background-image:url(images/image.jpg);background:-webkit-gradient(linear, left top, left bottom, from(red), to(blue));filter:alpha(opacity=100);}
#important{color:red !important;width:100% !important;height:20px !important;}
#data-uri{background:url(data:image/png;charset=utf-8;base64,
kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);background-image:url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);}
#svg-data-uri{background:transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');}
.uri_test{background-image:url(fonts.svg#MyGeometricModern);behavior:url(border-radius.htc);}

View File

@@ -0,0 +1,18 @@
#functions {
width: 16;
height: undefined("self");
border-width: 5;
variable: 11;
decrement: 9;
}
#built-in {
escaped: -Some::weird(#thing, y);
escaped1: -Some::weird(#thing, z);
format: "rgb(32, 128, 64)";
format-string: "hello world";
format-multiple: "hello earth 2";
format-url-encode: 'red is %23ff0000';
eformat: rgb(32, 128, 64);
rounded: 10;
roundedpx: 3px;
}

2
lesscpy/test/css/functions.min.css vendored Normal file
View File

@@ -0,0 +1,2 @@
#functions{width:16;height:undefined("self");border-width:5;variable:11;decrement:9;}
#built-in{escaped:-Some::weird(#thing, y);escaped1:-Some::weird(#thing, z);format:"rgb(32, 128, 64)";format-string:"hello world";format-multiple:"hello earth 2";format-url-encode:'red is %23ff0000';eformat:rgb(32, 128, 64);rounded:10;roundedpx:3px;}

View File

@@ -0,0 +1,9 @@
.nav {
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0);
}
.nav {
filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0);
}
.nav {
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0);
}

3
lesscpy/test/css/ie-filters.min.css vendored Normal file
View File

@@ -0,0 +1,3 @@
.nav{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0);}
.nav{filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);}
.nav{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="#000000", GradientType=0);}

7
lesscpy/test/css/import.css vendored Normal file
View File

@@ -0,0 +1,7 @@
@import 'some.css.file.css';
@import 'some/other.css.file.CSS';
.import {
color: red;
width: 6px;
height: 9px;
}

1
lesscpy/test/css/import.min.css vendored Normal file
View File

@@ -0,0 +1 @@
@import'some.css.file.css';@import'some/other.css.file.CSS';.import{color:red;width:6px;height:9px;}

View File

@@ -0,0 +1,3 @@
.tiny-scope {
color: #998899;
}

View File

@@ -0,0 +1,9 @@
.class {
width: 99px;
}
.overwrite {
width: 99px;
}
.nested .class {
width: 5px;
}

View File

@@ -0,0 +1,14 @@
.class .inner {
height: 300;
}
.class .inner .innest {
width: 30;
border-width: 60;
}
.class2 .inner {
height: 600;
}
.class2 .inner .innest {
width: 60;
border-width: 120;
}

View File

@@ -0,0 +1,49 @@
.zero {
zero: 0;
one: 1;
two: 2;
three: 3;
}
.one {
zero: 0;
one: 1;
one-req: 1;
two: 2;
three: 3;
}
.two {
zero: 0;
one: 1;
two: 2;
three: 3;
}
.three {
zero: 0;
one: 1;
two: 2;
three-req: 3;
three: 3;
}
.left {
left: 1;
}
.right {
right: 1;
}
.border-right {
color: black;
border-right: 4px;
}
.border-left {
color: black;
border-left: 4px;
}
.only-right {
right: 33;
}
.only-left {
left: 33;
}
.left-right {
both: 330;
}

View File

@@ -0,0 +1,21 @@
@media print {
.class {
color: blue;
}
.class .sub {
width: 42;
}
.top, header > h1 {
color: #444444;
}
}
@media screen {
body {
max-width: 480;
}
}
@media all and (orientation:portrait) {
aside {
float: none;
}
}

8
lesscpy/test/css/media.min.css vendored Normal file
View File

@@ -0,0 +1,8 @@
@media print{.class{color:blue;}
.class .sub{width:42;}
.top, header > h1{color:#444444;}
}
@media screen{body{max-width:480;}
}
@media all and (orientation:portrait){aside{float:none;}
}

View File

@@ -0,0 +1,50 @@
#hidden {
color: transparent;
}
.two-args {
color: blue;
width: 10px;
height: 99%;
border: 2px dotted black;
}
.one-arg {
width: 15px;
height: 49%;
}
.no-parens {
width: 5px;
height: 49%;
}
.no-args {
width: 5px;
height: 49%;
}
.var-args {
width: 45;
height: 17%;
}
.multi-mix {
width: 10px;
height: 29%;
margin: 4;
padding: 5;
}
body {
padding: 30px;
color: #f00;
}
.scope-mix {
width: 8;
}
#same-var-name {
radius: 5px;
}
#var-inside {
width: 10px;
}
.arguments {
border: 1px solid black;
}
.arguments2 {
border: 0px;
}

13
lesscpy/test/css/mixins-args.min.css vendored Normal file
View File

@@ -0,0 +1,13 @@
#hidden{color:transparent;}
.two-args{color:blue;width:10px;height:99%;border:2px dotted black;}
.one-arg{width:15px;height:49%;}
.no-parens{width:5px;height:49%;}
.no-args{width:5px;height:49%;}
.var-args{width:45;height:17%;}
.multi-mix{width:10px;height:29%;margin:4;padding:5;}
body{padding:30px;color:#f00;}
.scope-mix{width:8;}
#same-var-name{radius:5px;}
#var-inside{width:10px;}
.arguments{border:1px solid black;}
.arguments2{border:0px;}

View File

@@ -0,0 +1,25 @@
.ext {
color: red;
}
.ext {
color: blue;
}
.ext {
color: red;
}
.ext {
color: black;
}
.ext {
color: red;
}
.ext {
color: green;
}
p {
width: 1px;
color: green;
}
a {
color: green;
}

View File

@@ -0,0 +1,8 @@
.ext{color:red;}
.ext{color:blue;}
.ext{color:red;}
.ext{color:black;}
.ext{color:red;}
.ext{color:green;}
p{width:1px;color:green;}
a{color:green;}

View File

@@ -0,0 +1,74 @@
.mixin {
border: 1px solid black;
}
.mixout {
border-color: orange;
}
.borders {
border-style: dashed;
}
#namespace .borders {
border-style: dotted;
}
#namespace .biohazard {
content: "death";
}
#namespace .biohazard .man {
color: transparent;
}
#theme > .mixin {
background-color: grey;
}
#container {
color: black;
border: 1px solid black;
border-color: orange;
background-color: grey;
}
#header .milk {
color: white;
border: 1px solid black;
background-color: grey;
}
#header #cookie {
border-style: dashed;
}
#header #cookie .chips {
border-style: dotted;
}
#header #cookie .chips .calories {
color: black;
border: 1px solid black;
border-color: orange;
background-color: grey;
}
.secure-zone {
color: transparent;
}
.direct {
border-style: dotted;
}
.bo, .bar {
width: 100%;
}
.bo {
border: 1px;
}
.bo {
color: red;
}
.ar.bo.ca {
color: black;
}
.jo.ki {
background: none;
}
.extended {
width: 100%;
border: 1px;
color: red;
background: none;
}
.foo .bar {
width: 100%;
}

21
lesscpy/test/css/mixins.min.css vendored Normal file
View File

@@ -0,0 +1,21 @@
.mixin{border:1px solid black;}
.mixout{border-color:orange;}
.borders{border-style:dashed;}
#namespace .borders{border-style:dotted;}
#namespace .biohazard{content:"death";}
#namespace .biohazard .man{color:transparent;}
#theme > .mixin{background-color:grey;}
#container{color:black;border:1px solid black;border-color:orange;background-color:grey;}
#header .milk{color:white;border:1px solid black;background-color:grey;}
#header #cookie{border-style:dashed;}
#header #cookie .chips{border-style:dotted;}
#header #cookie .chips .calories{color:black;border:1px solid black;border-color:orange;background-color:grey;}
.secure-zone{color:transparent;}
.direct{border-style:dotted;}
.bo, .bar{width:100%;}
.bo{border:1px;}
.bo{color:red;}
.ar.bo.ca{color:black;}
.jo.ki{background:none;}
.extended{width:100%;border:1px;color:red;background:none;}
.foo .bar{width:100%;}

View File

@@ -0,0 +1,42 @@
#operations {
color: #111111;
height: 9px;
width: 3em;
text-shadow: -1px -1px 1px red, 6px 5px 5px yellow;
substraction: 0;
division: 1;
}
#operations .spacing {
height: 9px;
width: 3em;
}
.with-variables {
height: 16em;
width: 24em;
size: 1cm;
}
.negative {
height: 0;
width: 4px;
}
.shorthands {
padding: -1px 2px 0 -4px;
}
.colors {
color: #112233;
border-color: #334455;
background-color: #000000;
}
.colors .other {
color: #222222;
border-color: #222222;
}
.negations {
variable: -4px;
variable1: 0;
variable2: 0;
variable3: 8px;
variable4: 0;
paren: -4px;
paren2: 16px;
}

8
lesscpy/test/css/operations.min.css vendored Normal file
View File

@@ -0,0 +1,8 @@
#operations{color:#111111;height:9px;width:3em;text-shadow:-1px -1px 1px red, 6px 5px 5px yellow;substraction:0;division:1;}
#operations .spacing{height:9px;width:3em;}
.with-variables{height:16em;width:24em;size:1cm;}
.negative{height:0;width:4px;}
.shorthands{padding:-1px 2px 0 -4px;}
.colors{color:#112233;border-color:#334455;background-color:#000000;}
.colors .other{color:#222222;border-color:#222222;}
.negations{variable:-4px;variable1:0;variable2:0;variable3:8px;variable4:0;paren:-4px;paren2:16px;}

View File

@@ -0,0 +1,20 @@
.parens {
border: 2px solid black;
margin: 1px 3px 16 3;
width: 36;
padding: 2px 36px;
}
.more-parens {
padding: 8 4 4 4px;
width: 96;
height: 113;
margin: 12;
}
.nested-parens {
width: 71;
height: 6;
}
.mixed-units {
margin: 2px 4em 1 5pc;
padding: 6px 1em 2px 2;
}

4
lesscpy/test/css/parens.min.css vendored Normal file
View File

@@ -0,0 +1,4 @@
.parens{border:2px solid black;margin:1px 3px 16 3;width:36;padding:2px 36px;}
.more-parens{padding:8 4 4 4px;width:96;height:113;margin:12;}
.nested-parens{width:71;height:6;}
.mixed-units{margin:2px 4em 1 5pc;padding:6px 1em 2px 2;}

View File

@@ -0,0 +1,29 @@
#first > .one {
font-size: 2em;
}
#first > .one > #second .two > #deux {
width: 50%;
}
#first > .one > #second .two > #deux #third {
height: 100%;
}
#first > .one > #second .two > #deux #third:focus {
color: black;
}
#first > .one > #second .two > #deux #third:focus #fifth > #sixth .seventh #eighth {
color: purple;
}
#first > .one > #second .two > #deux #fourth, #first > .one > #second .two > #deux #five, #first > .one > #second .two > #deux #six {
color: #110000;
}
#first > .one > #second .two > #deux #fourth .seven,
#first > .one > #second .two > #deux #five .seven,
#first > .one > #second .two > #deux #six .seven,
#first > .one > #second .two > #deux #fourth .eight > #nine,
#first > .one > #second .two > #deux #five .eight > #nine,
#first > .one > #second .two > #deux #six .eight > #nine {
border: 1px solid black;
}
#first > .one > #second .two > #deux #fourth #ten, #first > .one > #second .two > #deux #five #ten, #first > .one > #second .two > #deux #six #ten {
color: red;
}

13
lesscpy/test/css/rulesets.min.css vendored Normal file
View File

@@ -0,0 +1,13 @@
#first > .one{font-size:2em;}
#first > .one > #second .two > #deux{width:50%;}
#first > .one > #second .two > #deux #third{height:100%;}
#first > .one > #second .two > #deux #third:focus{color:black;}
#first > .one > #second .two > #deux #third:focus #fifth > #sixth .seventh #eighth{color:purple;}
#first > .one > #second .two > #deux #fourth, #first > .one > #second .two > #deux #five, #first > .one > #second .two > #deux #six{color:#110000;}
#first > .one > #second .two > #deux #fourth .seven,
#first > .one > #second .two > #deux #five .seven,
#first > .one > #second .two > #deux #six .seven,
#first > .one > #second .two > #deux #fourth .eight > #nine,
#first > .one > #second .two > #deux #five .eight > #nine,
#first > .one > #second .two > #deux #six .eight > #nine{border:1px solid black;}
#first > .one > #second .two > #deux #fourth #ten, #first > .one > #second .two > #deux #five #ten, #first > .one > #second .two > #deux #six #ten{color:red;}

View File

@@ -0,0 +1,12 @@
.scope1 {
color: blue;
border-color: black;
}
.scope1 .scope2 {
color: blue;
}
.scope1 .scope2 .scope3 {
color: red;
border-color: black;
background-color: white;
}

3
lesscpy/test/css/scope.min.css vendored Normal file
View File

@@ -0,0 +1,3 @@
.scope1{color:blue;border-color:black;}
.scope1 .scope2{color:blue;}
.scope1 .scope2 .scope3{color:red;border-color:black;background-color:white;}

View File

@@ -0,0 +1,53 @@
h1 a:hover,
h2 a:hover,
h3 a:hover,
h1 p:hover,
h2 p:hover,
h3 p:hover {
color: red;
}
#all {
color: blue;
}
#the {
color: blue;
}
#same {
color: blue;
}
ul,
li,
div,
q,
blockquote,
textarea {
margin: 0;
}
td {
margin: 0;
padding: 0;
}
td, input {
line-height: 1em;
}
a {
color: red;
}
a:hover {
color: blue;
}
div a {
color: green;
}
p a span {
color: yellow;
}
.foo .bar .qux, .foo .baz .qux {
display: block;
}
.foo .qux .bar, .foo .qux .baz {
display: inline;
}
.foo .qux .bar .biz, .foo .qux .baz .biz {
display: none;
}

24
lesscpy/test/css/selectors.min.css vendored Normal file
View File

@@ -0,0 +1,24 @@
h1 a:hover,
h2 a:hover,
h3 a:hover,
h1 p:hover,
h2 p:hover,
h3 p:hover{color:red;}
#all{color:blue;}
#the{color:blue;}
#same{color:blue;}
ul,
li,
div,
q,
blockquote,
textarea{margin:0;}
td{margin:0;padding:0;}
td, input{line-height:1em;}
a{color:red;}
a:hover{color:blue;}
div a{color:green;}
p a span{color:yellow;}
.foo .bar .qux, .foo .baz .qux{display:block;}
.foo .qux .bar, .foo .qux .baz{display:inline;}
.foo .qux .bar .biz, .foo .qux .baz .biz{display:none;}

View File

@@ -0,0 +1,32 @@
#strings {
background-image: url("http://son-of-a-banana.com");
quotes: "~" "~";
content: "#*%:&^,)!.(~*})";
empty: "";
brackets: "{" "}";
}
#comments {
content: "/* hello */ // not-so-secret";
}
#single-quote {
quotes: "'" "'";
content: '""#!&""';
empty: '';
semi-colon: ';';
}
#escaped {
filter: DX.Transform.MS.BS.filter(opacity=50);
}
#one-line {
image: url(http://tooks.com);
}
#interpolation {
url: "http://lesscss.org/dev/image.jpg";
url2: "http://lesscss.org/image-256.jpg";
url3: "http://lesscss.org#445566";
url4: "http://lesscss.org/hello";
url5: "http://lesscss.org/54.4px";
}
.mix-mul-class {
color: orange;
}

7
lesscpy/test/css/strings.min.css vendored Normal file
View File

@@ -0,0 +1,7 @@
#strings{background-image:url("http://son-of-a-banana.com");quotes:"~" "~";content:"#*%:&^,)!.(~*})";empty:"";brackets:"{" "}";}
#comments{content:"/* hello */ // not-so-secret";}
#single-quote{quotes:"'" "'";content:'""#!&""';empty:'';semi-colon:';';}
#escaped{filter:DX.Transform.MS.BS.filter(opacity=50);}
#one-line{image:url(http://tooks.com);}
#interpolation{url:"http://lesscss.org/dev/image.jpg";url2:"http://lesscss.org/image-256.jpg";url3:"http://lesscss.org#445566";url4:"http://lesscss.org/hello";url5:"http://lesscss.org/54.4px";}
.mix-mul-class{color:orange;}

View File

@@ -0,0 +1,27 @@
.variables {
width: 14cm;
}
.variables {
height: 24px;
color: #888888;
font-family: "Trebuchet MS",Verdana,sans-serif;
quotes: "~""~";
}
.redefinition {
three: 3;
}
.values {
font-family: 'Trebuchet', 'Trebuchet', 'Trebuchet';
color: #888888 !important;
url: url('Trebuchet');
multi: something 'A',B,C, 'Trebuchet';
}
.variable-names {
name: 'hello';
}
.alpha {
filter: alpha(opacity=42);
}
.lazy-eval {
width: 100%;
}

7
lesscpy/test/css/variables.min.css vendored Normal file
View File

@@ -0,0 +1,7 @@
.variables{width:14cm;}
.variables{height:24px;color:#888888;font-family:"Trebuchet MS",Verdana,sans-serif;quotes:"~""~";}
.redefinition{three:3;}
.values{font-family:'Trebuchet', 'Trebuchet', 'Trebuchet';color:#888888 !important;url:url('Trebuchet');multi:something 'A',B,C, 'Trebuchet';}
.variable-names{name:'hello';}
.alpha{filter:alpha(opacity=42);}
.lazy-eval{width:100%;}

View File

@@ -0,0 +1,32 @@
.whitespace {
color: white;
}
.whitespace {
color: white;
}
.whitespace {
color: white;
}
.whitespace {
color: white;
}
.whitespace {
color: white;
}
.white, .space, .mania {
color: white;
}
.no-semi-column {
color: white;
}
.no-semi-column {
color: white;
white-space: pre;
}
.no-semi-column {
border: 2px solid white;
}
.newlines {
background: the, great, wall;
border: 2px solid black;
}

10
lesscpy/test/css/whitespace.min.css vendored Normal file
View File

@@ -0,0 +1,10 @@
.whitespace{color:white;}
.whitespace{color:white;}
.whitespace{color:white;}
.whitespace{color:white;}
.whitespace{color:white;}
.white, .space, .mania{color:white;}
.no-semi-column{color:white;}
.no-semi-column{color:white;white-space:pre;}
.no-semi-column{border:2px solid white;}
.newlines{background:the, great, wall;border:2px solid black;}

16
lesscpy/test/genast.py Normal file
View File

@@ -0,0 +1,16 @@
import sys, re
sys.path.append('..')
import lesscpy.lessc.parser
m = []
with open('../lessc/parser.py') as f:
for l in f.readlines():
r = re.findall('def[ \t]+(p_[a-z_0-9]+)([^\(]*)', l)
if r: m.append(r[0][0])
p = lesscpy.lessc.parser.LessParser
for u in m:
if u == 'p_error': continue
print(getattr(p, u).__doc__)

View File

@@ -0,0 +1,12 @@
.color_functions {
lighten: lighten(#ff0000, 40%);
darken: darken(#ff0000, 40%);
saturate: saturate(#29332f, 20%);
desaturate: desaturate(#203c31, 20%);
greyscale: greyscale(#203c31);
spin-p: spin(hsl(340, 50%, 50%), 40);
spin-n: spin(hsl(30, 50%, 50%), -40);
hue: hue(hsl(98, 12%, 95%));
saturation: saturation(hsl(98, 12%, 95%));
lightness: lightness(hsl(98, 12%, 95%));
}

View File

@@ -0,0 +1,52 @@
#yelow {
#short {
color: #fea;
}
#long {
color: #ffeeaa;
}
#rgba {
color: rgba(255, 238, 170, 0.1);
}
#argb {
color: argb(rgba(255, 238, 170, 0.1));
}
}
#blue {
#short {
color: #00f;
}
#long {
color: #0000ff;
}
#rgba {
color: rgba(0, 0, 255, 0.1);
}
#argb {
color: argb(rgba(0, 0, 255, 0.1));
}
}
#alpha #hsla {
color: hsla(11, 20%, 20%, 0.6);
}
#overflow {
.a { color: #111111 - #444444; } // #000000
.b { color: #eee + #fff; } // #ffffff
.c { color: #aaa * 3; } // #ffffff
.d { color: #00ee00 + #009900; } // #00ff00
}
#grey {
color: rgb(200, 200, 200);
}
#808080 {
color: hsl(50, 0%, 50%);
}
#00ff00 {
color: hsl(120, 100%, 50%);
}

View File

@@ -0,0 +1,65 @@
/******************\
* *
* Comment Header *
* *
\******************/
/*
Comment
*/
/*
* Comment Test
*
* - cloudhead (http://cloudhead.net)
*
*/
////////////////
@var: "content";
////////////////
/* Colors
* ------
* #EDF8FC (background blue)
* #166C89 (darkest blue)
*
* Text:
* #333 (standard text) // A comment within a comment!
* #1F9EC9 (standard link)
*
*/
/* @group Variables
------------------- */
#comments /* boo */ {
/**/ // An empty comment
color: red; /* A C-style comment */
background-color: orange; // A little comment
font-size: 12px;
/* lost comment */ content: @var;
border: 1px solid black;
// padding & margin //
padding: 0; // }{ '"
margin: 2em;
} //
/* commented out
#more-comments {
color: grey;
}
*/
.selector /* .with */, .lots, /* of */ .comments {
color: grey, /* blue */ orange;
-webkit-border-radius: 2px /* webkit only */;
-moz-border-radius: 2px * 4 /* moz only with operation */;
}
#last { color: blue }
//

View File

@@ -0,0 +1,66 @@
.comma-delimited {
background: url(bg.jpg) no-repeat, url(bg.png) repeat-x top left, url(bg);
text-shadow: -1px -1px 1px red, 6px 5px 5px yellow;
-moz-box-shadow: 0pt 0pt 2px rgba(255, 255, 255, 0.4) inset,
0pt 4px 6px rgba(255, 255, 255, 0.4) inset;
}
@font-face {
font-family: Headline;
src: local(Futura-Medium),
url(fonts.svg#MyGeometricModern) format("svg");
}
.other {
-moz-transform: translate(0, 11em) rotate(-90deg);
}
p:not([class*="lead"]) {
color: black;
}
input[type="text"].class#id[attr=32]:not(1) {
color: white;
}
div#id.class[a=1][b=2].class:not(1) {
color: white;
}
ul.comma > li:not(:only-child)::after {
color: white;
}
ol.comma > li:nth-last-child(2)::after {
color: white;
}
li:nth-child(4n+1),
li:nth-child(-5n),
li:nth-child(-n+2) {
color: white;
}
a[href^="http://"] {
color: black;
}
a[href$="http://"] {
color: black;
}
form[data-disabled] {
color: black;
}
p::before {
color: black;
}
#issue322 {
-webkit-animation: anim2 7s infinite ease-in-out;
}
/*
@-webkit-keyframes frames {
0% { border: 1px }
5.5% { border: 2px }
100% { border: 3px }
}
*/

View File

@@ -0,0 +1,23 @@
@ugly: fuchsia;
.escape\|random\|char {
color: red;
}
.mixin\!tUp {
font-weight: bold;
}
// class="404"
.\34 04 {
background: red;
strong {
color: @ugly;
.mixin\!tUp;
}
}
.trailingTest\+ {
color: red;
}

118
lesscpy/test/less/css.less Normal file
View File

@@ -0,0 +1,118 @@
@charset "utf-8";
div { color: black; }
div { width: 99%; }
* {
min-width: 45em;
}
h1, h2 > a > p, h3 {
color: none;
}
div.class {
color: blue;
}
div#id {
color: green;
}
.class#id {
color: purple;
}
.one.two.three {
color: grey;
}
.one .two .three {
color: grey;
}
@media print {
font-size: 3em;
}
@media screen {
font-size: 10px;
}
@font-face {
font-family: 'Garamond Pro';
src: url("/fonts/garamond-pro.ttf");
}
a:hover, a:link {
color: #999;
}
p, p:first-child {
text-transform: none;
}
q:lang(no) {
quotes: none;
}
p + h1 {
font-size: 2.2em;
}
#shorthands {
border: 1px solid #000;
font : 12px/16px Arial;
margin: 1px 0;
padding: 0 auto;
background: url("http://www.lesscss.org/spec.html") no-repeat 0 4px;
}
#shorthands {
font: 100%/16px Arial;
}
#more-shorthands {
margin: 0;
padding: 1px 0 2px 0;
font: normal small/20px 'Trebuchet MS', Verdana, sans-serif;
}
.misc {
-moz-border-radius: 2px;
display: -moz-inline-stack;
width: .1em;
background-color: #009998;
background-image: url(images/image.jpg);
background: -webkit-gradient(linear, left top, left bottom, from(red), to(blue));
margin: ;
filter: alpha(opacity=100);
}
#important {
color: red !important;
width: 100%!important;
height: 20px ! important;
}
#data-uri {
background: url(data:image/png;charset=utf-8;base64,
kiVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/
k//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U
kg9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC);
background-image: url(data:image/x-png,f9difSSFIIGFIFJD1f982FSDKAA9==);
}
#svg-data-uri {
background: transparent url('data:image/svg+xml, <svg version="1.1"><g></g></svg>');
}
.uri_test {
background-image: url(images/image.jpg);
background-image: url(../some/path);
background-image: url(./../some/path);
background-image: url(./images/image.jpg);
background-image: url(http://some/path/img.jpeg);
background-image: url(https://some.server.com:9696/path/img.jpeg);
behavior:url(border-radius.htc);
background-image: url(fonts.svg#MyGeometricModern);
}

View File

@@ -0,0 +1,22 @@
#functions {
@var: 10;
width: increment(15);
height: undefined("self");
border-width: add(2, 3);
variable: increment(@var);
decrement: decrement(@var);
}
#built-in {
@r: 32;
escaped: e("-Some::weird(#thing, y)");
escaped1: ~"-Some::weird(#thing, z)";
format: %("rgb(%d, %d, %d)", @r, 128, 64);
format-string: %("hello %s", "world");
format-multiple: %("hello %s %d", "earth", 2);
format-url-encode: %('red is %A', #ff0000);
eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64));
rounded: round(@r/3);
roundedpx: round(10px / 3);
}

View File

@@ -0,0 +1,12 @@
@fat: 0;
@cloudhead: "#000000";
.nav {
filter: ~'progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="@{cloudhead}", GradientType=@{fat})';
}
.nav {
filter: ~"progid:DXImageTransform.Microsoft.Alpha(opacity=@{fat})";
}
.nav {
filter: ~'progid:DXImageTransform.Microsoft.gradient(startColorstr="#333333", endColorstr="@{cloudhead}", GradientType=@{fat})';
}

10
lesscpy/test/less/import.less vendored Normal file
View File

@@ -0,0 +1,10 @@
@import './imports/import.less';
@import 'some.css.file.css';
@import './imports/import.less'; // redundant
@import './imports/import_f'; // No ext
@import 'some/other.css.file.CSS';
.import {
.mixin;
.mixf(6px);
height: @imported;
}

View File

@@ -0,0 +1 @@
@import 'circular.less';

5
lesscpy/test/less/imports/import.less vendored Normal file
View File

@@ -0,0 +1,5 @@
@imported: 9px;
.mixin {
color: red;
}

View File

@@ -0,0 +1,6 @@
/*
*/
.mixf (@var: 7px) {
width: @var;
}

View File

@@ -0,0 +1,8 @@
@mix: none;
.mixin {
@mix: #989;
}
.tiny-scope {
color: @mix; // #989
.mixin;
}

View File

@@ -0,0 +1,26 @@
.scope {
@var: 99px;
.mixin () {
width: @var;
}
}
.class {
.scope > .mixin;
}
.overwrite {
@var: 0px;
.scope > .mixin;
}
.nested {
@var: 5px;
.mixin () {
width: @var;
}
.class {
@var: 10px;
.mixin;
}
}

View File

@@ -0,0 +1,30 @@
.nested-ruleset (@width: 200px) {
width: @width;
.column { margin: @width; }
}
.content {
.nested-ruleset(600px);
}
.mix-inner (@var) {
border-width: @var;
}
.mix (@a: 10) {
.inner {
height: @a * 10;
.innest {
width: @a;
.mix-inner(@a * 2);
}
}
}
.class {
.mix(30);
}
.class2 {
.mix(60);
}

View File

@@ -0,0 +1,96 @@
.mixin () {
zero: 0;
}
.mixin (@a: 1px) {
one: 1;
}
.mixin (@a) {
one-req: 1;
}
.mixin (@a: 1px, @b: 2px) {
two: 2;
}
.mixin (@a, @b, @c) {
three-req: 3;
}
.mixin (@a: 1px, @b: 2px, @c: 3px) {
three: 3;
}
.zero {
.mixin();
}
.one {
.mixin(1);
}
.two {
.mixin(1, 2);
}
.three {
.mixin(1, 2, 3);
}
//
.mixout ('left') {
left: 1;
}
.mixout ('right') {
right: 1;
}
.left {
.mixout('left');
}
.right {
.mixout('right');
}
//
.border (@side, @width) {
color: black;
.border-side(@side, @width);
}
.border-side (left, @w) {
border-left: @w;
}
.border-side (right, @w) {
border-right: @w;
}
.border-right {
.border(right, 4px);
}
.border-left {
.border(left, 4px);
}
//
.border-radius (@r) {
both: @r * 10;
}
.border-radius (@r, left) {
left: @r;
}
.border-radius (@r, right) {
right: @r;
}
.only-right {
.border-radius(33, right);
}
.only-left {
.border-radius(33, left);
}
.left-right {
.border-radius(33);
}

View File

@@ -0,0 +1,5 @@
#foo {
+ .one {
font-size: 2em;
}
}

View File

@@ -0,0 +1,25 @@
// For now, variables can't be declared inside @media blocks.
@var: 42;
@media print {
.class {
color: blue;
.sub {
width: @var;
}
}
.top, header > h1 {
color: #222 * 2;
}
}
@media screen {
@base: 8;
body { max-width: @base * 60; }
}
@media all and (orientation:portrait) {
aside { float: none; }
}

View File

@@ -0,0 +1,110 @@
.mixin (@a: 1px, @b: 50%) {
width: @a * 5;
height: @b - 1%;
}
.mixina (@style, @width, @color: black) {
border: @width @style @color;
}
.mixiny
(@a: 0, @b: 0) {
margin: @a;
padding: @b;
}
.hidden() {
color: transparent; // asd
}
#hidden {
.hidden;
.hidden();
}
.two-args {
color: blue;
.mixin(2px, 100%);
.mixina(dotted, 2px);
}
.one-arg {
.mixin(3px);
}
.no-parens {
.mixin;
}
.no-args {
.mixin();
}
.var-args {
@var: 9;
.mixin(@var, @var * 2);
}
.multi-mix {
.mixin(2px, 30%);
.mixiny(4, 5);
}
.maxa(@arg1: 10, @arg2: #f00) {
padding: @arg1 * 2px;
color: @arg2;
}
body {
.maxa(15);
}
@glob: 5;
.global-mixin(@a:2) {
width: @glob + @a;
}
.scope-mix {
.global-mixin(3);
}
//
.same-var-name2(@radius) {
radius: @radius;
}
.same-var-name(@radius) {
.same-var-name2(@radius);
}
#same-var-name {
.same-var-name(5px);
}
//
.var-inside () {
@var: 10px;
width: @var;
}
#var-inside { .var-inside; }
// # mixins
/* #id-mixin () {
color: red;
} */
.id-class {
#id-mixin();
#id-mixin;
}
.mixin-arguments (@width: 0px) {
border: @arguments;
}
.arguments {
.mixin-arguments(1px, solid, black);
}
.arguments2 {
.mixin-arguments();
}

View File

@@ -0,0 +1,29 @@
.ext {
color: red;
}
.ext {
color: blue;
}
.ext {
color: red;
}
.ext {
color: black;
}
.ext {
color: red;
}
.ext {
color: green;
}
p {
width: 1px;
.ext;
}
a {
.ext;
.ext;
.ext;
.ext;
.ext;
}

View File

@@ -0,0 +1,72 @@
.mixin { border: 1px solid black; }
.mixout { border-color: orange; }
.borders { border-style: dashed; }
#namespace {
.borders {
border-style: dotted;
}
.biohazard {
content: "death";
.man {
color: transparent;
}
}
}
#theme {
> .mixin {
background-color: grey;
}
}
#container {
color: black;
.mixin;
.mixout;
#theme > .mixin;
}
#header {
.milk {
color: white;
.mixin;
#theme > .mixin;
}
#cookie {
.chips {
#namespace .borders;
.calories {
#container;
}
}
.borders;
}
}
.secure-zone { #namespace .biohazard .man; }
.direct {
#namespace > .borders;
}
.bo, .bar {
width: 100%;
}
.bo {
border: 1px;
}
.bo {
color: red;
}
.ar.bo.ca {
color: black;
}
.jo.ki {
background: none;
}
.extended {
.bo;
.jo.ki;
}
.foo .bar {
.bar;
}

View File

@@ -0,0 +1,53 @@
#operations {
color: #110000 + #000011 + #001100; // #111111
height: 10px / 2px + 6px - 1px * 2; // 9px
width: 2 * 4 - 5em; // 3em
text-shadow: -1px -1px 1px red, 6px 5px 5px yellow;
.spacing {
height: 10px / 2px+6px- 1px*2;
width: 2 * 4 - 5em;
}
substraction: 20 - 10 - 5 - 5; // 0
division: 20 / 5 / 4; // 1
}
@x: 4;
@y: 12em;
.with-variables {
height: @x + @y; // 16em
width: 12 + @y; // 24em
size: 5cm - @x; // 1cm
}
@z: -2;
.negative {
height: 2px + @z; // 0px
width: 2px - @z; // 4px
}
.shorthands {
padding: -1px 2px 0 -4px; //
}
.colors {
color: #123; // #112233
border-color: #234 + #111111; // #334455
background-color: #222222 - #fff; // #000000
.other {
color: 2 * #111; // #222222
border-color: #333333 / 3 + #111; // #222222
}
}
.negations {
@var: 4px;
variable: -@var; // 4
variable1: -@var + @var; // 0
variable2: @var + -@var; // 0
variable3: @var - -@var; // 8
variable4: -@var - -@var; // 0
paren: -(@var); // -4px
paren2: -(2 + 2) * -@var; // 16
}

Some files were not shown because too many files have changed in this diff Show More