JS is now complete. Need to fix css embeds.

This commit is contained in:
Samuel Clay 2011-11-07 09:35:51 -08:00
parent a8c62eb7eb
commit d3804e3265
40 changed files with 96 additions and 11365 deletions

View file

@ -22,8 +22,79 @@ compress_assets: on
javascripts:
common:
- media/js/jquery-*.js
- media/js/jquery-1.7.js
- media/js/jquery.json.js
- media/js/jquery.easing.js
- media/js/jquery.newsblur.js
- media/js/jquery.scrollTo.js
- media/js/jquery.corners.js
- media/js/jquery.hotkeys.js
- media/js/jquery.ajaxupload.js
- media/js/jquery.ajaxmanager.3.js
- media/js/jquery.simplemodal-1.3.js
- media/js/jquery.color.js
- media/js/jquery.rightclick.js
- media/js/jquery.ui.core.js
- media/js/jquery.ui.widget.js
- media/js/jquery.ui.mouse.js
- media/js/jquery.ui.position.js
- media/js/jquery.ui.draggable.js
- media/js/jquery.ui.sortable.js
- media/js/jquery.ui.slider.js
- media/js/jquery.ui.autocomplete.js
- media/js/jquery.ui.progressbar.js
- media/js/jquery.layout.js
- media/js/jquery.tinysort.js
- media/js/jquery.fieldselection.js
- media/js/jquery.flot.js
- media/js/jquery.tipsy.js
# media/'js/socket.io-client.0.8.7.js',
- media/js/inflector.js
- media/js/underscore.js
- media/js/underscore.string.js
- media/js/newsblur/reader_utils.js
- media/js/newsblur/assetmodel.js
- media/js/newsblur/reader.js
- media/js/newsblur/generate_bookmarklet.js
- media/js/newsblur/modal.js
- media/js/newsblur/reader_classifier.js
- media/js/newsblur/reader_add_feed.js
- media/js/newsblur/reader_mark_read.js
- media/js/newsblur/reader_goodies.js
- media/js/newsblur/reader_preferences.js
- media/js/newsblur/reader_account.js
- media/js/newsblur/reader_feedchooser.js
- media/js/newsblur/reader_statistics.js
- media/js/newsblur/reader_feed_exception.js
- media/js/newsblur/reader_keyboard.js
- media/js/newsblur/reader_recommend_feed.js
- media/js/newsblur/reader_send_email.js
- media/js/newsblur/reader_tutorial.js
- media/js/newsblur/about.js
- media/js/newsblur/faq.js
mobile:
- media/js/jquery-1.7.js
- media/js/mobile/jquery.mobile-1.0b1.js
- media/js/jquery.ajaxmanager.3.js
- media/js/underscore.js
- media/js/underscore.string.js
- media/js/inflector.js
- media/js/jquery.json.js
- media/js/jquery.easing.js
- media/js/jquery.newsblur.js
- media/js/newsblur/reader_utils.js
- media/js/newsblur/assetmodel.js
- media/js/mobile/newsblur/mobile_workspace.js
paypal:
- media/js/newsblur/paypal_return.js
bookmarklet:
- media/js/jquery-1.5.1.min.js
- media/js/jquery.noConflict.js
- media/js/jquery.newsblur.js
- media/js/jquery.tinysort.js
- media/js/jquery.simplemodal-1.3.js
- media/js/jquery.corners.js
stylesheets:
common:
- media/css/*.css

View file

View file

@ -1,20 +0,0 @@
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
COMPRESS = getattr(settings, 'COMPRESS', not settings.DEBUG)
COMPRESS_AUTO = getattr(settings, 'COMPRESS_AUTO', True)
COMPRESS_VERSION = getattr(settings, 'COMPRESS_VERSION', False)
COMPRESS_VERSION_PLACEHOLDER = getattr(settings, 'COMPRESS_VERSION_PLACEHOLDER', '?')
COMPRESS_VERSION_DEFAULT = getattr(settings, 'COMPRESS_VERSION_DEFAULT', '0')
COMPRESS_VERSIONING = getattr(settings, 'COMPRESS_VERSIONING', 'compress.versioning.mtime.MTimeVersioning')
COMPRESS_CSS_FILTERS = getattr(settings, 'COMPRESS_CSS_FILTERS', ['compress.filters.csstidy.CSSTidyFilter'])
COMPRESS_JS_FILTERS = getattr(settings, 'COMPRESS_JS_FILTERS', ['compress.filters.jsmin.JSMinFilter'])
COMPRESS_CSS = getattr(settings, 'COMPRESS_CSS', {})
COMPRESS_JS = getattr(settings, 'COMPRESS_JS', {})
if COMPRESS_CSS_FILTERS is None:
COMPRESS_CSS_FILTERS = []
if COMPRESS_JS_FILTERS is None:
COMPRESS_JS_FILTERS = []

View file

@ -1,14 +0,0 @@
class FilterBase:
def __init__(self, verbose):
self.verbose = verbose
def filter_css(self, css):
raise NotImplementedError
def filter_js(self, js):
raise NotImplementedError
class FilterError(Exception):
"""
This exception is raised when a filter fails
"""
pass

View file

@ -1,33 +0,0 @@
import os
import warnings
import tempfile
from django.conf import settings
from compress.filter_base import FilterBase
BINARY = getattr(settings, 'CSSTIDY_BINARY', 'csstidy')
ARGUMENTS = getattr(settings, 'CSSTIDY_ARGUMENTS', '--template=highest')
warnings.simplefilter('ignore', RuntimeWarning)
class CSSTidyFilter(FilterBase):
def filter_css(self, css):
tmp_file = tempfile.NamedTemporaryFile(mode='w+b')
tmp_file.write(css)
tmp_file.flush()
output_file = tempfile.NamedTemporaryFile(mode='w+b')
command = '%s %s %s %s' % (BINARY, tmp_file.name, ARGUMENTS, output_file.name)
command_output = os.popen(command).read()
filtered_css = output_file.read()
output_file.close()
tmp_file.close()
if self.verbose:
print command_output
return filtered_css

View file

@ -1,19 +0,0 @@
from django.conf import settings
from compress.filter_base import FilterBase
from compress.filters.csstidy_python.csstidy import CSSTidy
COMPRESS_CSSTIDY_SETTINGS = getattr(settings, 'COMPRESS_CSSTIDY_SETTINGS', {})
class CSSTidyFilter(FilterBase):
def filter_css(self, css):
tidy = CSSTidy()
for k, v in COMPRESS_CSSTIDY_SETTINGS.items():
tidy.setSetting(k, v)
tidy.parse(css)
r = tidy.Output('string')
return r

View file

@ -1,636 +0,0 @@
# CSSTidy - CSS Parse
#
# CSS Parser class
#
# This file is part of CSSTidy.
#
# CSSTidy is free software you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation either version 2 of the License, or
# (at your option) any later version.
#
# CSSTidy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CSSTidy if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# @license http://opensource.org/licenses/gpl-license.php GNU Public License
# @package csstidy
# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
import re
from optimizer import CSSOptimizer
from output import CSSPrinter
import data
from tools import SortedDict
class CSSTidy(object):
#Saves the parsed CSS
_css = ""
_raw_css = SortedDict()
_optimized_css = SortedDict()
#List of Tokens
_tokens = []
#Printer class
_output = None
#Optimiser class
_optimizer = None
#Saves the CSS charset (@charset)
_charset = ''
#Saves all @import URLs
_import = []
#Saves the namespace
_namespace = ''
#Contains the version of csstidy
_version = '1.3'
#Stores the settings
_settings = {}
# Saves the parser-status.
#
# Possible values:
# - is = in selector
# - ip = in property
# - iv = in value
# - instr = in string (started at " or ' or ( )
# - ic = in comment (ignore everything)
# - at = in @-block
_status = 'is'
#Saves the current at rule (@media)
_at = ''
#Saves the current selector
_selector = ''
#Saves the current property
_property = ''
#Saves the position of , in selectors
_sel_separate = []
#Saves the current value
_value = ''
#Saves the current sub-value
_sub_value = ''
#Saves all subvalues for a property.
_sub_value_arr = []
#Saves the char which opened the last string
_str_char = ''
_cur_string = ''
#Status from which the parser switched to ic or instr
_from = ''
#Variable needed to manage string-in-strings, for example url("foo.png")
_str_in_str = False
#=True if in invalid at-rule
_invalid_at = False
#=True if something has been added to the current selector
_added = False
#Saves the message log
_log = SortedDict()
#Saves the line number
_line = 1
def __init__(self):
self._settings['remove_bslash'] = True
self._settings['compress_colors'] = True
self._settings['compress_font-weight'] = True
self._settings['lowercase_s'] = False
self._settings['optimise_shorthands'] = 2
self._settings['remove_last_'] = False
self._settings['case_properties'] = 1
self._settings['sort_properties'] = False
self._settings['sort_selectors'] = False
self._settings['merge_selectors'] = 2
self._settings['discard_invalid_properties'] = False
self._settings['css_level'] = 'CSS2.1'
self._settings['preserve_css'] = False
self._settings['timestamp'] = False
self._settings['template'] = 'highest_compression'
#Maps self._status to methods
self.__statusMethod = {'is':self.__parseStatus_is, 'ip': self.__parseStatus_ip, 'iv':self.__parseStatus_iv, 'instr':self.__parseStatus_instr, 'ic':self.__parseStatus_ic, 'at':self.__parseStatus_at}
self._output = CSSPrinter(self)
self._optimizer = CSSOptimizer(self)
#Public Methods
def getSetting(self, setting):
return self._settings.get(setting, False)
#Set the value of a setting.
def setSetting(self, setting, value):
self._settings[setting] = value
return True
def log(self, message, ttype, line = -1):
if line == -1:
line = self._line
line = int(line)
add = {'m': message, 't': ttype}
if not self._log.has_key(line):
self._log[line] = []
self._log[line].append(add)
elif add not in self._log[line]:
self._log[line].append(add)
#Checks if a character is escaped (and returns True if it is)
def escaped(self, string, pos):
return not (string[pos-1] != '\\' or self.escaped(string, pos-1))
#Adds CSS to an existing media/selector
def merge_css_blocks(self, media, selector, css_add):
for prop, value in css_add.iteritems():
self.__css_add_property(media, selector, prop, value, False)
#Checks if $value is !important.
def is_important(self, value):
return '!important' in value.lower()
#Returns a value without !important
def gvw_important(self, value):
if self.is_important(value):
ret = value.strip()
ret = ret[0:-9]
ret = ret.strip()
ret = ret[0:-1]
ret = ret.strip()
return ret
return value
def parse(self, cssString):
#Switch from \r\n to \n
self._css = cssString.replace("\r\n", "\n") + ' '
self._raw_css = {}
self._optimized_css = {}
self._curComment = ''
#Start Parsing
i = 0
while i < len(cssString):
if self._css[i] == "\n" or self._css[i] == "\r":
self._line += 1
i += self.__statusMethod[self._status](i)
i += 1;
self._optimized_css = self._optimizer.optimize(self._raw_css)
def parseFile(self, filename):
try:
f = open(filename, "r")
self.parse(f.read())
finally:
f.close()
#Private Methods
def __parseStatus_is(self, idx):
"""
Parse in Selector
"""
ret = 0
if self.__is_token(self._css, idx):
if self._css[idx] == '/' and self._css[idx+1] == '*' and self._selector.strip() == '':
self._status = 'ic'
self._from = 'is'
return 1
elif self._css[idx] == '@' and self._selector.strip() == '':
#Check for at-rule
self._invalid_at = True
for name, ttype in data.at_rules.iteritems():
if self._css[idx+1:len(name)].lower() == name.lower():
if ttype == 'at':
self._at = '@' + name
else:
self._selector = '@' + name
self._status = ttype
self._invalid_at = False
ret += len(name)
if self._invalid_at:
self._selector = '@'
invalid_at_name = ''
for j in xrange(idx+1, len(self._css)):
if not self._css[j].isalpha():
break;
invalid_at_name += self._css[j]
self.log('Invalid @-rule: ' + invalid_at_name + ' (removed)', 'Warning')
elif self._css[idx] == '"' or self._css[idx] == "'":
self._cur_string = self._css[idx]
self._status = 'instr'
self._str_char = self._css[idx]
self._from = 'is'
elif self._invalid_at and self._css[idx] == ';':
self._invalid_at = False
self._status = 'is'
elif self._css[idx] == '{':
self._status = 'ip'
self.__add_token(data.SEL_START, self._selector)
self._added = False;
elif self._css[idx] == '}':
self.__add_token(data.AT_END, self._at)
self._at = ''
self._selector = ''
self._sel_separate = []
elif self._css[idx] == ',':
self._selector = self._selector.strip() + ','
self._sel_separate.append(len(self._selector))
elif self._css[idx] == '\\':
self._selector += self.__unicode(idx)
#remove unnecessary universal selector, FS#147
elif not (self._css[idx] == '*' and self._css[idx+1] in ('.', '#', '[', ':')):
self._selector += self._css[idx]
else:
lastpos = len(self._selector)-1
if lastpos == -1 or not ((self._selector[lastpos].isspace() or self.__is_token(self._selector, lastpos) and self._selector[lastpos] == ',') and self._css[idx].isspace()):
self._selector += self._css[idx]
return ret
def __parseStatus_ip(self, idx):
"""
Parse in property
"""
if self.__is_token(self._css, idx):
if (self._css[idx] == ':' or self._css[idx] == '=') and self._property != '':
self._status = 'iv'
if not self.getSetting('discard_invalid_properties') or self.__property_is_valid(self._property):
self.__add_token(data.PROPERTY, self._property)
elif self._css[idx] == '/' and self._css[idx+1] == '*' and self._property == '':
self._status = 'ic'
self._from = 'ip'
return 1
elif self._css[idx] == '}':
self.__explode_selectors()
self._status = 'is'
self._invalid_at = False
self.__add_token(data.SEL_END, self._selector)
self._selector = ''
self._property = ''
elif self._css[idx] == ';':
self._property = ''
elif self._css[idx] == '\\':
self._property += self.__unicode(idx)
elif not self._css[idx].isspace():
self._property += self._css[idx]
return 0
def __parseStatus_iv(self, idx):
"""
Parse in value
"""
pn = (( self._css[idx] == "\n" or self._css[idx] == "\r") and self.__property_is_next(idx+1) or idx == len(self._css)) #CHECK#
if self.__is_token(self._css, idx) or pn:
if self._css[idx] == '/' and self._css[idx+1] == '*':
self._status = 'ic'
self._from = 'iv'
return 1
elif self._css[idx] == '"' or self._css[idx] == "'" or self._css[idx] == '(':
self._cur_string = self._css[idx]
self._str_char = ')' if self._css[idx] == '(' else self._css[idx]
self._status = 'instr'
self._from = 'iv'
elif self._css[idx] == ',':
self._sub_value = self._sub_value.strip() + ','
elif self._css[idx] == '\\':
self._sub_value += self.__unicode(idx)
elif self._css[idx] == ';' or pn:
if len(self._selector) > 0 and self._selector[0] == '@' and data.at_rules.has_key(self._selector[1:]) and data.at_rules[self._selector[1:]] == 'iv':
self._sub_value_arr.append(self._sub_value.strip())
self._status = 'is'
if '@charset' in self._selector:
self._charset = self._sub_value_arr[0]
elif '@namespace' in self._selector:
self._namespace = ' '.join(self._sub_value_arr)
elif '@import' in self._selector:
self._import.append(' '.join(self._sub_value_arr))
self._sub_value_arr = []
self._sub_value = ''
self._selector = ''
self._sel_separate = []
else:
self._status = 'ip'
elif self._css[idx] != '}':
self._sub_value += self._css[idx]
if (self._css[idx] == '}' or self._css[idx] == ';' or pn) and self._selector != '':
if self._at == '':
self._at = data.DEFAULT_AT
#case settings
if self.getSetting('lowercase_s'):
self._selector = self._selector.lower()
self._property = self._property.lower()
if self._sub_value != '':
self._sub_value_arr.append(self._sub_value)
self._sub_value = ''
self._value = ' '.join(self._sub_value_arr)
self._selector = self._selector.strip()
valid = self.__property_is_valid(self._property)
if (not self._invalid_at or self.getSetting('preserve_css')) and (not self.getSetting('discard_invalid_properties') or valid):
self.__css_add_property(self._at, self._selector, self._property, self._value)
self.__add_token(data.VALUE, self._value)
if not valid:
if self.getSetting('discard_invalid_properties'):
self.log('Removed invalid property: ' + self._property, 'Warning')
else:
self.log('Invalid property in ' + self.getSetting('css_level').upper() + ': ' + self._property, 'Warning')
self._property = '';
self._sub_value_arr = []
self._value = ''
if self._css[idx] == '}':
self.__explode_selectors()
self.__add_token(data.SEL_END, self._selector)
self._status = 'is'
self._invalid_at = False
self._selector = ''
elif not pn:
self._sub_value += self._css[idx]
if self._css[idx].isspace():
if self._sub_value != '':
self._sub_value_arr.append(self._sub_value)
self._sub_value = ''
return 0
def __parseStatus_instr(self, idx):
"""
Parse in String
"""
if self._str_char == ')' and (self._css[idx] == '"' or self._css[idx] == "'") and not self.escaped(self._css, idx):
self._str_in_str = not self._str_in_str
temp_add = self._css[idx] # ...and no not-escaped backslash at the previous position
if (self._css[idx] == "\n" or self._css[idx] == "\r") and not (self._css[idx-1] == '\\' and not self.escaped(self._css, idx-1)):
temp_add = "\\A "
self.log('Fixed incorrect newline in string', 'Warning')
if not (self._str_char == ')' and self._css[idx].isspace() and not self._str_in_str):
self._cur_string += temp_add
if self._css[idx] == self._str_char and not self.escaped(self._css, idx) and not self._str_in_str:
self._status = self._from
regex = re.compile(r'([\s]+)', re.I | re.U | re.S)
if regex.match(self._cur_string) is None and self._property != 'content':
if self._str_char == '"' or self._str_char == "'":
self._cur_string = self._cur_string[1:-1]
elif len(self._cur_string) > 3 and (self._cur_string[1] == '"' or self._cur_string[1] == "'"):
self._cur_string = self._cur_string[0] + self._cur_string[2:-2] + self._cur_string[-1]
if self._from == 'iv':
self._sub_value += self._cur_string
elif self._from == 'is':
self._selector += self._cur_string
return 0
def __parseStatus_ic(self, idx):
"""
Parse css In Comment
"""
if self._css[idx] == '*' and self._css[idx+1] == '/':
self._status = self._from
self.__add_token(data.COMMENT, self._curComment)
self._curComment = ''
return 1
else:
self._curComment += self._css[idx]
return 0
def __parseStatus_at(self, idx):
"""
Parse in at-block
"""
if self.__is_token(string, idx):
if self._css[idx] == '/' and self._css[idx+1] == '*':
self._status = 'ic'
self._from = 'at'
return 1
elif self._css[i] == '{':
self._status = 'is'
self.__add_token(data.AT_START, self._at)
elif self._css[i] == ',':
self._at = self._at.strip() + ','
elif self._css[i] == '\\':
self._at += self.__unicode(i)
else:
lastpos = len(self._at)-1
if not (self._at[lastpos].isspace() or self.__is_token(self._at, lastpos) and self._at[lastpos] == ',') and self._css[i].isspace():
self._at += self._css[i]
return 0
def __explode_selectors(self):
#Explode multiple selectors
if self.getSetting('merge_selectors') == 1:
new_sels = []
lastpos = 0;
self._sel_separate.append(len(self._selector))
for num in xrange(len(self._sel_separate)):
pos = self._sel_separate[num]
if num == (len(self._sel_separate)): #CHECK#
pos += 1
new_sels.append(self._selector[lastpos:(pos-lastpos-1)])
lastpos = pos
if len(new_sels) > 1:
for selector in new_sels:
self.merge_css_blocks(self._at, selector, self._raw_css[self._at][self._selector])
del self._raw_css[self._at][self._selector]
self._sel_separate = []
#Adds a property with value to the existing CSS code
def __css_add_property(self, media, selector, prop, new_val):
if self.getSetting('preserve_css') or new_val.strip() == '':
return
if not self._raw_css.has_key(media):
self._raw_css[media] = SortedDict()
if not self._raw_css[media].has_key(selector):
self._raw_css[media][selector] = SortedDict()
self._added = True
if self._raw_css[media][selector].has_key(prop):
if (self.is_important(self._raw_css[media][selector][prop]) and self.is_important(new_val)) or not self.is_important(self._raw_css[media][selector][prop]):
del self._raw_css[media][selector][prop]
self._raw_css[media][selector][prop] = new_val.strip()
else:
self._raw_css[media][selector][prop] = new_val.strip()
#Checks if the next word in a string from pos is a CSS property
def __property_is_next(self, pos):
istring = self._css[pos: len(self._css)]
pos = istring.find(':')
if pos == -1:
return False;
istring = istring[:pos].strip().lower()
if data.all_properties.has_key(istring):
self.log('Added semicolon to the end of declaration', 'Warning')
return True
return False;
#Checks if a property is valid
def __property_is_valid(self, prop):
return (data.all_properties.has_key(prop) and data.all_properties[prop].find(self.getSetting('css_level').upper()) != -1)
#Adds a token to self._tokens
def __add_token(self, ttype, cssdata, do=False):
if self.getSetting('preserve_css') or do:
if ttype == data.COMMENT:
token = [ttype, cssdata]
else:
token = [ttype, cssdata.strip()]
self._tokens.append(token)
#Parse unicode notations and find a replacement character
def __unicode(self, idx):
##FIX##
return ''
#Starts parsing from URL
##USED?
def __parse_from_url(self, url):
try:
if "http" in url.lower() or "https" in url.lower():
f = urllib.urlopen(url)
else:
f = open(url)
data = f.read()
return self.parse(data)
finally:
f.close()
#Checks if there is a token at the current position
def __is_token(self, string, idx):
return (string[idx] in data.tokens and not self.escaped(string, idx))
#Property Methods
def _getOutput(self):
self._output.prepare(self._optimized_css)
return self._output.render
def _getLog(self):
ret = ""
ks = self._log.keys()
ks.sort()
for line in ks:
for msg in self._log[line]:
ret += "Type: " + msg['t'] + "\n"
ret += "Message: " + msg['m'] + "\n"
ret += "\n"
return ret
def _getCSS(self):
return self._css
#Properties
Output = property(_getOutput, None)
Log = property(_getLog, None)
CSS = property(_getCSS, None)
if __name__ == '__main__':
import sys
tidy = CSSTidy()
f = open(sys.argv[1], "r")
css = f.read()
f.close()
tidy.parse(css)
tidy.Output('file', filename="Stylesheet.min.css")
print tidy.Output()
#print tidy._import

View file

@ -1,421 +0,0 @@
# Various CSS Data for CSSTidy
#
# This file is part of CSSTidy.
#
# CSSTidy is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# CSSTidy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CSSTidy; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# @license http://opensource.org/licenses/gpl-license.php GNU Public License
# @package csstidy
# @author Florian Schmitz (floele at gmail dot com) 2005
AT_START = 1
AT_END = 2
SEL_START = 3
SEL_END = 4
PROPERTY = 5
VALUE = 6
COMMENT = 7
DEFAULT_AT = 41
# All whitespace allowed in CSS
#
# @global array whitespace
# @version 1.0
whitespace = frozenset([' ',"\n","\t","\r","\x0B"])
# All CSS tokens used by csstidy
#
# @global string tokens
# @version 1.0
tokens = '/@}{;:=\'"(,\\!$%&)#+.<>?[]^`|~'
# All CSS units (CSS 3 units included)
#
# @see compress_numbers()
# @global array units
# @version 1.0
units = frozenset(['in','cm','mm','pt','pc','px','rem','em','%','ex','gd','vw','vh','vm','deg','grad','rad','ms','s','khz','hz'])
# Available at-rules
#
# @global array at_rules
# @version 1.0
at_rules = {'page':'is', 'font-face':'is', 'charset':'iv', 'import':'iv', 'namespace':'iv', 'media':'at'}
# Properties that need a value with unit
#
# @todo CSS3 properties
# @see compress_numbers()
# @global array unit_values
# @version 1.2
unit_values = frozenset(['background', 'background-position', 'border', 'border-top', 'border-right', 'border-bottom',
'border-left', 'border-width', 'border-top-width', 'border-right-width', 'border-left-width',
'border-bottom-width', 'bottom', 'border-spacing', 'font-size','height', 'left', 'margin', 'margin-top',
'margin-right', 'margin-bottom', 'margin-left', 'max-height', 'max-width', 'min-height', 'min-width',
'outline-width', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left','position',
'right', 'top', 'text-indent', 'letter-spacing', 'word-spacing', 'width'
])
# Properties that allow <color> as value
#
# @todo CSS3 properties
# @see compress_numbers()
# @global array color_values
# @version 1.0
color_values = frozenset(['background-color', 'border-color', 'border-top-color', 'border-right-color',
'border-bottom-color', 'border-left-color', 'color', 'outline-color'])
# Default values for the background properties
#
# @todo Possibly property names will change during CSS3 development
# @global array background_prop_default
# @see dissolve_short_bg()
# @see merge_bg()
# @version 1.0
background_prop_default = {}
background_prop_default['background-image'] = 'none'
background_prop_default['background-size'] = 'auto'
background_prop_default['background-repeat'] = 'repeat'
background_prop_default['background-position'] = '0 0'
background_prop_default['background-attachment'] = 'scroll'
background_prop_default['background-clip'] = 'border'
background_prop_default['background-origin'] = 'padding'
background_prop_default['background-color'] = 'transparent'
# A list of non-W3C color names which get replaced by their hex-codes
#
# @global array replace_colors
# @see cut_color()
# @version 1.0
replace_colors = {}
replace_colors['aliceblue'] = '#F0F8FF'
replace_colors['antiquewhite'] = '#FAEBD7'
replace_colors['aquamarine'] = '#7FFFD4'
replace_colors['azure'] = '#F0FFFF'
replace_colors['beige'] = '#F5F5DC'
replace_colors['bisque'] = '#FFE4C4'
replace_colors['blanchedalmond'] = '#FFEBCD'
replace_colors['blueviolet'] = '#8A2BE2'
replace_colors['brown'] = '#A52A2A'
replace_colors['burlywood'] = '#DEB887'
replace_colors['cadetblue'] = '#5F9EA0'
replace_colors['chartreuse'] = '#7FFF00'
replace_colors['chocolate'] = '#D2691E'
replace_colors['coral'] = '#FF7F50'
replace_colors['cornflowerblue'] = '#6495ED'
replace_colors['cornsilk'] = '#FFF8DC'
replace_colors['crimson'] = '#DC143C'
replace_colors['cyan'] = '#00FFFF'
replace_colors['darkblue'] = '#00008B'
replace_colors['darkcyan'] = '#008B8B'
replace_colors['darkgoldenrod'] = '#B8860B'
replace_colors['darkgray'] = '#A9A9A9'
replace_colors['darkgreen'] = '#006400'
replace_colors['darkkhaki'] = '#BDB76B'
replace_colors['darkmagenta'] = '#8B008B'
replace_colors['darkolivegreen'] = '#556B2F'
replace_colors['darkorange'] = '#FF8C00'
replace_colors['darkorchid'] = '#9932CC'
replace_colors['darkred'] = '#8B0000'
replace_colors['darksalmon'] = '#E9967A'
replace_colors['darkseagreen'] = '#8FBC8F'
replace_colors['darkslateblue'] = '#483D8B'
replace_colors['darkslategray'] = '#2F4F4F'
replace_colors['darkturquoise'] = '#00CED1'
replace_colors['darkviolet'] = '#9400D3'
replace_colors['deeppink'] = '#FF1493'
replace_colors['deepskyblue'] = '#00BFFF'
replace_colors['dimgray'] = '#696969'
replace_colors['dodgerblue'] = '#1E90FF'
replace_colors['feldspar'] = '#D19275'
replace_colors['firebrick'] = '#B22222'
replace_colors['floralwhite'] = '#FFFAF0'
replace_colors['forestgreen'] = '#228B22'
replace_colors['gainsboro'] = '#DCDCDC'
replace_colors['ghostwhite'] = '#F8F8FF'
replace_colors['gold'] = '#FFD700'
replace_colors['goldenrod'] = '#DAA520'
replace_colors['greenyellow'] = '#ADFF2F'
replace_colors['honeydew'] = '#F0FFF0'
replace_colors['hotpink'] = '#FF69B4'
replace_colors['indianred'] = '#CD5C5C'
replace_colors['indigo'] = '#4B0082'
replace_colors['ivory'] = '#FFFFF0'
replace_colors['khaki'] = '#F0E68C'
replace_colors['lavender'] = '#E6E6FA'
replace_colors['lavenderblush'] = '#FFF0F5'
replace_colors['lawngreen'] = '#7CFC00'
replace_colors['lemonchiffon'] = '#FFFACD'
replace_colors['lightblue'] = '#ADD8E6'
replace_colors['lightcoral'] = '#F08080'
replace_colors['lightcyan'] = '#E0FFFF'
replace_colors['lightgoldenrodyellow'] = '#FAFAD2'
replace_colors['lightgrey'] = '#D3D3D3'
replace_colors['lightgreen'] = '#90EE90'
replace_colors['lightpink'] = '#FFB6C1'
replace_colors['lightsalmon'] = '#FFA07A'
replace_colors['lightseagreen'] = '#20B2AA'
replace_colors['lightskyblue'] = '#87CEFA'
replace_colors['lightslateblue'] = '#8470FF'
replace_colors['lightslategray'] = '#778899'
replace_colors['lightsteelblue'] = '#B0C4DE'
replace_colors['lightyellow'] = '#FFFFE0'
replace_colors['limegreen'] = '#32CD32'
replace_colors['linen'] = '#FAF0E6'
replace_colors['magenta'] = '#FF00FF'
replace_colors['mediumaquamarine'] = '#66CDAA'
replace_colors['mediumblue'] = '#0000CD'
replace_colors['mediumorchid'] = '#BA55D3'
replace_colors['mediumpurple'] = '#9370D8'
replace_colors['mediumseagreen'] = '#3CB371'
replace_colors['mediumslateblue'] = '#7B68EE'
replace_colors['mediumspringgreen'] = '#00FA9A'
replace_colors['mediumturquoise'] = '#48D1CC'
replace_colors['mediumvioletred'] = '#C71585'
replace_colors['midnightblue'] = '#191970'
replace_colors['mintcream'] = '#F5FFFA'
replace_colors['mistyrose'] = '#FFE4E1'
replace_colors['moccasin'] = '#FFE4B5'
replace_colors['navajowhite'] = '#FFDEAD'
replace_colors['oldlace'] = '#FDF5E6'
replace_colors['olivedrab'] = '#6B8E23'
replace_colors['orangered'] = '#FF4500'
replace_colors['orchid'] = '#DA70D6'
replace_colors['palegoldenrod'] = '#EEE8AA'
replace_colors['palegreen'] = '#98FB98'
replace_colors['paleturquoise'] = '#AFEEEE'
replace_colors['palevioletred'] = '#D87093'
replace_colors['papayawhip'] = '#FFEFD5'
replace_colors['peachpuff'] = '#FFDAB9'
replace_colors['peru'] = '#CD853F'
replace_colors['pink'] = '#FFC0CB'
replace_colors['plum'] = '#DDA0DD'
replace_colors['powderblue'] = '#B0E0E6'
replace_colors['rosybrown'] = '#BC8F8F'
replace_colors['royalblue'] = '#4169E1'
replace_colors['saddlebrown'] = '#8B4513'
replace_colors['salmon'] = '#FA8072'
replace_colors['sandybrown'] = '#F4A460'
replace_colors['seagreen'] = '#2E8B57'
replace_colors['seashell'] = '#FFF5EE'
replace_colors['sienna'] = '#A0522D'
replace_colors['skyblue'] = '#87CEEB'
replace_colors['slateblue'] = '#6A5ACD'
replace_colors['slategray'] = '#708090'
replace_colors['snow'] = '#FFFAFA'
replace_colors['springgreen'] = '#00FF7F'
replace_colors['steelblue'] = '#4682B4'
replace_colors['tan'] = '#D2B48C'
replace_colors['thistle'] = '#D8BFD8'
replace_colors['tomato'] = '#FF6347'
replace_colors['turquoise'] = '#40E0D0'
replace_colors['violet'] = '#EE82EE'
replace_colors['violetred'] = '#D02090'
replace_colors['wheat'] = '#F5DEB3'
replace_colors['whitesmoke'] = '#F5F5F5'
replace_colors['yellowgreen'] = '#9ACD32'
#A list of optimized colors
optimize_colors = {}
optimize_colors['black'] = '#000'
optimize_colors['fuchsia'] = '#F0F'
optimize_colors['white'] = '#FFF'
optimize_colors['yellow'] = '#FF0'
optimize_colors['cyan'] = '#0FF'
optimize_colors['magenta'] = '#F0F'
optimize_colors['lightslategray'] = '#789'
optimize_colors['#800000'] = 'maroon'
optimize_colors['#FFA500'] = 'orange'
optimize_colors['#808000'] = 'olive'
optimize_colors['#800080'] = 'purple'
optimize_colors['#008000'] = 'green'
optimize_colors['#000080'] = 'navy'
optimize_colors['#008080'] = 'teal'
optimize_colors['#C0C0C0'] = 'silver'
optimize_colors['#808080'] = 'gray'
optimize_colors['#4B0082'] = 'indigo'
optimize_colors['#FFD700'] = 'gold'
optimize_colors['#A52A2A'] = 'brown'
optimize_colors['#00FFFF'] = 'cyan'
optimize_colors['#EE82EE'] = 'violet'
optimize_colors['#DA70D6'] = 'orchid'
optimize_colors['#FFE4C4'] = 'bisque'
optimize_colors['#F0E68C'] = 'khaki'
optimize_colors['#F5DEB3'] = 'wheat'
optimize_colors['#FF7F50'] = 'coral'
optimize_colors['#F5F5DC'] = 'beige'
optimize_colors['#F0FFFF'] = 'azure'
optimize_colors['#A0522D'] = 'sienna'
optimize_colors['#CD853F'] = 'peru'
optimize_colors['#FFFFF0'] = 'ivory'
optimize_colors['#DDA0DD'] = 'plum'
optimize_colors['#D2B48C'] = 'tan'
optimize_colors['#FFC0CB'] = 'pink'
optimize_colors['#FFFAFA'] = 'snow'
optimize_colors['#FA8072'] = 'salmon'
optimize_colors['#FF6347'] = 'tomato'
optimize_colors['#FAF0E6'] = 'linen'
optimize_colors['#F00'] = 'red'
# A list of all shorthand properties that are devided into four properties and/or have four subvalues
#
# @global array shorthands
# @todo Are there new ones in CSS3?
# @see dissolve_4value_shorthands()
# @see merge_4value_shorthands()
# @version 1.0
shorthands = {}
shorthands['border-color'] = ['border-top-color','border-right-color','border-bottom-color','border-left-color']
shorthands['border-style'] = ['border-top-style','border-right-style','border-bottom-style','border-left-style']
shorthands['border-width'] = ['border-top-width','border-right-width','border-bottom-width','border-left-width']
shorthands['margin'] = ['margin-top','margin-right','margin-bottom','margin-left']
shorthands['padding'] = ['padding-top','padding-right','padding-bottom','padding-left']
shorthands['-moz-border-radius'] = 0
# All CSS Properties. Needed for csstidy::property_is_next()
#
# @global array all_properties
# @todo Add CSS3 properties
# @version 1.0
# @see csstidy::property_is_next()
all_properties = {}
all_properties['background'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['background-color'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['background-image'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['background-repeat'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['background-attachment'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['background-position'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-top'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-right'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-left'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-color'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-top-color'] = 'CSS2.0,CSS2.1'
all_properties['border-bottom-color'] = 'CSS2.0,CSS2.1'
all_properties['border-left-color'] = 'CSS2.0,CSS2.1'
all_properties['border-right-color'] = 'CSS2.0,CSS2.1'
all_properties['border-style'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-top-style'] = 'CSS2.0,CSS2.1'
all_properties['border-right-style'] = 'CSS2.0,CSS2.1'
all_properties['border-left-style'] = 'CSS2.0,CSS2.1'
all_properties['border-bottom-style'] = 'CSS2.0,CSS2.1'
all_properties['border-width'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-top-width'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-right-width'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-left-width'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-bottom-width'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['border-collapse'] = 'CSS2.0,CSS2.1'
all_properties['border-spacing'] = 'CSS2.0,CSS2.1'
all_properties['bottom'] = 'CSS2.0,CSS2.1'
all_properties['caption-side'] = 'CSS2.0,CSS2.1'
all_properties['content'] = 'CSS2.0,CSS2.1'
all_properties['clear'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['clip'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['color'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['counter-reset'] = 'CSS2.0,CSS2.1'
all_properties['counter-increment'] = 'CSS2.0,CSS2.1'
all_properties['cursor'] = 'CSS2.0,CSS2.1'
all_properties['empty-cells'] = 'CSS2.0,CSS2.1'
all_properties['display'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['direction'] = 'CSS2.0,CSS2.1'
all_properties['float'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['font'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['font-family'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['font-style'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['font-variant'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['font-weight'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['font-stretch'] = 'CSS2.0'
all_properties['font-size-adjust'] = 'CSS2.0'
all_properties['font-size'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['height'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['left'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['line-height'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['list-style'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['list-style-type'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['list-style-image'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['list-style-position'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['margin'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['margin-top'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['margin-right'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['margin-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['margin-left'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['marks'] = 'CSS1.0,CSS2.0'
all_properties['marker-offset'] = 'CSS2.0'
all_properties['max-height'] = 'CSS2.0,CSS2.1'
all_properties['max-width'] = 'CSS2.0,CSS2.1'
all_properties['min-height'] = 'CSS2.0,CSS2.1'
all_properties['min-width'] = 'CSS2.0,CSS2.1'
all_properties['overflow'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['orphans'] = 'CSS2.0,CSS2.1'
all_properties['outline'] = 'CSS2.0,CSS2.1'
all_properties['outline-width'] = 'CSS2.0,CSS2.1'
all_properties['outline-style'] = 'CSS2.0,CSS2.1'
all_properties['outline-color'] = 'CSS2.0,CSS2.1'
all_properties['padding'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['padding-top'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['padding-right'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['padding-bottom'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['padding-left'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['page-break-before'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['page-break-after'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['page-break-inside'] = 'CSS2.0,CSS2.1'
all_properties['page'] = 'CSS2.0'
all_properties['position'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['quotes'] = 'CSS2.0,CSS2.1'
all_properties['right'] = 'CSS2.0,CSS2.1'
all_properties['size'] = 'CSS1.0,CSS2.0'
all_properties['speak-header'] = 'CSS2.0,CSS2.1'
all_properties['table-layout'] = 'CSS2.0,CSS2.1'
all_properties['top'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['text-indent'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['text-align'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['text-decoration'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['text-shadow'] = 'CSS2.0'
all_properties['letter-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['word-spacing'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['text-transform'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['white-space'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['unicode-bidi'] = 'CSS2.0,CSS2.1'
all_properties['vertical-align'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['visibility'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['width'] = 'CSS1.0,CSS2.0,CSS2.1'
all_properties['widows'] = 'CSS2.0,CSS2.1'
all_properties['z-index'] = 'CSS1.0,CSS2.0,CSS2.1'
# Speech #
all_properties['volume'] = 'CSS2.0,CSS2.1'
all_properties['speak'] = 'CSS2.0,CSS2.1'
all_properties['pause'] = 'CSS2.0,CSS2.1'
all_properties['pause-before'] = 'CSS2.0,CSS2.1'
all_properties['pause-after'] = 'CSS2.0,CSS2.1'
all_properties['cue'] = 'CSS2.0,CSS2.1'
all_properties['cue-before'] = 'CSS2.0,CSS2.1'
all_properties['cue-after'] = 'CSS2.0,CSS2.1'
all_properties['play-during'] = 'CSS2.0,CSS2.1'
all_properties['azimuth'] = 'CSS2.0,CSS2.1'
all_properties['elevation'] = 'CSS2.0,CSS2.1'
all_properties['speech-rate'] = 'CSS2.0,CSS2.1'
all_properties['voice-family'] = 'CSS2.0,CSS2.1'
all_properties['pitch'] = 'CSS2.0,CSS2.1'
all_properties['pitch-range'] = 'CSS2.0,CSS2.1'
all_properties['stress'] = 'CSS2.0,CSS2.1'
all_properties['richness'] = 'CSS2.0,CSS2.1'
all_properties['speak-punctuation'] = 'CSS2.0,CSS2.1'
all_properties['speak-numeral'] = 'CSS2.0,CSS2.1'

View file

@ -1,383 +0,0 @@
# CSSTidy - CSS Optimizer
#
# CSS Optimizer class
#
# This file is part of CSSTidy.
#
# CSSTidy is free software you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation either version 2 of the License, or
# (at your option) any later version.
#
# CSSTidy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CSSTidy if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# @license http://opensource.org/licenses/gpl-license.php GNU Public License
# @package csstidy
# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
import data
from tools import SortedDict
class CSSOptimizer(object):
def __init__(self, parser):
#raw_css is a dict
self.parser = parser
self._optimized_css = SortedDict
#PUBLIC METHODS
def optimize(self, raw_css):
if self.parser.getSetting('preserve_css'):
return raw_css
self._optimized_css = raw_css
if self.parser.getSetting('merge_selectors') == 2:
self.__merge_selectors()
##OPTIMIZE##
for media, css in self._optimized_css.iteritems():
for selector, cssdata in css.iteritems():
if self.parser.getSetting('optimise_shorthands') >= 1:
cssdata = self.__merge_4value_shorthands(cssdata)
if self.parser.getSetting('optimise_shorthands') >= 2:
cssdata = self.__merge_bg(cssdata)
for item, value in cssdata.iteritems():
value = self.__compress_numbers(item, value)
value = self.__compress_important(value)
if item in data.color_values and self.parser.getSetting('compress_colors'):
old = value[:]
value = self.__compress_color(value)
if old != value:
self.parser.log('In "' + selector + '" Optimised ' + item + ': Changed ' + old + ' to ' + value, 'Information')
if item == 'font-weight' and self.parser.getSetting('compress_font-weight'):
if value == 'bold':
value = '700'
self.parser.log('In "' + selector + '" Optimised font-weight: Changed "bold" to "700"', 'Information')
elif value == 'normal':
value = '400'
self.parser.log('In "' + selector + '" Optimised font-weight: Changed "normal" to "400"', 'Information')
self._optimized_css[media][selector][item] = value
return self._optimized_css
#PRIVATE METHODS
def __merge_bg(self, cssdata):
"""
Merges all background properties
@cssdata (dict) is a dictionary of the selector properties
"""
#Max number of background images. CSS3 not yet fully implemented
img = 1
clr = 1
bg_img_list = []
if cssdata.has_key('background-image'):
img = len(cssdata['background-image'].split(','))
bg_img_list = self.parser.gvw_important(cssdata['background-image']).split(',')
elif cssdata.has_key('background-color'):
clr = len(cssdata['background-color'].split(','))
number_of_values = max(img, clr, 1)
new_bg_value = ''
important = ''
for i in xrange(number_of_values):
for bg_property, default_value in data.background_prop_default.iteritems():
#Skip if property does not exist
if not cssdata.has_key(bg_property):
continue
cur_value = cssdata[bg_property]
#Skip some properties if there is no background image
if (len(bg_img_list) > i and bg_img_list[i] == 'none') and bg_property in frozenset(['background-size', 'background-position', 'background-attachment', 'background-repeat']):
continue
#Remove !important
if self.parser.is_important(cur_value):
important = ' !important'
cur_value = self.parser.gvw_important(cur_value)
#Do not add default values
if cur_value == default_value:
continue
temp = cur_value.split(',')
if len(temp) > i:
if bg_property == 'background-size':
new_bg_value += '(' + temp[i] + ') '
else:
new_bg_value += temp[i] + ' '
new_bg_value = new_bg_value.strip()
if i != (number_of_values-1):
new_bg_value += ','
#Delete all background-properties
for bg_property, default_value in data.background_prop_default.iteritems():
try:
del cssdata[bg_property]
except:
pass
#Add new background property
if new_bg_value != '':
cssdata['background'] = new_bg_value + important
return cssdata
def __merge_4value_shorthands(self, cssdata):
"""
Merges Shorthand properties again, the opposite of dissolve_4value_shorthands()
@cssdata (dict) is a dictionary of the selector properties
"""
for key, value in data.shorthands.iteritems():
important = ''
if value != 0 and cssdata.has_key(value[0]) and cssdata.has_key(value[1]) and cssdata.has_key(value[2]) and cssdata.has_key(value[3]):
cssdata[key] = ''
for i in xrange(4):
val = cssdata[value[i]]
if self.parser.is_important(val):
important = '!important'
cssdata[key] += self.parser.gvw_important(val) + ' '
else:
cssdata[key] += val + ' '
del cssdata[value[i]]
if cssdata.has_key(key):
cssdata[key] = self.__shorthand(cssdata[key] + important.strip())
return cssdata
def __merge_selectors(self):
"""
Merges selectors with same properties. Example: a{color:red} b{color:red} . a,b{color:red}
Very basic and has at least one bug. Hopefully there is a replacement soon.
@selector_one (string) is the current selector
@value_one (dict) is a dictionary of the selector properties
Note: Currently is the elements of a selector are identical, but in a different order, they are not merged
"""
##OPTIMIZE##
##FIX##
raw_css = self._optimized_css.copy()
delete = []
add = SortedDict()
for media, css in raw_css.iteritems():
for selector_one, value_one in css.iteritems():
newsel = selector_one
for selector_two, value_two in css.iteritems():
if selector_one == selector_two:
#We need to skip self
continue
if value_one == value_two:
#Ok, we need to merge these two selectors
newsel += ', ' + selector_two
delete.append((media, selector_two))
if not add.has_key(media):
add[media] = SortedDict()
add[media][newsel] = value_one
delete.append((media, selector_one))
for item in delete:
try:
del self._optimized_css[item[0]][item[1]]
except:
#Must have already been deleted
continue
for media, css in add.iteritems():
self._optimized_css[media].update(css)
def __shorthand(self, value):
"""
Compresses shorthand values. Example: margin:1px 1px 1px 1px . margin:1px
@value (string)
"""
##FIX##
important = '';
if self.parser.is_important(value):
value_list = self.parser.gvw_important(value)
important = '!important'
else:
value_list = value
ret = value
value_list = value_list.split(' ')
if len(value_list) == 4:
if value_list[0] == value_list[1] and value_list[0] == value_list[2] and value_list[0] == value_list[3]:
ret = value_list[0] + important
elif value_list[1] == value_list[3] and value_list[0] == value_list[2]:
ret = value_list[0] + ' ' + value_list[1] + important
elif value_list[1] == value_list[3]:
ret = value_list[0] + ' ' + value_list[1] + ' ' + value_list[2] + important
elif len(value_list) == 3:
if value_list[0] == value_list[1] and value_list[0] == value_list[2]:
ret = value_list[0] + important
elif value_list[0] == value_list[2]:
return value_list[0] + ' ' + value_list[1] + important
elif len(value_list) == 2:
if value_list[0] == value_list[1]:
ret = value_list[0] + important
if ret != value:
self.parser.log('Optimised shorthand notation: Changed "' + value + '" to "' + ret + '"', 'Information')
return ret
def __compress_important(self, value):
"""
Removes unnecessary whitespace in ! important
@value (string)
"""
if self.parser.is_important(value):
value = self.parser.gvw_important(value) + '!important'
return value
def __compress_numbers(self, prop, value):
"""
Compresses numbers (ie. 1.0 becomes 1 or 1.100 becomes 1.1 )
@value (string) is the posible number to be compressed
"""
##FIX##
value = value.split('/')
for l in xrange(len(value)):
#continue if no numeric value
if not (len(value[l]) > 0 and (value[l][0].isdigit() or value[l][0] in ('+', '-') )):
continue
#Fix bad colors
if prop in data.color_values:
value[l] = '#' + value[l]
is_floatable = False
try:
float(value[l])
is_floatable = True
except:
pass
if is_floatable and float(value[l]) == 0:
value[l] = '0'
elif value[l][0] != '#':
unit_found = False
for unit in data.units:
pos = value[l].lower().find(unit)
if pos != -1 and prop not in data.shorthands:
value[l] = self.__remove_leading_zeros(float(value[l][:pos])) + unit
unit_found = True
break;
if not unit_found and prop in data.unit_values and prop not in data.shorthands:
value[l] = self.__remove_leading_zeros(float(value[l])) + 'px'
elif not unit_found and prop not in data.shorthands:
value[l] = self.__remove_leading_zeros(float(value[l]))
if len(value) > 1:
return '/'.join(value)
return value[0]
def __remove_leading_zeros(self, float_val):
"""
Removes the leading zeros from a float value
@float_val (float)
@returns (string)
"""
#Remove leading zero
if abs(float_val) < 1:
if float_val < 0:
float_val = '-' . str(float_val)[2:]
else:
float_val = str(float_val)[1:]
return str(float_val)
def __compress_color(self, color):
"""
Color compression function. Converts all rgb() values to #-values and uses the short-form if possible. Also replaces 4 color names by #-values.
@color (string) the {posible} color to change
"""
#rgb(0,0,0) . #000000 (or #000 in this case later)
if color[:4].lower() == 'rgb(':
color_tmp = color[4:(len(color)-5)]
color_tmp = color_tmp.split(',')
for c in color_tmp:
c = c.strip()
if c[:-1] == '%':
c = round((255*color_tmp[i])/100)
if color_tmp[i] > 255:
color_tmp[i] = 255
color = '#'
for i in xrange(3):
if color_tmp[i] < 16:
color += '0' + str(hex(color_tmp[i])).replace('0x', '')
else:
color += str(hex(color_tmp[i])).replace('0x', '')
#Fix bad color names
if data.replace_colors.has_key(color.lower()):
color = data.replace_colors[color.lower()]
#aabbcc . #abc
if len(color) == 7:
color_temp = color.lower()
if color_temp[0] == '#' and color_temp[1] == color_temp[2] and color_temp[3] == color_temp[4] and color_temp[5] == color_temp[6]:
color = '#' + color[1] + color[3] + color[5]
if data.optimize_colors.has_key(color.lower()):
color = data.optimize_colors[color.lower()]
return color

View file

@ -1,101 +0,0 @@
# CSSTidy - CSS Printer
#
# CSS Printer class
#
# This file is part of CSSTidy.
#
# CSSTidy is free software you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation either version 2 of the License, or
# (at your option) any later version.
#
# CSSTidy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CSSTidy if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# @license http://opensource.org/licenses/gpl-license.php GNU Public License
# @package csstidy
# @author Dj Gilcrease (digitalxero at gmail dot com) 2005-2006
import data
class CSSPrinter(object):
def __init__(self, parser):
self.parser = parser
self._css = {}
self.__renderMethods = {'string': self.__renderString, 'file': self.__renderFile}
#PUBLIC METHODS
def prepare(self, css):
self._css = css
def render(self, output="string", *args, **kwargs):
return self.__renderMethods[output](*args, **kwargs)
#PRIVATE METHODS
def __renderString(self, *args, **kwargs):
##OPTIMIZE##
template = self.parser.getSetting('template')
ret = ""
if template == 'highest_compression':
top_line_end = ""
iner_line_end = ""
bottom_line_end = ""
indent = ""
elif template == 'high_compression':
top_line_end = "\n"
iner_line_end = ""
bottom_line_end = "\n"
indent = ""
elif template == 'default':
top_line_end = "\n"
iner_line_end = "\n"
bottom_line_end = "\n\n"
indent = ""
elif template == 'low_compression':
top_line_end = "\n"
iner_line_end = "\n"
bottom_line_end = "\n\n"
indent = " "
if self.parser.getSetting('timestamp'):
ret += '/# CSSTidy ' + self.parser.version + ': ' + datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000") + ' #/' + top_line_end
for item in self.parser._import:
ret += '@import(' + item + ');' + top_line_end
for item in self.parser._charset:
ret += '@charset(' + item + ');' + top_line_end
for item in self.parser._namespace:
ret += '@namespace(' + item + ');' + top_line_end
for media, css in self._css.iteritems():
for selector, cssdata in css.iteritems():
ret += selector + '{' + top_line_end
for item, value in cssdata.iteritems():
ret += indent + item + ':' + value + ';' + iner_line_end
ret += '}' + bottom_line_end
return ret
def __renderFile(self, filename=None, *args, **kwargs):
if filename is None:
return self.__renderString()
try:
f = open(filename, "w")
f.write(self.__renderString())
finally:
f.close()

View file

@ -1,109 +0,0 @@
class SortedDict(dict):
"""
A dictionary that keeps its keys in the order in which they're inserted.
"""
def __init__(self, data=None):
if data is None:
data = {}
super(SortedDict, self).__init__(data)
if isinstance(data, dict):
self.keyOrder = data.keys()
else:
self.keyOrder = []
for key, value in data:
if key not in self.keyOrder:
self.keyOrder.append(key)
def __deepcopy__(self, memo):
from copy import deepcopy
return self.__class__([(key, deepcopy(value, memo))
for key, value in self.iteritems()])
def __setitem__(self, key, value):
super(SortedDict, self).__setitem__(key, value)
if key not in self.keyOrder:
self.keyOrder.append(key)
def __delitem__(self, key):
super(SortedDict, self).__delitem__(key)
self.keyOrder.remove(key)
def __iter__(self):
for k in self.keyOrder:
yield k
def pop(self, k, *args):
result = super(SortedDict, self).pop(k, *args)
try:
self.keyOrder.remove(k)
except ValueError:
# Key wasn't in the dictionary in the first place. No problem.
pass
return result
def popitem(self):
result = super(SortedDict, self).popitem()
self.keyOrder.remove(result[0])
return result
def items(self):
return zip(self.keyOrder, self.values())
def iteritems(self):
for key in self.keyOrder:
yield key, super(SortedDict, self).__getitem__(key)
def keys(self):
return self.keyOrder[:]
def iterkeys(self):
return iter(self.keyOrder)
def values(self):
return [super(SortedDict, self).__getitem__(k) for k in self.keyOrder]
def itervalues(self):
for key in self.keyOrder:
yield super(SortedDict, self).__getitem__(key)
def update(self, dict_):
for k, v in dict_.items():
self.__setitem__(k, v)
def setdefault(self, key, default):
if key not in self.keyOrder:
self.keyOrder.append(key)
return super(SortedDict, self).setdefault(key, default)
def value_for_index(self, index):
"""Returns the value of the item at the given zero-based index."""
return self[self.keyOrder[index]]
def insert(self, index, key, value):
"""Inserts the key, value pair before the item with the given index."""
if key in self.keyOrder:
n = self.keyOrder.index(key)
del self.keyOrder[n]
if n < index:
index -= 1
self.keyOrder.insert(index, key)
super(SortedDict, self).__setitem__(key, value)
def copy(self):
"""Returns a copy of this object."""
# This way of initializing the copy means it works for subclasses, too.
obj = self.__class__(self)
obj.keyOrder = self.keyOrder[:]
return obj
def __repr__(self):
"""
Replaces the normal dict.__repr__ with a version that returns the keys
in their sorted order.
"""
return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()])
def clear(self):
super(SortedDict, self).clear()
self.keyOrder = []

View file

@ -1,6 +0,0 @@
from compress.filters.jsmin.jsmin import jsmin
from compress.filter_base import FilterBase
class JSMinFilter(FilterBase):
def filter_js(self, js):
return jsmin(js)

View file

@ -1,218 +0,0 @@
#!/usr/bin/python
# This code is original from jsmin by Douglas Crockford, it was translated to
# Python by Baruch Even. The original code had the following copyright and
# license.
#
# /* jsmin.c
# 2007-05-22
#
# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
#
# 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 shall be used for Good, not Evil.
#
# 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.
# */
from StringIO import StringIO
def jsmin(js):
ins = StringIO(js)
outs = StringIO()
JavascriptMinify().minify(ins, outs)
str = outs.getvalue()
if len(str) > 0 and str[0] == '\n':
str = str[1:]
return str
def isAlphanum(c):
"""return true if the character is a letter, digit, underscore,
dollar sign, or non-ASCII character.
"""
return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
(c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
class UnterminatedComment(Exception):
pass
class UnterminatedStringLiteral(Exception):
pass
class UnterminatedRegularExpression(Exception):
pass
class JavascriptMinify(object):
def _outA(self):
self.outstream.write(self.theA)
def _outB(self):
self.outstream.write(self.theB)
def _get(self):
"""return the next character from stdin. Watch out for lookahead. If
the character is a control character, translate it to a space or
linefeed.
"""
c = self.theLookahead
self.theLookahead = None
if c == None:
c = self.instream.read(1)
if c >= ' ' or c == '\n':
return c
if c == '': # EOF
return '\000'
if c == '\r':
return '\n'
return ' '
def _peek(self):
self.theLookahead = self._get()
return self.theLookahead
def _next(self):
"""get the next character, excluding comments. peek() is used to see
if a '/' is followed by a '/' or '*'.
"""
c = self._get()
if c == '/':
p = self._peek()
if p == '/':
c = self._get()
while c > '\n':
c = self._get()
return c
if p == '*':
c = self._get()
while 1:
c = self._get()
if c == '*':
if self._peek() == '/':
self._get()
return ' '
if c == '\000':
raise UnterminatedComment()
return c
def _action(self, action):
"""do something! What you do is determined by the argument:
1 Output A. Copy B to A. Get the next B.
2 Copy B to A. Get the next B. (Delete A).
3 Get the next B. (Delete B).
action treats a string as a single character. Wow!
action recognizes a regular expression if it is preceded by ( or , or =.
"""
if action <= 1:
self._outA()
if action <= 2:
self.theA = self.theB
if self.theA == "'" or self.theA == '"':
while 1:
self._outA()
self.theA = self._get()
if self.theA == self.theB:
break
if self.theA <= '\n':
raise UnterminatedStringLiteral()
if self.theA == '\\':
self._outA()
self.theA = self._get()
if action <= 3:
self.theB = self._next()
if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
self.theA == '=' or self.theA == ':' or
self.theA == '[' or self.theA == '?' or
self.theA == '!' or self.theA == '&' or
self.theA == '|' or self.theA == ';' or
self.theA == '{' or self.theA == '}' or
self.theA == '\n'):
self._outA()
self._outB()
while 1:
self.theA = self._get()
if self.theA == '/':
break
elif self.theA == '\\':
self._outA()
self.theA = self._get()
elif self.theA <= '\n':
raise UnterminatedRegularExpression()
self._outA()
self.theB = self._next()
def _jsmin(self):
"""Copy the input to the output, deleting the characters which are
insignificant to JavaScript. Comments will be removed. Tabs will be
replaced with spaces. Carriage returns will be replaced with linefeeds.
Most spaces and linefeeds will be removed.
"""
self.theA = '\n'
self._action(3)
while self.theA != '\000':
if self.theA == ' ':
if isAlphanum(self.theB):
self._action(1)
else:
self._action(2)
elif self.theA == '\n':
if self.theB in ['{', '[', '(', '+', '-']:
self._action(1)
elif self.theB == ' ':
self._action(3)
else:
if isAlphanum(self.theB):
self._action(1)
else:
self._action(2)
else:
if self.theB == ' ':
if isAlphanum(self.theA):
self._action(1)
else:
self._action(3)
elif self.theB == '\n':
if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
self._action(1)
else:
if isAlphanum(self.theA):
self._action(1)
else:
self._action(3)
else:
self._action(1)
def minify(self, instream, outstream):
self.instream = instream
self.outstream = outstream
self.theA = '\n'
self.theB = None
self.theLookahead = None
self._jsmin()
self.instream.close()
if __name__ == '__main__':
import sys
jsm = JavascriptMinify()
jsm.minify(sys.stdin, sys.stdout)

