| # 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 |
| python_text_type = str |
| else: |
| import StringIO |
| try: |
| import cStringIO |
| except ImportError: |
| cStringIO = None |
| python_text_type = basestring |
| |
| |
| __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 str(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 = '' |
| |
| def read(n): |
| char = self.ins.read(n) |
| if not isinstance(char, python_text_type): |
| raise ValueError("ERROR: The script jsmin.py can only handle text input, but it received input of type %s" % type(char)) |
| return char |
| |
| 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 str(previous) >= "!": |
| if str(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 str(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 str(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 str(next1) in "'\"`": |
| in_quote = next1 |
| quote_buf = [] |
| |
| previous = next1 |
| next1 = next2 |
| |
| if str(previous) >= '!': |
| previous_non_space = previous |
| |
| if previous == '\\': |
| escape_slash_count += 1 |
| else: |
| escape_slash_count = 0 |
| |
| if __name__ == '__main__': |
| minifier = JavascriptMinify(sys.stdin, sys.stdout) |
| minifier.minify() |
| sys.stdout.write('\n') |