1#!/usr/local/bin/python3.8
2
3# This code is original from jsmin by Douglas Crockford, it was translated to
4# Python by Baruch Even. It was rewritten by Dave St.Germain for speed.
5#
6# The MIT License (MIT)
7#
8# Copyright (c) 2013 Dave St.Germain
9#
10# Permission is hereby granted, free of charge, to any person obtaining a copy
11# of this software and associated documentation files (the "Software"), to deal
12# in the Software without restriction, including without limitation the rights
13# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14# copies of the Software, and to permit persons to whom the Software is
15# furnished to do so, subject to the following conditions:
16#
17# The above copyright notice and this permission notice shall be included in
18# all copies or substantial portions of the Software.
19#
20# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26# THE SOFTWARE.
27
28
29import sys
30is_3 = sys.version_info >= (3, 0)
31if is_3:
32    import io
33else:
34    import StringIO
35    try:
36        import cStringIO
37    except ImportError:
38        cStringIO = None
39
40
41__all__ = ['jsmin', 'JavascriptMinify']
42__version__ = '2.1.6'
43
44
45def jsmin(js, **kwargs):
46    """
47    returns a minified version of the javascript string
48    """
49    if not is_3:
50        if cStringIO and not isinstance(js, unicode):
51            # strings can use cStringIO for a 3x performance
52            # improvement, but unicode (in python2) cannot
53            klass = cStringIO.StringIO
54        else:
55            klass = StringIO.StringIO
56    else:
57        klass = io.StringIO
58    ins = klass(js)
59    outs = klass()
60    JavascriptMinify(ins, outs, **kwargs).minify()
61    return outs.getvalue()
62
63
64class JavascriptMinify(object):
65    """
66    Minify an input stream of javascript, writing
67    to an output stream
68    """
69
70    def __init__(self, instream=None, outstream=None, quote_chars="'\""):
71        self.ins = instream
72        self.outs = outstream
73        self.quote_chars = quote_chars
74
75    def minify(self, instream=None, outstream=None):
76        if instream and outstream:
77            self.ins, self.outs = instream, outstream
78
79        self.is_return = False
80        self.return_buf = ''
81
82        def write(char):
83            # all of this is to support literal regular expressions.
84            # sigh
85            if char in 'return':
86                self.return_buf += char
87                self.is_return = self.return_buf == 'return'
88            else:
89                self.return_buf = ''
90            self.outs.write(char)
91            if self.is_return:
92                self.return_buf = ''
93
94        read = self.ins.read
95
96        space_strings = "abcdefghijklmnopqrstuvwxyz"\
97        "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\"
98        self.space_strings = space_strings
99        starters, enders = '{[(+-', '}])+-/' + self.quote_chars
100        newlinestart_strings = starters + space_strings + self.quote_chars
101        newlineend_strings = enders + space_strings + self.quote_chars
102        self.newlinestart_strings = newlinestart_strings
103        self.newlineend_strings = newlineend_strings
104
105        do_newline = False
106        do_space = False
107        escape_slash_count = 0
108        in_quote = ''
109        quote_buf = []
110
111        previous = ';'
112        previous_non_space = ';'
113        next1 = read(1)
114
115        while next1:
116            next2 = read(1)
117            if in_quote:
118                quote_buf.append(next1)
119
120                if next1 == in_quote:
121                    numslashes = 0
122                    for c in reversed(quote_buf[:-1]):
123                        if c != '\\':
124                            break
125                        else:
126                            numslashes += 1
127                    if numslashes % 2 == 0:
128                        in_quote = ''
129                        write(''.join(quote_buf))
130            elif next1 in '\r\n':
131                next2, do_newline = self.newline(
132                    previous_non_space, next2, do_newline)
133            elif next1 < '!':
134                if (previous_non_space in space_strings \
135                    or previous_non_space > '~') \
136                    and (next2 in space_strings or next2 > '~'):
137                    do_space = True
138                elif previous_non_space in '-+' and next2 == previous_non_space:
139                    # protect against + ++ or - -- sequences
140                    do_space = True
141                elif self.is_return and next2 == '/':
142                    # returning a regex...
143                    write(' ')
144            elif next1 == '/':
145                if do_space:
146                    write(' ')
147                if next2 == '/':
148                    # Line comment: treat it as a newline, but skip it
149                    next2 = self.line_comment(next1, next2)
150                    next1 = '\n'
151                    next2, do_newline = self.newline(
152                        previous_non_space, next2, do_newline)
153                elif next2 == '*':
154                    self.block_comment(next1, next2)
155                    next2 = read(1)
156                    if previous_non_space in space_strings:
157                        do_space = True
158                    next1 = previous
159                else:
160                    if previous_non_space in '{(,=:[?!&|;' or self.is_return:
161                        self.regex_literal(next1, next2)
162                        # hackish: after regex literal next1 is still /
163                        # (it was the initial /, now it's the last /)
164                        next2 = read(1)
165                    else:
166                        write('/')
167            else:
168                if do_newline:
169                    write('\n')
170                    do_newline = False
171                    do_space = False
172                if do_space:
173                    do_space = False
174                    write(' ')
175
176                write(next1)
177                if next1 in self.quote_chars:
178                    in_quote = next1
179                    quote_buf = []
180
181            if next1 >= '!':
182                previous_non_space = next1
183
184            if next1 == '\\':
185                escape_slash_count += 1
186            else:
187                escape_slash_count = 0
188
189            previous = next1
190            next1 = next2
191
192    def regex_literal(self, next1, next2):
193        assert next1 == '/'  # otherwise we should not be called!
194
195        self.return_buf = ''
196
197        read = self.ins.read
198        write = self.outs.write
199
200        in_char_class = False
201
202        write('/')
203
204        next = next2
205        while next != '/' or in_char_class:
206            write(next)
207            if next == '\\':
208                write(read(1))  # whatever is next is escaped
209            elif next == '[':
210                write(read(1))  # character class cannot be empty
211                in_char_class = True
212            elif next == ']':
213                in_char_class = False
214            next = read(1)
215
216        write('/')
217
218    def line_comment(self, next1, next2):
219        assert next1 == next2 == '/'
220
221        read = self.ins.read
222
223        while next1 and next1 not in '\r\n':
224            next1 = read(1)
225        while next1 and next1 in '\r\n':
226            next1 = read(1)
227
228        return next1
229
230    def block_comment(self, next1, next2):
231        assert next1 == '/'
232        assert next2 == '*'
233
234        read = self.ins.read
235
236        # Skip past first /* and avoid catching on /*/...*/
237        next1 = read(1)
238        next2 = read(1)
239        while next1 != '*' or next2 != '/':
240            next1 = next2
241            next2 = read(1)
242
243    def newline(self, previous_non_space, next2, do_newline):
244        read = self.ins.read
245
246        if previous_non_space and (
247                        previous_non_space in self.newlineend_strings
248                        or previous_non_space > '~'):
249            while 1:
250                if next2 < '!':
251                    next2 = read(1)
252                    if not next2:
253                        break
254                else:
255                    if next2 in self.newlinestart_strings \
256                            or next2 > '~' or next2 == '/':
257                        do_newline = True
258                    break
259
260        return next2, do_newline
261
262if __name__ == '__main__':
263	import sys, os, glob
264
265#for f in sys.argv[1:]:
266#    with open(f, 'r') as js:
267#        minifier = JavascriptMinify(js, sys.stdout)
268#        minifier.minify()
269#    sys.stdout.write('\n')
270
271	minifier = JavascriptMinify(sys.stdin, sys.stdout)
272	minifier.minify()
273	sys.stdout.write('\n')
274
275