• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3# This code is original from jsmin by Douglas Crockford, it was translated to
4# Python by Baruch Even. The original code had the following copyright and
5# license.
6#
7# /* jsmin.c
8#    2007-05-22
9#
10# Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
11#
12# Permission is hereby granted, free of charge, to any person obtaining a copy of
13# this software and associated documentation files (the "Software"), to deal in
14# the Software without restriction, including without limitation the rights to
15# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
16# of the Software, and to permit persons to whom the Software is furnished to do
17# so, subject to the following conditions:
18#
19# The above copyright notice and this permission notice shall be included in all
20# copies or substantial portions of the Software.
21#
22# The Software shall be used for Good, not Evil.
23#
24# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
30# SOFTWARE.
31# */
32
33# imports adjusted for speed (cStringIO) and python 3 (io) -- nd
34try:
35    from cStringIO import StringIO
36except ImportError:
37    try:
38        from StringIO import StringIO
39    except ImportError:
40        from io import StringIO
41
42
43def jsmin(js):
44    ins = StringIO(js)
45    outs = StringIO()
46    JavascriptMinify().minify(ins, outs)
47    str = outs.getvalue()
48    if len(str) > 0 and str[0] == '\n':
49        str = str[1:]
50    return str
51
52def isAlphanum(c):
53    """return true if the character is a letter, digit, underscore,
54           dollar sign, or non-ASCII character.
55    """
56    return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
57            (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
58
59class UnterminatedComment(Exception):
60    pass
61
62class UnterminatedStringLiteral(Exception):
63    pass
64
65class UnterminatedRegularExpression(Exception):
66    pass
67
68class JavascriptMinify(object):
69
70    def _outA(self):
71        self.outstream.write(self.theA)
72    def _outB(self):
73        self.outstream.write(self.theB)
74
75    def _get(self):
76        """return the next character from stdin. Watch out for lookahead. If
77           the character is a control character, translate it to a space or
78           linefeed.
79        """
80        c = self.theLookahead
81        self.theLookahead = None
82        if c == None:
83            c = self.instream.read(1)
84        if c >= ' ' or c == '\n':
85            return c
86        if c == '': # EOF
87            return '\000'
88        if c == '\r':
89            return '\n'
90        return ' '
91
92    def _peek(self):
93        self.theLookahead = self._get()
94        return self.theLookahead
95
96    def _next(self):
97        """get the next character, excluding comments. peek() is used to see
98           if an unescaped '/' is followed by a '/' or '*'.
99        """
100        c = self._get()
101        if c == '/' and self.theA != '\\':
102            p = self._peek()
103            if p == '/':
104                c = self._get()
105                while c > '\n':
106                    c = self._get()
107                return c
108            if p == '*':
109                c = self._get()
110                while 1:
111                    c = self._get()
112                    if c == '*':
113                        if self._peek() == '/':
114                            self._get()
115                            return ' '
116                    if c == '\000':
117                        raise UnterminatedComment()
118
119        return c
120
121    def _action(self, action):
122        """do something! What you do is determined by the argument:
123           1   Output A. Copy B to A. Get the next B.
124           2   Copy B to A. Get the next B. (Delete A).
125           3   Get the next B. (Delete B).
126           action treats a string as a single character. Wow!
127           action recognizes a regular expression if it is preceded by ( or , or =.
128        """
129        if action <= 1:
130            self._outA()
131
132        if action <= 2:
133            self.theA = self.theB
134            if self.theA == "'" or self.theA == '"':
135                while 1:
136                    self._outA()
137                    self.theA = self._get()
138                    if self.theA == self.theB:
139                        break
140                    if self.theA <= '\n':
141                        raise UnterminatedStringLiteral()
142                    if self.theA == '\\':
143                        self._outA()
144                        self.theA = self._get()
145
146
147        if action <= 3:
148            self.theB = self._next()
149            if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
150                                     self.theA == '=' or self.theA == ':' or
151                                     self.theA == '[' or self.theA == '?' or
152                                     self.theA == '!' or self.theA == '&' or
153                                     self.theA == '|' or self.theA == ';' or
154                                     self.theA == '{' or self.theA == '}' or
155                                     self.theA == '\n'):
156                self._outA()
157                self._outB()
158                while 1:
159                    self.theA = self._get()
160                    if self.theA == '/':
161                        break
162                    elif self.theA == '\\':
163                        self._outA()
164                        self.theA = self._get()
165                    elif self.theA <= '\n':
166                        raise UnterminatedRegularExpression()
167                    self._outA()
168                self.theB = self._next()
169
170
171    def _jsmin(self):
172        """Copy the input to the output, deleting the characters which are
173           insignificant to JavaScript. Comments will be removed. Tabs will be
174           replaced with spaces. Carriage returns will be replaced with linefeeds.
175           Most spaces and linefeeds will be removed.
176        """
177        self.theA = '\n'
178        self._action(3)
179
180        while self.theA != '\000':
181            if self.theA == ' ':
182                if isAlphanum(self.theB):
183                    self._action(1)
184                else:
185                    self._action(2)
186            elif self.theA == '\n':
187                if self.theB in ['{', '[', '(', '+', '-']:
188                    self._action(1)
189                elif self.theB == ' ':
190                    self._action(3)
191                else:
192                    if isAlphanum(self.theB):
193                        self._action(1)
194                    else:
195                        self._action(2)
196            else:
197                if self.theB == ' ':
198                    if isAlphanum(self.theA):
199                        self._action(1)
200                    else:
201                        self._action(3)
202                elif self.theB == '\n':
203                    if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
204                        self._action(1)
205                    else:
206                        if isAlphanum(self.theA):
207                            self._action(1)
208                        else:
209                            self._action(3)
210                else:
211                    self._action(1)
212
213    def minify(self, instream, outstream):
214        self.instream = instream
215        self.outstream = outstream
216        self.theA = '\n'
217        self.theB = None
218        self.theLookahead = None
219
220        self._jsmin()
221        self.instream.close()
222
223if __name__ == '__main__':
224    import sys
225    jsm = JavascriptMinify()
226    jsm.minify(sys.stdin, sys.stdout)
227