View file

@ -1,44 +0,0 @@
import subprocess
from django.conf import settings
from compress.filter_base import FilterBase, FilterError
BINARY = getattr(settings, 'COMPRESS_YUI_BINARY', 'java -jar yuicompressor.jar')
CSS_ARGUMENTS = getattr(settings, 'COMPRESS_YUI_CSS_ARGUMENTS', '')
JS_ARGUMENTS = getattr(settings, 'COMPRESS_YUI_JS_ARGUMENTS', '')
class YUICompressorFilter(FilterBase):
def filter_common(self, content, type_, arguments):
command = '%s --type=%s %s' % (BINARY, type_, arguments)
if self.verbose:
command += ' --verbose'
p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
p.stdin.write(content)
p.stdin.close()
filtered_css = p.stdout.read()
p.stdout.close()
err = p.stderr.read()
p.stderr.close()
if p.wait() != 0:
if not err:
err = 'Unable to apply YUI Compressor filter'
raise FilterError(err)
if self.verbose:
print err
return filtered_css
def filter_js(self, js):
return self.filter_common(js, 'js', JS_ARGUMENTS)
def filter_css(self, css):
return self.filter_common(css, 'css', CSS_ARGUMENTS)

View file

@ -1,58 +0,0 @@
from django.core.management.base import NoArgsCommand
from optparse import make_option
from django.conf import settings
class Command(NoArgsCommand):
option_list = NoArgsCommand.option_list + (
make_option('--force', action='store_true', default=False, help='Force update of all files, even if the source files are older than the current compressed file.'),
)
help = 'Updates and compresses CSS and JavsScript on-demand, without restarting Django'
args = ''
def handle_noargs(self, **options):
force = options.get('force', False)
verbosity = int(options.get('verbosity', 1))
from compress.utils import needs_update, filter_css, filter_js
for name, css in settings.COMPRESS_CSS.items():
u, version = needs_update(css['output_filename'],
css['source_filenames'])
if (force or u) or verbosity >= 2:
msg = 'CSS Group \'%s\'' % name
print msg
print len(msg) * '-'
print "Version: %s" % version
if force or u:
filter_css(css, verbosity)
if (force or u) or verbosity >= 2:
print
for name, js in settings.COMPRESS_JS.items():
u, version = needs_update(js['output_filename'],
js['source_filenames'])
if (force or u) or verbosity >= 2:
msg = 'JavaScript Group \'%s\'' % name
print msg
print len(msg) * '-'
print "Version: %s" % version
if force or u:
filter_js(js, verbosity)
if (force or u) or verbosity >= 2:
print
# Backwards compatibility for Django r9110
if not [opt for opt in Command.option_list if opt.dest=='verbosity']:
Command.option_list += (
make_option('--verbosity', '-v', action="store", dest="verbosity",
default='1', type='choice', choices=['0', '1', '2'],
help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
)

View file

View file

@ -1,4 +0,0 @@
from django.dispatch import Signal
css_filtered = Signal()
js_filtered = Signal()

View file

@ -1 +0,0 @@
<link href="{{ url }}" rel="stylesheet" type="text/css"{% if media %} media="{{ media }}"{% endif %}{% if title %} title="{{ title|default:"all" }}"{% endif %}{% if charset %} charset="{{ charset }}"{% endif %} />

View file

@ -1 +0,0 @@
<!--[if {{ condition|default:"IE" }}]><link href="{{ url }}" rel="stylesheet" type="text/css" media="{{ media|default:"all" }}" title="{{ title|default:"all" }}" charset="utf-8" /><![endif]-->

View file

@ -1 +0,0 @@
<script type="text/javascript" src="{{ url }}" charset="utf-8"></script>

View file

@ -1 +0,0 @@
<!--[if {{ condition|default:"IE" }}]><script type="text/javascript" src="{{ url }}" charset="utf-8"></script><![endif]-->

View file

@ -1,123 +0,0 @@
import os
from django import template
from django.conf import settings as django_settings
from compress.conf import settings
from compress.utils import media_root, media_url, needs_update, filter_css, filter_js, get_output_filename, get_version, get_version_from_file
register = template.Library()
def render_common(template_name, obj, filename, version):
if settings.COMPRESS:
filename = get_output_filename(filename, version)
context = obj.get('extra_context', {})
if filename.startswith('http://'):
context['url'] = filename
else:
context['url'] = media_url(filename)
return template.loader.render_to_string(template_name, context)
def render_css(css, filename, version=None):
return render_common(css.get('template_name', 'compress/css.html'), css, filename, version)
def render_js(js, filename, version=None):
return render_common(js.get('template_name', 'compress/js.html'), js, filename, version)
class CompressedCSSNode(template.Node):
def __init__(self, name):
self.name = name
def render(self, context):
css_name = template.Variable(self.name).resolve(context)
try:
css = settings.COMPRESS_CSS[css_name]
except KeyError:
return '' # fail silently, do not return anything if an invalid group is specified
if settings.COMPRESS:
version = None
if settings.COMPRESS_AUTO:
u, version = needs_update(css['output_filename'],
css['source_filenames'])
if u:
filter_css(css)
else:
filename_base, filename = os.path.split(css['output_filename'])
path_name = media_root(filename_base)
version = get_version_from_file(path_name, filename)
return render_css(css, css['output_filename'], version)
else:
# output source files
r = ''
for source_file in css['source_filenames']:
r += render_css(css, source_file)
return r
class CompressedJSNode(template.Node):
def __init__(self, name):
self.name = name
def render(self, context):
js_name = template.Variable(self.name).resolve(context)
try:
js = settings.COMPRESS_JS[js_name]
except KeyError:
return '' # fail silently, do not return anything if an invalid group is specified
if 'external_urls' in js:
r = ''
for url in js['external_urls']:
r += render_js(js, url)
return r
if settings.COMPRESS:
version = None
if settings.COMPRESS_AUTO:
u, version = needs_update(js['output_filename'],
js['source_filenames'])
if u:
filter_js(js)
else:
filename_base, filename = os.path.split(js['output_filename'])
path_name = media_root(filename_base)
version = get_version_from_file(path_name, filename)
return render_js(js, js['output_filename'], version)
else:
# output source files
r = ''
for source_file in js['source_filenames']:
r += render_js(js, source_file)
return r
#@register.tag
def compressed_css(parser, token):
try:
tag_name, name = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the COMPRESS_CSS setting' % token.split_contents()[0]
return CompressedCSSNode(name)
compressed_css = register.tag(compressed_css)
#@register.tag
def compressed_js(parser, token):
try:
tag_name, name = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError, '%r requires exactly one argument: the name of a group in the COMPRESS_JS setting' % token.split_contents()[0]
return CompressedJSNode(name)
compressed_js = register.tag(compressed_js)

View file

@ -1,133 +0,0 @@
import os
import re
import tempfile
from django.conf import settings as django_settings
from django.utils.http import urlquote
from django.dispatch import dispatcher
from compress.conf import settings
from compress.signals import css_filtered, js_filtered
def get_class(class_string):
"""
Convert a string version of a function name to the callable object.
"""
if not hasattr(class_string, '__bases__'):
try:
class_string = class_string.encode('ascii')
mod_name, class_name = get_mod_func(class_string)
if class_name != '':
class_string = getattr(__import__(mod_name, {}, {}, ['']), class_name)
except (ImportError, AttributeError):
raise Exception('Failed to import filter %s' % class_string)
return class_string
def get_mod_func(callback):
"""
Converts 'django.views.news.stories.story_detail' to
('django.views.news.stories', 'story_detail')
"""
try:
dot = callback.rindex('.')
except ValueError:
return callback, ''
return callback[:dot], callback[dot+1:]
def needs_update(output_file, source_files, verbosity=0):
"""
Scan the source files for changes and returns True if the output_file needs to be updated.
"""
version = get_version(source_files)
on = get_output_filename(output_file, version)
compressed_file_full = media_root(on)
if not os.path.exists(compressed_file_full):
return True, version
update_needed = getattr(get_class(settings.COMPRESS_VERSIONING)(), 'needs_update')(output_file, source_files, version)
return update_needed
def media_root(filename):
"""
Return the full path to ``filename``. ``filename`` is a relative path name in MEDIA_ROOT
"""
return os.path.join(django_settings.MEDIA_ROOT, filename)
def media_url(url):
return django_settings.MEDIA_URL + urlquote(url)
def concat(filenames, separator=''):
"""
Concatenate the files from the list of the ``filenames``, ouput separated with ``separator``.
"""
r = ''
for filename in filenames:
fd = open(media_root(filename), 'rb')
r += fd.read()
r += separator
fd.close()
return r
def max_mtime(files):
return int(max([os.stat(media_root(f)).st_mtime for f in files]))
def save_file(filename, contents):
fd = open(media_root(filename), 'wb+')
fd.write(contents)
fd.close()
def get_output_filename(filename, version):
if settings.COMPRESS_VERSION and version is not None:
return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, version)
else:
return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, settings.COMPRESS_VERSION_DEFAULT)
def get_version(source_files, verbosity=0):
version = getattr(get_class(settings.COMPRESS_VERSIONING)(), 'get_version')(source_files)
return version
def get_version_from_file(path, filename):
regex = re.compile(r'^%s$' % (get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'([A-Za-z0-9]+)')))
for f in os.listdir(path):
result = regex.match(f)
if result and result.groups():
return result.groups()[0]
def remove_files(path, filename, verbosity=0):
regex = re.compile(r'^%s$' % (os.path.basename(get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'[A-Za-z0-9]+'))))
for f in os.listdir(path):
if regex.match(f):
if verbosity >= 1:
print "Removing outdated file %s" % f
os.unlink(os.path.join(path, f))
def filter_common(obj, verbosity, filters, attr, separator, signal):
output = concat(obj['source_filenames'], separator)
filename = get_output_filename(obj['output_filename'], get_version(obj['source_filenames']))
if settings.COMPRESS_VERSION:
remove_files(os.path.dirname(media_root(filename)), obj['output_filename'], verbosity)
if verbosity >= 1:
print "Saving %s" % filename
for f in filters:
output = getattr(get_class(f)(verbose=(verbosity >= 2)), attr)(output)
save_file(filename, output)
signal.send(None)
def filter_css(css, verbosity=0):
return filter_common(css, verbosity, filters=settings.COMPRESS_CSS_FILTERS, attr='filter_css', separator='', signal=css_filtered)
def filter_js(js, verbosity=0):
return filter_common(js, verbosity, filters=settings.COMPRESS_JS_FILTERS, attr='filter_js', separator='', signal=js_filtered)

