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