summaryrefslogtreecommitdiff
path: root/compressor/base.py
blob: 0f88d108b66c277dd020c412bf8ec0541a89abe6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import os
from itertools import chain

from django.template.loader import render_to_string
from django.core.files.base import ContentFile

from compressor.cache import get_hexdigest, get_mtime
from compressor.conf import settings
from compressor.exceptions import UncompressableFileError
from compressor.storage import default_storage
from compressor.utils import get_class, cached_property

class Compressor(object):

    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.charset = settings.DEFAULT_CHARSET

    def split_contents(self):
        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))
        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)
        return filename

    @cached_property
    def parser(self):
        return get_class(settings.COMPRESS_PARSER)(self.content)

    @cached_property
    def cached_filters(self):
        return [get_class(filter_cls) for filter_cls in self.filters]

    @cached_property
    def mtimes(self):
        for kind, value, elem in self.split_contents():
            if kind == 'file':
                yield str(get_mtime(value))

    @cached_property
    def cachekey(self):
        cachestr = "".join(
            chain([self.content], self.mtimes)).encode(self.charset)
        return "django_compressor.%s" % get_hexdigest(cachestr)[:12]

    @cached_property
    def storage(self):
        return default_storage

    @cached_property
    def hunks(self):
        for kind, value, elem in self.split_contents():
            attribs = self.parser.elem_attribs(elem)
            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, "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):
        return "\n".join((hunk.encode(self.charset) for hunk in self.hunks))

    def filter(self, content, method, **kwargs):
        for filter_cls in self.cached_filters:
            filter_func = getattr(
                filter_cls(content, filter_type=self.type), method)
            try:
                if callable(filter_func):
                    content = filter_func(**kwargs)
            except NotImplementedError:
                pass
        return content

    @cached_property
    def combined(self):
        return self.filter(self.concat(), 'output')

    @cached_property
    def hash(self):
        return get_hexdigest(self.combined)[:12]

    @cached_property
    def new_filepath(self):
        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):
            return False
        self.storage.save(self.new_filepath, ContentFile(self.combined))
        return True

    def output(self, forced=False):
        if not settings.COMPRESS_ENABLED and not forced:
            return self.content
        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):
        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)