#
# Jasy - Web Tooling Framework
# Copyright 2013-2014 Sebastian Werner
#
import json
import re
import sys
import jasy.style.Util as Util
ascii_encoder = json.JSONEncoder(ensure_ascii=True)
[docs]class CompressorError(Exception):
def __init__(self, message, node):
Exception.__init__(self, "Compressor Error: %s for node type=%s in %s at line %s!" % (message, node.type, node.getFileName(), node.line))
[docs]class Compressor:
__useIndenting = False
__useBlockBreaks = False
__useStatementBreaks = False
__useWhiteSpace = False
__indentLevel = 0
__simple = ["true", "false", "null"]
__dividers = {
"plus" : '+',
"minus" : '-',
"mul" : '*',
"div" : '/',
"mod" : '%',
"dot" : '.',
"eq" : '==',
"ne" : '!=',
"le" : '<=',
"lt" : '<',
"ge" : '>=',
"gt" : '>'
}
__prefixes = {
"unary_plus" : "+",
"unary_minus" : "-"
}
def __init__(self, optimize=None, format=None):
if optimize:
if optimize.has("colors"):
self.__optimizeColorNames = True
if format:
if format.has("indent"):
self.__useIndenting = True
if format.has("blocks"):
self.__useBlockBreaks = True
if format.has("statements"):
self.__useStatementBreaks = True
if format.has("whitespace"):
self.__useWhiteSpace = True
[docs] def compress(self, node):
"""Compresses the given node and returns the compressed text result."""
type = node.type
if type in self.__simple:
result = type
elif type in self.__prefixes:
if getattr(node, "postfix", False):
result = self.compress(node[0]) + self.__prefixes[node.type]
else:
result = self.__prefixes[node.type] + self.compress(node[0])
elif type in self.__dividers:
first = self.compress(node[0])
second = self.compress(node[1])
divider = self.__dividers[node.type]
# Fast path
if node.type not in ("plus", "minus"):
result = "%s%s%s" % (first, divider, second)
# Special code for dealing with situations like x + ++y and y-- - x
else:
result = first
if first.endswith(divider):
result += " "
result += divider
if second.startswith(divider):
result += " "
result += second
else:
try:
result = getattr(self, "type_%s" % type)(node)
except AttributeError:
raise Exception("Style compressor does not support type '%s' from line %s in file %s" % (type, node.line, node.getFileName()))
return result
[docs] def indent(self, code):
"""Indents the given code by the current indenting setup."""
if not self.__useIndenting:
return code
lines = code.split("\n")
result = []
prefix = self.__indentLevel * " "
for line in lines:
if line:
result.append("%s%s" % (prefix, line))
return "\n".join(result)
#
# Sheet Scope
#
[docs] def type_sheet(self, node):
return self.__statements(node)
[docs] def type_selector(self, node):
# Ignore selectors without rules
if len(node.rules) == 0:
return ""
selector = node.name
if self.__useBlockBreaks:
result = ",\n".join(selector)
elif self.__useWhiteSpace:
result = ", ".join(selector)
else:
result = ",".join(selector)
return self.indent("%s%s" % (result, self.compress(node.rules)))
[docs] def type_string(self, node):
if hasattr(node, "quote"):
return "%s%s%s" % (node.quote, node.value, node.quote)
else:
return ascii_encoder.encode(node.value)
[docs] def type_number(self, node):
value = node.value
# Apply basic rounding to reduce size overhead of floating point
if isinstance(value, float) and not hasattr(node, "precision"):
value = round(value, 4)
# Special handling for protected float/exponential
if isinstance(value, str):
# Convert zero-prefix
if value.startswith("0.") and len(value) > 2:
value = value[1:]
# Convert zero postfix
elif value.endswith(".0"):
value = value[:-2]
elif int(value) == value and node.parent.type != "dot":
value = int(value)
return "%s%s" % (value, getattr(node, "unit", ""))
[docs] def type_identifier(self, node):
return node.value
[docs] def type_slash(self, node):
return "/"
[docs] def type_expr(self, node):
child = node[0]
return self.compress(child)
[docs] def type_raw(self, node):
child = node[0]
result = self.compress(child)
# Remove string quotes
if child.type == "string":
result = result[1:-1]
return result
[docs] def type_list(self, node):
return " ".join([self.compress(child) for child in node])
[docs] def type_comma(self, node):
first = self.compress(node[0])
second = self.compress(node[1])
if self.__useWhiteSpace:
res = "%s, %s"
else:
res = "%s,%s"
return res % (first, second)
[docs] def type_block(self, node):
self.__indentLevel += 1
inner = self.__statements(node)
self.__indentLevel -= 1
if self.__useBlockBreaks:
return "{\n%s\n}\n" % inner
else:
return "{%s}" % inner
[docs] def type_property(self, node):
self.__indentLevel += 1
inner = self.__values(node)
self.__indentLevel -= 1
if self.__useWhiteSpace:
return self.indent("%s: %s;" % (node.name, inner))
else:
return self.indent("%s:%s;" % (node.name, inner))
[docs] def type_call(self, node):
params = []
callParams = getattr(node, "params", None)
if callParams:
for paramNode in callParams:
params.append(self.compress(paramNode))
if self.__useWhiteSpace:
paramsJoin = ", "
else:
paramsJoin = ","
return "%s(%s)" % (node[0].value, paramsJoin.join(params))
[docs] def type_mixin(self, node):
# Filter out non-extend mixins
if not hasattr(node, "selector"):
raise CompressorError("Left mixin \"%s\" found in tree to compress" % node.name, node)
selector = node.selector
if self.__useBlockBreaks:
result = ",\n".join(selector)
elif self.__useWhiteSpace:
result = ", ".join(selector)
else:
result = ",".join(selector)
self.__indentLevel += 1
inner = self.__statements(node.rules)
self.__indentLevel -= 1
if self.__useBlockBreaks:
result += "{\n%s\n}" % inner
else:
result += "{%s}" % inner
return result
[docs] def type_function(self, node):
name = node.name
if self.__useWhiteSpace:
separator = ", "
else:
separator = ","
return "%s(%s)" % (name, separator.join([self.compress(child) for child in node.params]))
[docs] def type_keyframes(self, node):
vendor = getattr(node, "vendor", None)
if vendor:
result = "@-%s-keyframes " % vendor
else:
result = "@keyframes "
result += "%s{" % node.name
if self.__useBlockBreaks:
result += "\n"
self.__indentLevel += 1
frames = "".join([self.compress(child) for child in node])
result += self.indent(frames)
self.__indentLevel -= 1
if self.__useBlockBreaks:
result += "\n"
result += "}"
return result
[docs] def type_frame(self, node):
result = ""
if self.__useWhiteSpace:
result += node.value.replace(",", ", ")
else:
result += node.value
result += self.compress(node.rules)
return result
[docs] def type_page(self, node):
result = "@page"
if node.name:
result += " %s" % node.name
result += self.compress(node.rules)
return self.indent(result)
[docs] def type_supports(self, node):
if self.__useBlockBreaks:
separator = ",\n"
elif self.__useWhiteSpace:
separator = ", "
else:
separator = ","
result = "@supports %s" % node.name
result += self.compress(node.rules)
return self.indent(result)
[docs] def type_fontface(self, node):
result = "@font-face" + self.compress(node.rules)
return self.indent(result)
#
# Helpers
#
def __statements(self, node):
result = []
for child in node:
result.append(self.compress(child))
if self.__useStatementBreaks:
return "\n".join(result)
else:
return "".join(result)
def __values(self, node):
result = []
for child in node:
result.append(str(self.compress(child)))
return " ".join(result)