#39: editorial note
[redakcja.git] / apps / 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
36 def jsmin(js):
37     ins = StringIO(js)
38     outs = StringIO()
39     JavascriptMinify().minify(ins, outs)
40     str = outs.getvalue()
41     if len(str) > 0 and str[0] == '\n':
42         str = str[1:]
43     return str
44
45
46 def isAlphanum(c):
47     """return true if the character is a letter, digit, underscore,
48            dollar sign, or non-ASCII character.
49     """
50     return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') \
51         or (c >= '0' and c <= '9') or c == '_' or c == '$' or c == '\\' \
52         or (c is not None and ord(c) > 126)
53
54
55 class UnterminatedComment(Exception):
56     pass
57
58
59 class UnterminatedStringLiteral(Exception):
60     pass
61
62
63 class UnterminatedRegularExpression(Exception):
64     pass
65
66
67 class JavascriptMinify(object):
68
69     def _outA(self):
70         self.outstream.write(self.theA)
71
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 a '/' is followed by a '/' or '*'.
99         """
100         c = self._get()
101         if c == '/':
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         if action <= 3:
147             self.theB = self._next()
148             if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
149                                      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 == '\n'):
155                 self._outA()
156                 self._outB()
157                 while 1:
158                     self.theA = self._get()
159                     if self.theA == '/':
160                         break
161                     elif self.theA == '\\':
162                         self._outA()
163                         self.theA = self._get()
164                     elif self.theA <= '\n':
165                         raise UnterminatedRegularExpression()
166                     self._outA()
167                 self.theB = self._next()
168
169     def _jsmin(self):
170         """Copy the input to the output, deleting the characters which are
171            insignificant to JavaScript. Comments will be removed. Tabs will be
172            replaced with spaces. Carriage returns will be replaced with linefeeds.
173            Most spaces and linefeeds will be removed.
174         """
175         self.theA = '\n'
176         self._action(3)
177
178         while self.theA != '\000':
179             if self.theA == ' ':
180                 if isAlphanum(self.theB):
181                     self._action(1)
182                 else:
183                     self._action(2)
184             elif self.theA == '\n':
185                 if self.theB in ['{', '[', '(', '+', '-']:
186                     self._action(1)
187                 elif self.theB == ' ':
188                     self._action(3)
189                 else:
190                     if isAlphanum(self.theB):
191                         self._action(1)
192                     else:
193                         self._action(2)
194             else:
195                 if self.theB == ' ':
196                     if isAlphanum(self.theA):
197                         self._action(1)
198                     else:
199                         self._action(3)
200                 elif self.theB == '\n':
201                     if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
202                         self._action(1)
203                     else:
204                         if isAlphanum(self.theA):
205                             self._action(1)
206                         else:
207                             self._action(3)
208                 else:
209                     self._action(1)
210
211     def minify(self, instream, outstream):
212         self.instream = instream
213         self.outstream = outstream
214         self.theA = '\n'
215         self.theB = None
216         self.theLookahead = None
217
218         self._jsmin()
219         self.instream.close()
220
221 if __name__ == '__main__':
222     import sys
223     jsm = JavascriptMinify()
224     jsm.minify(sys.stdin, sys.stdout)