1#!/usr/bin/python
2#
3# Copyright (C) 2010 Google Inc. All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9#         * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11#         * Redistributions in binary form must reproduce the above
12# copyright notice, this list of conditions and the following disclaimer
13# in the documentation and/or other materials provided with the
14# distribution.
15#         * Neither the name of Google Inc. nor the names of its
16# contributors may be used to endorse or promote products derived from
17# this software without specific prior written permission.
18#
19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30#
31# This is a Python port of Douglas Crockford's jsmin.cc. See original
32# copyright notice below.
33#
34# Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
35#
36# Permission is hereby granted, free of charge, to any person obtaining a copy of
37# this software and associated documentation files (the "Software"), to deal in
38# the Software without restriction, including without limitation the rights to
39# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
40# of the Software, and to permit persons to whom the Software is furnished to do
41# so, subject to the following conditions:
42#
43# The above copyright notice and this permission notice shall be included in all
44# copies or substantial portions of the Software.
45#
46# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
47# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
48# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
49# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
50# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
51# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
52# SOFTWARE.
53#
54
55from cStringIO import StringIO
56import sys
57
58
59class UnterminatedComment(Exception):
60    pass
61
62
63class UnterminatedStringLiteral(Exception):
64    pass
65
66
67class UnterminatedRegularExpression(Exception):
68    pass
69
70
71EOF = ''
72
73
74def jsmin(text):
75    minifier = JavaScriptMinifier()
76    minifier.input = StringIO(text)
77    minifier.output = StringIO()
78    minifier.jsmin()
79    return minifier.output.getvalue()
80
81
82class JavaScriptMinifier(object):
83
84    def isAlphanum(self, c):
85        """ return true if the character is a letter, digit, underscore,
86            dollar sign, or non-ASCII character.
87        """
88        return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
89            (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or
90            c > 126)
91
92    def get(self):
93        """ return the next character from stdin. Watch out for lookahead. If
94            the character is a control character, translate it to a space or
95            linefeed.
96        """
97        c = self.theLookahead
98        self.theLookahead = EOF
99        if c == EOF:
100            c = self.input.read(1)
101        if c >= ' ' or c == '\n' or c == EOF:
102            return c
103        if c == '\r':
104            return '\n'
105        return ' '
106
107    def peek(self):
108        """ get the next character without getting it. """
109        self.theLookahead = self.get()
110        return self.theLookahead
111
112    def next(self):
113        """ get the next character, excluding comments. peek() is used to see
114            if a '/' is followed by a '/' or '*'.
115        """
116        c = self.get()
117        if c == '/':
118            peek = self.peek()
119            if peek == '/':
120                while True:
121                    c = self.get()
122                    if c <= '\n':
123                        return c
124            elif peek == '*':
125                self.get()
126                while True:
127                    get = self.get()
128                    if get == '*':
129                        if self.peek() == '/':
130                            self.get()
131                            return ' '
132                    elif get == EOF:
133                        raise UnterminatedComment()
134            else:
135                return c
136        return c
137
138    def putc(self, c):
139        self.output.write(c)
140
141    def action(self, d):
142        """ do something! What you do is determined by the argument:
143               1   Output A. Copy B to A. Get the next B.
144               2   Copy B to A. Get the next B. (Delete A).
145               3   Get the next B. (Delete B).
146            action treats a string as a single character. Wow!
147            action recognizes a regular expression if it is preceded by ( or , or =.
148        """
149        if d <= 1:
150            self.putc(self.theA)
151        if d <= 2:
152            self.theA = self.theB
153            if self.theA == '\'' or self.theA == '"':
154                while True:
155                    self.putc(self.theA)
156                    self.theA = self.get()
157                    if self.theA == self.theB:
158                        break
159                    if self.theA == '\\':
160                        self.putc(self.theA)
161                        self.theA = self.get()
162                    if self.theA == EOF:
163                        raise UnterminatedString()
164        if d <= 3:
165            self.theB = self.next()
166            if self.theB == '/' and self.theA in ['(', ',', '=', ':', '[', '!', '&', '|', '?', '{', '}', ';', '\n']:
167                self.putc(self.theA)
168                self.putc(self.theB)
169                while True:
170                    self.theA = self.get()
171                    if self.theA == '/':
172                        break
173                    if self.theA == '\\':
174                        self.putc(self.theA)
175                        self.theA = self.get()
176                    if self.theA == EOF:
177                        raise UnterminatedRegularExpression()
178                    self.putc(self.theA)
179                self.theB = self.next()
180
181    def jsmin(self):
182        """ Copy the input to the output, deleting the characters which are
183            insignificant to JavaScript. Comments will be removed. Tabs will be
184            replaced with spaces. Carriage returns will be replaced with linefeeds.
185            Most spaces and linefeeds will be removed.
186        """
187        self.theA = '\n'
188        self.theLookahead = EOF
189        self.action(3)
190        while self.theA != EOF:
191            if self.theA == ' ':
192                if self.isAlphanum(self.theB):
193                    self.action(1)
194                else:
195                    self.action(2)
196            elif self.theA == '\n':
197                if self.theB in ['{', '[', '(', '+', '-']:
198                    self.action(1)
199                elif self.theB == ' ':
200                    self.action(3)
201                else:
202                    if self.isAlphanum(self.theB):
203                        self.action(1)
204                    else:
205                        self.action(2)
206            else:
207                if self.theB == ' ':
208                    if self.isAlphanum(self.theA):
209                        self.action(1)
210                    else:
211                        self.action(3)
212                elif self.theB == '\n':
213                    if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
214                        self.action(1)
215                    else:
216                        if self.isAlphanum(self.theA):
217                            self.action(1)
218                        else:
219                            self.action(3)
220                else:
221                    self.action(1)
222
223
224if __name__ == '__main__':
225    minifier = JavaScriptMinifier()
226    minifier.input = sys.stdin
227    minifier.output = sys.stdout
228    minifier.jsmin()
229    sys.stdin.close()
230