]>
Commit | Line | Data |
---|---|---|
81345200 A |
1 | # This code is original from jsmin by Douglas Crockford, it was translated to |
2 | # Python by Baruch Even. It was rewritten by Dave St.Germain for speed. | |
3 | # | |
4 | # The MIT License (MIT) | |
5 | # | |
6 | # Copyright (c) 2013 Dave St.Germain | |
7 | # | |
8 | # Permission is hereby granted, free of charge, to any person obtaining a copy | |
9 | # of this software and associated documentation files (the "Software"), to deal | |
10 | # in the Software without restriction, including without limitation the rights | |
11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
12 | # copies of the Software, and to permit persons to whom the Software is | |
13 | # furnished to do so, subject to the following conditions: | |
14 | # | |
15 | # The above copyright notice and this permission notice shall be included in | |
16 | # all copies or substantial portions of the Software. | |
17 | # | |
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
24 | # THE SOFTWARE. | |
25 | ||
26 | ||
27 | import sys | |
28 | is_3 = sys.version_info >= (3, 0) | |
29 | if is_3: | |
30 | import io | |
31 | else: | |
32 | import StringIO | |
33 | try: | |
34 | import cStringIO | |
35 | except ImportError: | |
36 | cStringIO = None | |
37 | ||
38 | ||
39 | __all__ = ['jsmin', 'JavascriptMinify'] | |
40 | __version__ = '2.0.9' | |
41 | ||
42 | ||
43 | def jsmin(js): | |
44 | """ | |
45 | returns a minified version of the javascript string | |
46 | """ | |
47 | if not is_3: | |
48 | if cStringIO and not isinstance(js, unicode): | |
49 | # strings can use cStringIO for a 3x performance | |
50 | # improvement, but unicode (in python2) cannot | |
51 | klass = cStringIO.StringIO | |
52 | else: | |
53 | klass = StringIO.StringIO | |
54 | else: | |
55 | klass = io.StringIO | |
56 | ins = klass(js) | |
57 | outs = klass() | |
58 | JavascriptMinify(ins, outs).minify() | |
59 | return outs.getvalue() | |
60 | ||
61 | ||
62 | class JavascriptMinify(object): | |
63 | """ | |
64 | Minify an input stream of javascript, writing | |
65 | to an output stream | |
66 | """ | |
67 | ||
68 | def __init__(self, instream=None, outstream=None): | |
69 | self.ins = instream | |
70 | self.outs = outstream | |
71 | ||
72 | def minify(self, instream=None, outstream=None): | |
73 | if instream and outstream: | |
74 | self.ins, self.outs = instream, outstream | |
75 | ||
76 | self.is_return = False | |
77 | self.return_buf = '' | |
78 | ||
79 | def write(char): | |
80 | # all of this is to support literal regular expressions. | |
81 | # sigh | |
82 | if char in 'return': | |
83 | self.return_buf += char | |
84 | self.is_return = self.return_buf == 'return' | |
85 | self.outs.write(char) | |
86 | if self.is_return: | |
87 | self.return_buf = '' | |
88 | ||
89 | read = self.ins.read | |
90 | ||
91 | space_strings = "abcdefghijklmnopqrstuvwxyz"\ | |
92 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\" | |
93 | starters, enders = '{[(+-', '}])+-"\'' | |
94 | newlinestart_strings = starters + space_strings | |
95 | newlineend_strings = enders + space_strings | |
96 | do_newline = False | |
97 | do_space = False | |
98 | escape_slash_count = 0 | |
99 | doing_single_comment = False | |
100 | previous_before_comment = '' | |
101 | doing_multi_comment = False | |
102 | in_re = False | |
103 | in_quote = '' | |
104 | quote_buf = [] | |
105 | ||
106 | previous = read(1) | |
107 | if previous == '\\': | |
108 | escape_slash_count += 1 | |
109 | next1 = read(1) | |
110 | if previous == '/': | |
111 | if next1 == '/': | |
112 | doing_single_comment = True | |
113 | elif next1 == '*': | |
114 | doing_multi_comment = True | |
115 | previous = next1 | |
116 | next1 = read(1) | |
117 | else: | |
118 | write(previous) | |
119 | elif not previous: | |
120 | return | |
121 | elif previous >= '!': | |
122 | if previous in "'\"": | |
123 | in_quote = previous | |
124 | write(previous) | |
125 | previous_non_space = previous | |
126 | else: | |
127 | previous_non_space = ' ' | |
128 | if not next1: | |
129 | return | |
130 | ||
131 | while 1: | |
132 | next2 = read(1) | |
133 | if not next2: | |
134 | last = next1.strip() | |
135 | if not (doing_single_comment or doing_multi_comment)\ | |
136 | and last not in ('', '/'): | |
137 | if in_quote: | |
138 | write(''.join(quote_buf)) | |
139 | write(last) | |
140 | break | |
141 | if doing_multi_comment: | |
142 | if next1 == '*' and next2 == '/': | |
143 | doing_multi_comment = False | |
144 | next2 = read(1) | |
145 | elif doing_single_comment: | |
146 | if next1 in '\r\n': | |
147 | doing_single_comment = False | |
148 | while next2 in '\r\n': | |
149 | next2 = read(1) | |
150 | if not next2: | |
151 | break | |
152 | if previous_before_comment in ')}]': | |
153 | do_newline = True | |
154 | elif previous_before_comment in space_strings: | |
155 | write('\n') | |
156 | elif in_quote: | |
157 | quote_buf.append(next1) | |
158 | ||
159 | if next1 == in_quote: | |
160 | numslashes = 0 | |
161 | for c in reversed(quote_buf[:-1]): | |
162 | if c != '\\': | |
163 | break | |
164 | else: | |
165 | numslashes += 1 | |
166 | if numslashes % 2 == 0: | |
167 | in_quote = '' | |
168 | write(''.join(quote_buf)) | |
169 | elif next1 in '\r\n': | |
170 | if previous_non_space in newlineend_strings \ | |
171 | or previous_non_space > '~': | |
172 | while 1: | |
173 | if next2 < '!': | |
174 | next2 = read(1) | |
175 | if not next2: | |
176 | break | |
177 | else: | |
178 | if next2 in newlinestart_strings \ | |
179 | or next2 > '~' or next2 == '/': | |
180 | do_newline = True | |
181 | break | |
182 | elif next1 < '!' and not in_re: | |
183 | if (previous_non_space in space_strings \ | |
184 | or previous_non_space > '~') \ | |
185 | and (next2 in space_strings or next2 > '~'): | |
186 | do_space = True | |
187 | elif previous_non_space in '-+' and next2 == previous_non_space: | |
188 | # protect against + ++ or - -- sequences | |
189 | do_space = True | |
190 | elif self.is_return and next2 == '/': | |
191 | # returning a regex... | |
192 | write(' ') | |
193 | elif next1 == '/': | |
194 | if do_space: | |
195 | write(' ') | |
196 | if in_re: | |
197 | if previous != '\\' or (not escape_slash_count % 2) or next2 in 'gimy': | |
198 | in_re = False | |
199 | write('/') | |
200 | elif next2 == '/': | |
201 | doing_single_comment = True | |
202 | previous_before_comment = previous_non_space | |
203 | elif next2 == '*': | |
204 | doing_multi_comment = True | |
205 | previous = next1 | |
206 | next1 = next2 | |
207 | next2 = read(1) | |
208 | else: | |
209 | in_re = previous_non_space in '(,=:[?!&|' or self.is_return # literal regular expression | |
210 | write('/') | |
211 | else: | |
212 | if do_space: | |
213 | do_space = False | |
214 | write(' ') | |
215 | if do_newline: | |
216 | write('\n') | |
217 | do_newline = False | |
218 | ||
219 | write(next1) | |
220 | if not in_re and next1 in "'\"": | |
221 | in_quote = next1 | |
222 | quote_buf = [] | |
223 | ||
224 | previous = next1 | |
225 | next1 = next2 | |
226 | ||
227 | if previous >= '!': | |
228 | previous_non_space = previous | |
229 | ||
230 | if previous == '\\': | |
231 | escape_slash_count += 1 | |
232 | else: | |
233 | escape_slash_count = 0 | |
234 | ||
235 | if __name__ == '__main__': | |
236 | minifier = JavascriptMinify(sys.stdin, sys.stdout) | |
237 | minifier.minify() | |
238 | sys.stdout.write('\n') |