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