View file

@ -1,13 +0,0 @@
class VersioningBase(object):
def get_version(self, source_files):
raise NotImplementedError
def needs_update(self, output_file, source_files, version):
raise NotImplementedError
class VersioningError(Exception):
"""
This exception is raised when version creation fails
"""
pass

View file

@ -1,47 +0,0 @@
import cStringIO
from hashlib import md5, sha1
import os
from compress.conf import settings
from compress.utils import concat, get_output_filename
from compress.versioning.base import VersioningBase
class HashVersioningBase(VersioningBase):
def __init__(self, hash_method):
self.hash_method = hash_method
def needs_update(self, output_file, source_files, version):
output_file_name = get_output_filename(output_file, version)
ph = settings.COMPRESS_VERSION_PLACEHOLDER
of = output_file
try:
phi = of.index(ph)
old_version = output_file_name[phi:phi+len(ph)-len(of)]
return (version != old_version), version
except ValueError:
# no placeholder found, do not update, manual update if needed
return False, version
def get_version(self, source_files):
buf = concat(source_files)
s = cStringIO.StringIO(buf)
version = self.get_hash(s)
s.close()
return version
def get_hash(self, f, CHUNK=2**16):
m = self.hash_method()
while 1:
chunk = f.read(CHUNK)
if not chunk:
break
m.update(chunk)
return m.hexdigest()
class MD5Versioning(HashVersioningBase):
def __init__(self):
super(MD5Versioning, self).__init__(md5)
class SHA1Versioning(HashVersioningBase):
def __init__(self):
super(SHA1Versioning, self).__init__(sha1)

