author | Giulio Cesare Solaroli <giulio.cesare@solaroli.it> | 2011-10-03 16:04:12 (UTC) |
---|---|---|
committer | Giulio Cesare Solaroli <giulio.cesare@solaroli.it> | 2011-10-03 16:04:12 (UTC) |
commit | 541bb378ddece2eab135a8066a16994e94436dea (patch) (unidiff) | |
tree | ff160ea3e26f7fe07fcfd401387c5a0232ca715e /scripts/builder/jsmin.py | |
parent | 1bf431fd3d45cbdf4afa3e12afefe5d24f4d3bc7 (diff) | |
parent | ecad5e895831337216544e81f1a467e0c68c4a6a (diff) | |
download | clipperz-541bb378ddece2eab135a8066a16994e94436dea.zip clipperz-541bb378ddece2eab135a8066a16994e94436dea.tar.gz clipperz-541bb378ddece2eab135a8066a16994e94436dea.tar.bz2 |
Merge pull request #1 from gcsolaroli/master
First version of the restructured repository
-rw-r--r-- | scripts/builder/jsmin.py | 246 |
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 | |||
4 | import 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 | |||
36 | from StringIO import StringIO | ||
37 | |||
38 | def 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 | |||
47 | def 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 | |||
54 | class UnterminatedComment(Exception): | ||
55 | pass | ||
56 | |||
57 | class UnterminatedStringLiteral(Exception): | ||
58 | pass | ||
59 | |||
60 | class UnterminatedRegularExpression(Exception): | ||
61 | pass | ||
62 | |||
63 | class 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 | |||
218 | def 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 | ||