Home | History | Annotate | Download | only in bench
      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