summaryrefslogtreecommitdiff
path: root/scripts/builder/jsmin.py
Unidiff
Diffstat (limited to 'scripts/builder/jsmin.py') (more/less context) (show whitespace changes)
-rw-r--r--scripts/builder/jsmin.py246
1 files changed, 246 insertions, 0 deletions
diff --git a/scripts/builder/jsmin.py b/scripts/builder/jsmin.py
new file mode 100644
index 0000000..91d6307
--- a/dev/null
+++ b/scripts/builder/jsmin.py
@@ -0,0 +1,246 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4import os, os.path, shutil
5
6# This code is original from jsmin by Douglas Crockford, it was translated to
7# Python by Baruch Even. The original code had the following copyright and
8# license.
9#
10# /* jsmin.c
11# 2007-05-22
12#
13# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
14#
15# Permission is hereby granted, free of charge, to any person obtaining a copy of
16# this software and associated documentation files (the "Software"), to deal in
17# the Software without restriction, including without limitation the rights to
18# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
19# of the Software, and to permit persons to whom the Software is furnished to do
20# so, subject to the following conditions:
21#
22# The above copyright notice and this permission notice shall be included in all
23# copies or substantial portions of the Software.
24#
25# The Software shall be used for Good, not Evil.
26#
27# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
28# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
29# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
30# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
31# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33# SOFTWARE.
34# */
35
36from StringIO import StringIO
37
38def jsmin(js):
39 ins = StringIO(js)
40 outs = StringIO()
41 JavascriptMinify().minify(ins, outs)
42 str = outs.getvalue()
43 if len(str) > 0 and str[0] == '\n':
44 str = str[1:]
45 return str
46
47def isAlphanum(c):
48 """return true if the character is a letter, digit, underscore,
49 dollar sign, or non-ASCII character.
50 """
51 return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
52 (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
53
54class UnterminatedComment(Exception):
55 pass
56
57class UnterminatedStringLiteral(Exception):
58 pass
59
60class UnterminatedRegularExpression(Exception):
61 pass
62
63class JavascriptMinify(object):
64
65 def _outA(self):
66 self.outstream.write(self.theA)
67 def _outB(self):
68 self.outstream.write(self.theB)
69
70 def _get(self):
71 """return the next character from stdin. Watch out for lookahead. If
72 the character is a control character, translate it to a space or
73 linefeed.
74 """
75 c = self.theLookahead
76 self.theLookahead = None
77 if c == None:
78 c = self.instream.read(1)
79 if c >= ' ' or c == '\n':
80 return c
81 if c == '': # EOF
82 return '\000'
83 if c == '\r':
84 return '\n'
85 return ' '
86
87 def _peek(self):
88 self.theLookahead = self._get()
89 return self.theLookahead
90
91 def _next(self):
92 """get the next character, excluding comments. peek() is used to see
93 if an unescaped '/' is followed by a '/' or '*'.
94 """
95 c = self._get()
96 if c == '/' and self.theA != '\\':
97 p = self._peek()
98 if p == '/':
99 c = self._get()
100 while c > '\n':
101 c = self._get()
102 return c
103 if p == '*':
104 c = self._get()
105 while 1:
106 c = self._get()
107 if c == '*':
108 if self._peek() == '/':
109 self._get()
110 return ' '
111 if c == '\000':
112 raise UnterminatedComment()
113
114 return c
115
116 def _action(self, action):
117 """do something! What you do is determined by the argument:
118 1 Output A. Copy B to A. Get the next B.
119 2 Copy B to A. Get the next B. (Delete A).
120 3 Get the next B. (Delete B).
121 action treats a string as a single character. Wow!
122 action recognizes a regular expression if it is preceded by ( or , or =.
123 """
124 if action <= 1:
125 self._outA()
126
127 if action <= 2:
128 self.theA = self.theB
129 if self.theA == "'" or self.theA == '"':
130 while 1:
131 self._outA()
132 self.theA = self._get()
133 if self.theA == self.theB:
134 break
135 if self.theA <= '\n':
136 raise UnterminatedStringLiteral()
137 if self.theA == '\\':
138 self._outA()
139 self.theA = self._get()
140
141
142 if action <= 3:
143 self.theB = self._next()
144 if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
145 self.theA == '=' or self.theA == ':' or
146 self.theA == '[' or self.theA == '?' or
147 self.theA == '!' or self.theA == '&' or
148 self.theA == '|' or self.theA == ';' or
149 self.theA == '{' or self.theA == '}' or
150 self.theA == '\n'):
151 self._outA()
152 self._outB()
153 while 1:
154 self.theA = self._get()
155 if self.theA == '/':
156 break
157 elif self.theA == '\\':
158 self._outA()
159 self.theA = self._get()
160 elif self.theA <= '\n':
161 raise UnterminatedRegularExpression()
162 self._outA()
163 self.theB = self._next()
164
165
166 def _jsmin(self):
167 """Copy the input to the output, deleting the characters which are
168 insignificant to JavaScript. Comments will be removed. Tabs will be
169 replaced with spaces. Carriage returns will be replaced with linefeeds.
170 Most spaces and linefeeds will be removed.
171 """
172 self.theA = '\n'
173 self._action(3)
174
175 while self.theA != '\000':
176 if self.theA == ' ':
177 if isAlphanum(self.theB):
178 self._action(1)
179 else:
180 self._action(2)
181 elif self.theA == '\n':
182 if self.theB in ['{', '[', '(', '+', '-']:
183 self._action(1)
184 elif self.theB == ' ':
185 self._action(3)
186 else:
187 if isAlphanum(self.theB):
188 self._action(1)
189 else:
190 self._action(2)
191 else:
192 if self.theB == ' ':
193 if isAlphanum(self.theA):
194 self._action(1)
195 else:
196 self._action(3)
197 elif self.theB == '\n':
198 if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
199 self._action(1)
200 else:
201 if isAlphanum(self.theA):
202 self._action(1)
203 else:
204 self._action(3)
205 else:
206 self._action(1)
207
208 def minify(self, instream, outstream):
209 self.instream = instream
210 self.outstream = outstream
211 self.theA = '\n'
212 self.theB = None
213 self.theLookahead = None
214
215 self._jsmin()
216 self.instream.close()
217
218def compress(in_files, out_file, in_type='js', verbose=False, temp_file='.temp'):
219 temp = open(temp_file, 'w')
220 for f in in_files:
221 fh = open(f)
222 data = fh.read() + '\n'
223 fh.close()
224
225 temp.write(data)
226
227 print ' + %s' % f
228 temp.close()
229
230 out = open(out_file, 'w')
231
232 jsm = JavascriptMinify()
233 jsm.minify(open(temp_file,'r'), out)
234
235 out.close()
236
237 org_size = os.path.getsize(temp_file)
238 new_size = os.path.getsize(out_file)
239
240 print '=> %s' % out_file
241 print 'Original: %.2f kB' % (org_size / 1024.0)
242 print 'Compressed: %.2f kB' % (new_size / 1024.0)
243 print 'Reduction: %.1f%%' % (float(org_size - new_size) / org_size * 100)
244 print ''
245
246 os.remove(temp_file) \ No newline at end of file