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