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
27import sys
28is_3 = sys.version_info >= (3, 0)
29if is_3:
30    import io
31else:
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
43def 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
62class 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