|
1 |
| -import os |
2 |
| -from BeautifulSoup import BeautifulSoup |
3 |
| - |
4 |
| -from django import template |
5 |
| -from django.conf import settings as django_settings |
6 |
| -from django.template.loader import render_to_string |
7 |
| - |
8 |
| -from django.core.cache import cache |
9 |
| -from django.core.files.base import ContentFile |
10 |
| -from django.core.files.storage import get_storage_class |
11 |
| - |
12 |
| -from compressor.conf import settings |
13 |
| -from compressor import filters |
14 |
| - |
15 |
| - |
16 |
| -register = template.Library() |
17 |
| - |
18 |
| - |
19 |
| -class UncompressableFileError(Exception): |
20 |
| - pass |
21 |
| - |
22 |
| - |
23 |
| -def get_hexdigest(plaintext): |
24 |
| - try: |
25 |
| - import hashlib |
26 |
| - return hashlib.sha1(plaintext).hexdigest() |
27 |
| - except ImportError: |
28 |
| - import sha |
29 |
| - return sha.new(plaintext).hexdigest() |
30 |
| - |
31 |
| - |
32 |
| -def get_mtime(filename): |
33 |
| - if settings.MTIME_DELAY: |
34 |
| - key = "django_compressor.mtime.%s" % filename |
35 |
| - mtime = cache.get(key) |
36 |
| - if mtime is None: |
37 |
| - mtime = os.path.getmtime(filename) |
38 |
| - cache.set(key, mtime, settings.MTIME_DELAY) |
39 |
| - return mtime |
40 |
| - return os.path.getmtime(filename) |
41 |
| - |
42 |
| -class Compressor(object): |
43 |
| - |
44 |
| - def __init__(self, content, output_prefix="compressed"): |
45 |
| - self.content = content |
46 |
| - self.type = None |
47 |
| - self.output_prefix = output_prefix |
48 |
| - self.split_content = [] |
49 |
| - |
50 |
| - def split_contents(self): |
51 |
| - raise NotImplementedError('split_contents must be defined in a subclass') |
52 |
| - |
53 |
| - def get_filename(self, url): |
54 |
| - if not url.startswith(self.storage.base_url): |
55 |
| - raise UncompressableFileError('"%s" is not in COMPRESS_URL ("%s") and can not be compressed' % (url, self.storage.base_url)) |
56 |
| - basename = url.replace(self.storage.base_url, "", 1) |
57 |
| - if not self.storage.exists(basename): |
58 |
| - raise UncompressableFileError('"%s" does not exist' % self.storage.path(basename)) |
59 |
| - if hasattr(self.storage, 'source_path'): |
60 |
| - return self.storage.source_path(basename) |
61 |
| - return self.storage.path(basename) |
62 |
| - |
63 |
| - @property |
64 |
| - def soup(self): |
65 |
| - return BeautifulSoup(self.content) |
66 |
| - |
67 |
| - @property |
68 |
| - def mtimes(self): |
69 |
| - return [get_mtime(h[1]) for h in self.split_contents() if h[0] == 'file'] |
70 |
| - |
71 |
| - @property |
72 |
| - def cachekey(self): |
73 |
| - cachebits = [self.content] |
74 |
| - cachebits.extend([str(m) for m in self.mtimes]) |
75 |
| - cachestr = "".join(cachebits).encode(django_settings.DEFAULT_CHARSET) |
76 |
| - return "django_compressor.%s" % get_hexdigest(cachestr)[:12] |
77 |
| - |
78 |
| - @property |
79 |
| - def storage(self): |
80 |
| - return get_storage_class(settings.STORAGE)() |
81 |
| - |
82 |
| - @property |
83 |
| - def hunks(self): |
84 |
| - if getattr(self, '_hunks', ''): |
85 |
| - return self._hunks |
86 |
| - self._hunks = [] |
87 |
| - for kind, v, elem in self.split_contents(): |
88 |
| - if kind == 'hunk': |
89 |
| - input = v |
90 |
| - if self.filters: |
91 |
| - input = self.filter(input, 'input', elem=elem) |
92 |
| - # Let's cast BeautifulSoup element to unicode here since |
93 |
| - # it will try to encode using ascii internally later |
94 |
| - self._hunks.append(unicode(input)) |
95 |
| - if kind == 'file': |
96 |
| - # TODO: wrap this in a try/except for IoErrors(?) |
97 |
| - fd = open(v, 'rb') |
98 |
| - input = fd.read() |
99 |
| - if self.filters: |
100 |
| - input = self.filter(input, 'input', filename=v, elem=elem) |
101 |
| - charset = elem.get('charset', django_settings.DEFAULT_CHARSET) |
102 |
| - self._hunks.append(unicode(input, charset)) |
103 |
| - fd.close() |
104 |
| - return self._hunks |
105 |
| - |
106 |
| - def concat(self): |
107 |
| - # Design decision needed: either everything should be unicode up to |
108 |
| - # here or we encode strings as soon as we acquire them. Currently |
109 |
| - # concat() expects all hunks to be unicode and does the encoding |
110 |
| - return "\n".join([hunk.encode(django_settings.DEFAULT_CHARSET) for hunk in self.hunks]) |
111 |
| - |
112 |
| - def filter(self, content, method, **kwargs): |
113 |
| - for f in self.filters: |
114 |
| - filter = getattr(filters.get_class(f)(content, filter_type=self.type), method) |
115 |
| - try: |
116 |
| - if callable(filter): |
117 |
| - content = filter(**kwargs) |
118 |
| - except NotImplementedError: |
119 |
| - pass |
120 |
| - return content |
121 |
| - |
122 |
| - @property |
123 |
| - def combined(self): |
124 |
| - if getattr(self, '_output', ''): |
125 |
| - return self._output |
126 |
| - output = self.concat() |
127 |
| - if self.filters: |
128 |
| - output = self.filter(output, 'output') |
129 |
| - self._output = output |
130 |
| - return self._output |
131 |
| - |
132 |
| - @property |
133 |
| - def hash(self): |
134 |
| - return get_hexdigest(self.combined)[:12] |
135 |
| - |
136 |
| - @property |
137 |
| - def new_filepath(self): |
138 |
| - filename = "".join([self.hash, self.extension]) |
139 |
| - return os.path.join( |
140 |
| - settings.OUTPUT_DIR.strip(os.sep), self.output_prefix, filename) |
141 |
| - |
142 |
| - def save_file(self): |
143 |
| - if self.storage.exists(self.new_filepath): |
144 |
| - return False |
145 |
| - self.storage.save(self.new_filepath, ContentFile(self.combined)) |
146 |
| - return True |
147 |
| - |
148 |
| - def output(self): |
149 |
| - if not settings.COMPRESS: |
150 |
| - return self.content |
151 |
| - self.save_file() |
152 |
| - context = getattr(self, 'extra_context', {}) |
153 |
| - context['url'] = self.storage.url(self.new_filepath) |
154 |
| - return render_to_string(self.template_name, context) |
155 |
| - |
156 |
| - |
157 |
| -class CssCompressor(Compressor): |
158 |
| - |
159 |
| - def __init__(self, content, output_prefix="css"): |
160 |
| - self.extension = ".css" |
161 |
| - self.template_name = "compressor/css.html" |
162 |
| - self.filters = ['compressor.filters.css_default.CssAbsoluteFilter'] |
163 |
| - self.filters.extend(settings.COMPRESS_CSS_FILTERS) |
164 |
| - self.type = 'css' |
165 |
| - super(CssCompressor, self).__init__(content, output_prefix) |
166 |
| - |
167 |
| - def split_contents(self): |
168 |
| - if self.split_content: |
169 |
| - return self.split_content |
170 |
| - split = self.soup.findAll({'link' : True, 'style' : True}) |
171 |
| - self.media_nodes = [] |
172 |
| - for elem in split: |
173 |
| - data = None |
174 |
| - if elem.name == 'link' and elem['rel'] == 'stylesheet': |
175 |
| - try: |
176 |
| - data = ('file', self.get_filename(elem['href']), elem) |
177 |
| - except UncompressableFileError: |
178 |
| - if django_settings.DEBUG: |
179 |
| - raise |
180 |
| - elif elem.name == 'style': |
181 |
| - data = ('hunk', elem.string, elem) |
182 |
| - if data: |
183 |
| - self.split_content.append(data) |
184 |
| - media = elem.get('media', None) |
185 |
| - # Append to the previous node if it had the same media type, |
186 |
| - # otherwise create a new node. |
187 |
| - if self.media_nodes and self.media_nodes[-1][0] == media: |
188 |
| - self.media_nodes[-1][1].split_content.append(data) |
189 |
| - else: |
190 |
| - node = CssCompressor(content='') |
191 |
| - node.split_content.append(data) |
192 |
| - self.media_nodes.append((media, node)) |
193 |
| - return self.split_content |
194 |
| - |
195 |
| - def output(self): |
196 |
| - self.split_contents() |
197 |
| - if not hasattr(self, 'media_nodes'): |
198 |
| - return super(CssCompressor, self).output() |
199 |
| - if not settings.COMPRESS: |
200 |
| - return self.content |
201 |
| - ret = [] |
202 |
| - for media, subnode in self.media_nodes: |
203 |
| - subnode.extra_context = {'media': media} |
204 |
| - ret.append(subnode.output()) |
205 |
| - return ''.join(ret) |
206 |
| - |
207 |
| - |
208 |
| -class JsCompressor(Compressor): |
209 |
| - |
210 |
| - def __init__(self, content, output_prefix="js"): |
211 |
| - self.extension = ".js" |
212 |
| - self.template_name = "compressor/js.html" |
213 |
| - self.filters = settings.COMPRESS_JS_FILTERS |
214 |
| - self.type = 'js' |
215 |
| - super(JsCompressor, self).__init__(content, output_prefix) |
216 |
| - |
217 |
| - def split_contents(self): |
218 |
| - if self.split_content: |
219 |
| - return self.split_content |
220 |
| - split = self.soup.findAll('script') |
221 |
| - for elem in split: |
222 |
| - if elem.has_key('src'): |
223 |
| - try: |
224 |
| - self.split_content.append(('file', self.get_filename(elem['src']), elem)) |
225 |
| - except UncompressableFileError: |
226 |
| - if django_settings.DEBUG: |
227 |
| - raise |
228 |
| - else: |
229 |
| - self.split_content.append(('hunk', elem.string, elem)) |
230 |
| - return self.split_content |
| 1 | +from compressor.base import Compressor |
| 2 | +from compressor.js import JsCompressor |
| 3 | +from compressor.css import CssCompressor |
| 4 | +from compressor.utils import get_hexdigest, get_mtime |
| 5 | +from compressor.exceptions import UncompressableFileError |
0 commit comments