Merge branch 'develop' of github.com:jezdez/django_compressor into develop
Conflicts: compressor/parser/html5lib.py compressor/storage.py compressor/tests/test_parsers.py
This commit is contained in:
5
AUTHORS
5
AUTHORS
@@ -57,7 +57,8 @@ Julien Phalip
|
||||
Justin Lilly
|
||||
Luis Nell
|
||||
Lukas Lehner
|
||||
Lukasz Balcerzak
|
||||
Łukasz Balcerzak
|
||||
Łukasz Langa
|
||||
Maciek Szczesniak
|
||||
Maor Ben-Dayan
|
||||
Mark Lavin
|
||||
@@ -89,4 +90,4 @@ Ulrich Petri
|
||||
Ulysses V
|
||||
Vladislav Poluhin
|
||||
wesleyb
|
||||
Wilson Júnior
|
||||
Wilson Júnior
|
||||
|
||||
2
Makefile
2
Makefile
@@ -2,3 +2,5 @@ test:
|
||||
flake8 compressor --ignore=E501,E128
|
||||
coverage run --branch --source=compressor `which django-admin.py` test --settings=compressor.test_settings compressor
|
||||
coverage report --omit=compressor/test*,compressor/filters/jsmin/rjsmin*,compressor/filters/cssmin/cssmin*,compressor/utils/stringformat*
|
||||
|
||||
.PHONY: test
|
||||
|
||||
@@ -20,39 +20,42 @@ class Html5LibParser(ParserBase):
|
||||
self.html5lib = html5lib
|
||||
|
||||
def _serialize(self, elem):
|
||||
fragment = self.html5lib.treebuilders.simpletree.DocumentFragment()
|
||||
fragment.appendChild(elem)
|
||||
return self.html5lib.serialize(fragment,
|
||||
quote_attr_values=True, omit_optional_tags=False)
|
||||
return self.html5lib.serialize(
|
||||
elem, tree="etree", quote_attr_values=True,
|
||||
omit_optional_tags=False, use_trailing_solidus=True,
|
||||
)
|
||||
|
||||
def _find(self, *names):
|
||||
for node in self.html.childNodes:
|
||||
if node.type == 5 and node.name in names:
|
||||
yield node
|
||||
for elem in self.html:
|
||||
if elem.tag in names:
|
||||
yield elem
|
||||
|
||||
@cached_property
|
||||
def html(self):
|
||||
try:
|
||||
return self.html5lib.parseFragment(self.content)
|
||||
return self.html5lib.parseFragment(self.content, treebuilder="etree")
|
||||
except ImportError as err:
|
||||
raise ImproperlyConfigured("Error while importing html5lib: %s" % err)
|
||||
except Exception as err:
|
||||
raise ParserError("Error while initializing Parser: %s" % err)
|
||||
|
||||
def css_elems(self):
|
||||
return self._find('style', 'link')
|
||||
return self._find('{http://www.w3.org/1999/xhtml}link',
|
||||
'{http://www.w3.org/1999/xhtml}style')
|
||||
|
||||
def js_elems(self):
|
||||
return self._find('script')
|
||||
return self._find('{http://www.w3.org/1999/xhtml}script')
|
||||
|
||||
def elem_attribs(self, elem):
|
||||
return elem.attributes
|
||||
return elem.attrib
|
||||
|
||||
def elem_content(self, elem):
|
||||
return elem.childNodes[0].value
|
||||
return smart_unicode(elem.text)
|
||||
|
||||
def elem_name(self, elem):
|
||||
return elem.name
|
||||
if '}' in elem.tag:
|
||||
return elem.tag.split('}')[1]
|
||||
return elem.tag
|
||||
|
||||
def elem_str(self, elem):
|
||||
# This method serializes HTML in a way that does not pass all tests.
|
||||
|
||||
@@ -2,8 +2,8 @@ from __future__ import unicode_literals
|
||||
import errno
|
||||
import gzip
|
||||
import os
|
||||
from os import path
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from django.core.files.storage import FileSystemStorage, get_storage_class
|
||||
from django.utils.functional import LazyObject, SimpleLazyObject
|
||||
@@ -28,13 +28,13 @@ class CompressorFileStorage(FileSystemStorage):
|
||||
*args, **kwargs)
|
||||
|
||||
def accessed_time(self, name):
|
||||
return datetime.fromtimestamp(path.getatime(self.path(name)))
|
||||
return datetime.fromtimestamp(os.path.getatime(self.path(name)))
|
||||
|
||||
def created_time(self, name):
|
||||
return datetime.fromtimestamp(path.getctime(self.path(name)))
|
||||
return datetime.fromtimestamp(os.path.getctime(self.path(name)))
|
||||
|
||||
def modified_time(self, name):
|
||||
return datetime.fromtimestamp(path.getmtime(self.path(name)))
|
||||
return datetime.fromtimestamp(os.path.getmtime(self.path(name)))
|
||||
|
||||
def get_available_name(self, name):
|
||||
"""
|
||||
@@ -67,14 +67,26 @@ class GzipCompressorFileStorage(CompressorFileStorage):
|
||||
"""
|
||||
def save(self, filename, content):
|
||||
filename = super(GzipCompressorFileStorage, self).save(filename, content)
|
||||
|
||||
# workaround for http://bugs.python.org/issue13664
|
||||
name = os.path.basename(filename).encode('latin1', 'replace')
|
||||
f_in = open(self.path(filename), 'rb')
|
||||
f_out = gzip.GzipFile(name, fileobj=open('%s.gz' % self.path(filename), 'wb'))
|
||||
f_out.write(f_in.read())
|
||||
f_out.close()
|
||||
f_in.close()
|
||||
orig_path = self.path(filename)
|
||||
compressed_path = '%s.gz' % orig_path
|
||||
|
||||
f_in = open(orig_path, 'rb')
|
||||
f_out = open('%s.gz' % compressed_path, 'wb')
|
||||
try:
|
||||
f_out = gzip.GzipFile(name, fileobj=f_out)
|
||||
f_out.write(f_in.read())
|
||||
finally:
|
||||
f_out.close()
|
||||
f_in.close()
|
||||
# Ensure the file timestamps match.
|
||||
# os.stat() returns nanosecond resolution on Linux, but os.utime()
|
||||
# only sets microsecond resolution. Set times on both files to
|
||||
# ensure they are equal.
|
||||
stamp = time.time()
|
||||
os.utime(orig_path, (stamp, stamp))
|
||||
os.utime(compressed_path, (stamp, stamp))
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ except ImportError:
|
||||
|
||||
from compressor.base import SOURCE_HUNK, SOURCE_FILE
|
||||
from compressor.conf import settings
|
||||
from compressor.css import CssCompressor
|
||||
from compressor.tests.test_base import CompressorTestCase
|
||||
|
||||
try:
|
||||
@@ -29,7 +28,6 @@ except ImportError:
|
||||
|
||||
|
||||
class ParserTestCase(object):
|
||||
|
||||
def setUp(self):
|
||||
self.old_parser = settings.COMPRESS_PARSER
|
||||
settings.COMPRESS_PARSER = self.parser_cls
|
||||
@@ -47,34 +45,86 @@ class LxmlParserTests(ParserTestCase, CompressorTestCase):
|
||||
@ut2.skipIf(html5lib is None, 'html5lib not found')
|
||||
class Html5LibParserTests(ParserTestCase, CompressorTestCase):
|
||||
parser_cls = 'compressor.parser.Html5LibParser'
|
||||
|
||||
def setUp(self):
|
||||
super(Html5LibParserTests, self).setUp()
|
||||
# special version of the css since the parser sucks
|
||||
self.css = """\
|
||||
<link href="/static/css/one.css" rel="stylesheet" type="text/css">
|
||||
<style type="text/css">p { border:5px solid green;}</style>
|
||||
<link href="/static/css/two.css" rel="stylesheet" type="text/css">"""
|
||||
self.css_node = CssCompressor(self.css)
|
||||
# Special test variants required since xml.etree holds attributes
|
||||
# as a plain dictionary, e.g. key order is unpredictable.
|
||||
|
||||
def test_css_split(self):
|
||||
out = [
|
||||
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, 'css', 'one.css'), 'css/one.css', '<link href="/static/css/one.css" rel="stylesheet" type="text/css">'),
|
||||
(SOURCE_HUNK, 'p { border:5px solid green;}', None, '<style type="text/css">p { border:5px solid green;}</style>'),
|
||||
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, 'css', 'two.css'), 'css/two.css', '<link href="/static/css/two.css" rel="stylesheet" type="text/css">'),
|
||||
]
|
||||
split = self.css_node.split_contents()
|
||||
split = [(x[0], x[1], x[2], self.css_node.parser.elem_str(x[3])) for x in split]
|
||||
self.assertEqual(out, split)
|
||||
out0 = (
|
||||
SOURCE_FILE,
|
||||
os.path.join(settings.COMPRESS_ROOT, 'css', 'one.css'),
|
||||
'css/one.css',
|
||||
'{http://www.w3.org/1999/xhtml}link',
|
||||
{'rel': 'stylesheet', 'href': '/static/css/one.css',
|
||||
'type': 'text/css'},
|
||||
)
|
||||
self.assertEqual(out0, split[0][:3] + (split[0][3].tag,
|
||||
split[0][3].attrib))
|
||||
out1 = (
|
||||
SOURCE_HUNK,
|
||||
'p { border:5px solid green;}',
|
||||
None,
|
||||
'<style type="text/css">p { border:5px solid green;}</style>',
|
||||
)
|
||||
self.assertEqual(out1, split[1][:3] +
|
||||
(self.css_node.parser.elem_str(split[1][3]),))
|
||||
out2 = (
|
||||
SOURCE_FILE,
|
||||
os.path.join(settings.COMPRESS_ROOT, 'css', 'two.css'),
|
||||
'css/two.css',
|
||||
'{http://www.w3.org/1999/xhtml}link',
|
||||
{'rel': 'stylesheet', 'href': '/static/css/two.css',
|
||||
'type': 'text/css'},
|
||||
)
|
||||
self.assertEqual(out2, split[2][:3] + (split[2][3].tag,
|
||||
split[2][3].attrib))
|
||||
|
||||
def test_js_split(self):
|
||||
out = [
|
||||
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, 'js', 'one.js'), 'js/one.js', '<script src="/static/js/one.js" type="text/javascript"></script>'),
|
||||
(SOURCE_HUNK, 'obj.value = "value";', None, '<script type="text/javascript">obj.value = "value";</script>'),
|
||||
]
|
||||
split = self.js_node.split_contents()
|
||||
split = [(x[0], x[1], x[2], self.js_node.parser.elem_str(x[3])) for x in split]
|
||||
self.assertEqual(out, split)
|
||||
out0 = (
|
||||
SOURCE_FILE,
|
||||
os.path.join(settings.COMPRESS_ROOT, 'js', 'one.js'),
|
||||
'js/one.js',
|
||||
'{http://www.w3.org/1999/xhtml}script',
|
||||
{'src': '/static/js/one.js', 'type': 'text/javascript'},
|
||||
None,
|
||||
)
|
||||
self.assertEqual(out0, split[0][:3] + (split[0][3].tag,
|
||||
split[0][3].attrib,
|
||||
split[0][3].text))
|
||||
out1 = (
|
||||
SOURCE_HUNK,
|
||||
'obj.value = "value";',
|
||||
None,
|
||||
'{http://www.w3.org/1999/xhtml}script',
|
||||
{'type': 'text/javascript'},
|
||||
'obj.value = "value";',
|
||||
)
|
||||
self.assertEqual(out1, split[1][:3] + (split[1][3].tag,
|
||||
split[1][3].attrib,
|
||||
split[1][3].text))
|
||||
|
||||
def test_css_return_if_off(self):
|
||||
settings.COMPRESS_ENABLED = False
|
||||
# Yes, they are semantically equal but attributes might be
|
||||
# scrambled in unpredictable order. A more elaborate check
|
||||
# would require parsing both arguments with a different parser
|
||||
# and then evaluating the result, which no longer is
|
||||
# a meaningful unit test.
|
||||
self.assertEqual(len(self.css), len(self.css_node.output()))
|
||||
|
||||
def test_js_return_if_off(self):
|
||||
try:
|
||||
enabled = settings.COMPRESS_ENABLED
|
||||
precompilers = settings.COMPRESS_PRECOMPILERS
|
||||
settings.COMPRESS_ENABLED = False
|
||||
settings.COMPRESS_PRECOMPILERS = {}
|
||||
# As above.
|
||||
self.assertEqual(len(self.js), len(self.js_node.output()))
|
||||
finally:
|
||||
settings.COMPRESS_ENABLED = enabled
|
||||
settings.COMPRESS_PRECOMPILERS = precompilers
|
||||
|
||||
|
||||
|
||||
@ut2.skipIf(BeautifulSoup is None, 'BeautifulSoup not found')
|
||||
|
||||
Reference in New Issue
Block a user