4f9d384f116609e47fadb200ca0c8585bf035950
[wolnelektury.git] / compress / filters / jsmin / jsmin.py
1 #!/usr/bin/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 from StringIO import StringIO
34
35 def jsmin(js):
36     ins = StringIO(js)
37     outs = StringIO()
38     JavascriptMinify().minify(ins, outs)
39     str = outs.getvalue()
40     if len(str) > 0 and str[0] == '\n':
41         str = str[1:]
42     return str
43
44 def isAlphanum(c):
45     """return true if the character is a letter, digit, underscore,
46            dollar sign, or non-ASCII character.
47     """
48     return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
49             (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
50
51 class UnterminatedComment(Exception):
52     pass
53
54 class UnterminatedStringLiteral(Exception):
55     pass
56
57 class UnterminatedRegularExpression(Exception):
58     pass
59
60 class JavascriptMinify(object):
61
62     def _outA(self):
63         self.outstream.write(self.theA)
64     def _outB(self):
65         self.outstream.write(self.theB)
66
67     def _get(self):
68         """return the next character from stdin. Watch out for lookahead. If
69            the character is a control character, translate it to a space or
70            linefeed.
71         """
72         c = self.theLookahead
73         self.theLookahead = None
74         if c == None:
75             c = self.instream.read(1)
76         if c >= ' ' or c == '\n':
77             return c
78         if c == '': # EOF
79             return '\000'
80         if c == '\r':
81             return '\n'
82         return ' '
83
84     def _peek(self):
85         self.theLookahead = self._get()
86         return self.theLookahead
87
88     def _next(self):
89         """get the next character, excluding comments. peek() is used to see
90            if a '/' is followed by a '/' or '*'.
91         """
92         c = self._get()
93         if c == '/':
94             p = self._peek()
95             if p == '/':
96                 c = self._get()
97                 while c > '\n':
98                     c = self._get()
99                 return c
100             if p == '*':
101                 c = self._get()
102                 while 1:
103                     c = self._get()
104                     if c == '*':
105                         if self._peek() == '/':
106                             self._get()
107                             return ' '
108                     if c == '\000':
109                         raise UnterminatedComment()
110
111         return c
112
113     def _action(self, action):
114         """do something! What you do is determined by the argument:
115            1   Output A. Copy B to A. Get the next B.
116            2   Copy B to A. Get the next B. (Delete A).
117            3   Get the next B. (Delete B).
118            action treats a string as a single character. Wow!
119            action recognizes a regular expression if it is preceded by ( or , or =.
120         """
121         if action <= 1:
122             self._outA()
123
124         if action <= 2:
125             self.theA = self.theB
126             if self.theA == "'" or self.theA == '"':
127                 while 1:
128                     self._outA()
129                     self.theA = self._get()
130                     if self.theA == self.theB:
131                         break
132                     if self.theA <= '\n':
133                         raise UnterminatedStringLiteral()
134                     if self.theA == '\\':
135                         self._outA()
136                         self.theA = self._get()
137
138
139         if action <= 3:
140             self.theB = self._next()
141             if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
142                                      self.theA == '=' or self.theA == ':' or
143                                      self.theA == '[' or self.theA == '?' or
144                                      self.theA == '!' or self.theA == '&' or
145                                      self.theA == '|' or self.theA == ';' or
146                                      self.theA == '{' or self.theA == '}' or
147                                      self.theA == '\n'):
148                 self._outA()
149                 self._outB()
150                 while 1:
151                     self.theA = self._get()
152                     if self.theA == '/':
153                         break
154                     elif self.theA == '\\':
155                         self._outA()
156                         self.theA = self._get()
157                     elif self.theA <= '\n':
158                         raise UnterminatedRegularExpression()
159                     self._outA()
160                 self.theB = self._next()
161
162
163     def _jsmin(self):
164         """Copy the input to the output, deleting the characters which are
165            insignificant to JavaScript. Comments will be removed. Tabs will be
166            replaced with spaces. Carriage returns will be replaced with linefeeds.
167            Most spaces and linefeeds will be removed.
168         """
169         self.theA = '\n'
170         self._action(3)
171
172         while self.theA != '\000':
173             if self.theA == ' ':
174                 if isAlphanum(self.theB):
175                     self._action(1)
176                 else:
177                     self._action(2)
178             elif self.theA == '\n':
179                 if self.theB in ['{', '[', '(', '+', '-']:
180                     self._action(1)
181                 elif self.theB == ' ':
182                     self._action(3)
183                 else:
184                     if isAlphanum(self.theB):
185                         self._action(1)
186                     else:
187                         self._action(2)
188             else:
189                 if self.theB == ' ':
190                     if isAlphanum(self.theA):
191                         self._action(1)
192                     else:
193                         self._action(3)
194                 elif self.theB == '\n':
195                     if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
196                         self._action(1)
197                     else:
198                         if isAlphanum(self.theA):
199                             self._action(1)
200                         else:
201                             self._action(3)
202                 else:
203                     self._action(1)
204
205     def minify(self, instream, outstream):
206         self.instream = instream
207         self.outstream = outstream
208         self.theA = '\n'
209         self.theB = None
210         self.theLookahead = None
211
212         self._jsmin()
213         self.instream.close()
214
215 if __name__ == '__main__':
216     import sys
217     jsm = JavascriptMinify()
218     jsm.minify(sys.stdin, sys.stdout)