# This code is original from jsmin by Douglas Crockford, it was translated to # Python by Baruch Even. It was rewritten by Dave St.Germain for speed. # # The MIT License (MIT) # # Copyright (c) 2013 Dave St.Germain # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. import sys is_3 = sys.version_info >= (3, 0) if is_3: import io else: import StringIO try: import cStringIO except ImportError: cStringIO = None __all__ = ['jsmin', 'JavascriptMinify'] __version__ = '2.0.9' def jsmin(js): """ returns a minified version of the javascript string """ if not is_3: if cStringIO and not isinstance(js, unicode): # strings can use cStringIO for a 3x performance # improvement, but unicode (in python2) cannot klass = cStringIO.StringIO else: klass = StringIO.StringIO else: klass = io.StringIO ins = klass(js) outs = klass() JavascriptMinify(ins, outs).minify() return outs.getvalue() class JavascriptMinify(object): """ Minify an input stream of javascript, writing to an output stream """ def __init__(self, instream=None, outstream=None): self.ins = instream self.outs = outstream def minify(self, instream=None, outstream=None): if instream and outstream: self.ins, self.outs = instream, outstream self.is_return = False self.return_buf = '' def write(char): # all of this is to support literal regular expressions. # sigh if char in 'return': self.return_buf += char self.is_return = self.return_buf == 'return' self.outs.write(char) if self.is_return: self.return_buf = '' read = self.ins.read space_strings = "abcdefghijklmnopqrstuvwxyz"\ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\" starters, enders = '{[(+-', '}])+-"\'' newlinestart_strings = starters + space_strings newlineend_strings = enders + space_strings do_newline = False do_space = False escape_slash_count = 0 doing_single_comment = False previous_before_comment = '' doing_multi_comment = False in_re = False in_quote = '' quote_buf = [] previous = read(1) if previous == '\\': escape_slash_count += 1 next1 = read(1) if previous == '/': if next1 == '/': doing_single_comment = True elif next1 == '*': doing_multi_comment = True previous = next1 next1 = read(1) else: write(previous) elif not previous: return elif previous >= '!': if previous in "'\"": in_quote = previous write(previous) previous_non_space = previous else: previous_non_space = ' ' if not next1: return while 1: next2 = read(1) if not next2: last = next1.strip() if not (doing_single_comment or doing_multi_comment)\ and last not in ('', '/'): if in_quote: write(''.join(quote_buf)) write(last) break if doing_multi_comment: if next1 == '*' and next2 == '/': doing_multi_comment = False next2 = read(1) elif doing_single_comment: if next1 in '\r\n': doing_single_comment = False while next2 in '\r\n': next2 = read(1) if not next2: break if previous_before_comment in ')}]': do_newline = True elif previous_before_comment in space_strings: write('\n') elif in_quote: quote_buf.append(next1) if next1 == in_quote: numslashes = 0 for c in reversed(quote_buf[:-1]): if c != '\\': break else: numslashes += 1 if numslashes % 2 == 0: in_quote = '' write(''.join(quote_buf)) elif next1 in '\r\n': if previous_non_space in newlineend_strings \ or previous_non_space > '~': while 1: if next2 < '!': next2 = read(1) if not next2: break else: if next2 in newlinestart_strings \ or next2 > '~' or next2 == '/': do_newline = True break elif next1 < '!' and not in_re: if (previous_non_space in space_strings \ or previous_non_space > '~') \ and (next2 in space_strings or next2 > '~'): do_space = True elif previous_non_space in '-+' and next2 == previous_non_space: # protect against + ++ or - -- sequences do_space = True elif self.is_return and next2 == '/': # returning a regex... write(' ') elif next1 == '/': if do_space: write(' ') if in_re: if previous != '\\' or (not escape_slash_count % 2) or next2 in 'gimy': in_re = False write('/') elif next2 == '/': doing_single_comment = True previous_before_comment = previous_non_space elif next2 == '*': doing_multi_comment = True previous = next1 next1 = next2 next2 = read(1) else: in_re = previous_non_space in '(,=:[?!&|' or self.is_return # literal regular expression write('/') else: if do_space: do_space = False write(' ') if do_newline: write('\n') do_newline = False write(next1) if not in_re and next1 in "'\"": in_quote = next1 quote_buf = [] previous = next1 next1 = next2 if previous >= '!': previous_non_space = previous if previous == '\\': escape_slash_count += 1 else: escape_slash_count = 0