diff options
Diffstat (limited to 'compressor/base.py')
| -rw-r--r-- | compressor/base.py | 145 |
1 files changed, 69 insertions, 76 deletions
diff --git a/compressor/base.py b/compressor/base.py index 860b0f2..0f88d10 100644 --- a/compressor/base.py +++ b/compressor/base.py @@ -1,127 +1,116 @@ import os +from itertools import chain from django.template.loader import render_to_string from django.core.files.base import ContentFile -from compressor import filters from compressor.cache import get_hexdigest, get_mtime from compressor.conf import settings from compressor.exceptions import UncompressableFileError -from compressor.utils import get_class +from compressor.storage import default_storage +from compressor.utils import get_class, cached_property class Compressor(object): - def __init__(self, content, output_prefix="compressed"): - self.content = content + def __init__(self, content=None, output_prefix="compressed"): + self.content = content or "" + self.extra_context = {} self.type = None self.output_prefix = output_prefix self.split_content = [] - self._parser = None + self.charset = settings.DEFAULT_CHARSET def split_contents(self): - raise NotImplementedError('split_contents must be defined in a subclass') + raise NotImplementedError( + "split_contents must be defined in a subclass") def get_filename(self, url): try: base_url = self.storage.base_url except AttributeError: base_url = settings.COMPRESS_URL - if not url.startswith(base_url): - raise UncompressableFileError('"%s" is not in COMPRESS_URL ("%s") and can not be compressed' % (url, base_url)) + raise UncompressableFileError( + "'%s' is not in COMPRESS_URL ('%s') and can not be compressed" + % (url, base_url)) basename = url.replace(base_url, "", 1) filename = os.path.join(settings.COMPRESS_ROOT, basename) if not os.path.exists(filename): - raise UncompressableFileError('"%s" does not exist' % filename) + raise UncompressableFileError("'%s' does not exist" % filename) return filename - def _get_parser(self): - if self._parser: - return self._parser - parser_cls = get_class(settings.COMPRESS_PARSER) - self._parser = parser_cls(self.content) - return self._parser + @cached_property + def parser(self): + return get_class(settings.COMPRESS_PARSER)(self.content) - def _set_parser(self, parser): - self._parser = parser - parser = property(_get_parser, _set_parser) + @cached_property + def cached_filters(self): + return [get_class(filter_cls) for filter_cls in self.filters] - @property + @cached_property def mtimes(self): - return [get_mtime(h[1]) for h in self.split_contents() if h[0] == 'file'] + for kind, value, elem in self.split_contents(): + if kind == 'file': + yield str(get_mtime(value)) - @property + @cached_property def cachekey(self): - cachebits = [self.content] - cachebits.extend([str(m) for m in self.mtimes]) - cachestr = "".join(cachebits).encode(settings.DEFAULT_CHARSET) + cachestr = "".join( + chain([self.content], self.mtimes)).encode(self.charset) return "django_compressor.%s" % get_hexdigest(cachestr)[:12] - @property + @cached_property def storage(self): - from compressor.storage import default_storage return default_storage - @property + @cached_property def hunks(self): - if getattr(self, '_hunks', ''): - return self._hunks - self._hunks = [] - for kind, v, elem in self.split_contents(): + for kind, value, elem in self.split_contents(): attribs = self.parser.elem_attribs(elem) - if kind == 'hunk': - input = v - if self.filters: - input = self.filter(input, 'input', elem=elem) + if kind == "hunk": # Let's cast BeautifulSoup element to unicode here since # it will try to encode using ascii internally later - self._hunks.append(unicode(input)) - if kind == 'file': - # TODO: wrap this in a try/except for IoErrors(?) - fd = open(v, 'rb') - input = fd.read() - if self.filters: - input = self.filter(input, 'input', filename=v, elem=elem) - charset = attribs.get('charset', settings.DEFAULT_CHARSET) - self._hunks.append(unicode(input, charset)) - fd.close() - return self._hunks + yield unicode(self.filter(value, "input", elem=elem)) + elif kind == "file": + content = "" + try: + fd = open(value, 'rb') + try: + content = fd.read() + finally: + fd.close() + except IOError, e: + raise UncompressableFileError( + "IOError while processing '%s': %s" % (value, e)) + content = self.filter(content, "input", filename=value, elem=elem) + yield unicode(content, attribs.get("charset", self.charset)) def concat(self): - # Design decision needed: either everything should be unicode up to - # here or we encode strings as soon as we acquire them. Currently - # concat() expects all hunks to be unicode and does the encoding - return "\n".join([hunk.encode(settings.DEFAULT_CHARSET) for hunk in self.hunks]) + return "\n".join((hunk.encode(self.charset) for hunk in self.hunks)) def filter(self, content, method, **kwargs): - for f in self.filters: - filter = getattr(get_class(f)(content, filter_type=self.type), method) + for filter_cls in self.cached_filters: + filter_func = getattr( + filter_cls(content, filter_type=self.type), method) try: - if callable(filter): - content = filter(**kwargs) + if callable(filter_func): + content = filter_func(**kwargs) except NotImplementedError: pass return content - @property + @cached_property def combined(self): - if getattr(self, '_output', ''): - return self._output - output = self.concat() - if self.filters: - output = self.filter(output, 'output') - self._output = output - return self._output - - @property + return self.filter(self.concat(), 'output') + + @cached_property def hash(self): return get_hexdigest(self.combined)[:12] - @property + @cached_property def new_filepath(self): - filename = "".join([self.hash, self.extension]) - return os.path.join( - settings.COMPRESS_OUTPUT_DIR.strip(os.sep), self.output_prefix, filename) + return os.path.join(settings.COMPRESS_OUTPUT_DIR.strip(os.sep), + self.output_prefix, "%s.%s" % (self.hash, self.type)) def save_file(self): if self.storage.exists(self.new_filepath): @@ -129,16 +118,20 @@ class Compressor(object): self.storage.save(self.new_filepath, ContentFile(self.combined)) return True - def output(self): - if not settings.COMPRESS_ENABLED: + def output(self, forced=False): + if not settings.COMPRESS_ENABLED and not forced: return self.content - self.save_file() - context = getattr(self, 'extra_context', {}) - context['url'] = self.storage.url(self.new_filepath) + context = { + "saved": self.save_file(), + "url": self.storage.url(self.new_filepath), + } + context.update(self.extra_context) return render_to_string(self.template_name, context) def output_inline(self): - context = {'content': settings.COMPRESS_ENABLED and self.combined or self.concat()} - if hasattr(self, 'extra_context'): - context.update(self.extra_context) + if settings.COMPRESS_ENABLED: + content = self.combined + else: + content = self.concat() + context = dict(content=content, **self.extra_context) return render_to_string(self.template_name_inline, context) |
