Refactored precompilation to use mimetypes only (no filename matching anymore) and new-style string formatting for the commands.
This commit is contained in:
@@ -26,7 +26,6 @@ class Compressor(object):
|
||||
self.content = content or ""
|
||||
self.output_prefix = output_prefix
|
||||
self.charset = settings.DEFAULT_CHARSET
|
||||
self.precompilers = settings.COMPRESS_PRECOMPILERS
|
||||
self.storage = default_storage
|
||||
self.split_content = []
|
||||
self.extra_context = {}
|
||||
@@ -78,19 +77,18 @@ class Compressor(object):
|
||||
if kind == "hunk":
|
||||
# Let's cast BeautifulSoup element to unicode here since
|
||||
# it will try to encode using ascii internally later
|
||||
yield unicode(
|
||||
self.filter(value, method="input", elem=elem, kind=kind))
|
||||
yield unicode(self.filter(
|
||||
value, method="input", elem=elem, kind=kind))
|
||||
elif kind == "file":
|
||||
content = ""
|
||||
fd = open(value, 'rb')
|
||||
try:
|
||||
fd = open(value, 'rb')
|
||||
try:
|
||||
content = fd.read()
|
||||
finally:
|
||||
fd.close()
|
||||
content = fd.read()
|
||||
except IOError, e:
|
||||
raise UncompressableFileError(
|
||||
"IOError while processing '%s': %s" % (value, e))
|
||||
finally:
|
||||
fd.close()
|
||||
content = self.filter(content,
|
||||
method="input", filename=value, elem=elem, kind=kind)
|
||||
attribs = self.parser.elem_attribs(elem)
|
||||
@@ -101,41 +99,18 @@ class Compressor(object):
|
||||
def concat(self):
|
||||
return '\n'.join((hunk.encode(self.charset) for hunk in self.hunks))
|
||||
|
||||
def matches_patterns(self, path, patterns=[]):
|
||||
"""
|
||||
Return True or False depending on whether the ``path`` matches the
|
||||
list of give the given patterns.
|
||||
"""
|
||||
if not isinstance(patterns, (list, tuple)):
|
||||
patterns = (patterns,)
|
||||
for pattern in patterns:
|
||||
if fnmatch.fnmatchcase(path, pattern):
|
||||
return True
|
||||
return False
|
||||
|
||||
def compiler_options(self, kind, filename, elem):
|
||||
if kind == "file" and filename:
|
||||
for patterns, options in self.precompilers.items():
|
||||
if self.matches_patterns(filename, patterns):
|
||||
yield options
|
||||
elif kind == "hunk" and elem is not None:
|
||||
# get the mimetype of the file and handle "text/<type>" cases
|
||||
attrs = self.parser.elem_attribs(elem)
|
||||
mimetype = attrs.get("type", "").split("/")[-1]
|
||||
for options in self.precompilers.values():
|
||||
if (mimetype and
|
||||
mimetype == options.get("mimetype", "").split("/")[-1]):
|
||||
yield options
|
||||
|
||||
def precompile(self, content, kind=None, elem=None, filename=None, **kwargs):
|
||||
if not kind:
|
||||
return content
|
||||
for options in self.compiler_options(kind, filename, elem):
|
||||
command = options.get("command")
|
||||
if command is None:
|
||||
continue
|
||||
content = CompilerFilter(content,
|
||||
filter_type=self.type, command=command).output(**kwargs)
|
||||
attrs = self.parser.elem_attribs(elem)
|
||||
mimetype = attrs.get("type", None)
|
||||
if mimetype is not None:
|
||||
for mimetypes, command in settings.COMPRESS_PRECOMPILERS:
|
||||
if not isinstance(mimetypes, (list, tuple)):
|
||||
mimetypes = (mimetypes,)
|
||||
if mimetype in mimetypes:
|
||||
content = CompilerFilter(content, filter_type=self.type,
|
||||
command=command).output(**kwargs)
|
||||
return content
|
||||
|
||||
def filter(self, content, method, **kwargs):
|
||||
@@ -174,7 +149,7 @@ class Compressor(object):
|
||||
# including precompilation (or if it's forced)
|
||||
if settings.COMPRESS_ENABLED or forced:
|
||||
content = self.combined
|
||||
elif self.precompilers:
|
||||
elif settings.COMPRESS_PRECOMPILERS:
|
||||
# or concatting it, if pre-compilation is enabled
|
||||
content = self.concat
|
||||
else:
|
||||
|
||||
@@ -5,7 +5,7 @@ import tempfile
|
||||
|
||||
from compressor.conf import settings
|
||||
from compressor.exceptions import FilterError
|
||||
from compressor.utils import cmd_split
|
||||
from compressor.utils import cmd_split, FormattableString
|
||||
|
||||
logger = logging.getLogger("compressor.filters")
|
||||
|
||||
@@ -38,26 +38,28 @@ class CompilerFilter(FilterBase):
|
||||
self.command = command
|
||||
if self.command is None:
|
||||
raise FilterError("Required command attribute not set")
|
||||
self.options = {}
|
||||
self.stdout = subprocess.PIPE
|
||||
self.stdin = subprocess.PIPE
|
||||
self.stderr = subprocess.PIPE
|
||||
|
||||
def output(self, **kwargs):
|
||||
infile = outfile = ""
|
||||
infile = None
|
||||
outfile = None
|
||||
options = {}
|
||||
try:
|
||||
if "%(infile)s" in self.command:
|
||||
if "{infile}" in self.command:
|
||||
infile = tempfile.NamedTemporaryFile(mode='w')
|
||||
infile.write(self.content)
|
||||
infile.flush()
|
||||
self.options["infile"] = infile.name
|
||||
if "%(outfile)s" in self.command:
|
||||
options["infile"] = infile.name
|
||||
if "{outfile}" in self.command:
|
||||
ext = ".%s" % self.type and self.type or ""
|
||||
outfile = tempfile.NamedTemporaryFile(mode='w', suffix=ext)
|
||||
self.options["outfile"] = outfile.name
|
||||
proc = subprocess.Popen(cmd_split(self.command % self.options),
|
||||
options["outfile"] = outfile.name
|
||||
cmd = FormattableString(self.command).format(**options)
|
||||
proc = subprocess.Popen(cmd_split(cmd),
|
||||
stdout=self.stdout, stdin=self.stdin, stderr=self.stderr)
|
||||
if infile:
|
||||
if infile is not None:
|
||||
filtered, err = proc.communicate()
|
||||
else:
|
||||
filtered, err = proc.communicate(self.content)
|
||||
@@ -74,7 +76,7 @@ class CompilerFilter(FilterBase):
|
||||
raise FilterError(err)
|
||||
if self.verbose:
|
||||
self.logger.debug(err)
|
||||
if outfile:
|
||||
if outfile is not None:
|
||||
try:
|
||||
outfile_obj = open(outfile.name)
|
||||
filtered = outfile_obj.read()
|
||||
|
||||
@@ -22,20 +22,12 @@ class CompressorSettings(AppSettings):
|
||||
|
||||
CSS_FILTERS = ['compressor.filters.css_default.CssAbsoluteFilter']
|
||||
JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter']
|
||||
PRECOMPILERS = {
|
||||
# "*.coffee": {
|
||||
# "command": "coffee --compile --stdio",
|
||||
# "mimetype": "text/coffeescript",
|
||||
# },
|
||||
# "*.less": {
|
||||
# "command": "lessc %(infile)s %(outfile)s",
|
||||
# "mimetype": "text/less",
|
||||
# },
|
||||
# ("*.sass", "*.scss"): {
|
||||
# "command": "sass %(infile)s %(outfile)s",
|
||||
# "mimetype": "sass",
|
||||
# },
|
||||
}
|
||||
PRECOMPILERS = (
|
||||
# ('text/coffeescript', 'coffee --compile --stdio'),
|
||||
# ('text/less', 'lessc {infile} {outfile}'),
|
||||
# ('text/x-sass', 'sass {infile} {outfile}'),
|
||||
# ('text/x-scss', 'sass --scss {infile} {outfile}'),
|
||||
)
|
||||
CLOSURE_COMPILER_BINARY = 'java -jar compiler.jar'
|
||||
CLOSURE_COMPILER_ARGUMENTS = ''
|
||||
CSSTIDY_BINARY = 'csstidy'
|
||||
@@ -124,15 +116,7 @@ class CompressorSettings(AppSettings):
|
||||
return value
|
||||
|
||||
def configure_precompilers(self, value):
|
||||
for patterns, options in value.items():
|
||||
if options.get("command", None) is None:
|
||||
raise ImproperlyConfigured("Please specify a command "
|
||||
"for compiler with the pattern %r." % patterns)
|
||||
mimetype = options.get("mimetype", None)
|
||||
if mimetype is None:
|
||||
raise ImproperlyConfigured("Please specify a mimetype "
|
||||
"for compiler with the pattern %r." % patterns)
|
||||
if mimetype.startswith("text/"):
|
||||
options["mimetype"] = mimetype[5:]
|
||||
value[patterns].update(options)
|
||||
if not isinstance(value, (list, tuple)):
|
||||
raise ImproperlyConfigured("The COMPRESS_PRECOMPILERS setting "
|
||||
"must be a list or tuple. Check for missing commas.")
|
||||
return value
|
||||
|
||||
@@ -101,10 +101,13 @@ class CompressorTestCase(TestCase):
|
||||
def test_js_return_if_off(self):
|
||||
try:
|
||||
enabled = settings.COMPRESS_ENABLED
|
||||
precompilers = settings.COMPRESS_PRECOMPILERS
|
||||
settings.COMPRESS_ENABLED = False
|
||||
settings.COMPRESS_PRECOMPILERS = {}
|
||||
self.assertEqual(self.js, self.js_node.output())
|
||||
finally:
|
||||
settings.COMPRESS_ENABLED = enabled
|
||||
settings.COMPRESS_PRECOMPILERS = precompilers
|
||||
|
||||
def test_js_return_if_on(self):
|
||||
output = u'<script type="text/javascript" src="/media/CACHE/js/3f33b9146e12.js" charset="utf-8"></script>'
|
||||
|
||||
@@ -288,38 +288,30 @@ A list of filters that will be applied to javascript.
|
||||
COMPRESS_PRECOMPILERS
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:Default: ``{}``
|
||||
:Default: ``()``
|
||||
|
||||
A mapping of file pattern(s) to compiler options to be used on the
|
||||
matching files. The options dictionary requires two items:
|
||||
|
||||
* command
|
||||
The command to call on each of the files. Standard Python string
|
||||
formatting will be provided for the two variables ``%(infile)s`` and
|
||||
``%(outfile)s`` and will also trigger the actual creation of those
|
||||
temporary files. If not given in the command string, django_compressor
|
||||
will use ``stdin`` and ``stdout`` respectively instead.
|
||||
An iterable of two-tuples whose first item is the mimetype of the files or
|
||||
hunks you want to compile with the command specified as the second item:
|
||||
|
||||
* mimetype
|
||||
The mimetype of the file in case inline code should be compiled
|
||||
(see below).
|
||||
The mimetype of the file or inline code should that should be compiled.
|
||||
|
||||
* command
|
||||
The command to call on each of the files. Modern Python string
|
||||
formatting will be provided for the two placeholders ``{infile}`` and
|
||||
``{outfile}`` whose existence in the command string also triggers the
|
||||
actual creation of those temporary files. If not given in the command
|
||||
string, django_compressor will use ``stdin`` and ``stdout`` respectively
|
||||
instead.
|
||||
|
||||
Example::
|
||||
|
||||
COMPRESS_PRECOMPILERS = {
|
||||
"*.coffee": {
|
||||
"command": "coffee --compile --stdio",
|
||||
"mimetype": "text/coffeescript",
|
||||
},
|
||||
"*.less": {
|
||||
"command": "lessc %(infile)s %(outfile)s",
|
||||
"mimetype": "text/less",
|
||||
},
|
||||
("*.sass", "*.scss"): {
|
||||
"command": "sass %(infile)s %(outfile)s",
|
||||
"mimetype": "sass",
|
||||
},
|
||||
}
|
||||
COMPRESS_PRECOMPILERS = (
|
||||
('text/coffeescript', 'coffee --compile --stdio'),
|
||||
('text/less', 'lessc {infile} {outfile}'),
|
||||
('text/x-sass', 'sass {infile} {outfile}'),
|
||||
('text/x-scss', 'sass --scss {infile} {outfile}'),
|
||||
)
|
||||
|
||||
With that setting (and CoffeeScript_ installed), you could add the following
|
||||
code to your templates:
|
||||
@@ -347,7 +339,7 @@ The same works for less_, too:
|
||||
{% load compress %}
|
||||
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" href="/static/css/styles.less" charset="utf-8">
|
||||
<link type="text/less" rel="stylesheet" href="/static/css/styles.less" charset="utf-8">
|
||||
<style type="text/less">
|
||||
@color: #4D926F;
|
||||
|
||||
@@ -524,12 +516,10 @@ This section lists features and settings that are deprecated or removed
|
||||
in newer versions of django_compressor.
|
||||
|
||||
* ``COMPRESS_LESSC_BINARY``
|
||||
Superseded by the COMPRESS_PRECOMPILERS_ setting. Just add the following
|
||||
to your settings::
|
||||
Superseded by the COMPRESS_PRECOMPILERS_ setting. Just make sure to
|
||||
use the correct mimetype when linking to less files or adding inline
|
||||
code and add the following to your settings::
|
||||
|
||||
COMPRESS_PRECOMPILERS = {
|
||||
"*.less": {
|
||||
"command": "lessc %(infile)s %(outfile)s",
|
||||
"mimetype": "text/less",
|
||||
},
|
||||
}
|
||||
COMPRESS_PRECOMPILERS = (
|
||||
('text/less', 'lessc {infile} {outfile}'),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user