1 | #!/usr/bin/env python |
---|
2 | # -*- coding: utf-8 -*- |
---|
3 | |
---|
4 | """ |
---|
5 | High-level CSS and JS minification class for web2py. |
---|
6 | Called by response.include_files() |
---|
7 | Created by: Ross Peoples <ross.peoples@gmail.com> |
---|
8 | Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com> |
---|
9 | """ |
---|
10 | |
---|
11 | from . import cssmin |
---|
12 | from . import jsmin |
---|
13 | import os |
---|
14 | import hashlib |
---|
15 | import re |
---|
16 | import sys |
---|
17 | PY2 = sys.version_info[0] == 2 |
---|
18 | |
---|
19 | if PY2: |
---|
20 | hashlib_md5 = hashlib.md5 |
---|
21 | else: |
---|
22 | hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8')) |
---|
23 | |
---|
24 | def open_py23(filename, mode): |
---|
25 | if PY2: |
---|
26 | f = open(filename, mode + 'b') |
---|
27 | else: |
---|
28 | f = open(filename, mode, encoding="utf8") |
---|
29 | return f |
---|
30 | |
---|
31 | def read_binary_file(filename): |
---|
32 | f = open_py23(filename, 'r') |
---|
33 | data = f.read() |
---|
34 | f.close() |
---|
35 | return data |
---|
36 | |
---|
37 | def write_binary_file(filename, data): |
---|
38 | f = open_py23(filename, 'w') |
---|
39 | f.write(data) |
---|
40 | f.close() |
---|
41 | |
---|
42 | def fix_links(css, static_path): |
---|
43 | return re.sub(r'url\((["\'])\.\./', 'url(\\1' + static_path, css) |
---|
44 | |
---|
45 | def minify(files, path_info, folder, optimize_css, optimize_js, |
---|
46 | ignore_concat=[], |
---|
47 | ignore_minify=['/jquery.js', '/anytime.js']): |
---|
48 | |
---|
49 | """ |
---|
50 | Input: |
---|
51 | files: is a list of URLs to JS and CSS files (not repeated) |
---|
52 | path_info: is the URL of a temp static folder |
---|
53 | folder: is the application folder |
---|
54 | optimize_css: is a string of the form 'concat|minify|inline' |
---|
55 | optimize_js: is a string of the form 'concat|minify|inline' |
---|
56 | (minify requires concat, inline requires concat also) |
---|
57 | |
---|
58 | Returns a new list of: |
---|
59 | - filename (absolute or relative, css or js, actual or temporary) or |
---|
60 | - ('css:inline','...css..') |
---|
61 | - ('js:inline','...js..') |
---|
62 | """ |
---|
63 | optimize_css = optimize_css or '' |
---|
64 | optimize_js = optimize_js or '' |
---|
65 | concat_css = 'concat' in optimize_css |
---|
66 | minify_css = 'minify' in optimize_css |
---|
67 | inline_css = 'inline' in optimize_css |
---|
68 | concat_js = 'concat' in optimize_js |
---|
69 | minify_js = 'minify' in optimize_js |
---|
70 | inline_js = 'inline' in optimize_js |
---|
71 | static_path, temp = path_info.rsplit('/', 1) |
---|
72 | new_files = [] |
---|
73 | css = [] |
---|
74 | js = [] |
---|
75 | processed = [] |
---|
76 | for k, filename in enumerate(files): |
---|
77 | if not filename.startswith('/') or \ |
---|
78 | any(filename.endswith(x) |
---|
79 | for x in ignore_concat): |
---|
80 | new_files.append(filename) |
---|
81 | continue |
---|
82 | |
---|
83 | abs_filename = os.path.join( |
---|
84 | folder, 'static', filename[len(static_path) + 1:]) |
---|
85 | |
---|
86 | if filename.lower().endswith('.css'): |
---|
87 | processed.append(filename) |
---|
88 | spath_info, sfilename = \ |
---|
89 | path_info.split('/'), filename.split('/') |
---|
90 | u = 0 |
---|
91 | for i, a in enumerate(sfilename): |
---|
92 | try: |
---|
93 | if a != spath_info[i]: |
---|
94 | u = i |
---|
95 | break |
---|
96 | except: |
---|
97 | pass |
---|
98 | if concat_css: |
---|
99 | contents = read_binary_file(abs_filename) |
---|
100 | replacement = '/'.join(spath_info[:u]) + '/' |
---|
101 | contents = fix_links(contents, replacement) |
---|
102 | if minify_css: |
---|
103 | css.append(cssmin.cssmin(contents)) |
---|
104 | else: |
---|
105 | css.append(contents) |
---|
106 | else: |
---|
107 | css.append(filename) |
---|
108 | elif filename.lower().endswith('.js'): |
---|
109 | processed.append(filename) |
---|
110 | if concat_js: |
---|
111 | contents = read_binary_file(abs_filename) |
---|
112 | |
---|
113 | if minify_js and \ |
---|
114 | not filename.endswith('.min.js') and \ |
---|
115 | not any(filename.endswith(x) |
---|
116 | for x in ignore_minify): |
---|
117 | js.append(jsmin.jsmin(contents)) |
---|
118 | else: |
---|
119 | js.append(contents) |
---|
120 | else: |
---|
121 | js.append(filename) |
---|
122 | dest_key = hashlib_md5(repr(processed)).hexdigest() |
---|
123 | if css and concat_css: |
---|
124 | css = '\n\n'.join(contents for contents in css) |
---|
125 | if not inline_css: |
---|
126 | temppath = os.path.join(folder, 'static', temp) |
---|
127 | if not os.path.exists(temppath): |
---|
128 | os.mkdir(temppath) |
---|
129 | dest = "compressed_%s.css" % dest_key |
---|
130 | tempfile = os.path.join(temppath, dest) |
---|
131 | write_binary_file(tempfile, css) |
---|
132 | css = path_info + '/%s' % dest |
---|
133 | new_files.append(css) |
---|
134 | else: |
---|
135 | new_files.append(('css:inline', css)) |
---|
136 | else: |
---|
137 | new_files += css |
---|
138 | if js and concat_js: |
---|
139 | js = '\n'.join(contents for contents in js) |
---|
140 | if inline_js: |
---|
141 | js = ('js:inline', js) |
---|
142 | else: |
---|
143 | temppath = os.path.join(folder, 'static', temp) |
---|
144 | if not os.path.exists(temppath): |
---|
145 | os.mkdir(temppath) |
---|
146 | dest = "compressed_%s.js" % dest_key |
---|
147 | tempfile = os.path.join(folder, 'static', temp, dest) |
---|
148 | write_binary_file(tempfile, js) |
---|
149 | js = path_info + '/%s' % dest |
---|
150 | new_files.append(js) |
---|
151 | else: |
---|
152 | new_files += js |
---|
153 | return new_files |
---|