diff --git a/compressor/conf/settings.py b/compressor/conf/settings.py index a106cb4..f165801 100644 --- a/compressor/conf/settings.py +++ b/compressor/conf/settings.py @@ -5,6 +5,7 @@ from django.conf import settings MEDIA_URL = getattr(settings, 'COMPRESS_URL', settings.MEDIA_URL) MEDIA_ROOT = getattr(settings, 'COMPRESS_ROOT', settings.MEDIA_ROOT) PREFIX = getattr(settings, 'COMPRESS_PREFIX', 'compressed') +OUTPUT_DIR = getattr(settings, 'COMPRESS_OUTPUT_DIR', 'COMPRESSOR_CACHE') COMPRESS = getattr(settings, 'COMPRESS', not settings.DEBUG) COMPRESS_CSS_FILTERS = getattr(settings, 'COMPRESS_CSS_FILTERS', []) diff --git a/compressor/filters/__init__.py b/compressor/filters/__init__.py index 9b98531..1b30057 100644 --- a/compressor/filters/__init__.py +++ b/compressor/filters/__init__.py @@ -1,5 +1,5 @@ class FilterBase: - def __init__(self, verbose): + def __init__(self, verbose=0): self.verbose = verbose def filter_css(self, css): @@ -11,4 +11,33 @@ class FilterError(Exception): """ This exception is raised when a filter fails """ - pass \ No newline at end of file + pass + +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 != '': + cls = getattr(__import__(mod_name, {}, {}, ['']), class_name) + except (ImportError, AttributeError): + raise FilterError('Failed to import filter %s' % class_string) + + return cls + +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:] diff --git a/compressor/filters/jsmin/__init__.py b/compressor/filters/jsmin/__init__.py index 93ca6bf..a4acea7 100644 --- a/compressor/filters/jsmin/__init__.py +++ b/compressor/filters/jsmin/__init__.py @@ -1,4 +1,4 @@ -from compress.filters.jsmin.jsmin import jsmin +from compressor.filters.jsmin.jsmin import jsmin from compressor.filters import FilterBase class JSMinFilter(FilterBase): diff --git a/compressor/templates/compressor/css.html b/compressor/templates/compressor/css.html index 3751a4a..c82b161 100644 --- a/compressor/templates/compressor/css.html +++ b/compressor/templates/compressor/css.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/compressor/templatetags/compress.py b/compressor/templatetags/compress.py index de4043b..f4aef4b 100644 --- a/compressor/templatetags/compress.py +++ b/compressor/templatetags/compress.py @@ -1,8 +1,12 @@ import os +from hashlib import sha1 as hash from BeautifulSoup import BeautifulSoup from django import template +from django.template.loader import render_to_string + from compressor.conf import settings +from compressor import filters register = template.Library() @@ -12,7 +16,6 @@ class CompressedNode(template.Node): def __init__(self, content, ouput_prefix="compressed"): self.content = content self.ouput_prefix = ouput_prefix - self.hunks = [] self.split_content = [] self.soup = BeautifulSoup(self.content) @@ -33,30 +36,76 @@ class CompressedNode(template.Node): return filename def get_hunks(self): - if self.hunks: - return self.hunks + if getattr(self, '_hunks', ''): + return self._hunks + self._hunks = [] for k, v in self.split_contents(): if k == 'hunk': - self.hunks.append(v) + self._hunks.append(v) if k == 'file': fd = open(v, 'rb') - self.hunks.append(fd.read()) + self._hunks.append(fd.read()) fd.close() - return self.hunks + return self._hunks + hunks = property(get_hunks) def concat(self): return "\n".join(self.get_hunks()) + + def get_output(self): + if getattr(self, '_output', ''): + return self._output + output = self.concat() + filter_method = getattr(self, 'filter_method', None) + if filter_method and self.filters: + for f in self.filters: + filter = getattr(filters.get_class(f)(), filter_method) + if callable(filter): + output = filter(output) + self._output = output + return self._output + output = property(get_output) + + def get_hash(self): + return hash(self.output).hexdigest()[:12] + hash = property(get_hash) + + def get_new_filepath(self): + filename = "".join([self.hash, self.extension]) + filepath = "%s/%s/%s" % (settings.OUTPUT_DIR.strip('/'), self.ouput_prefix, filename) + return filepath + new_filepath = property(get_new_filepath) + + def save_file(self): + filename = "%s/%s" % (settings.MEDIA_ROOT.rstrip('/'), self.new_filepath) + if os.path.exists(filename): + return False + dirname = os.path.dirname(filename) + if not os.path.exists(dirname): + os.makedirs(dirname) + fd = open(filename, 'wb+') + fd.write(self.output) + fd.close() + return True def render(self): if not settings.COMPRESS: return self.content - return "fail" + url = "%s/%s" % (settings.MEDIA_URL.rstrip('/'), self.new_filepath) + self.save_file() + context = getattr(self, 'extra_context', {}) + context['url'] = url + return render_to_string(self.template_name, context) class CompressedCssNode(CompressedNode): def __init__(self, content, ouput_prefix="css", media="all"): - self.media = media + self.extra_context = { 'media': media } + self.extension = ".css" + self.template_name = "compressor/css.html" + self.filters = settings.COMPRESS_CSS_FILTERS + self.filter_method = 'filter_css' super(CompressedCssNode, self).__init__(content, ouput_prefix) def split_contents(self): @@ -74,6 +123,10 @@ class CompressedCssNode(CompressedNode): class CompressedJsNode(CompressedNode): def __init__(self, content, ouput_prefix="js"): + self.extension = ".js" + self.template_name = "compressor/js.html" + self.filters = settings.COMPRESS_JS_FILTERS + self.filter_method = 'filter_js' super(CompressedJsNode, self).__init__(content, ouput_prefix) def split_contents(self): diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/core/tests.py b/tests/core/tests.py index 5ff30a5..41a357d 100644 --- a/tests/core/tests.py +++ b/tests/core/tests.py @@ -14,14 +14,12 @@ class CompressedNodeTestCase(TestCase): """ - self.css_output = '' self.cssNode = CompressedCssNode(self.css) self.js = """ """ - self.js_output = '' self.jsNode = CompressedJsNode(self.js) def test_css_split(self): @@ -34,18 +32,22 @@ class CompressedNodeTestCase(TestCase): def test_css_hunks(self): out = ['body { background:#990; }', u'p { border:5px solid green;}', 'body { color:#fff; }'] - self.assertEqual(out, self.cssNode.get_hunks()) + self.assertEqual(out, self.cssNode.hunks) - def test_css_concat(self): + def test_css_output(self): out = u'body { background:#990; }\np { border:5px solid green;}\nbody { color:#fff; }' - self.assertEqual(out, self.cssNode.concat()) + self.assertEqual(out, self.cssNode.output) def test_css_return_if_off(self): settings.COMPRESS = False self.assertEqual(self.css, self.cssNode.render()) + def test_css_hash(self): + self.assertEqual('f7c661b7a124', self.cssNode.hash) + def test_css_return_if_on(self): - self.assertEqual(self.css_output, self.cssNode.render()) + output = u'' + self.assertEqual(output, self.cssNode.render()) def test_js_split(self): @@ -57,15 +59,33 @@ class CompressedNodeTestCase(TestCase): def test_js_hunks(self): out = ['obj = {};', u'obj.value = "value";'] - self.assertEqual(out, self.jsNode.get_hunks()) + self.assertEqual(out, self.jsNode.hunks) def test_js_concat(self): out = u'obj = {};\nobj.value = "value";' self.assertEqual(out, self.jsNode.concat()) + def test_js_output(self): + out = u'obj={};obj.value="value";' + self.assertEqual(out, self.jsNode.output) + def test_js_return_if_off(self): settings.COMPRESS = False self.assertEqual(self.js, self.jsNode.render()) def test_js_return_if_on(self): - self.assertEqual(self.js_output, self.jsNode.render()) + output = u'' + self.assertEqual(output, self.jsNode.render()) + + +class CssAbsolutizingTestCase(TestCase): + def setUp(self): + settings.COMPRESS = True + self.css = """ + + """ + self.cssNode = CompressedCssNode(self.css) + + def test_fail(self): + settings.COMPRESS = False + self.assertEqual(self.js, self.jsNode.render()) diff --git a/tests/settings.py b/tests/settings.py index 57deef7..10a9c4b 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -3,7 +3,6 @@ from os.path import dirname, abspath, join TEST_DIR = dirname(abspath(__file__)) DEBUG = True -XDADD = "" ROOT_URLCONF = 'urls' @@ -11,7 +10,7 @@ MEDIA_URL = '/media/' MEDIA_ROOT = join(TEST_DIR, 'media') DATABASE_ENGINE = 'sqlite3' -DATABASE_NAME = 'django_inlines_tests.db' +DATABASE_NAME = 'django_compressor_tests.db' INSTALLED_APPS = [ 'core',