View file

@ -1,19 +0,0 @@
import os
from compress.utils import get_output_filename, media_root
from compress.versioning.base import VersioningBase
class MTimeVersioning(VersioningBase):
def get_version(self, source_files):
# Return the modification time for the newest source file
return str(max([int(os.stat(media_root(f)).st_mtime) for f in source_files]))
def needs_update(self, output_file, source_files, version):
output_file_name = get_output_filename(output_file, version)
compressed_file_full = media_root(output_file_name)
return (int(os.stat(compressed_file_full).st_mtime) < int(version)), version

8936
media/js/jquery-1.6.1.js vendored

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,7 @@ CURRENT_DIR = os.path.dirname(__file__)
NEWSBLUR_DIR = CURRENT_DIR
TEMPLATE_DIRS = (os.path.join(CURRENT_DIR, 'templates'),)
MEDIA_ROOT = os.path.join(CURRENT_DIR, 'media')
STATIC_ROOT = os.path.join(CURRENT_DIR, 'static')
UTILS_ROOT = os.path.join(CURRENT_DIR, 'utils')
VENDOR_ROOT = os.path.join(CURRENT_DIR, 'vendor')
LOG_FILE = os.path.join(CURRENT_DIR, 'logs/newsblur.log')
@ -480,5 +481,5 @@ MONGODB = connect(MONGO_DB.pop('name'), **MONGO_DB)
REDIS_POOL = redis.ConnectionPool(host=REDIS['host'], port=6379, db=0)
jammit = jammit.JammitAssets()
print jammit
JAMMIT = jammit.JammitAssets()
print JAMMIT

