blob: 7f110c9d3d25c9fd972fae38c25d563e5075c633 [file] [log] [blame]
# 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')