| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # -*- coding: ascii -*- | |
| 3 # | |
| 4 # Copyright 2011 - 2013 | |
| 5 # Andr\xe9 Malo or his licensors, as applicable | |
| 6 # | |
| 7 # Licensed under the Apache License, Version 2.0 (the "License"); | |
| 8 # you may not use this file except in compliance with the License. | |
| 9 # You may obtain a copy of the License at | |
| 10 # | |
| 11 # http://www.apache.org/licenses/LICENSE-2.0 | |
| 12 # | |
| 13 # Unless required by applicable law or agreed to in writing, software | |
| 14 # distributed under the License is distributed on an "AS IS" BASIS, | |
| 15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 16 # See the License for the specific language governing permissions and | |
| 17 # limitations under the License. | |
| 18 r""" | |
| 19 ===================== | |
| 20 Javascript Minifier | |
| 21 ===================== | |
| 22 | |
| 23 rJSmin is a javascript minifier written in python. | |
| 24 | |
| 25 The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\. | |
| 26 | |
| 27 The module is a re-implementation aiming for speed, so it can be used at | |
| 28 runtime (rather than during a preprocessing step). Usually it produces the | |
| 29 same results as the original ``jsmin.c``. It differs in the following ways: | |
| 30 | |
| 31 - there is no error detection: unterminated string, regex and comment | |
| 32 literals are treated as regular javascript code and minified as such. | |
| 33 - Control characters inside string and regex literals are left untouched; they | |
| 34 are not converted to spaces (nor to \n) | |
| 35 - Newline characters are not allowed inside string and regex literals, except | |
| 36 for line continuations in string literals (ECMA-5). | |
| 37 - "return /regex/" is recognized correctly. | |
| 38 - "+ +" and "- -" sequences are not collapsed to '++' or '--' | |
| 39 - Newlines before ! operators are removed more sensibly | |
| 40 - rJSmin does not handle streams, but only complete strings. (However, the | |
| 41 module provides a "streamy" interface). | |
| 42 | |
| 43 Since most parts of the logic are handled by the regex engine it's way | |
| 44 faster than the original python port of ``jsmin.c`` by Baruch Even. The speed | |
| 45 factor varies between about 6 and 55 depending on input and python version | |
| 46 (it gets faster the more compressed the input already is). Compared to the | |
| 47 speed-refactored python port by Dave St.Germain the performance gain is less | |
| 48 dramatic but still between 1.2 and 7. See the docs/BENCHMARKS file for | |
| 49 details. | |
| 50 | |
| 51 rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more. | |
| 52 | |
| 53 Both python 2 and python 3 are supported. | |
| 54 | |
| 55 .. _jsmin.c by Douglas Crockford: | |
| 56 http://www.crockford.com/javascript/jsmin.c | |
| 57 """ | |
| 58 __author__ = "Andr\xe9 Malo" | |
| 59 __author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1') | |
| 60 __docformat__ = "restructuredtext en" | |
| 61 __license__ = "Apache License, Version 2.0" | |
| 62 __version__ = '1.0.7' | |
| 63 __all__ = ['jsmin'] | |
| 64 | |
| 65 import re as _re | |
| 66 | |
| 67 | |
| 68 def _make_jsmin(python_only=False): | |
| 69 """ | |
| 70 Generate JS minifier based on `jsmin.c by Douglas Crockford`_ | |
| 71 | |
| 72 .. _jsmin.c by Douglas Crockford: | |
| 73 http://www.crockford.com/javascript/jsmin.c | |
| 74 | |
| 75 :Parameters: | |
| 76 `python_only` : ``bool`` | |
| 77 Use only the python variant. If true, the c extension is not even | |
| 78 tried to be loaded. | |
| 79 | |
| 80 :Return: Minifier | |
| 81 :Rtype: ``callable`` | |
| 82 """ | |
| 83 # pylint: disable = R0912, R0914, W0612 | |
| 84 if not python_only: | |
| 85 try: | |
| 86 import _rjsmin | |
| 87 except ImportError: | |
| 88 pass | |
| 89 else: | |
| 90 return _rjsmin.jsmin | |
| 91 try: | |
| 92 xrange | |
| 93 except NameError: | |
| 94 xrange = range # pylint: disable = W0622 | |
| 95 | |
| 96 space_chars = r'[\000-\011\013\014\016-\040]' | |
| 97 | |
| 98 line_comment = r'(?://[^\r\n]*)' | |
| 99 space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' | |
| 100 string1 = \ | |
| 101 r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)' | |
| 102 string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")' | |
| 103 strings = r'(?:%s|%s)' % (string1, string2) | |
| 104 | |
| 105 charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])' | |
| 106 nospecial = r'[^/\\\[\r\n]' | |
| 107 regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % ( | |
| 108 nospecial, charclass, nospecial | |
| 109 ) | |
| 110 space = r'(?:%s|%s)' % (space_chars, space_comment) | |
| 111 newline = r'(?:%s?[\r\n])' % line_comment | |
| 112 | |
| 113 def fix_charclass(result): | |
| 114 """ Fixup string of chars to fit into a regex char class """ | |
| 115 pos = result.find('-') | |
| 116 if pos >= 0: | |
| 117 result = r'%s%s-' % (result[:pos], result[pos + 1:]) | |
| 118 | |
| 119 def sequentize(string): | |
| 120 """ | |
| 121 Notate consecutive characters as sequence | |
| 122 | |
| 123 (1-4 instead of 1234) | |
| 124 """ | |
| 125 first, last, result = None, None, [] | |
| 126 for char in map(ord, string): | |
| 127 if last is None: | |
| 128 first = last = char | |
| 129 elif last + 1 == char: | |
| 130 last = char | |
| 131 else: | |
| 132 result.append((first, last)) | |
| 133 first = last = char | |
| 134 if last is not None: | |
| 135 result.append((first, last)) | |
| 136 return ''.join(['%s%s%s' % ( | |
| 137 chr(first), | |
| 138 last > first + 1 and '-' or '', | |
| 139 last != first and chr(last) or '' | |
| 140 ) for first, last in result]) | |
| 141 | |
| 142 return _re.sub(r'([\000-\040\047])', # for better portability | |
| 143 lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result) | |
| 144 .replace('\\', '\\\\') | |
| 145 .replace('[', '\\[') | |
| 146 .replace(']', '\\]') | |
| 147 ) | |
| 148 ) | |
| 149 | |
| 150 def id_literal_(what): | |
| 151 """ Make id_literal like char class """ | |
| 152 match = _re.compile(what).match | |
| 153 result = ''.join([ | |
| 154 chr(c) for c in xrange(127) if not match(chr(c)) | |
| 155 ]) | |
| 156 return '[^%s]' % fix_charclass(result) | |
| 157 | |
| 158 def not_id_literal_(keep): | |
| 159 """ Make negated id_literal like char class """ | |
| 160 match = _re.compile(id_literal_(keep)).match | |
| 161 result = ''.join([ | |
| 162 chr(c) for c in xrange(127) if not match(chr(c)) | |
| 163 ]) | |
| 164 return r'[%s]' % fix_charclass(result) | |
| 165 | |
| 166 not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]') | |
| 167 preregex1 = r'[(,=:\[!&|?{};\r\n]' | |
| 168 preregex2 = r'%(not_id_literal)sreturn' % locals() | |
| 169 | |
| 170 id_literal = id_literal_(r'[a-zA-Z0-9_$]') | |
| 171 id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]') | |
| 172 id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]') | |
| 173 | |
| 174 dull = r'[^\047"/\000-\040]' | |
| 175 | |
| 176 space_sub = _re.compile(( | |
| 177 r'(%(dull)s+)' | |
| 178 r'|(%(strings)s%(dull)s*)' | |
| 179 r'|(?<=%(preregex1)s)' | |
| 180 r'%(space)s*(?:%(newline)s%(space)s*)*' | |
| 181 r'(%(regex)s%(dull)s*)' | |
| 182 r'|(?<=%(preregex2)s)' | |
| 183 r'%(space)s*(?:%(newline)s%(space)s)*' | |
| 184 r'(%(regex)s%(dull)s*)' | |
| 185 r'|(?<=%(id_literal_close)s)' | |
| 186 r'%(space)s*(?:(%(newline)s)%(space)s*)+' | |
| 187 r'(?=%(id_literal_open)s)' | |
| 188 r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' | |
| 189 r'|(?<=\+)(%(space)s)+(?=\+)' | |
| 190 r'|(?<=-)(%(space)s)+(?=-)' | |
| 191 r'|%(space)s+' | |
| 192 r'|(?:%(newline)s%(space)s*)+' | |
| 193 ) % locals()).sub | |
| 194 #print space_sub.__self__.pattern | |
| 195 | |
| 196 def space_subber(match): | |
| 197 """ Substitution callback """ | |
| 198 # pylint: disable = C0321, R0911 | |
| 199 groups = match.groups() | |
| 200 if groups[0]: return groups[0] | |
| 201 elif groups[1]: return groups[1] | |
| 202 elif groups[2]: return groups[2] | |
| 203 elif groups[3]: return groups[3] | |
| 204 elif groups[4]: return '\n' | |
| 205 elif groups[5] or groups[6] or groups[7]: return ' ' | |
| 206 else: return '' | |
| 207 | |
| 208 def jsmin(script): # pylint: disable = W0621 | |
| 209 r""" | |
| 210 Minify javascript based on `jsmin.c by Douglas Crockford`_\. | |
| 211 | |
| 212 Instead of parsing the stream char by char, it uses a regular | |
| 213 expression approach which minifies the whole script with one big | |
| 214 substitution regex. | |
| 215 | |
| 216 .. _jsmin.c by Douglas Crockford: | |
| 217 http://www.crockford.com/javascript/jsmin.c | |
| 218 | |
| 219 :Parameters: | |
| 220 `script` : ``str`` | |
| 221 Script to minify | |
| 222 | |
| 223 :Return: Minified script | |
| 224 :Rtype: ``str`` | |
| 225 """ | |
| 226 return space_sub(space_subber, '\n%s\n' % script).strip() | |
| 227 | |
| 228 return jsmin | |
| 229 | |
| 230 jsmin = _make_jsmin() | |
| 231 | |
| 232 | |
| 233 def jsmin_for_posers(script): | |
| 234 r""" | |
| 235 Minify javascript based on `jsmin.c by Douglas Crockford`_\. | |
| 236 | |
| 237 Instead of parsing the stream char by char, it uses a regular | |
| 238 expression approach which minifies the whole script with one big | |
| 239 substitution regex. | |
| 240 | |
| 241 .. _jsmin.c by Douglas Crockford: | |
| 242 http://www.crockford.com/javascript/jsmin.c | |
| 243 | |
| 244 :Warning: This function is the digest of a _make_jsmin() call. It just | |
| 245 utilizes the resulting regex. It's just for fun here and may | |
| 246 vanish any time. Use the `jsmin` function instead. | |
| 247 | |
| 248 :Parameters: | |
| 249 `script` : ``str`` | |
| 250 Script to minify | |
| 251 | |
| 252 :Return: Minified script | |
| 253 :Rtype: ``str`` | |
| 254 """ | |
| 255 def subber(match): | |
| 256 """ Substitution callback """ | |
| 257 groups = match.groups() | |
| 258 return ( | |
| 259 groups[0] or | |
| 260 groups[1] or | |
| 261 groups[2] or | |
| 262 groups[3] or | |
| 263 (groups[4] and '\n') or | |
| 264 (groups[5] and ' ') or | |
| 265 (groups[6] and ' ') or | |
| 266 (groups[7] and ' ') or | |
| 267 '' | |
| 268 ) | |
| 269 | |
| 270 return _re.sub( | |
| 271 r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?' | |
| 272 r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|' | |
| 273 r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?{};\r\n])(?' | |
| 274 r':[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*' | |
| 275 r'(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*' | |
| 276 r'[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(' | |
| 277 r'?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[' | |
| 278 r'\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[\000-#%-,./:-@\[-^`{-~-]return' | |
| 279 r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/' | |
| 280 r'))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:' | |
| 281 r'/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?' | |
| 282 r':(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/' | |
| 283 r'\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\^`{|' | |
| 284 r'~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)' | |
| 285 r'*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]' | |
| 286 r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,./' | |
| 287 r':-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\013\01' | |
| 288 r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./:' | |
| 289 r'-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*' | |
| 290 r'\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013\014\016-' | |
| 291 r'\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\000-\011\013' | |
| 292 r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^' | |
| 293 r'\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^' | |
| 294 r'/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script | |
| 295 ).strip() | |
| 296 | |
| 297 | |
| 298 if __name__ == '__main__': | |
| 299 import sys as _sys | |
| 300 _sys.stdout.write(jsmin(_sys.stdin.read())) | |
| OLD | NEW |