Merge branch 'py3_new' of git://github.com/kudlatyamroth/django_compressor into kudlatyamroth-py3_new

Conflicts:
	.travis.yml
This commit is contained in:
Jannis Leidel
2013-05-27 10:52:42 +02:00
33 changed files with 641 additions and 312 deletions

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ MANIFEST
docs/_build/
.sass-cache
.coverage
.tox

View File

@@ -2,11 +2,15 @@ language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
before_install:
- sudo apt-get update
- sudo apt-get install csstidy libxml2-dev libxslt-dev
install:
- pip install -e .
- if [[ $TRAVIS_PYTHON_VERSION == 2.* ]]; then pip install unittest2 BeautifulSoup --use-mirrors; fi
- if [[ $TRAVIS_PYTHON_VERSION == 3.* ]]; then pip install BeautifulSoup4 --use-mirrors; fi
- pip install -r requirements/tests.txt Django==$DJANGO
script:
- make test
@@ -15,3 +19,9 @@ env:
- DJANGO=1.5.1
notifications:
irc: "irc.freenode.org#django-compressor"
matrix:
exclude:
- python: "3.2"
env: DJANGO=1.4.5
- python: "3.3"
env: DJANGO=1.4.5

View File

@@ -1,17 +1,19 @@
from __future__ import with_statement
from __future__ import with_statement, unicode_literals
import os
import codecs
import urllib
from django.core.files.base import ContentFile
from django.template import Context
from django.template.loader import render_to_string
from django.utils.encoding import smart_unicode
from django.utils.importlib import import_module
from django.utils.safestring import mark_safe
from compressor.cache import get_hexdigest, get_mtime
try:
from urllib.request import url2pathname
except ImportError:
from urllib import url2pathname
from compressor.cache import get_hexdigest, get_mtime
from compressor.conf import settings
from compressor.exceptions import (CompressorError, UncompressableFileError,
FilterDoesNotExist)
@@ -34,7 +36,7 @@ class Compressor(object):
type = None
def __init__(self, content=None, output_prefix=None, context=None, *args, **kwargs):
self.content = content or ""
self.content = content or "" # rendered contents of {% compress %} tag
self.output_prefix = output_prefix or "compressed"
self.output_dir = settings.COMPRESS_OUTPUT_DIR.strip('/')
self.charset = settings.DEFAULT_CHARSET
@@ -65,6 +67,10 @@ class Compressor(object):
return "compressor/%s_%s.html" % (self.type, mode)
def get_basename(self, url):
"""
Takes full path to a static file (eg. "/static/css/style.css") and
returns path with storage's base url removed (eg. "css/style.css").
"""
try:
base_url = self.storage.base_url
except AttributeError:
@@ -78,6 +84,17 @@ class Compressor(object):
return basename.split("?", 1)[0]
def get_filepath(self, content, basename=None):
"""
Returns file path for an output file based on contents.
Returned path is relative to compressor storage's base url, for
example "CACHE/css/e41ba2cc6982.css".
When `basename` argument is provided then file name (without extension)
will be used as a part of returned file name, for example:
get_filepath(content, "my_file.css") -> 'CACHE/css/my_file.e41ba2cc6982.css'
"""
parts = []
if basename:
filename = os.path.split(basename)[1]
@@ -86,6 +103,11 @@ class Compressor(object):
return os.path.join(self.output_dir, self.output_prefix, '.'.join(parts))
def get_filename(self, basename):
"""
Returns full path to a file, for example:
get_filename('css/one.css') -> '/full/path/to/static/css/one.css'
"""
filename = None
# first try finding the file in the root
try:
@@ -100,7 +122,7 @@ class Compressor(object):
filename = compressor_file_storage.path(basename)
# secondly try to find it with staticfiles (in debug mode)
if not filename and self.finders:
filename = self.finders.find(urllib.url2pathname(basename))
filename = self.finders.find(url2pathname(basename))
if filename:
return filename
# or just raise an exception as the last resort
@@ -110,13 +132,16 @@ class Compressor(object):
self.finders and " or with staticfiles." or "."))
def get_filecontent(self, filename, charset):
with codecs.open(filename, 'rb', charset) as fd:
"""
Reads file contents using given `charset` and returns it as text.
"""
with codecs.open(filename, 'r', charset) as fd:
try:
return fd.read()
except IOError, e:
except IOError as e:
raise UncompressableFileError("IOError while processing "
"'%s': %s" % (filename, e))
except UnicodeDecodeError, e:
except UnicodeDecodeError as e:
raise UncompressableFileError("UnicodeDecodeError while "
"processing '%s' with "
"charset %s: %s" %
@@ -143,7 +168,7 @@ class Compressor(object):
def hunks(self, forced=False):
"""
The heart of content parsing, iterates of the
The heart of content parsing, iterates over the
list of split contents and looks at its kind
to decide what to do with it. Should yield a
bunch of precompiled and/or rendered hunks.
@@ -159,6 +184,7 @@ class Compressor(object):
'elem': elem,
'kind': kind,
'basename': basename,
'charset': charset,
}
if kind == SOURCE_FILE:
@@ -169,12 +195,11 @@ class Compressor(object):
precompiled, value = self.precompile(value, **options)
if enabled:
value = self.filter(value, **options)
yield smart_unicode(value, charset.lower())
yield self.filter(value, **options)
else:
if precompiled:
value = self.handle_output(kind, value, forced=True, basename=basename)
yield smart_unicode(value, charset.lower())
yield self.handle_output(kind, value, forced=True,
basename=basename)
else:
yield self.parser.elem_str(elem)
@@ -195,7 +220,13 @@ class Compressor(object):
content.append(hunk)
return content
def precompile(self, content, kind=None, elem=None, filename=None, **kwargs):
def precompile(self, content, kind=None, elem=None, filename=None,
charset=None, **kwargs):
"""
Processes file using a pre compiler.
This is the place where files like coffee script are processed.
"""
if not kind:
return False, content
attrs = self.parser.elem_attribs(elem)
@@ -212,18 +243,21 @@ class Compressor(object):
try:
mod = import_module(mod_name)
except ImportError:
return True, CompilerFilter(content, filter_type=self.type,
command=filter_or_command, filename=filename).input(
**kwargs)
filter = CompilerFilter(
content, filter_type=self.type, filename=filename,
charset=charset, command=filter_or_command)
return True, filter.input(**kwargs)
try:
precompiler_class = getattr(mod, cls_name)
except AttributeError:
raise FilterDoesNotExist('Could not find "%s".' %
filter_or_command)
else:
return True, precompiler_class(content, attrs,
filter_type=self.type, filename=filename).input(
**kwargs)
filter = precompiler_class(
content, attrs, filter_type=self.type, charset=charset,
filename=filename)
return True, filter.input(**kwargs)
return False, content
def filter(self, content, method, **kwargs):
@@ -243,11 +277,10 @@ class Compressor(object):
any custom modification. Calls other mode specific methods or simply
returns the content directly.
"""
content = self.filter_input(forced)
if not content:
return ''
output = '\n'.join(self.filter_input(forced))
output = '\n'.join(c.encode(self.charset) for c in content)
if not output:
return ''
if settings.COMPRESS_ENABLED or forced:
filtered_output = self.filter_output(output)

View File

@@ -1,3 +1,4 @@
import json
import hashlib
import os
import socket
@@ -5,8 +6,7 @@ import time
from django.core.cache import get_cache
from django.core.files.base import ContentFile
from django.utils import simplejson
from django.utils.encoding import smart_str
from django.utils.encoding import force_text, smart_bytes
from django.utils.functional import SimpleLazyObject
from django.utils.importlib import import_module
@@ -18,18 +18,18 @@ _cachekey_func = None
def get_hexdigest(plaintext, length=None):
digest = hashlib.md5(smart_str(plaintext)).hexdigest()
digest = hashlib.md5(smart_bytes(plaintext)).hexdigest()
if length:
return digest[:length]
return digest
def simple_cachekey(key):
return 'django_compressor.%s' % smart_str(key)
return 'django_compressor.%s' % force_text(key)
def socket_cachekey(key):
return "django_compressor.%s.%s" % (socket.gethostname(), smart_str(key))
return "django_compressor.%s.%s" % (socket.gethostname(), force_text(key))
def get_cachekey(*args, **kwargs):
@@ -39,7 +39,7 @@ def get_cachekey(*args, **kwargs):
mod_name, func_name = get_mod_func(
settings.COMPRESS_CACHE_KEY_FUNCTION)
_cachekey_func = getattr(import_module(mod_name), func_name)
except (AttributeError, ImportError), e:
except (AttributeError, ImportError) as e:
raise ImportError("Couldn't import cache key function %s: %s" %
(settings.COMPRESS_CACHE_KEY_FUNCTION, e))
return _cachekey_func(*args, **kwargs)
@@ -70,7 +70,8 @@ def get_offline_manifest():
if _offline_manifest is None:
filename = get_offline_manifest_filename()
if default_storage.exists(filename):
_offline_manifest = simplejson.load(default_storage.open(filename))
with default_storage.open(filename) as fp:
_offline_manifest = json.loads(fp.read().decode('utf8'))
else:
_offline_manifest = {}
return _offline_manifest
@@ -83,8 +84,8 @@ def flush_offline_manifest():
def write_offline_manifest(manifest):
filename = get_offline_manifest_filename()
default_storage.save(filename,
ContentFile(simplejson.dumps(manifest, indent=2)))
content = json.dumps(manifest, indent=2).encode('utf8')
default_storage.save(filename, ContentFile(content))
flush_offline_manifest()
@@ -118,12 +119,10 @@ def get_hashed_content(filename, length=12):
filename = os.path.realpath(filename)
except OSError:
return None
hash_file = open(filename)
try:
content = hash_file.read()
finally:
hash_file.close()
return get_hexdigest(content, length)
# should we make sure that file is utf-8 encoded?
with open(filename, 'rb') as file:
return get_hexdigest(file.read(), length)
def cache_get(key):

View File

@@ -1,3 +1,4 @@
from __future__ import unicode_literals
import os
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

View File

@@ -10,7 +10,7 @@ class CompressorExtension(CompressorMixin, Extension):
tags = set(['compress'])
def parse(self, parser):
lineno = parser.stream.next().lineno
lineno = next(parser.stream).lineno
kindarg = parser.parse_expression()
# Allow kind to be defined as jinja2 name node
if isinstance(kindarg, nodes.Name):

View File

@@ -1,28 +1,37 @@
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals
import io
import logging
import subprocess
from django.core.exceptions import ImproperlyConfigured
from django.core.files.temp import NamedTemporaryFile
from django.utils.importlib import import_module
from django.utils.encoding import smart_unicode
from django.utils.encoding import smart_text
from django.utils import six
from compressor.conf import settings
from compressor.exceptions import FilterError
from compressor.utils import get_mod_func
from compressor.utils.stringformat import FormattableString as fstr
logger = logging.getLogger("compressor.filters")
class FilterBase(object):
"""
A base class for filters that does nothing.
def __init__(self, content, filter_type=None, filename=None, verbose=0):
Subclasses should implement `input` and/or `output` methods which must
return a string (unicode under python 2) or raise a NotImplementedError.
"""
def __init__(self, content, filter_type=None, filename=None, verbose=0,
charset=None):
self.type = filter_type
self.content = content
self.verbose = verbose or settings.COMPRESS_VERBOSE
self.logger = logger
self.filename = filename
self.charset = charset
def input(self, **kwargs):
raise NotImplementedError
@@ -32,6 +41,16 @@ class FilterBase(object):
class CallbackOutputFilter(FilterBase):
"""
A filter which takes function path in `callback` attribute, imports it
and uses that function to filter output string::
class MyFilter(CallbackOutputFilter):
callback = 'path.to.my.callback'
Callback should be a function which takes a string as first argument and
returns a string (unicode under python 2).
"""
callback = None
args = []
kwargs = {}
@@ -40,12 +59,13 @@ class CallbackOutputFilter(FilterBase):
def __init__(self, *args, **kwargs):
super(CallbackOutputFilter, self).__init__(*args, **kwargs)
if self.callback is None:
raise ImproperlyConfigured("The callback filter %s must define"
"a 'callback' attribute." % self)
raise ImproperlyConfigured(
"The callback filter %s must define a 'callback' attribute." %
self.__class__.__name__)
try:
mod_name, func_name = get_mod_func(self.callback)
func = getattr(import_module(mod_name), func_name)
except ImportError, e:
except ImportError:
if self.dependencies:
if len(self.dependencies) == 1:
warning = "dependency (%s) is" % self.dependencies[0]
@@ -54,17 +74,19 @@ class CallbackOutputFilter(FilterBase):
", ".join([dep for dep in self.dependencies]))
else:
warning = ""
raise ImproperlyConfigured("The callback %s couldn't be imported. "
"Make sure the %s correctly installed."
% (self.callback, warning))
except AttributeError, e:
raise ImproperlyConfigured("An error occured while importing the "
raise ImproperlyConfigured(
"The callback %s couldn't be imported. Make sure the %s "
"correctly installed." % (self.callback, warning))
except AttributeError as e:
raise ImproperlyConfigured("An error occurred while importing the "
"callback filter %s: %s" % (self, e))
else:
self._callback_func = func
def output(self, **kwargs):
return self._callback_func(self.content, *self.args, **self.kwargs)
ret = self._callback_func(self.content, *self.args, **self.kwargs)
assert isinstance(ret, six.text_type)
return ret
class CompilerFilter(FilterBase):
@@ -74,72 +96,93 @@ class CompilerFilter(FilterBase):
"""
command = None
options = ()
default_encoding = settings.FILE_CHARSET
def __init__(self, content, command=None, *args, **kwargs):
super(CompilerFilter, self).__init__(content, *args, **kwargs)
self.cwd = None
if command:
self.command = command
if self.command is None:
raise FilterError("Required attribute 'command' not given")
if isinstance(self.options, dict):
# turn dict into a tuple
new_options = ()
for item in kwargs.iteritems():
for item in kwargs.items():
new_options += (item,)
self.options = new_options
for item in kwargs.iteritems():
# append kwargs to self.options
for item in kwargs.items():
self.options += (item,)
self.stdout = subprocess.PIPE
self.stdin = subprocess.PIPE
self.stderr = subprocess.PIPE
self.infile, self.outfile = None, None
self.stdout = self.stdin = self.stderr = subprocess.PIPE
self.infile = self.outfile = None
def input(self, **kwargs):
encoding = self.default_encoding
options = dict(self.options)
if self.infile is None:
if "{infile}" in self.command:
if self.filename is None:
self.infile = NamedTemporaryFile(mode="w")
self.infile.write(self.content.encode('utf8'))
self.infile.flush()
options["infile"] = self.infile.name
else:
self.infile = open(self.filename)
options["infile"] = self.filename
if self.infile is None and "{infile}" in self.command:
# create temporary input file if needed
if self.filename is None:
self.infile = NamedTemporaryFile(mode='wb')
self.infile.write(self.content.encode(encoding))
self.infile.flush()
options["infile"] = self.infile.name
else:
# we use source file directly, which may be encoded using
# something different than utf8. If that's the case file will
# be included with charset="something" html attribute and
# charset will be available as filter's charset attribute
encoding = self.charset # or self.default_encoding
self.infile = open(self.filename)
options["infile"] = self.filename
if "{outfile}" in self.command and not "outfile" in options:
# create temporary output file if needed
ext = self.type and ".%s" % self.type or ""
self.outfile = NamedTemporaryFile(mode='r+', suffix=ext)
options["outfile"] = self.outfile.name
try:
command = fstr(self.command).format(**options)
proc = subprocess.Popen(command, shell=True, cwd=self.cwd,
stdout=self.stdout, stdin=self.stdin, stderr=self.stderr)
command = self.command.format(**options)
proc = subprocess.Popen(
command, shell=True, cwd=self.cwd, stdout=self.stdout,
stdin=self.stdin, stderr=self.stderr)
if self.infile is None:
filtered, err = proc.communicate(self.content.encode('utf8'))
# if infile is None then send content to process' stdin
filtered, err = proc.communicate(
self.content.encode(encoding))
else:
filtered, err = proc.communicate()
except (IOError, OSError), e:
filtered, err = filtered.decode(encoding), err.decode(encoding)
except (IOError, OSError) as e:
raise FilterError('Unable to apply %s (%r): %s' %
(self.__class__.__name__, self.command, e))
else:
if proc.wait() != 0:
# command failed, raise FilterError exception
if not err:
err = ('Unable to apply %s (%s)' %
(self.__class__.__name__, self.command))
if filtered:
err += '\n%s' % filtered
raise FilterError(err)
if self.verbose:
self.logger.debug(err)
outfile_path = options.get('outfile')
if outfile_path:
self.outfile = open(outfile_path, 'r')
with io.open(outfile_path, 'r', encoding=encoding) as file:
filtered = file.read()
finally:
if self.infile is not None:
self.infile.close()
if self.outfile is not None:
filtered = self.outfile.read()
self.outfile.close()
return smart_unicode(filtered)
return smart_text(filtered)

View File

@@ -28,14 +28,8 @@
#
"""`cssmin` - A Python port of the YUI CSS compressor."""
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO # noqa
import re
__version__ = '0.1.4'

View File

@@ -1,3 +1,4 @@
from __future__ import unicode_literals
import os
import re
import mimetypes
@@ -39,7 +40,8 @@ class DataUriFilter(FilterBase):
if not url.startswith('data:'):
path = self.get_file_path(url)
if os.stat(path).st_size <= settings.COMPRESS_DATA_URI_MAX_SIZE:
data = b64encode(open(path, 'rb').read())
with open(path, 'rb') as file:
data = b64encode(file.read()).decode('ascii')
return 'url("data:%s;base64,%s")' % (
mimetypes.guess_type(path)[0], data)
return 'url("%s")' % url

View File

@@ -1,18 +1,15 @@
# flake8: noqa
import io
import os
import sys
from types import MethodType
from fnmatch import fnmatch
from optparse import make_option
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO # noqa
from django.core.management.base import NoArgsCommand, CommandError
from django.template import (Context, Template,
TemplateDoesNotExist, TemplateSyntaxError)
from django.utils import six
from django.utils.datastructures import SortedDict
from django.utils.importlib import import_module
from django.template.loader import get_template # noqa Leave this in to preload template locations
@@ -30,6 +27,16 @@ from compressor.conf import settings
from compressor.exceptions import OfflineGenerationError
from compressor.templatetags.compress import CompressorNode
if six.PY3:
# there is an 'io' module in python 2.6+, but io.StringIO does not
# accept regular strings, just unicode objects
from io import StringIO
else:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
def patched_render(self, context):
# 'Fake' _render method that just returns the context instead of
@@ -213,17 +220,13 @@ class Command(NoArgsCommand):
compressor_nodes = SortedDict()
for template_name in templates:
try:
template_file = open(template_name)
try:
template = Template(template_file.read().decode(
settings.FILE_CHARSET))
finally:
template_file.close()
with io.open(template_name, encoding=settings.FILE_CHARSET) as file:
template = Template(file.read())
except IOError: # unreadable file -> ignore
if verbosity > 0:
log.write("Unreadable template at: %s\n" % template_name)
continue
except TemplateSyntaxError, e: # broken template -> ignore
except TemplateSyntaxError as e: # broken template -> ignore
if verbosity > 0:
log.write("Invalid template %s: %s\n" % (template_name, e))
continue
@@ -255,7 +258,7 @@ class Command(NoArgsCommand):
count = 0
results = []
offline_manifest = SortedDict()
for template, nodes in compressor_nodes.iteritems():
for template, nodes in compressor_nodes.items():
context = Context(settings.COMPRESS_OFFLINE_CONTEXT)
template._log = log
template._log_verbosity = verbosity
@@ -275,7 +278,7 @@ class Command(NoArgsCommand):
key = get_offline_hexdigest(node.nodelist.render(context))
try:
result = node.render(context, forced=True)
except Exception, e:
except Exception as e:
raise CommandError("An error occured during rendering %s: "
"%s" % (template.template_name, e))
offline_manifest[key] = result
@@ -331,7 +334,7 @@ class Command(NoArgsCommand):
if not settings.COMPRESS_ENABLED and not options.get("force"):
raise CommandError(
"Compressor is disabled. Set the COMPRESS_ENABLED "
"settting or use --force to override.")
"setting or use --force to override.")
if not settings.COMPRESS_OFFLINE:
if not options.get("force"):
raise CommandError(

View File

@@ -74,9 +74,9 @@ class Command(NoArgsCommand):
if keys_to_delete:
cache.delete_many(list(keys_to_delete))
print "Deleted mtimes of %d files from the cache." % len(keys_to_delete)
print("Deleted mtimes of %d files from the cache." % len(keys_to_delete))
if files_to_add:
for filename in files_to_add:
get_mtime(filename)
print "Added mtimes of %d files to cache." % len(files_to_add)
print("Added mtimes of %d files to cache." % len(files_to_add))

View File

@@ -1,3 +1,4 @@
from django.utils import six
from django.utils.functional import LazyObject
from django.utils.importlib import import_module
@@ -11,8 +12,9 @@ from compressor.parser.html5lib import Html5LibParser # noqa
class AutoSelectParser(LazyObject):
options = (
('lxml.html', LxmlParser), # lxml, extremely fast
('HTMLParser', HtmlParser), # fast and part of the Python stdlib
# TODO: make lxml.html parser first again
(six.moves.html_parser.__name__, HtmlParser), # fast and part of the Python stdlib
('lxml.html', LxmlParser), # lxml, extremely fast
)
def __init__(self, content):

View File

@@ -1,10 +1,16 @@
from __future__ import absolute_import
from django.core.exceptions import ImproperlyConfigured
from django.utils.encoding import smart_unicode
from compressor.exceptions import ParserError
from compressor.parser import ParserBase
from compressor.utils.decorators import cached_property
from django.utils import six
try:
from django.utils.encoding import smart_text
except ImportError:
# django < 1.4.2
from django.utils.encoding import smart_unicode as smart_text
class BeautifulSoupParser(ParserBase):
@@ -12,18 +18,27 @@ class BeautifulSoupParser(ParserBase):
@cached_property
def soup(self):
try:
from BeautifulSoup import BeautifulSoup
if six.PY3:
from bs4 import BeautifulSoup
else:
from BeautifulSoup import BeautifulSoup
return BeautifulSoup(self.content)
except ImportError, err:
except ImportError as err:
raise ImproperlyConfigured("Error while importing BeautifulSoup: %s" % err)
except Exception, err:
except Exception as err:
raise ParserError("Error while initializing Parser: %s" % err)
def css_elems(self):
return self.soup.findAll({'link': True, 'style': True})
if six.PY3:
return self.soup.find_all({'link': True, 'style': True})
else:
return self.soup.findAll({'link': True, 'style': True})
def js_elems(self):
return self.soup.findAll('script')
if six.PY3:
return self.soup.find_all('script')
else:
return self.soup.findAll('script')
def elem_attribs(self, elem):
return dict(elem.attrs)
@@ -35,4 +50,4 @@ class BeautifulSoupParser(ParserBase):
return elem.name
def elem_str(self, elem):
return smart_unicode(elem)
return smart_text(elem)

View File

@@ -1,13 +1,18 @@
from HTMLParser import HTMLParser
from django.utils.encoding import smart_unicode
from django.utils import six
from compressor.exceptions import ParserError
from compressor.parser import ParserBase
try:
from django.utils.encoding import smart_text
except ImportError:
# django < 1.4.2
from django.utils.encoding import smart_unicode as smart_text
class DefaultHtmlParser(ParserBase, HTMLParser):
class DefaultHtmlParser(ParserBase, six.moves.html_parser.HTMLParser):
def __init__(self, content):
HTMLParser.__init__(self)
six.moves.html_parser.HTMLParser.__init__(self)
self.content = content
self._css_elems = []
self._js_elems = []
@@ -15,7 +20,7 @@ class DefaultHtmlParser(ParserBase, HTMLParser):
try:
self.feed(self.content)
self.close()
except Exception, err:
except Exception as err:
lineno = err.lineno
line = self.content.splitlines()[lineno]
raise ParserError("Error while initializing HtmlParser: %s (line: %s)" % (err, repr(line)))
@@ -65,7 +70,7 @@ class DefaultHtmlParser(ParserBase, HTMLParser):
return elem['attrs_dict']
def elem_content(self, elem):
return smart_unicode(elem['text'])
return smart_text(elem['text'])
def elem_str(self, elem):
tag = {}

View File

@@ -1,11 +1,16 @@
from __future__ import absolute_import
from django.utils.encoding import smart_unicode
from django.core.exceptions import ImproperlyConfigured
from compressor.exceptions import ParserError
from compressor.parser import ParserBase
from compressor.utils.decorators import cached_property
try:
from django.utils.encoding import smart_text
except ImportError:
# django < 1.4.2
from django.utils.encoding import smart_unicode as smart_text
class Html5LibParser(ParserBase):
@@ -29,9 +34,9 @@ class Html5LibParser(ParserBase):
def html(self):
try:
return self.html5lib.parseFragment(self.content)
except ImportError, err:
except ImportError as err:
raise ImproperlyConfigured("Error while importing html5lib: %s" % err)
except Exception, err:
except Exception as err:
raise ParserError("Error while initializing Parser: %s" % err)
def css_elems(self):
@@ -53,4 +58,4 @@ class Html5LibParser(ParserBase):
# This method serializes HTML in a way that does not pass all tests.
# However, this method is only called in tests anyway, so it doesn't
# really matter.
return smart_unicode(self._serialize(elem))
return smart_text(self._serialize(elem))

View File

@@ -1,35 +1,64 @@
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals
from django.utils import six
from django.core.exceptions import ImproperlyConfigured
from django.utils.encoding import smart_unicode
from compressor.exceptions import ParserError
from compressor.parser import ParserBase
from compressor.utils.decorators import cached_property
try:
from django.utils.encoding import smart_text
except ImportError:
# django < 1.4.2
from django.utils.encoding import smart_unicode as smart_text
class LxmlParser(ParserBase):
"""
LxmlParser will use `lxml.html` parser to parse rendered contents of
{% compress %} tag. Under python 2 it will also try to use beautiful
soup parser in case of any problems with encoding.
"""
def __init__(self, content):
try:
from lxml.html import fromstring, soupparser
from lxml.html import fromstring
from lxml.etree import tostring
self.fromstring = fromstring
self.soupparser = soupparser
self.tostring = tostring
except ImportError, err:
except ImportError as err:
raise ImproperlyConfigured("Error while importing lxml: %s" % err)
except Exception, err:
raise ParserError("Error while initializing Parser: %s" % err)
except Exception as err:
raise ParserError("Error while initializing parser: %s" % err)
if not six.PY3:
# soupparser uses Beautiful Soup 3 which does not run on python 3.x
try:
from lxml.html import soupparser
except ImportError as err:
raise ImportError("Error while importing lxml: %s" % err)
except Exception as err:
raise ParserError("Error while initializing parser: %s" % err)
else:
soupparser = None
self.soupparser = soupparser
self.fromstring = fromstring
self.tostring = tostring
super(LxmlParser, self).__init__(content)
@cached_property
def tree(self):
"""
Document tree.
"""
content = '<root>%s</root>' % self.content
tree = self.fromstring(content)
try:
self.tostring(tree, encoding=unicode)
self.tostring(tree, encoding=six.text_type)
except UnicodeDecodeError:
tree = self.soupparser.fromstring(content)
if self.soupparser: # use soup parser on python 2
tree = self.soupparser.fromstring(content)
else: # raise an error on python 3
raise
return tree
def css_elems(self):
@@ -43,14 +72,14 @@ class LxmlParser(ParserBase):
return elem.attrib
def elem_content(self, elem):
return smart_unicode(elem.text)
return smart_text(elem.text)
def elem_name(self, elem):
return elem.tag
def elem_str(self, elem):
elem_as_string = smart_unicode(
self.tostring(elem, method='html', encoding=unicode))
elem_as_string = smart_text(
self.tostring(elem, method='html', encoding=six.text_type))
if elem.tag == 'link':
# This makes testcases happy
return elem_as_string.replace('>', ' />')

View File

@@ -1,5 +1,7 @@
from __future__ import unicode_literals
import errno
import gzip
import os
from os import path
from datetime import datetime
@@ -49,7 +51,7 @@ class CompressorFileStorage(FileSystemStorage):
"""
try:
super(CompressorFileStorage, self).delete(name)
except OSError, e:
except OSError as e:
if e.errno != errno.ENOENT:
raise
@@ -65,9 +67,15 @@ class GzipCompressorFileStorage(CompressorFileStorage):
"""
def save(self, filename, content):
filename = super(GzipCompressorFileStorage, self).save(filename, content)
out = gzip.open(u'%s.gz' % self.path(filename), 'wb')
out.writelines(open(self.path(filename), 'rb'))
out.close()
# 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()
return filename

View File

@@ -1,5 +1,6 @@
from django import template
from django.core.exceptions import ImproperlyConfigured
from django.utils import six
from compressor.cache import (cache_get, cache_set, get_offline_hexdigest,
get_offline_manifest, get_templatetag_cachekey)
@@ -50,7 +51,7 @@ class CompressorMixin(object):
Check if offline compression is enabled or forced
Defaults to just checking the settings and forced argument,
but can be overriden to completely disable compression for
but can be overridden to completely disable compression for
a subclass, for instance.
"""
return (settings.COMPRESS_ENABLED and
@@ -107,6 +108,7 @@ class CompressorMixin(object):
rendered_output = self.render_output(compressor, mode, forced=forced)
if cache_key:
cache_set(cache_key, rendered_output)
assert isinstance(rendered_output, six.text_type)
return rendered_output
except Exception:
if settings.DEBUG or forced:

View File

@@ -28,7 +28,7 @@ def main():
with open(options.outfile, 'w') as f:
f.write(content)
else:
print content
print(content)
if __name__ == '__main__':

View File

@@ -1,9 +1,13 @@
from __future__ import with_statement
from __future__ import with_statement, unicode_literals
import os
import re
from BeautifulSoup import BeautifulSoup
try:
from bs4 import BeautifulSoup
except ImportError:
from BeautifulSoup import BeautifulSoup
from django.utils import six
from django.core.cache.backends import locmem
from django.test import SimpleTestCase
@@ -14,15 +18,24 @@ from compressor.js import JsCompressor
from compressor.exceptions import FilterDoesNotExist
def make_soup(markup):
# we use html.parser instead of lxml because it doesn't work on python 3.3
if six.PY3:
return BeautifulSoup(markup, 'html.parser')
else:
return BeautifulSoup(markup)
def css_tag(href, **kwargs):
rendered_attrs = ''.join(['%s="%s" ' % (k, v) for k, v in kwargs.items()])
template = u'<link rel="stylesheet" href="%s" type="text/css" %s/>'
template = '<link rel="stylesheet" href="%s" type="text/css" %s/>'
return template % (href, rendered_attrs)
class TestPrecompiler(object):
"""A filter whose output is always the string 'OUTPUT' """
def __init__(self, content, attrs, filter_type=None, filename=None):
def __init__(self, content, attrs, filter_type=None, filename=None,
charset=None):
pass
def input(self, **kwargs):
@@ -51,20 +64,34 @@ class CompressorTestCase(SimpleTestCase):
def test_css_split(self):
out = [
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css', u'one.css'), u'css/one.css', u'<link rel="stylesheet" href="/static/css/one.css" type="text/css" />'),
(SOURCE_HUNK, u'p { border:5px solid green;}', None, u'<style type="text/css">p { border:5px solid green;}</style>'),
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css', u'two.css'), u'css/two.css', u'<link rel="stylesheet" href="/static/css/two.css" type="text/css" />'),
(
SOURCE_FILE,
os.path.join(settings.COMPRESS_ROOT, 'css', 'one.css'),
'css/one.css', '<link rel="stylesheet" href="/static/css/one.css" 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 rel="stylesheet" href="/static/css/two.css" 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)
def test_css_hunks(self):
out = ['body { background:#990; }', u'p { border:5px solid green;}', 'body { color:#fff; }']
out = ['body { background:#990; }', 'p { border:5px solid green;}', 'body { color:#fff; }']
self.assertEqual(out, list(self.css_node.hunks()))
def test_css_output(self):
out = u'body { background:#990; }\np { border:5px solid green;}\nbody { color:#fff; }'
out = 'body { background:#990; }\np { border:5px solid green;}\nbody { color:#fff; }'
hunks = '\n'.join([h for h in self.css_node.hunks()])
self.assertEqual(out, hunks)
@@ -89,28 +116,38 @@ class CompressorTestCase(SimpleTestCase):
def test_js_split(self):
out = [
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'js', u'one.js'), u'js/one.js', '<script src="/static/js/one.js" type="text/javascript"></script>'),
(SOURCE_HUNK, u'obj.value = "value";', None, '<script type="text/javascript">obj.value = "value";</script>'),
(
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)
def test_js_hunks(self):
out = ['obj = {};', u'obj.value = "value";']
out = ['obj = {};', 'obj.value = "value";']
self.assertEqual(out, list(self.js_node.hunks()))
def test_js_output(self):
out = u'<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
out = '<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
self.assertEqual(out, self.js_node.output())
def test_js_override_url(self):
self.js_node.context.update({'url': u'This is not a url, just a text'})
out = u'<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
self.js_node.context.update({'url': 'This is not a url, just a text'})
out = '<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
self.assertEqual(out, self.js_node.output())
def test_css_override_url(self):
self.css_node.context.update({'url': u'This is not a url, just a text'})
self.css_node.context.update({'url': 'This is not a url, just a text'})
output = css_tag('/static/CACHE/css/e41ba2cc6982.css')
self.assertEqual(output, self.css_node.output().strip())
@@ -126,20 +163,20 @@ class CompressorTestCase(SimpleTestCase):
settings.COMPRESS_PRECOMPILERS = precompilers
def test_js_return_if_on(self):
output = u'<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
output = '<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
self.assertEqual(output, self.js_node.output())
def test_custom_output_dir(self):
try:
old_output_dir = settings.COMPRESS_OUTPUT_DIR
settings.COMPRESS_OUTPUT_DIR = 'custom'
output = u'<script type="text/javascript" src="/static/custom/js/066cd253eada.js"></script>'
output = '<script type="text/javascript" src="/static/custom/js/066cd253eada.js"></script>'
self.assertEqual(output, JsCompressor(self.js).output())
settings.COMPRESS_OUTPUT_DIR = ''
output = u'<script type="text/javascript" src="/static/js/066cd253eada.js"></script>'
output = '<script type="text/javascript" src="/static/js/066cd253eada.js"></script>'
self.assertEqual(output, JsCompressor(self.js).output())
settings.COMPRESS_OUTPUT_DIR = '/custom/nested/'
output = u'<script type="text/javascript" src="/static/custom/nested/js/066cd253eada.js"></script>'
output = '<script type="text/javascript" src="/static/custom/nested/js/066cd253eada.js"></script>'
self.assertEqual(output, JsCompressor(self.js).output())
finally:
settings.COMPRESS_OUTPUT_DIR = old_output_dir
@@ -153,7 +190,7 @@ class CompressorTestCase(SimpleTestCase):
)
css = '<style type="text/foobar">p { border:10px solid red;}</style>'
css_node = CssCompressor(css)
output = BeautifulSoup(css_node.output('inline'))
output = make_soup(css_node.output('inline'))
self.assertEqual(output.text, 'OUTPUT')
finally:
settings.COMPRESS_PRECOMPILERS = original_precompilers
@@ -182,16 +219,22 @@ class CssMediaTestCase(SimpleTestCase):
def test_css_output(self):
css_node = CssCompressor(self.css)
links = BeautifulSoup(css_node.output()).findAll('link')
media = [u'screen', u'print', u'all', None]
if six.PY3:
links = make_soup(css_node.output()).find_all('link')
else:
links = make_soup(css_node.output()).findAll('link')
media = ['screen', 'print', 'all', None]
self.assertEqual(len(links), 4)
self.assertEqual(media, [l.get('media', None) for l in links])
def test_avoid_reordering_css(self):
css = self.css + '<style type="text/css" media="print">p { border:10px solid red;}</style>'
css_node = CssCompressor(css)
media = [u'screen', u'print', u'all', None, u'print']
links = BeautifulSoup(css_node.output()).findAll('link')
media = ['screen', 'print', 'all', None, 'print']
if six.PY3:
links = make_soup(css_node.output()).find_all('link')
else:
links = make_soup(css_node.output()).findAll('link')
self.assertEqual(media, [l.get('media', None) for l in links])
def test_passthough_when_compress_disabled(self):
@@ -205,10 +248,13 @@ class CssMediaTestCase(SimpleTestCase):
<link rel="stylesheet" href="/static/css/two.css" type="text/css" media="screen">
<style type="text/foobar" media="screen">h1 { border:5px solid green;}</style>"""
css_node = CssCompressor(css)
output = BeautifulSoup(css_node.output()).findAll(['link', 'style'])
self.assertEqual([u'/static/css/one.css', u'/static/css/two.css', None],
if six.PY3:
output = make_soup(css_node.output()).find_all(['link', 'style'])
else:
output = make_soup(css_node.output()).findAll(['link', 'style'])
self.assertEqual(['/static/css/one.css', '/static/css/two.css', None],
[l.get('href', None) for l in output])
self.assertEqual([u'screen', u'screen', u'screen'],
self.assertEqual(['screen', 'screen', 'screen'],
[l.get('media', None) for l in output])
settings.COMPRESS_PRECOMPILERS = original_precompilers

View File

@@ -1,8 +1,10 @@
from __future__ import with_statement
from __future__ import with_statement, unicode_literals
import io
import os
import sys
from unittest2 import skipIf
import textwrap
from django.utils import six
from django.test import TestCase
from compressor.cache import get_hashed_mtime, get_hashed_content
@@ -15,62 +17,72 @@ from compressor.filters.css_default import CssAbsoluteFilter
from compressor.filters.template import TemplateFilter
from compressor.tests.test_base import test_dir
try:
from django.utils import unittest as ut2
except ImportError:
import unittest2 as ut2
@ut2.skipIf(find_command(settings.COMPRESS_CSSTIDY_BINARY) is None,
'CSStidy binary %r not found' % settings.COMPRESS_CSSTIDY_BINARY)
class CssTidyTestCase(TestCase):
def test_tidy(self):
content = """
/* Some comment */
font,th,td,p{
color: black;
}
"""
content = textwrap.dedent("""\
/* Some comment */
font,th,td,p{
color: black;
}
""")
from compressor.filters.csstidy import CSSTidyFilter
ret = CSSTidyFilter(content).input()
self.assertIsInstance(ret, six.text_type)
self.assertEqual(
"font,th,td,p{color:#000;}", CSSTidyFilter(content).input())
CssTidyTestCase = skipIf(
find_command(settings.COMPRESS_CSSTIDY_BINARY) is None,
'CSStidy binary %r not found' % settings.COMPRESS_CSSTIDY_BINARY,
)(CssTidyTestCase)
class PrecompilerTestCase(TestCase):
def setUp(self):
self.filename = os.path.join(test_dir, 'static/css/one.css')
with open(self.filename) as f:
self.content = f.read()
with io.open(self.filename, encoding=settings.FILE_CHARSET) as file:
self.content = file.read()
self.test_precompiler = os.path.join(test_dir, 'precompiler.py')
def test_precompiler_infile_outfile(self):
command = '%s %s -f {infile} -o {outfile}' % (sys.executable, self.test_precompiler)
compiler = CompilerFilter(content=self.content, filename=self.filename, command=command)
self.assertEqual(u"body { color:#990; }", compiler.input())
compiler = CompilerFilter(
content=self.content, filename=self.filename,
charset=settings.FILE_CHARSET, command=command)
self.assertEqual("body { color:#990; }", compiler.input())
def test_precompiler_infile_stdout(self):
command = '%s %s -f {infile}' % (sys.executable, self.test_precompiler)
compiler = CompilerFilter(content=self.content, filename=None, command=command)
self.assertEqual(u"body { color:#990; }%s" % os.linesep, compiler.input())
compiler = CompilerFilter(
content=self.content, filename=None, charset=None, command=command)
self.assertEqual("body { color:#990; }%s" % os.linesep, compiler.input())
def test_precompiler_stdin_outfile(self):
command = '%s %s -o {outfile}' % (sys.executable, self.test_precompiler)
compiler = CompilerFilter(content=self.content, filename=None, command=command)
self.assertEqual(u"body { color:#990; }", compiler.input())
compiler = CompilerFilter(
content=self.content, filename=None, charset=None, command=command)
self.assertEqual("body { color:#990; }", compiler.input())
def test_precompiler_stdin_stdout(self):
command = '%s %s' % (sys.executable, self.test_precompiler)
compiler = CompilerFilter(content=self.content, filename=None, command=command)
self.assertEqual(u"body { color:#990; }%s" % os.linesep, compiler.input())
compiler = CompilerFilter(
content=self.content, filename=None, charset=None, command=command)
self.assertEqual("body { color:#990; }%s" % os.linesep, compiler.input())
def test_precompiler_stdin_stdout_filename(self):
command = '%s %s' % (sys.executable, self.test_precompiler)
compiler = CompilerFilter(content=self.content, filename=self.filename, command=command)
self.assertEqual(u"body { color:#990; }%s" % os.linesep, compiler.input())
compiler = CompilerFilter(
content=self.content, filename=self.filename,
charset=settings.FILE_CHARSET, command=command)
self.assertEqual("body { color:#990; }%s" % os.linesep, compiler.input())
def test_precompiler_output_unicode(self):
command = '%s %s' % (sys.executable, self.test_precompiler)
compiler = CompilerFilter(content=self.content, filename=self.filename, command=command)
self.assertEqual(type(compiler.input()), unicode)
self.assertEqual(type(compiler.input()), six.text_type)
class CssMinTestCase(TestCase):
@@ -82,7 +94,7 @@ class CssMinTestCase(TestCase):
}
"""
"""
output = "p{background:#369 url('../../images/image.gif')}"
self.assertEqual(output, CSSMinFilter(content).output())
@@ -215,14 +227,14 @@ class CssAbsolutizingTestCase(TestCase):
'hash1': self.hashing_func(os.path.join(settings.COMPRESS_ROOT, 'img/python.png')),
'hash2': self.hashing_func(os.path.join(settings.COMPRESS_ROOT, 'img/add.png')),
}
self.assertEqual([u"""\
self.assertEqual(["""\
p { background: url('/static/img/python.png?%(hash1)s'); }
p { background: url('/static/img/python.png?%(hash1)s'); }
p { background: url('/static/img/python.png?%(hash1)s'); }
p { background: url('/static/img/python.png?%(hash1)s'); }
p { filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/static/img/python.png?%(hash1)s'); }
""" % hash_dict,
u"""\
"""\
p { background: url('/static/img/add.png?%(hash2)s'); }
p { background: url('/static/img/add.png?%(hash2)s'); }
p { background: url('/static/img/add.png?%(hash2)s'); }
@@ -269,7 +281,7 @@ class CssDataUriTestCase(TestCase):
def test_data_uris(self):
datauri_hash = get_hashed_mtime(os.path.join(settings.COMPRESS_ROOT, 'img/python.png'))
out = [u'''.add { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }
out = ['''.add { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }
.add-with-hash { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJvSURBVDjLpZPrS5NhGIf9W7YvBYOkhlkoqCklWChv2WyKik7blnNris72bi6dus0DLZ0TDxW1odtopDs4D8MDZuLU0kXq61CijSIIasOvv94VTUfLiB74fXngup7nvrnvJABJ/5PfLnTTdcwOj4RsdYmo5glBWP6iOtzwvIKSWstI0Wgx80SBblpKtE9KQs/We7EaWoT/8wbWP61gMmCH0lMDvokT4j25TiQU/ITFkek9Ow6+7WH2gwsmahCPdwyw75uw9HEO2gUZSkfyI9zBPCJOoJ2SMmg46N61YO/rNoa39Xi41oFuXysMfh36/Fp0b7bAfWAH6RGi0HglWNCbzYgJaFjRv6zGuy+b9It96N3SQvNKiV9HvSaDfFEIxXItnPs23BzJQd6DDEVM0OKsoVwBG/1VMzpXVWhbkUM2K4oJBDYuGmbKIJ0qxsAbHfRLzbjcnUbFBIpx/qH3vQv9b3U03IQ/HfFkERTzfFj8w8jSpR7GBE123uFEYAzaDRIqX/2JAtJbDat/COkd7CNBva2cMvq0MGxp0PRSCPF8BXjWG3FgNHc9XPT71Ojy3sMFdfJRCeKxEsVtKwFHwALZfCUk3tIfNR8XiJwc1LmL4dg141JPKtj3WUdNFJqLGFVPC4OkR4BxajTWsChY64wmCnMxsWPCHcutKBxMVp5mxA1S+aMComToaqTRUQknLTH62kHOVEE+VQnjahscNCy0cMBWsSI0TCQcZc5ALkEYckL5A5noWSBhfm2AecMAjbcRWV0pUTh0HE64TNf0mczcnnQyu/MilaFJCae1nw2fbz1DnVOxyGTlKeZft/Ff8x1BRssfACjTwQAAAABJRU5ErkJggg=="); }
.python { background-image: url("/static/img/python.png?%s"); }
.datauri { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9YGARc5KB0XV+IAAAAddEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIFRoZSBHSU1Q72QlbgAAAF1JREFUGNO9zL0NglAAxPEfdLTs4BZM4DIO4C7OwQg2JoQ9LE1exdlYvBBeZ7jqch9//q1uH4TLzw4d6+ErXMMcXuHWxId3KOETnnXXV6MJpcq2MLaI97CER3N0 vr4MkhoXe0rZigAAAABJRU5ErkJggg=="); }

View File

@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
from __future__ import with_statement
from __future__ import with_statement, unicode_literals
from django.test import TestCase
@@ -53,13 +53,13 @@ class TestJinja2CompressorExtension(TestCase):
settings.COMPRESS_ENABLED = org_COMPRESS_ENABLED
def test_empty_tag(self):
template = self.env.from_string(u"""{% compress js %}{% block js %}
template = self.env.from_string("""{% compress js %}{% block js %}
{% endblock %}{% endcompress %}""")
context = {'STATIC_URL': settings.COMPRESS_URL}
self.assertEqual(u'', template.render(context))
self.assertEqual('', template.render(context))
def test_css_tag(self):
template = self.env.from_string(u"""{% compress css -%}
template = self.env.from_string("""{% compress css -%}
<link rel="stylesheet" href="{{ STATIC_URL }}css/one.css" type="text/css" charset="utf-8">
<style type="text/css">p { border:5px solid green;}</style>
<link rel="stylesheet" href="{{ STATIC_URL }}css/two.css" type="text/css" charset="utf-8">
@@ -69,7 +69,7 @@ class TestJinja2CompressorExtension(TestCase):
self.assertEqual(out, template.render(context))
def test_nonascii_css_tag(self):
template = self.env.from_string(u"""{% compress css -%}
template = self.env.from_string("""{% compress css -%}
<link rel="stylesheet" href="{{ STATIC_URL }}css/nonasc.css" type="text/css" charset="utf-8">
<style type="text/css">p { border:5px solid green;}</style>
{% endcompress %}""")
@@ -78,34 +78,34 @@ class TestJinja2CompressorExtension(TestCase):
self.assertEqual(out, template.render(context))
def test_js_tag(self):
template = self.env.from_string(u"""{% compress js -%}
template = self.env.from_string("""{% compress js -%}
<script src="{{ STATIC_URL }}js/one.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">obj.value = "value";</script>
{% endcompress %}""")
context = {'STATIC_URL': settings.COMPRESS_URL}
out = u'<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
out = '<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
self.assertEqual(out, template.render(context))
def test_nonascii_js_tag(self):
template = self.env.from_string(u"""{% compress js -%}
template = self.env.from_string("""{% compress js -%}
<script src="{{ STATIC_URL }}js/nonasc.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">var test_value = "\u2014";</script>
{% endcompress %}""")
context = {'STATIC_URL': settings.COMPRESS_URL}
out = u'<script type="text/javascript" src="/static/CACHE/js/e214fe629b28.js"></script>'
out = '<script type="text/javascript" src="/static/CACHE/js/e214fe629b28.js"></script>'
self.assertEqual(out, template.render(context))
def test_nonascii_latin1_js_tag(self):
template = self.env.from_string(u"""{% compress js -%}
template = self.env.from_string("""{% compress js -%}
<script src="{{ STATIC_URL }}js/nonasc-latin1.js" type="text/javascript" charset="latin-1"></script>
<script type="text/javascript">var test_value = "\u2014";</script>
{% endcompress %}""")
context = {'STATIC_URL': settings.COMPRESS_URL}
out = u'<script type="text/javascript" src="/static/CACHE/js/be9e078b5ca7.js"></script>'
out = '<script type="text/javascript" src="/static/CACHE/js/be9e078b5ca7.js"></script>'
self.assertEqual(out, template.render(context))
def test_css_inline(self):
template = self.env.from_string(u"""{% compress css, inline -%}
template = self.env.from_string("""{% compress css, inline -%}
<link rel="stylesheet" href="{{ STATIC_URL }}css/one.css" type="text/css" charset="utf-8">
<style type="text/css">p { border:5px solid green;}</style>
{% endcompress %}""")
@@ -117,7 +117,7 @@ class TestJinja2CompressorExtension(TestCase):
self.assertEqual(out, template.render(context))
def test_js_inline(self):
template = self.env.from_string(u"""{% compress js, inline -%}
template = self.env.from_string("""{% compress js, inline -%}
<script src="{{ STATIC_URL }}js/one.js" type="text/css" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">obj.value = "value";</script>
{% endcompress %}""")
@@ -128,11 +128,11 @@ class TestJinja2CompressorExtension(TestCase):
def test_nonascii_inline_css(self):
org_COMPRESS_ENABLED = settings.COMPRESS_ENABLED
settings.COMPRESS_ENABLED = False
template = self.env.from_string(u'{% compress css %}'
u'<style type="text/css">'
u'/* русский текст */'
u'</style>{% endcompress %}')
out = u'<link rel="stylesheet" href="/static/CACHE/css/b2cec0f8cb24.css" type="text/css" />'
template = self.env.from_string('{% compress css %}'
'<style type="text/css">'
'/* русский текст */'
'</style>{% endcompress %}')
out = '<link rel="stylesheet" href="/static/CACHE/css/b2cec0f8cb24.css" type="text/css" />'
settings.COMPRESS_ENABLED = org_COMPRESS_ENABLED
context = {'STATIC_URL': settings.COMPRESS_URL}
self.assertEqual(out, template.render(context))

View File

@@ -1,11 +1,11 @@
from __future__ import with_statement
from __future__ import with_statement, unicode_literals
import io
import os
from StringIO import StringIO
from unittest2 import skipIf
import django
from django.template import Template, Context
from django.test import TestCase
from django.utils import six
from django.core.management.base import CommandError
from compressor.cache import flush_offline_manifest, get_offline_manifest
@@ -14,6 +14,21 @@ from compressor.exceptions import OfflineGenerationError
from compressor.management.commands.compress import Command as CompressCommand
from compressor.storage import default_storage
try:
from django.utils import unittest as ut2
except ImportError:
import unittest2 as ut2
if six.PY3:
# there is an 'io' module in python 2.6+, but io.StringIO does not
# accept regular strings, just unicode objects
from io import StringIO
else:
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
class OfflineTestCaseMixin(object):
template_name = "test_compressor_offline.html"
@@ -39,14 +54,14 @@ class OfflineTestCaseMixin(object):
settings.COMPRESS_ENABLED = True
settings.COMPRESS_OFFLINE = True
self.template_path = os.path.join(settings.TEMPLATE_DIRS[0], self.template_name)
self.template_file = open(self.template_path)
self.template = Template(self.template_file.read().decode(settings.FILE_CHARSET))
with io.open(self.template_path, encoding=settings.FILE_CHARSET) as file:
self.template = Template(file.read())
def tearDown(self):
settings.COMPRESS_ENABLED = self._old_compress
settings.COMPRESS_OFFLINE = self._old_compress_offline
settings.TEMPLATE_DIRS = self._old_template_dirs
self.template_file.close()
manifest_path = os.path.join('CACHE', 'manifest.json')
if default_storage.exists(manifest_path):
default_storage.delete(manifest_path)
@@ -55,7 +70,7 @@ class OfflineTestCaseMixin(object):
count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity)
self.assertEqual(1, count)
self.assertEqual([
u'<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (self.expected_hash, ),
'<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (self.expected_hash, ),
], result)
rendered_template = self.template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT))
self.assertEqual(rendered_template, "".join(result) + "\n")
@@ -97,8 +112,8 @@ class OfflineGenerationBlockSuperTestCaseWithExtraContent(OfflineTestCaseMixin,
count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity)
self.assertEqual(2, count)
self.assertEqual([
u'<script type="text/javascript" src="/static/CACHE/js/ced14aec5856.js"></script>',
u'<script type="text/javascript" src="/static/CACHE/js/7c02d201f69d.js"></script>'
'<script type="text/javascript" src="/static/CACHE/js/ced14aec5856.js"></script>',
'<script type="text/javascript" src="/static/CACHE/js/7c02d201f69d.js"></script>'
], result)
rendered_template = self.template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT))
self.assertEqual(rendered_template, "".join(result) + "\n")
@@ -129,7 +144,7 @@ class OfflineGenerationStaticTemplateTagTestCase(OfflineTestCaseMixin, TestCase)
templates_dir = "test_static_templatetag"
expected_hash = "dfa2bb387fa8"
# This test uses {% static %} which was introduced in django 1.4
OfflineGenerationStaticTemplateTagTestCase = skipIf(
OfflineGenerationStaticTemplateTagTestCase = ut2.skipIf(
django.VERSION[1] < 4, 'Django 1.4 not found'
)(OfflineGenerationStaticTemplateTagTestCase)
@@ -156,8 +171,8 @@ class OfflineGenerationTestCaseErrors(OfflineTestCaseMixin, TestCase):
def test_offline(self):
count, result = CompressCommand().compress(log=self.log, verbosity=self.verbosity)
self.assertEqual(2, count)
self.assertIn(u'<script type="text/javascript" src="/static/CACHE/js/3872c9ae3f42.js"></script>', result)
self.assertIn(u'<script type="text/javascript" src="/static/CACHE/js/cd8870829421.js"></script>', result)
self.assertIn('<script type="text/javascript" src="/static/CACHE/js/3872c9ae3f42.js"></script>', result)
self.assertIn('<script type="text/javascript" src="/static/CACHE/js/cd8870829421.js"></script>', result)
class OfflineGenerationTestCaseWithError(OfflineTestCaseMixin, TestCase):
@@ -209,7 +224,7 @@ class OfflineGenerationTestCase(OfflineTestCaseMixin, TestCase):
default_storage.delete(manifest_path)
self.assertEqual(1, count)
self.assertEqual([
u'<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (self.expected_hash, ),
'<script type="text/javascript" src="/static/CACHE/js/%s.js"></script>' % (self.expected_hash, ),
], result)
rendered_template = self.template.render(Context(settings.COMPRESS_OFFLINE_CONTEXT))
self.assertEqual(rendered_template, "".join(result) + "\n")
@@ -244,7 +259,7 @@ class OfflineGenerationInlineNonAsciiTestCase(OfflineTestCaseMixin, TestCase):
def setUp(self):
self.old_offline_context = settings.COMPRESS_OFFLINE_CONTEXT
settings.COMPRESS_OFFLINE_CONTEXT = {
'test_non_ascii_value': u'\u2014',
'test_non_ascii_value': '\u2014',
}
super(OfflineGenerationInlineNonAsciiTestCase, self).setUp()

View File

@@ -1,6 +1,5 @@
from __future__ import with_statement
import os
from unittest2 import skipIf
try:
import lxml
@@ -23,6 +22,11 @@ from compressor.conf import settings
from compressor.css import CssCompressor
from compressor.tests.test_base import CompressorTestCase
try:
from django.utils import unittest as ut2
except ImportError:
import unittest2 as ut2
class ParserTestCase(object):
@@ -35,11 +39,12 @@ class ParserTestCase(object):
settings.COMPRESS_PARSER = self.old_parser
@ut2.skipIf(lxml is None, 'lxml not found')
class LxmlParserTests(ParserTestCase, CompressorTestCase):
parser_cls = 'compressor.parser.LxmlParser'
LxmlParserTests = skipIf(lxml is None, 'lxml not found')(LxmlParserTests)
@ut2.skipIf(html5lib is None, 'html5lib not found')
class Html5LibParserTests(ParserTestCase, CompressorTestCase):
parser_cls = 'compressor.parser.Html5LibParser'
@@ -54,9 +59,9 @@ class Html5LibParserTests(ParserTestCase, CompressorTestCase):
def test_css_split(self):
out = [
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css', u'one.css'), u'css/one.css', u'<link href="/static/css/one.css" rel="stylesheet" type="text/css">'),
(SOURCE_HUNK, u'p { border:5px solid green;}', None, u'<style type="text/css">p { border:5px solid green;}</style>'),
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'css', u'two.css'), u'css/two.css', u'<link href="/static/css/two.css" rel="stylesheet" type="text/css">'),
(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]
@@ -64,23 +69,18 @@ class Html5LibParserTests(ParserTestCase, CompressorTestCase):
def test_js_split(self):
out = [
(SOURCE_FILE, os.path.join(settings.COMPRESS_ROOT, u'js', u'one.js'), u'js/one.js', u'<script src="/static/js/one.js" type="text/javascript"></script>'),
(SOURCE_HUNK, u'obj.value = "value";', None, u'<script type="text/javascript">obj.value = "value";</script>'),
(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)
Html5LibParserTests = skipIf(
html5lib is None, 'html5lib not found')(Html5LibParserTests)
@ut2.skipIf(BeautifulSoup is None, 'BeautifulSoup not found')
class BeautifulSoupParserTests(ParserTestCase, CompressorTestCase):
parser_cls = 'compressor.parser.BeautifulSoupParser'
BeautifulSoupParserTests = skipIf(
BeautifulSoup is None, 'BeautifulSoup not found')(BeautifulSoupParserTests)
class HtmlParserTests(ParserTestCase, CompressorTestCase):
parser_cls = 'compressor.parser.HtmlParser'

View File

@@ -34,9 +34,9 @@ class PostCompressSignalTestCase(TestCase):
post_compress.connect(callback)
self.js_node.output()
args, kwargs = callback.call_args
self.assertEquals(JsCompressor, kwargs['sender'])
self.assertEquals('js', kwargs['type'])
self.assertEquals('file', kwargs['mode'])
self.assertEqual(JsCompressor, kwargs['sender'])
self.assertEqual('js', kwargs['type'])
self.assertEqual('file', kwargs['mode'])
context = kwargs['context']
assert 'url' in context['compressed']
@@ -47,9 +47,9 @@ class PostCompressSignalTestCase(TestCase):
post_compress.connect(callback)
self.css_node.output()
args, kwargs = callback.call_args
self.assertEquals(CssCompressor, kwargs['sender'])
self.assertEquals('css', kwargs['type'])
self.assertEquals('file', kwargs['mode'])
self.assertEqual(CssCompressor, kwargs['sender'])
self.assertEqual('css', kwargs['type'])
self.assertEqual('file', kwargs['mode'])
context = kwargs['context']
assert 'url' in context['compressed']
@@ -65,4 +65,4 @@ class PostCompressSignalTestCase(TestCase):
callback = Mock(wraps=listener)
post_compress.connect(callback)
css_node.output()
self.assertEquals(3, callback.call_count)
self.assertEqual(3, callback.call_count)

View File

@@ -1,4 +1,4 @@
from __future__ import with_statement
from __future__ import with_statement, unicode_literals
import errno
import os
@@ -23,7 +23,7 @@ class StorageTestCase(TestCase):
base.default_storage = self._storage
def test_css_tag_with_storage(self):
template = u"""{% load compress %}{% compress css %}
template = """{% load compress %}{% compress css %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/one.css" type="text/css">
<style type="text/css">p { border:5px solid white;}</style>
<link rel="stylesheet" href="{{ STATIC_URL }}css/two.css" type="text/css">
@@ -40,7 +40,7 @@ class StorageTestCase(TestCase):
def race_remove(path):
"Patched os.remove to raise ENOENT (No such file or directory)"
original_remove(path)
raise OSError(errno.ENOENT, u'Fake ENOENT')
raise OSError(errno.ENOENT, 'Fake ENOENT')
try:
os.remove = race_remove

View File

@@ -1,4 +1,4 @@
from __future__ import with_statement
from __future__ import with_statement, unicode_literals
import os
import sys
@@ -34,12 +34,12 @@ class TemplatetagTestCase(TestCase):
settings.COMPRESS_ENABLED = self.old_enabled
def test_empty_tag(self):
template = u"""{% load compress %}{% compress js %}{% block js %}
template = """{% load compress %}{% compress js %}{% block js %}
{% endblock %}{% endcompress %}"""
self.assertEqual(u'', render(template, self.context))
self.assertEqual('', render(template, self.context))
def test_css_tag(self):
template = u"""{% load compress %}{% compress css %}
template = """{% load compress %}{% compress css %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/one.css" type="text/css">
<style type="text/css">p { border:5px solid green;}</style>
<link rel="stylesheet" href="{{ STATIC_URL }}css/two.css" type="text/css">
@@ -50,7 +50,7 @@ class TemplatetagTestCase(TestCase):
maxDiff = None
def test_uppercase_rel(self):
template = u"""{% load compress %}{% compress css %}
template = """{% load compress %}{% compress css %}
<link rel="StyleSheet" href="{{ STATIC_URL }}css/one.css" type="text/css">
<style type="text/css">p { border:5px solid green;}</style>
<link rel="StyleSheet" href="{{ STATIC_URL }}css/two.css" type="text/css">
@@ -59,7 +59,7 @@ class TemplatetagTestCase(TestCase):
self.assertEqual(out, render(template, self.context))
def test_nonascii_css_tag(self):
template = u"""{% load compress %}{% compress css %}
template = """{% load compress %}{% compress css %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/nonasc.css" type="text/css">
<style type="text/css">p { border:5px solid green;}</style>
{% endcompress %}
@@ -68,40 +68,40 @@ class TemplatetagTestCase(TestCase):
self.assertEqual(out, render(template, self.context))
def test_js_tag(self):
template = u"""{% load compress %}{% compress js %}
template = """{% load compress %}{% compress js %}
<script src="{{ STATIC_URL }}js/one.js" type="text/javascript"></script>
<script type="text/javascript">obj.value = "value";</script>
{% endcompress %}
"""
out = u'<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
out = '<script type="text/javascript" src="/static/CACHE/js/066cd253eada.js"></script>'
self.assertEqual(out, render(template, self.context))
def test_nonascii_js_tag(self):
template = u"""{% load compress %}{% compress js %}
template = """{% load compress %}{% compress js %}
<script src="{{ STATIC_URL }}js/nonasc.js" type="text/javascript"></script>
<script type="text/javascript">var test_value = "\u2014";</script>
{% endcompress %}
"""
out = u'<script type="text/javascript" src="/static/CACHE/js/e214fe629b28.js"></script>'
out = '<script type="text/javascript" src="/static/CACHE/js/e214fe629b28.js"></script>'
self.assertEqual(out, render(template, self.context))
def test_nonascii_latin1_js_tag(self):
template = u"""{% load compress %}{% compress js %}
template = """{% load compress %}{% compress js %}
<script src="{{ STATIC_URL }}js/nonasc-latin1.js" type="text/javascript" charset="latin-1"></script>
<script type="text/javascript">var test_value = "\u2014";</script>
{% endcompress %}
"""
out = u'<script type="text/javascript" src="/static/CACHE/js/be9e078b5ca7.js"></script>'
out = '<script type="text/javascript" src="/static/CACHE/js/be9e078b5ca7.js"></script>'
self.assertEqual(out, render(template, self.context))
def test_compress_tag_with_illegal_arguments(self):
template = u"""{% load compress %}{% compress pony %}
template = """{% load compress %}{% compress pony %}
<script type="pony/application">unicorn</script>
{% endcompress %}"""
self.assertRaises(TemplateSyntaxError, render, template, {})
def test_debug_toggle(self):
template = u"""{% load compress %}{% compress js %}
template = """{% load compress %}{% compress js %}
<script src="{{ STATIC_URL }}js/one.js" type="text/javascript"></script>
<script type="text/javascript">obj.value = "value";</script>
{% endcompress %}
@@ -111,12 +111,12 @@ class TemplatetagTestCase(TestCase):
GET = {settings.COMPRESS_DEBUG_TOGGLE: 'true'}
context = dict(self.context, request=MockDebugRequest())
out = u"""<script src="/static/js/one.js" type="text/javascript"></script>
out = """<script src="/static/js/one.js" type="text/javascript"></script>
<script type="text/javascript">obj.value = "value";</script>"""
self.assertEqual(out, render(template, context))
def test_named_compress_tag(self):
template = u"""{% load compress %}{% compress js inline foo %}
template = """{% load compress %}{% compress js inline foo %}
<script type="text/javascript">obj.value = "value";</script>
{% endcompress %}
"""
@@ -151,14 +151,14 @@ class PrecompilerTemplatetagTestCase(TestCase):
settings.COMPRESS_PRECOMPILERS = self.old_precompilers
def test_compress_coffeescript_tag(self):
template = u"""{% load compress %}{% compress js %}
template = """{% load compress %}{% compress js %}
<script type="text/coffeescript"># this is a comment.</script>
{% endcompress %}"""
out = script(src="/static/CACHE/js/e920d58f166d.js")
self.assertEqual(out, render(template, self.context))
def test_compress_coffeescript_tag_and_javascript_tag(self):
template = u"""{% load compress %}{% compress js %}
template = """{% load compress %}{% compress js %}
<script type="text/coffeescript"># this is a comment.</script>
<script type="text/javascript"># this too is a comment.</script>
{% endcompress %}"""
@@ -169,7 +169,7 @@ class PrecompilerTemplatetagTestCase(TestCase):
self.old_enabled = settings.COMPRESS_ENABLED
settings.COMPRESS_ENABLED = False
try:
template = u"""{% load compress %}{% compress js %}
template = """{% load compress %}{% compress js %}
<script type="text/coffeescript"># this is a comment.</script>
<script type="text/javascript"># this too is a comment.</script>
{% endcompress %}"""
@@ -183,7 +183,7 @@ class PrecompilerTemplatetagTestCase(TestCase):
self.old_enabled = settings.COMPRESS_ENABLED
settings.COMPRESS_ENABLED = False
try:
template = u"""{% load compress %}{% compress js %}
template = """{% load compress %}{% compress js %}
<script type="text/coffeescript"># this is a comment.</script>
{% endcompress %}"""
out = script("# this is a comment.\n")
@@ -195,7 +195,7 @@ class PrecompilerTemplatetagTestCase(TestCase):
self.old_enabled = settings.COMPRESS_ENABLED
settings.COMPRESS_ENABLED = False
try:
template = u"""
template = """
{% load compress %}{% compress js %}
<script type="text/coffeescript" src="{{ STATIC_URL }}js/one.coffee">
</script>
@@ -210,7 +210,7 @@ class PrecompilerTemplatetagTestCase(TestCase):
self.old_enabled = settings.COMPRESS_ENABLED
settings.COMPRESS_ENABLED = False
try:
template = u"""
template = """
{% load compress %}{% compress js %}
<script type="text/coffeescript" src="{{ STATIC_URL }}js/one.coffee">
</script>
@@ -232,7 +232,7 @@ class PrecompilerTemplatetagTestCase(TestCase):
settings.COMPRESS_ENABLED = False
assert(settings.COMPRESS_PRECOMPILERS)
try:
template = u"""
template = """
{% load compress %}{% compress css %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/one.css"></link>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/two.css"></link>
@@ -250,7 +250,7 @@ class PrecompilerTemplatetagTestCase(TestCase):
settings.COMPRESS_ENABLED = False
assert(settings.COMPRESS_PRECOMPILERS)
try:
template = u"""
template = """
{% load compress %}{% compress css %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/one.css"/>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/two.css"/>
@@ -272,9 +272,9 @@ def script(content="", src="", scripttype="text/javascript"):
>>> script('#this is a comment', scripttype="text/applescript")
'<script type="text/applescript">#this is a comment</script>'
"""
out_script = u'<script '
out_script = '<script '
if scripttype:
out_script += u'type="%s" ' % scripttype
out_script += 'type="%s" ' % scripttype
if src:
out_script += u'src="%s" ' % src
return out_script[:-1] + u'>%s</script>' % content
out_script += 'src="%s" ' % src
return out_script[:-1] + '>%s</script>' % content

View File

@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
from django.utils import six
from compressor.exceptions import FilterError
@@ -10,15 +13,14 @@ def get_class(class_string, exception=FilterError):
"""
if not hasattr(class_string, '__bases__'):
try:
class_string = class_string.encode('ascii')
class_string = str(class_string)
mod_name, class_name = get_mod_func(class_string)
if class_name != '':
cls = getattr(__import__(mod_name, {}, {}, ['']), class_name)
if class_name:
return getattr(__import__(mod_name, {}, {}, [str('')]), class_name)
except (ImportError, AttributeError):
pass
else:
return cls
raise exception('Failed to import %s' % class_string)
raise exception('Failed to import %s' % class_string)
raise exception("Invalid class path '%s'" % class_string)
def get_mod_func(callback):
@@ -48,7 +50,7 @@ def find_command(cmd, paths=None, pathext=None):
"""
if paths is None:
paths = os.environ.get('PATH', '').split(os.pathsep)
if isinstance(paths, basestring):
if isinstance(paths, six.string_types):
paths = [paths]
# check if there are funny path extensions for executables, e.g. Windows
if pathext is None:

View File

@@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import absolute_import, unicode_literals
from django.core.exceptions import ImproperlyConfigured

View File

@@ -6,8 +6,12 @@ An implementation of the advanced string formatting (PEP 3101).
Author: Florent Xicluna
"""
from __future__ import unicode_literals
import re
from django.utils import six
_format_str_re = re.compile(
r'((?<!{)(?:{{)+' # '{{'
r'|(?:}})+(?!})' # '}}
@@ -128,7 +132,7 @@ def _format_field(value, parts, conv, spec, want_bytes=False):
value = value.strftime(str(spec))
else:
value = _strformat(value, spec)
if want_bytes and isinstance(value, unicode):
if want_bytes and isinstance(value, six.text_type):
return str(value)
return value
@@ -138,9 +142,9 @@ class FormattableString(object):
The method format() behaves like str.format() in python 2.6+.
>>> FormattableString(u'{a:5}').format(a=42)
... # Same as u'{a:5}'.format(a=42)
u' 42'
>>> FormattableString('{a:5}').format(a=42)
... # Same as '{a:5}'.format(a=42)
' 42'
"""
@@ -244,13 +248,13 @@ def selftest():
import datetime
F = FormattableString
assert F(u"{0:{width}.{precision}s}").format('hello world',
width=8, precision=5) == u'hello '
assert F("{0:{width}.{precision}s}").format('hello world',
width=8, precision=5) == 'hello '
d = datetime.date(2010, 9, 7)
assert F(u"The year is {0.year}").format(d) == u"The year is 2010"
assert F(u"Tested on {0:%Y-%m-%d}").format(d) == u"Tested on 2010-09-07"
print 'Test successful'
assert F("The year is {0.year}").format(d) == "The year is 2010"
assert F("Tested on {0:%Y-%m-%d}").format(d) == "Tested on 2010-09-07"
print('Test successful')
if __name__ == '__main__':
selftest()

View File

@@ -1,9 +1,7 @@
flake8
django-discover-runner
coverage
unittest2
BeautifulSoup==3.2.0
html5lib
mock
jinja2
lxml
lxml

View File

@@ -1,3 +1,4 @@
from __future__ import print_function
import os
import re
import sys
@@ -8,7 +9,9 @@ from setuptools import setup, find_packages
def read(*parts):
return codecs.open(os.path.join(os.path.dirname(__file__), *parts)).read()
filename = os.path.join(os.path.dirname(__file__), *parts)
with codecs.open(filename, encoding='utf-8') as fp:
return fp.read()
def find_version(*file_paths):
@@ -76,9 +79,8 @@ def find_package_data(where='.', package='',
if (fnmatchcase(name, pattern) or fn.lower() == pattern.lower()):
bad_name = True
if show_ignored:
print >> sys.stderr, (
"Directory %s ignored by pattern %s"
% (fn, pattern))
print("Directory %s ignored by pattern %s" %
(fn, pattern), file=sys.stderr)
break
if bad_name:
continue
@@ -97,9 +99,8 @@ def find_package_data(where='.', package='',
if (fnmatchcase(name, pattern) or fn.lower() == pattern.lower()):
bad_name = True
if show_ignored:
print >> sys.stderr, (
"File %s ignored by pattern %s"
% (fn, pattern))
print("File %s ignored by pattern %s" %
(fn, pattern), file=sys.stderr)
break
if bad_name:
continue
@@ -126,6 +127,8 @@ setup(
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Topic :: Internet :: WWW/HTTP',
],
zip_safe=False,

97
tox.ini Normal file
View File

@@ -0,0 +1,97 @@
[tox]
distribute = False
envlist =
py33-1.5.X,
py32-1.5.X,
py27-1.5.X,
py26-1.5.X,
py27-1.4.X,
py26-1.4.X
[testenv]
downloadcache = {toxworkdir}/_download/
commands =
django-admin.py --version
make test
[testenv:py33-1.5.X]
basepython = python3.3
deps =
Django>=1.5,<1.6
flake8
django-discover-runner
coverage
html5lib
mock
jinja2
lxml
BeautifulSoup4
[testenv:py32-1.5.X]
basepython = python3.2
deps =
Django>=1.5,<1.6
flake8
django-discover-runner
coverage
html5lib
mock
jinja2
lxml
BeautifulSoup4
[testenv:py27-1.5.X]
basepython = python2.7
deps =
Django>=1.5,<1.6
flake8
django-discover-runner
coverage
html5lib
mock
jinja2
lxml
BeautifulSoup
unittest2
[testenv:py26-1.5.X]
basepython = python2.6
deps =
Django>=1.5,<1.6
flake8
django-discover-runner
coverage
html5lib
mock
jinja2
lxml
BeautifulSoup
unittest2
[testenv:py27-1.4.X]
basepython = python2.7
deps =
Django>=1.4,<1.5
flake8
django-discover-runner
coverage
html5lib
mock
jinja2
lxml
BeautifulSoup
unittest2
[testenv:py26-1.4.X]
basepython = python2.6
deps =
Django>=1.4,<1.5
flake8
django-discover-runner
coverage
html5lib
mock
jinja2
lxml
BeautifulSoup
unittest2