Skip to content

Commit

Permalink
Refactored precompilation to use mimetypes only (no filename matching…
Browse files Browse the repository at this point in the history
… anymore) and new-style string formatting for the commands.
  • Loading branch information
jezdez committed Apr 7, 2011
1 parent e1d230e commit 931374a
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 112 deletions.
57 changes: 16 additions & 41 deletions compressor/base.py
Expand Up @@ -26,7 +26,6 @@ def __init__(self, content=None, output_prefix="compressed"):
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 = {}
Expand Down Expand Up @@ -78,19 +77,18 @@ def hunks(self):
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)
Expand All @@ -101,41 +99,18 @@ def hunks(self):
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):
Expand Down Expand Up @@ -174,7 +149,7 @@ def output(self, mode='file', forced=False):
# 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:
Expand Down
22 changes: 12 additions & 10 deletions compressor/filters/base.py
Expand Up @@ -5,7 +5,7 @@

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")

Expand Down Expand Up @@ -38,26 +38,28 @@ def __init__(self, content, filter_type=None, verbose=0, command=None):
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)
Expand All @@ -74,7 +76,7 @@ def output(self, **kwargs):
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()
Expand Down
34 changes: 9 additions & 25 deletions compressor/settings.py
Expand Up @@ -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'
Expand Down Expand Up @@ -124,15 +116,7 @@ def configure_offline_context(self, value):
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
3 changes: 3 additions & 0 deletions compressor/tests/tests.py
Expand Up @@ -101,10 +101,13 @@ def test_js_output(self):
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>'
Expand Down
62 changes: 26 additions & 36 deletions docs/index.txt
Expand Up @@ -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:
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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::

COMPRESS_PRECOMPILERS = {
"*.less": {
"command": "lessc %(infile)s %(outfile)s",
"mimetype": "text/less",
},
}
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 = (
('text/less', 'lessc {infile} {outfile}'),
)

0 comments on commit 931374a

Please sign in to comment.