View file

@ -1,4 +1,4 @@
{% load compressed utils_tags %}
{% load utils_tags %}
<!DOCTYPE html>
<html>
<head>
@ -58,7 +58,7 @@
};
</script>
{% include_stylehseets "common" %}
{% include_stylesheets "common" %}
{% block head_js %}
{% include_javascripts "common" %}
{% endblock head_js %}

View file

@ -1,4 +1,4 @@
{% load compressed utils_tags %}
{% load utils_tags %}
<!DOCTYPE html>
<html>
<head>
@ -32,7 +32,7 @@
};
</script>
{% include_stylehseets "mobile" %}
{% include_stylesheets "mobile" %}
{% block head_js %}
{% include_javascripts "mobile" %}
{% endblock head_js %}

View file

@ -1,9 +1,9 @@
{% extends 'base.html' %}
{% load typogrify_tags compressed %}
{% load typogrify_tags utils_tags %}
{% block extra_head_js %}
{% include_stylehseets "paypal" %}
{% include_stylesheets "paypal" %}
{% include_javascripts "paypal" %}
{% endblock %}

View file

@ -27,3 +27,7 @@ if settings.DEVELOPMENT:
(r'^media/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT}),
)
urlpatterns += patterns('',
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.STATIC_ROOT}),
)

