From 0c7222154dcd55072efe4ebc7b7587bf3461bd19 Mon Sep 17 00:00:00 2001 From: Aida Tavakkolie Date: Mon, 14 Mar 2011 16:26:51 -0700 Subject: [PATCH 1/2] Adding support for cssmin - a pure python port of YUI CSS Compressor, for those of us who don't want to install java. Set property DJANGO_STATIC_ CSSMIN = True in settings to enable. Requires cssmin install. --- django_static/templatetags/django_static.py | 19 ++++++++++++++++--- settings.py | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/django_static/templatetags/django_static.py b/django_static/templatetags/django_static.py index 1809e7a..1cd636c 100644 --- a/django_static/templatetags/django_static.py +++ b/django_static/templatetags/django_static.py @@ -18,8 +18,7 @@ register = template.Library() try: - from slimmer import (css_slimmer, guessSyntax, html_slimmer, js_slimmer, - xhtml_slimmer) + from slimmer import (css_slimmer, guessSyntax, html_slimmer, js_slimmer, xhtml_slimmer) slimmer = 'installed' except ImportError: slimmer = None @@ -27,6 +26,12 @@ # and Google Closure compiler. #warnings.warn("slimmer is not installed. (easy_install slimmer)") +try: + import cssmin +except ImportError: + warnings.warn("cssmin is not installed. (easy_install cssmin)") + + if sys.platform == "win32": _CAN_SYMLINK = False else: @@ -700,13 +705,14 @@ def has_optimizer(type_): if type_ == CSS: if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): return True + if getattr(settings, 'DJANGO_STATIC_CSSMIN', None): + return True return slimmer is not None elif type_ == JS: if getattr(settings, 'DJANGO_STATIC_CLOSURE_COMPILER', None): return True if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): return True - return slimmer is not None else: raise ValueError("Invalid type %r" % type_) @@ -715,6 +721,8 @@ def optimize(content, type_): if type_ == CSS: if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): return _run_yui_compressor(content, type_) + if getattr(settings, 'DJANGO_STATIC_CSSMIN', None): + return _run_cssmin(content) return css_slimmer(content) elif type_ == JS: if getattr(settings, 'DJANGO_STATIC_CLOSURE_COMPILER', None): @@ -758,3 +766,8 @@ def _run_yui_compressor(code, type_): return "/* ERRORS WHEN RUNNING YUI COMPRESSOR\n" + stderrdata + '\n*/\n' + code return stdoutdata + + +def _run_cssmin(code): + output = cssmin.cssmin(code) + return output diff --git a/settings.py b/settings.py index 4f2ade1..f803937 100644 --- a/settings.py +++ b/settings.py @@ -94,5 +94,5 @@ DJANGO_STATIC = True #DJANGO_STATIC_CLOSURE_COMPILER = '/home/peterbe/tmp/compiler-latest/compiler.jar' - -#DJANGO_STATIC_YUI_COMPRESSOR = '/home/peterbe/tmp/yuicompressor-2.4.2/build/yuicompressor-2.4.2.jar' \ No newline at end of file +#DJANGO_STATIC_YUI_COMPRESSOR = '/home/peterbe/tmp/yuicompressor-2.4.2/build/yuicompressor-2.4.2.jar' +#DJANGO_STATIC_CSSMIN = True From 737f9de8f85c9842753144e85f8c4c7de87b00d6 Mon Sep 17 00:00:00 2001 From: Aida Tavakkolie Date: Tue, 15 Mar 2011 14:01:19 -0700 Subject: [PATCH 2/2] Updated README:: Set default css filter to cssmin:: Added support for jsmin filter --- README.md | 15 ++ django_static/filters/__init__.py | 1 + django_static/filters/jsmin.py | 217 ++++++++++++++++++++ django_static/templatetags/django_static.py | 27 ++- settings.py | 2 +- setup.py | 2 +- 6 files changed, 255 insertions(+), 9 deletions(-) create mode 100644 django_static/filters/__init__.py create mode 100644 django_static/filters/jsmin.py diff --git a/README.md b/README.md index ac0d26d..534d706 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,21 @@ like this: return on_my_cdn.get(uri, uri) +Compression Filters +--------------------------- + +Default (cssmin) +---------------------------- +django-static uses cssmin by default if it is installed. +Get the source here: https://github.com/zacharyvoase/cssmin + +Using jsmin +---------------------------- +If you would like to use jsmin instead of default js_slimmer, you just need to set +the variable in your settings.py file: + + DJANGO_STATIC_JSMIN = True + Using Google Closure Compiler ----------------------------- diff --git a/django_static/filters/__init__.py b/django_static/filters/__init__.py new file mode 100644 index 0000000..792d600 --- /dev/null +++ b/django_static/filters/__init__.py @@ -0,0 +1 @@ +# diff --git a/django_static/filters/jsmin.py b/django_static/filters/jsmin.py new file mode 100644 index 0000000..4121c83 --- /dev/null +++ b/django_static/filters/jsmin.py @@ -0,0 +1,217 @@ +#!/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) diff --git a/django_static/templatetags/django_static.py b/django_static/templatetags/django_static.py index 1cd636c..d635d50 100644 --- a/django_static/templatetags/django_static.py +++ b/django_static/templatetags/django_static.py @@ -28,8 +28,15 @@ try: import cssmin + __cssmin_installed__ = True except ImportError: - warnings.warn("cssmin is not installed. (easy_install cssmin)") + __cssmin_installed__ = False + +try: + from ..filters import jsmin + __jsmin_installed__ = True +except ImportError: + __jsmin_installed__ = False if sys.platform == "win32": @@ -705,30 +712,32 @@ def has_optimizer(type_): if type_ == CSS: if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): return True - if getattr(settings, 'DJANGO_STATIC_CSSMIN', None): - return True - return slimmer is not None + return slimmer is not None or __cssmin_installed__ elif type_ == JS: if getattr(settings, 'DJANGO_STATIC_CLOSURE_COMPILER', None): return True if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): return True + if getattr(settings, 'DJANGO_STATIC_JSMIN', None): + return True return slimmer is not None else: raise ValueError("Invalid type %r" % type_) def optimize(content, type_): if type_ == CSS: - if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): + if __cssmin_installed__: + return _run_cssmin(content) + elif getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): return _run_yui_compressor(content, type_) - if getattr(settings, 'DJANGO_STATIC_CSSMIN', None): - return _run_cssmin(content) return css_slimmer(content) elif type_ == JS: if getattr(settings, 'DJANGO_STATIC_CLOSURE_COMPILER', None): return _run_closure_compiler(content) if getattr(settings, 'DJANGO_STATIC_YUI_COMPRESSOR', None): return _run_yui_compressor(content, type_) + if getattr(settings, 'DJANGO_STATIC_JSMIN', None): + return _run_jsmin(content) return js_slimmer(content) else: raise ValueError("Invalid type %r" % type_) @@ -771,3 +780,7 @@ def _run_yui_compressor(code, type_): def _run_cssmin(code): output = cssmin.cssmin(code) return output + +def _run_jsmin(code): + output = jsmin.jsmin(code) + return output diff --git a/settings.py b/settings.py index f803937..05a8181 100644 --- a/settings.py +++ b/settings.py @@ -95,4 +95,4 @@ #DJANGO_STATIC_CLOSURE_COMPILER = '/home/peterbe/tmp/compiler-latest/compiler.jar' #DJANGO_STATIC_YUI_COMPRESSOR = '/home/peterbe/tmp/yuicompressor-2.4.2/build/yuicompressor-2.4.2.jar' -#DJANGO_STATIC_CSSMIN = True +#DJANGO_STATIC_JSMIN = True diff --git a/setup.py b/setup.py index 68735a3..a5979ce 100644 --- a/setup.py +++ b/setup.py @@ -23,6 +23,7 @@ license='BSD', packages=[ 'django_static', + 'django_static.filters', 'django_static.templatetags', ], classifiers=[ @@ -35,5 +36,4 @@ 'Topic :: Software Development :: Libraries :: Application Frameworks', 'Topic :: Software Development :: Libraries :: Python Modules', ], - include_package_data=True, )