5e13222f551e2ab37f23ea7b2d3fb2eac1cad93a
[redakcja.git] / apps / compress / utils.py
1 import os
2 import re
3 import tempfile
4
5 from django.conf import settings as django_settings
6 from django.utils.http import urlquote
7 from django.dispatch import dispatcher
8
9 from compress.conf import settings
10 from compress.signals import css_filtered, js_filtered
11
12 def get_class(class_string):
13     """
14     Convert a string version of a function name to the callable object.
15     """
16
17     if not hasattr(class_string, '__bases__'):
18
19         try:
20             class_string = class_string.encode('ascii')
21             mod_name, class_name = get_mod_func(class_string)
22             if class_name != '':
23                 class_string = getattr(__import__(mod_name, {}, {}, ['']), class_name)
24         except (ImportError, AttributeError):
25             raise Exception('Failed to import filter %s' % class_string)
26
27     return class_string
28
29 def get_mod_func(callback):
30     """
31     Converts 'django.views.news.stories.story_detail' to
32     ('django.views.news.stories', 'story_detail')
33     """
34
35     try:
36         dot = callback.rindex('.')
37     except ValueError:
38         return callback, ''
39     return callback[:dot], callback[dot+1:]
40
41 def needs_update(output_file, source_files, verbosity=0):
42     """
43     Scan the source files for changes and returns True if the output_file needs to be updated.
44     """
45
46     version = get_version(source_files)
47     
48     on = get_output_filename(output_file, version)
49     compressed_file_full = media_root(on)
50
51     if not os.path.exists(compressed_file_full):
52         return True, version
53         
54     update_needed = getattr(get_class(settings.COMPRESS_VERSIONING)(), 'needs_update')(output_file, source_files, version)
55     return update_needed
56
57 def media_root(filename):
58     """
59     Return the full path to ``filename``. ``filename`` is a relative path name in MEDIA_ROOT
60     """
61     return os.path.join(django_settings.STATIC_ROOT, filename)
62
63 def media_url(url, prefix=None):
64     if prefix:
65         return prefix + urlquote(url)
66     return django_settings.STATIC_URL + urlquote(url)
67
68 def concat(filenames, separator=''):
69     """
70     Concatenate the files from the list of the ``filenames``, ouput separated with ``separator``.
71     """
72     r = ''
73     for filename in filenames:
74         fd = open(media_root(filename), 'rb')
75         r += fd.read()
76         r += separator
77         fd.close()
78     return r
79
80 def max_mtime(files):
81     return int(max([os.stat(media_root(f)).st_mtime for f in files]))
82
83 def save_file(filename, contents):
84     dirname = os.path.dirname(media_root(filename))
85     if not os.path.exists(dirname):
86         os.makedirs(dirname)
87     fd = open(media_root(filename), 'wb+')
88     fd.write(contents)
89     fd.close()
90
91 def get_output_filename(filename, version):
92     if settings.COMPRESS_VERSION and version is not None:
93         return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, version)
94     else:
95         return filename.replace(settings.COMPRESS_VERSION_PLACEHOLDER, settings.COMPRESS_VERSION_DEFAULT)
96
97 def get_version(source_files, verbosity=0):
98     version = getattr(get_class(settings.COMPRESS_VERSIONING)(), 'get_version')(source_files)
99     return version
100     
101 def get_version_from_file(path, filename):
102     regex = re.compile(r'^%s$' % (get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'([A-Za-z0-9]+)')))
103     for f in os.listdir(path):
104         result = regex.match(f)
105         if result and result.groups():
106             return result.groups()[0]
107
108 def remove_files(path, filename, verbosity=0):    
109     regex = re.compile(r'^%s$' % (os.path.basename(get_output_filename(settings.COMPRESS_VERSION_PLACEHOLDER.join([re.escape(part) for part in filename.split(settings.COMPRESS_VERSION_PLACEHOLDER)]), r'[A-Za-z0-9]+'))))
110     if os.path.exists(path):
111         for f in os.listdir(path):
112             if regex.match(f):
113                 if verbosity >= 1:
114                     print "Removing outdated file %s" % f
115         
116                 os.unlink(os.path.join(path, f))
117
118 def filter_common(obj, verbosity, filters, attr, separator, signal):
119     output = concat(obj['source_filenames'], separator)
120     
121     filename = get_output_filename(obj['output_filename'], get_version(obj['source_filenames']))
122
123     if settings.COMPRESS_VERSION:
124         remove_files(os.path.dirname(media_root(filename)), obj['output_filename'], verbosity)
125
126     if verbosity >= 1:
127         print "Saving %s" % filename
128
129     for f in filters:
130         output = getattr(get_class(f)(verbose=(verbosity >= 2)), attr)(output)
131
132     save_file(filename, output)
133     signal.send(None)
134
135 def filter_css(css, verbosity=0):
136     return filter_common(css, verbosity, filters=settings.COMPRESS_CSS_FILTERS, attr='filter_css', separator='', signal=css_filtered)
137
138 def filter_js(js, verbosity=0):
139     return filter_common(js, verbosity, filters=settings.COMPRESS_JS_FILTERS, attr='filter_js', separator='', signal=js_filtered)