View file

@ -34,7 +34,7 @@ class JammitAssets:
`use_compressed_assets` profile setting.
"""
tags = []
if settings.DEBUG:
if not settings.DEBUG:
if asset_type == 'javascripts':
asset_type_ext = 'js'
elif asset_type == 'stylesheets':
@ -75,7 +75,7 @@ class JammitAssets:
return '<script src="%s" type="text/javascript" charset="utf-8"></script>' % path
def javascript_tag_compressed(self, asset_package, asset_type_ext):
filename = 'static/assets/%s.%s' % (asset_package, asset_type_ext)
filename = 'static/%s.%s' % (asset_package, asset_type_ext)
asset_mtime = int(os.path.getmtime(filename))
path = '/%s?%s' % (filename, asset_mtime)
return self.javascript_tag(path)
@ -84,8 +84,8 @@ class JammitAssets:
return '<link rel="stylesheet" href="%s" type="text/css" charset="utf-8">' % path
def stylesheet_tag_compressed(self, asset_package, asset_type_ext):
datauri_filename = 'static/assets/%s-datauri.%s' % (asset_package, asset_type_ext)
original_filename = 'static/assets/%s.%s' % (asset_package, asset_type_ext)
datauri_filename = 'static/%s-datauri.%s' % (asset_package, asset_type_ext)
original_filename = 'static/%s.%s' % (asset_package, asset_type_ext)
asset_mtime = int(os.path.getmtime(datauri_filename))
datauri_path = '/%s?%s' % (datauri_filename, asset_mtime)
original_path = '/%s?%s' % (original_filename, asset_mtime)

View file

@ -80,18 +80,14 @@ def commify(n):
@register.simple_tag
def include_javascripts():
def include_javascripts(asset_package):
"""Prints out a template of <script> tags based on an asset package name."""
def render(self, asset_package):
asset_type = 'javascripts'
return settings.jammit.render_tags(asset_type, asset_package)
asset_type = 'javascripts'
return settings.JAMMIT.render_tags(asset_type, asset_package)
@register.simple_tag
def include_stylesheets():
def include_stylesheets(asset_package):
"""Prints out a template of <link> tags based on an asset package name."""
def render(self, asset_package):
asset_type = 'stylesheets'
return settings.jammit.render_tags(asset_type, asset_package)
asset_type = 'stylesheets'
return settings.JAMMIT.render_tags(asset_type, asset_package)