Skip to content

Commit

Permalink
added support for @import statements
Browse files Browse the repository at this point in the history
Syntax is similar to css, but expects a clever css file -- namely, they must be defined at the top of the file, and will be parsed, pulling in any variables defined in the other css file.
  • Loading branch information
kroo committed May 27, 2010
1 parent 34507b5 commit d468b9d
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 5 deletions.
1 change: 1 addition & 0 deletions BUGFIXES
@@ -1,5 +1,6 @@
List of bug fixes since CleverCSS-0.1
===================================
5/27/10 - v.0.1.6 - submitted by Elliot Kroo - added support for @import statements. Syntax is similar to css, but expects a clever css file -- namely, they must be defined at the top of the file, and will be parsed as a clevercss file.

9/18/09 - v.0.1.5 - submitted by Tim Babych - The Parser was not aware of negative numbers. Thus any minus sign was an operator, leading to unneeded calculations. Say, "margin: -2px -2px" was converted into "margin: -4px"

Expand Down
36 changes: 31 additions & 5 deletions clevercss.py
Expand Up @@ -223,9 +223,10 @@
import re
import colorsys
import operator
import os.path


VERSION = '0.1.5'
VERSION = '0.1.6'

__all__ = ['convert']

Expand Down Expand Up @@ -423,6 +424,7 @@
_color_re = re.compile(r'#' + ('[a-fA-f0-9]{1,2}' * 3))
_string_re = re.compile('%s|([^\s*/();,.+$]+|\.(?!%s))+' % (_r_string, _r_call))
_url_re = re.compile(r'url\(\s*(%s|.*?)\s*\)' % _r_string)
_import_re = re.compile(r'\@import\s+url\(\s*"?(%s|.*?)"?\s*\)' % _r_string)
_var_re = re.compile(r'(?<!\\)\$(?:([a-zA-Z_][a-zA-Z0-9_]*)|'
r'\{([a-zA-Z_][a-zA-Z0-9_]*)\})')
_call_re = re.compile(r'\.' + _r_call)
Expand Down Expand Up @@ -576,18 +578,25 @@ def __init__(self, source, parser=None, fname=None):
if parser is None:
parser = Parser(fname=fname)
self._parser = parser
self.rules, self._vars = parser.parse(source)
self.rules, self._vars, self._imports = parser.parse(source)

def evaluate(self, context=None):
"""Evaluate code."""
expr = None
if not isinstance(context, dict):
context = {}

for key, value in context.iteritems():
if isinstance(value, str):
expr = self._parser.parse_expr(1, value)
context[key] = expr
context.update(self._vars)

# pull in imports
for fname, source in self._imports.iteritems():
for selectors, defs in Engine(source[1], fname=fname).evaluate(context):
yield selectors, defs

for selectors, defs in self.rules:
yield selectors, [(key, expr.to_string(context))
for key, expr in defs]
Expand Down Expand Up @@ -1175,6 +1184,7 @@ def preparse(self, source):
"""
rule = (None, [], [])
vars = {}
imports = {}
indention_stack = [0]
state_stack = ['root']
group_block_stack = []
Expand Down Expand Up @@ -1237,6 +1247,7 @@ def parse_definition():
# new rule blocks
if line.endswith(','):
sub_rules.append(line)

elif line.endswith(':'):
sub_rules.append(line[:-1].rstrip())
s_rule = ' '.join(sub_rules)
Expand All @@ -1260,6 +1271,17 @@ def parse_definition():
if key in vars:
fail('variable "%s" defined twice' % key)
vars[key] = (lineiter.lineno, m.group(2))
elif line.startswith("@"):
m = _import_re.search(line)
if m is None:
fail('invalid import syntax')
url = m.group(1)
if url in imports:
fail('file "%s" imported twice' % url)
if not os.path.isfile(url):
fail('file "%s" was not found' % url)
imports[url] = (lineiter.lineno, open(url).read())

else:
fail('Style definitions or group blocks are only '
'allowed inside a rule or group block.')
Expand All @@ -1284,7 +1306,7 @@ def parse_definition():
else:
fail('unexpected character %s' % line[0])

return root_rules, vars
return root_rules, vars, imports

def parse(self, source):
"""
Expand Down Expand Up @@ -1341,7 +1363,7 @@ def get_selectors():
branches = new_branches
return [' '.join(branch) for branch in branches]

root_rules, vars = self.preparse(source)
root_rules, vars, imports = self.preparse(source)
result = []
stack = []
for rule in root_rules:
Expand All @@ -1351,7 +1373,7 @@ def get_selectors():
for name, args in vars.iteritems():
real_vars[name] = self.parse_expr(*args)

return result, real_vars
return result, real_vars, imports

def parse_expr(self, lineno, s):
def parse():
Expand Down Expand Up @@ -1383,6 +1405,7 @@ def process_string(m):
(_color_re, process('color')),
(_number_re, process('number')),
(_url_re, process('url', 1)),
(_import_re, process('import', 1)),
(_string_re, process_string),
(_var_re, lambda m: (m.group(1) or m.group(2), 'var')),
(_whitespace_re, None))
Expand Down Expand Up @@ -1496,6 +1519,9 @@ def primary(self, stream):
elif token == 'url':
stream.next()
node = URL(value, lineno=stream.lineno)
elif token == 'import':
stream.next()
node = Import(value, lineno=stream.lineno)
elif token == 'var':
stream.next()
node = Var(value, lineno=stream.lineno)
Expand Down
29 changes: 29 additions & 0 deletions tests/__init__.py
Expand Up @@ -144,6 +144,35 @@ def test_eigen(self):
}
""").strip())

def test_import_line(self):
"""
Tests the @import url() command. assumes the code is running in the main
directory. (i.e. python -c 'from tests import *; main()' from the same
dir as clevercss)
"""
self.assertEqual(convert(dedent("""
@import url(tests/example.ccss)
div:
color: $arg
""")), dedent("""
#test1 {
color: blue;
}
#test2 {
color: blue;
}
#test3 {
color: blue;
}
div {
color: blue;
}""").strip())


def test_multiline_rule(self):
self.assertEqual(convert(dedent("""
ul.item1 li.item1,
Expand Down
8 changes: 8 additions & 0 deletions tests/example.ccss
@@ -0,0 +1,8 @@
arg = blue

#test1:
color: $arg
#test2:
color: $arg
#test3:
color: $arg

0 comments on commit d468b9d

Please sign in to comment.