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