| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # | 2 # -*- coding: ascii -*- |
| 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""" | 3 r""" |
| 18 ===================== | 4 ===================== |
| 19 Javascript Minifier | 5 Javascript Minifier |
| 20 ===================== | 6 ===================== |
| 21 | 7 |
| 22 rJSmin is a javascript minifier written in python. | 8 rJSmin is a javascript minifier written in python. |
| 23 | 9 |
| 24 The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\. | 10 The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\\. |
| 11 |
| 12 :Copyright: |
| 13 |
| 14 Copyright 2011 - 2015 |
| 15 Andr\xe9 Malo or his licensors, as applicable |
| 16 |
| 17 :License: |
| 18 |
| 19 Licensed under the Apache License, Version 2.0 (the "License"); |
| 20 you may not use this file except in compliance with the License. |
| 21 You may obtain a copy of the License at |
| 22 |
| 23 http://www.apache.org/licenses/LICENSE-2.0 |
| 24 |
| 25 Unless required by applicable law or agreed to in writing, software |
| 26 distributed under the License is distributed on an "AS IS" BASIS, |
| 27 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 28 See the License for the specific language governing permissions and |
| 29 limitations under the License. |
| 25 | 30 |
| 26 The module is a re-implementation aiming for speed, so it can be used at | 31 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 | 32 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: | 33 same results as the original ``jsmin.c``. It differs in the following ways: |
| 29 | 34 |
| 30 - there is no error detection: unterminated string, regex and comment | 35 - there is no error detection: unterminated string, regex and comment |
| 31 literals are treated as regular javascript code and minified as such. | 36 literals are treated as regular javascript code and minified as such. |
| 32 - Control characters inside string and regex literals are left untouched; they | 37 - Control characters inside string and regex literals are left untouched; they |
| 33 are not converted to spaces (nor to \n) | 38 are not converted to spaces (nor to \\n) |
| 34 - Newline characters are not allowed inside string and regex literals, except | 39 - Newline characters are not allowed inside string and regex literals, except |
| 35 for line continuations in string literals (ECMA-5). | 40 for line continuations in string literals (ECMA-5). |
| 36 - "return /regex/" is recognized correctly. | 41 - "return /regex/" is recognized correctly. |
| 42 - Line terminators after regex literals are handled more sensibly |
| 37 - "+ +" and "- -" sequences are not collapsed to '++' or '--' | 43 - "+ +" and "- -" sequences are not collapsed to '++' or '--' |
| 38 - Newlines before ! operators are removed more sensibly | 44 - Newlines before ! operators are removed more sensibly |
| 45 - Comments starting with an exclamation mark (``!``) can be kept optionally |
| 39 - rJSmin does not handle streams, but only complete strings. (However, the | 46 - rJSmin does not handle streams, but only complete strings. (However, the |
| 40 module provides a "streamy" interface). | 47 module provides a "streamy" interface). |
| 41 | 48 |
| 42 Since most parts of the logic are handled by the regex engine it's way | 49 Since most parts of the logic are handled by the regex engine it's way faster |
| 43 faster than the original python port of ``jsmin.c`` by Baruch Even. The speed | 50 than the original python port of ``jsmin.c`` by Baruch Even. The speed factor |
| 44 factor varies between about 6 and 55 depending on input and python version | 51 varies between about 6 and 55 depending on input and python version (it gets |
| 45 (it gets faster the more compressed the input already is). Compared to the | 52 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 | 53 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 | 54 dramatic but still between 3 and 50 (for huge inputs). See the docs/BENCHMARKS |
| 48 details. | 55 file for details. |
| 49 | 56 |
| 50 rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more. | 57 rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more. |
| 51 | 58 |
| 52 Both python 2 and python 3 are supported. | 59 Both python 2 and python 3 are supported. |
| 53 | 60 |
| 54 .. _jsmin.c by Douglas Crockford: | 61 .. _jsmin.c by Douglas Crockford: |
| 55 http://www.crockford.com/javascript/jsmin.c | 62 http://www.crockford.com/javascript/jsmin.c |
| 56 """ | 63 """ |
| 57 __author__ = "Andr\xe9 Malo" | 64 if __doc__: |
| 58 __author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1') | 65 # pylint: disable = redefined-builtin |
| 66 __doc__ = __doc__.encode('ascii').decode('unicode_escape') |
| 67 __author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') |
| 59 __docformat__ = "restructuredtext en" | 68 __docformat__ = "restructuredtext en" |
| 60 __license__ = "Apache License, Version 2.0" | 69 __license__ = "Apache License, Version 2.0" |
| 61 __version__ = '1.0.7' | 70 __version__ = '1.0.12' |
| 62 __all__ = ['jsmin'] | 71 __all__ = ['jsmin'] |
| 63 | 72 |
| 64 import re as _re | 73 import re as _re |
| 65 | 74 |
| 66 | 75 |
| 67 def _make_jsmin(python_only=False): | 76 def _make_jsmin(python_only=False): |
| 68 """ | 77 """ |
| 69 Generate JS minifier based on `jsmin.c by Douglas Crockford`_ | 78 Generate JS minifier based on `jsmin.c by Douglas Crockford`_ |
| 70 | 79 |
| 71 .. _jsmin.c by Douglas Crockford: | 80 .. _jsmin.c by Douglas Crockford: |
| 72 http://www.crockford.com/javascript/jsmin.c | 81 http://www.crockford.com/javascript/jsmin.c |
| 73 | 82 |
| 74 :Parameters: | 83 :Parameters: |
| 75 `python_only` : ``bool`` | 84 `python_only` : ``bool`` |
| 76 Use only the python variant. If true, the c extension is not even | 85 Use only the python variant. If true, the c extension is not even |
| 77 tried to be loaded. | 86 tried to be loaded. |
| 78 | 87 |
| 79 :Return: Minifier | 88 :Return: Minifier |
| 80 :Rtype: ``callable`` | 89 :Rtype: ``callable`` |
| 81 """ | 90 """ |
| 82 # pylint: disable = R0912, R0914, W0612 | 91 # pylint: disable = unused-variable |
| 92 # pylint: disable = too-many-locals |
| 93 |
| 83 if not python_only: | 94 if not python_only: |
| 84 try: | 95 try: |
| 85 import _rjsmin | 96 import _rjsmin |
| 86 except ImportError: | 97 except ImportError: |
| 87 pass | 98 pass |
| 88 else: | 99 else: |
| 89 return _rjsmin.jsmin | 100 return _rjsmin.jsmin |
| 90 try: | 101 try: |
| 91 xrange | 102 xrange |
| 92 except NameError: | 103 except NameError: |
| 93 xrange = range # pylint: disable = W0622 | 104 xrange = range # pylint: disable = redefined-builtin |
| 94 | 105 |
| 95 space_chars = r'[\000-\011\013\014\016-\040]' | 106 space_chars = r'[\000-\011\013\014\016-\040]' |
| 96 | 107 |
| 97 line_comment = r'(?://[^\r\n]*)' | 108 line_comment = r'(?://[^\r\n]*)' |
| 98 space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' | 109 space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' |
| 110 space_comment_nobang = r'(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/)' |
| 111 bang_comment = r'(?:/\*![^*]*\*+(?:[^/*][^*]*\*+)*/)' |
| 112 |
| 99 string1 = \ | 113 string1 = \ |
| 100 r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)' | 114 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]*)*")' | 115 string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")' |
| 102 string3 = r'(?:`(?:[^`\\]|\\.)*`)' | 116 string3 = r'(?:`(?:[^`\\]|\\.)*`)' |
| 103 strings = r'(?:%s|%s|%s)' % (string1, string2, string3) | 117 strings = r'(?:%s|%s|%s)' % (string1, string2, string3) |
| 104 | 118 |
| 105 charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])' | 119 charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])' |
| 106 nospecial = r'[^/\\\[\r\n]' | 120 nospecial = r'[^/\\\[\r\n]' |
| 107 regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % ( | 121 regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % ( |
| 108 nospecial, charclass, nospecial) | 122 nospecial, charclass, nospecial |
| 123 ) |
| 109 space = r'(?:%s|%s)' % (space_chars, space_comment) | 124 space = r'(?:%s|%s)' % (space_chars, space_comment) |
| 110 newline = r'(?:%s?[\r\n])' % line_comment | 125 newline = r'(?:%s?[\r\n])' % line_comment |
| 111 | 126 |
| 112 def fix_charclass(result): | 127 def fix_charclass(result): |
| 113 """ Fixup string of chars to fit into a regex char class """ | 128 """ Fixup string of chars to fit into a regex char class """ |
| 114 pos = result.find('-') | 129 pos = result.find('-') |
| 115 if pos >= 0: | 130 if pos >= 0: |
| 116 result = r'%s%s-' % (result[:pos], result[pos + 1:]) | 131 result = r'%s%s-' % (result[:pos], result[pos + 1:]) |
| 117 | 132 |
| 118 def sequentize(string): | 133 def sequentize(string): |
| 119 """ | 134 """ |
| 120 Notate consecutive characters as sequence | 135 Notate consecutive characters as sequence |
| 121 | 136 |
| 122 (1-4 instead of 1234) | 137 (1-4 instead of 1234) |
| 123 """ | 138 """ |
| 124 first, last, result = None, None, [] | 139 first, last, result = None, None, [] |
| 125 for char in map(ord, string): | 140 for char in map(ord, string): |
| 126 if last is None: | 141 if last is None: |
| 127 first = last = char | 142 first = last = char |
| 128 elif last + 1 == char: | 143 elif last + 1 == char: |
| 129 last = char | 144 last = char |
| 130 else: | 145 else: |
| 131 result.append((first, last)) | 146 result.append((first, last)) |
| 132 first = last = char | 147 first = last = char |
| 133 if last is not None: | 148 if last is not None: |
| 134 result.append((first, last)) | 149 result.append((first, last)) |
| 135 return ''.join(['%s%s%s' % ( | 150 return ''.join(['%s%s%s' % ( |
| 136 chr(first), | 151 chr(first), |
| 137 last > first + 1 and '-' or '', | 152 last > first + 1 and '-' or '', |
| 138 last != first and chr(last) or '') for first, last in result]) | 153 last != first and chr(last) or '' |
| 154 ) for first, last in result]) # noqa |
| 139 | 155 |
| 140 return _re.sub(r'([\000-\040\047])', # for better portability | 156 return _re.sub( |
| 141 lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result) | 157 r'([\000-\040\047])', # \047 for better portability |
| 158 lambda m: '\\%03o' % ord(m.group(1)), ( |
| 159 sequentize(result) |
| 142 .replace('\\', '\\\\') | 160 .replace('\\', '\\\\') |
| 143 .replace('[', '\\[') | 161 .replace('[', '\\[') |
| 144 .replace(']', '\\]'))) | 162 .replace(']', '\\]') |
| 163 ) |
| 164 ) |
| 145 | 165 |
| 146 def id_literal_(what): | 166 def id_literal_(what): |
| 147 """ Make id_literal like char class """ | 167 """ Make id_literal like char class """ |
| 148 match = _re.compile(what).match | 168 match = _re.compile(what).match |
| 149 result = ''.join([chr(c) for c in xrange(127) if not match(chr(c))]) | 169 result = ''.join([ |
| 170 chr(c) for c in xrange(127) if not match(chr(c)) |
| 171 ]) |
| 150 return '[^%s]' % fix_charclass(result) | 172 return '[^%s]' % fix_charclass(result) |
| 151 | 173 |
| 152 def not_id_literal_(keep): | 174 def not_id_literal_(keep): |
| 153 """ Make negated id_literal like char class """ | 175 """ Make negated id_literal like char class """ |
| 154 match = _re.compile(id_literal_(keep)).match | 176 match = _re.compile(id_literal_(keep)).match |
| 155 result = ''.join([chr(c) for c in xrange(127) if not match(chr(c))]) | 177 result = ''.join([ |
| 178 chr(c) for c in xrange(127) if not match(chr(c)) |
| 179 ]) |
| 156 return r'[%s]' % fix_charclass(result) | 180 return r'[%s]' % fix_charclass(result) |
| 157 | 181 |
| 158 not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]') | 182 not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]') |
| 159 preregex1 = r'[(,=:\[!&|?{};\r\n]' | 183 preregex1 = r'[(,=:\[!&|?{};\r\n]' |
| 160 preregex2 = r'%(not_id_literal)sreturn' % locals() | 184 preregex2 = r'%(not_id_literal)sreturn' % locals() |
| 161 | 185 |
| 162 id_literal = id_literal_(r'[a-zA-Z0-9_$]') | 186 id_literal = id_literal_(r'[a-zA-Z0-9_$]') |
| 163 id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]') | 187 id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]') |
| 164 id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]') | 188 id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]') |
| 189 post_regex_off = id_literal_(r'[^\000-\040}\])?:|,;.&=+-]') |
| 165 | 190 |
| 166 dull = r'[^\047"`/\000-\040]' | 191 dull = r'[^\047"`/\000-\040]' |
| 167 | 192 |
| 168 space_sub = _re.compile(( | 193 space_sub_simple = _re.compile(( |
| 169 r'(%(dull)s+)' | 194 # noqa pylint: disable = bad-continuation |
| 170 r'|(%(strings)s%(dull)s*)' | 195 |
| 196 r'(%(dull)s+)' # 0 |
| 197 r'|(%(strings)s%(dull)s*)' # 1 |
| 171 r'|(?<=%(preregex1)s)' | 198 r'|(?<=%(preregex1)s)' |
| 172 r'%(space)s*(?:%(newline)s%(space)s*)*' | 199 r'%(space)s*(?:%(newline)s%(space)s*)*' |
| 173 r'(%(regex)s%(dull)s*)' | 200 r'(%(regex)s)' # 2 |
| 201 r'(%(space)s*(?:%(newline)s%(space)s*)+' # 3 |
| 202 r'(?=%(post_regex_off)s))?' |
| 174 r'|(?<=%(preregex2)s)' | 203 r'|(?<=%(preregex2)s)' |
| 175 r'%(space)s*(?:%(newline)s%(space)s)*' | 204 r'%(space)s*(?:(%(newline)s)%(space)s*)*' # 4 |
| 176 r'(%(regex)s%(dull)s*)' | 205 r'(%(regex)s)' # 5 |
| 206 r'(%(space)s*(?:%(newline)s%(space)s*)+' # 6 |
| 207 r'(?=%(post_regex_off)s))?' |
| 177 r'|(?<=%(id_literal_close)s)' | 208 r'|(?<=%(id_literal_close)s)' |
| 178 r'%(space)s*(?:(%(newline)s)%(space)s*)+' | 209 r'%(space)s*(?:(%(newline)s)%(space)s*)+' # 7 |
| 179 r'(?=%(id_literal_open)s)' | 210 r'(?=%(id_literal_open)s)' |
| 180 r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' | 211 r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' # 8 |
| 181 r'|(?<=\+)(%(space)s)+(?=\+)' | 212 r'|(?<=\+)(%(space)s)+(?=\+)' # 9 |
| 182 r'|(?<=-)(%(space)s)+(?=-)' | 213 r'|(?<=-)(%(space)s)+(?=-)' # 10 |
| 183 r'|%(space)s+' | 214 r'|%(space)s+' |
| 184 r'|(?:%(newline)s%(space)s*)+') % locals()).sub | 215 r'|(?:%(newline)s%(space)s*)+' |
| 185 #print space_sub.__self__.pattern | 216 ) % locals()).sub |
| 186 | 217 |
| 187 def space_subber(match): | 218 # print space_sub_simple.__self__.pattern |
| 219 |
| 220 def space_subber_simple(match): |
| 188 """ Substitution callback """ | 221 """ Substitution callback """ |
| 189 # pylint: disable = C0321, R0911 | 222 # pylint: disable = too-many-return-statements |
| 223 |
| 190 groups = match.groups() | 224 groups = match.groups() |
| 191 if groups[0]: | 225 if groups[0]: |
| 192 return groups[0] | 226 return groups[0] |
| 193 elif groups[1]: | 227 elif groups[1]: |
| 194 return groups[1] | 228 return groups[1] |
| 195 elif groups[2]: | 229 elif groups[2]: |
| 230 if groups[3]: |
| 231 return groups[2] + '\n' |
| 196 return groups[2] | 232 return groups[2] |
| 197 elif groups[3]: | 233 elif groups[5]: |
| 198 return groups[3] | 234 return "%s%s%s" % ( |
| 199 elif groups[4]: | 235 groups[4] and '\n' or '', |
| 236 groups[5], |
| 237 groups[6] and '\n' or '', |
| 238 ) |
| 239 elif groups[7]: |
| 200 return '\n' | 240 return '\n' |
| 201 elif groups[5] or groups[6] or groups[7]: | 241 elif groups[8] or groups[9] or groups[10]: |
| 202 return ' ' | 242 return ' ' |
| 203 else: | 243 else: |
| 204 return '' | 244 return '' |
| 205 | 245 |
| 206 def jsmin(script): # pylint: disable = W0621 | 246 space_sub_banged = _re.compile(( |
| 247 # noqa pylint: disable = bad-continuation |
| 248 |
| 249 r'(%(dull)s+)' # 0 |
| 250 r'|(%(strings)s%(dull)s*)' # 1 |
| 251 r'|(?<=%(preregex1)s)' |
| 252 r'(%(space)s*(?:%(newline)s%(space)s*)*)' # 2 |
| 253 r'(%(regex)s)' # 3 |
| 254 r'(%(space)s*(?:%(newline)s%(space)s*)+' # 4 |
| 255 r'(?=%(post_regex_off)s))?' |
| 256 r'|(?<=%(preregex2)s)' |
| 257 r'(%(space)s*(?:(%(newline)s)%(space)s*)*)' # 5, 6 |
| 258 r'(%(regex)s)' # 7 |
| 259 r'(%(space)s*(?:%(newline)s%(space)s*)+' # 8 |
| 260 r'(?=%(post_regex_off)s))?' |
| 261 r'|(?<=%(id_literal_close)s)' |
| 262 r'(%(space)s*(?:%(newline)s%(space)s*)+)' # 9 |
| 263 r'(?=%(id_literal_open)s)' |
| 264 r'|(?<=%(id_literal)s)(%(space)s+)(?=%(id_literal)s)' # 10 |
| 265 r'|(?<=\+)(%(space)s+)(?=\+)' # 11 |
| 266 r'|(?<=-)(%(space)s+)(?=-)' # 12 |
| 267 r'|(%(space)s+)' # 13 |
| 268 r'|((?:%(newline)s%(space)s*)+)' # 14 |
| 269 ) % locals()).sub |
| 270 |
| 271 # print space_sub_banged.__self__.pattern |
| 272 |
| 273 keep = _re.compile(( |
| 274 r'%(space_chars)s+|%(space_comment_nobang)s+|%(newline)s+' |
| 275 r'|(%(bang_comment)s+)' |
| 276 ) % locals()).sub |
| 277 keeper = lambda m: m.groups()[0] or '' |
| 278 |
| 279 # print keep.__self__.pattern |
| 280 |
| 281 def space_subber_banged(match): |
| 282 """ Substitution callback """ |
| 283 # pylint: disable = too-many-return-statements |
| 284 |
| 285 groups = match.groups() |
| 286 if groups[0]: |
| 287 return groups[0] |
| 288 elif groups[1]: |
| 289 return groups[1] |
| 290 elif groups[3]: |
| 291 return "%s%s%s%s" % ( |
| 292 keep(keeper, groups[2]), |
| 293 groups[3], |
| 294 keep(keeper, groups[4] or ''), |
| 295 groups[4] and '\n' or '', |
| 296 ) |
| 297 elif groups[7]: |
| 298 return "%s%s%s%s%s" % ( |
| 299 keep(keeper, groups[5]), |
| 300 groups[6] and '\n' or '', |
| 301 groups[7], |
| 302 keep(keeper, groups[8] or ''), |
| 303 groups[8] and '\n' or '', |
| 304 ) |
| 305 elif groups[9]: |
| 306 return keep(keeper, groups[9]) + '\n' |
| 307 elif groups[10] or groups[11] or groups[12]: |
| 308 return keep(keeper, groups[10] or groups[11] or groups[12]) or ' ' |
| 309 else: |
| 310 return keep(keeper, groups[13] or groups[14]) |
| 311 |
| 312 def jsmin(script, keep_bang_comments=False): |
| 207 r""" | 313 r""" |
| 208 Minify javascript based on `jsmin.c by Douglas Crockford`_\. | 314 Minify javascript based on `jsmin.c by Douglas Crockford`_\. |
| 209 | 315 |
| 210 Instead of parsing the stream char by char, it uses a regular | 316 Instead of parsing the stream char by char, it uses a regular |
| 211 expression approach which minifies the whole script with one big | 317 expression approach which minifies the whole script with one big |
| 212 substitution regex. | 318 substitution regex. |
| 213 | 319 |
| 214 .. _jsmin.c by Douglas Crockford: | 320 .. _jsmin.c by Douglas Crockford: |
| 215 http://www.crockford.com/javascript/jsmin.c | 321 http://www.crockford.com/javascript/jsmin.c |
| 216 | 322 |
| 217 :Parameters: | 323 :Parameters: |
| 218 `script` : ``str`` | 324 `script` : ``str`` |
| 219 Script to minify | 325 Script to minify |
| 220 | 326 |
| 327 `keep_bang_comments` : ``bool`` |
| 328 Keep comments starting with an exclamation mark? (``/*!...*/``) |
| 329 |
| 221 :Return: Minified script | 330 :Return: Minified script |
| 222 :Rtype: ``str`` | 331 :Rtype: ``str`` |
| 223 """ | 332 """ |
| 224 return space_sub(space_subber, '\n%s\n' % script).strip() | 333 # pylint: disable = redefined-outer-name |
| 334 |
| 335 if keep_bang_comments: |
| 336 return space_sub_banged( |
| 337 space_subber_banged, '\n%s\n' % script |
| 338 ).strip() |
| 339 else: |
| 340 return space_sub_simple( |
| 341 space_subber_simple, '\n%s\n' % script |
| 342 ).strip() |
| 225 | 343 |
| 226 return jsmin | 344 return jsmin |
| 227 | 345 |
| 228 jsmin = _make_jsmin() | 346 jsmin = _make_jsmin() |
| 229 | 347 |
| 230 | 348 |
| 231 def jsmin_for_posers(script): | 349 def jsmin_for_posers(script, keep_bang_comments=False): |
| 232 r""" | 350 r""" |
| 233 Minify javascript based on `jsmin.c by Douglas Crockford`_\. | 351 Minify javascript based on `jsmin.c by Douglas Crockford`_\. |
| 234 | 352 |
| 235 Instead of parsing the stream char by char, it uses a regular | 353 Instead of parsing the stream char by char, it uses a regular |
| 236 expression approach which minifies the whole script with one big | 354 expression approach which minifies the whole script with one big |
| 237 substitution regex. | 355 substitution regex. |
| 238 | 356 |
| 239 .. _jsmin.c by Douglas Crockford: | 357 .. _jsmin.c by Douglas Crockford: |
| 240 http://www.crockford.com/javascript/jsmin.c | 358 http://www.crockford.com/javascript/jsmin.c |
| 241 | 359 |
| 242 :Warning: This function is the digest of a _make_jsmin() call. It just | 360 :Warning: This function is the digest of a _make_jsmin() call. It just |
| 243 utilizes the resulting regex. It's just for fun here and may | 361 utilizes the resulting regexes. It's here for fun and may |
| 244 vanish any time. Use the `jsmin` function instead. | 362 vanish any time. Use the `jsmin` function instead. |
| 245 | 363 |
| 246 :Parameters: | 364 :Parameters: |
| 247 `script` : ``str`` | 365 `script` : ``str`` |
| 248 Script to minify | 366 Script to minify |
| 249 | 367 |
| 368 `keep_bang_comments` : ``bool`` |
| 369 Keep comments starting with an exclamation mark? (``/*!...*/``) |
| 370 |
| 250 :Return: Minified script | 371 :Return: Minified script |
| 251 :Rtype: ``str`` | 372 :Rtype: ``str`` |
| 252 """ | 373 """ |
| 253 def subber(match): | 374 if not keep_bang_comments: |
| 254 """ Substitution callback """ | 375 rex = ( |
| 255 groups = match.groups() | 376 r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' |
| 256 return ( | 377 r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' |
| 257 groups[0] or | 378 r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?' |
| 258 groups[1] or | 379 r'{};\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*' |
| 259 groups[2] or | 380 r'][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\0' |
| 260 groups[3] or | 381 r'14\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r' |
| 261 (groups[4] and '\n') or | 382 r'\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r' |
| 262 (groups[5] and ' ') or | 383 r'\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011\013\014' |
| 263 (groups[6] and ' ') or | 384 r'\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^\r' |
| 264 (groups[7] and ' ') or | 385 r'\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:' |
| 265 '') | 386 r'[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;=?\]|}-]))?|(?<=[\00' |
| 387 r'0-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\016-\040]|(?' |
| 388 r':/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]' |
| 389 r'))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*' |
| 390 r'\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[' |
| 391 r'[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((' |
| 392 r'?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)' |
| 393 r'*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\04' |
| 394 r'0]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;' |
| 395 r'=?\]|}-]))?|(?<=[^\000-!#%&(*,./:-@\[\\^`{|~])(?:[\000-\011\01' |
| 396 r'3\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?:' |
| 397 r'//[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]' |
| 398 r'*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,./:-@\\-^' |
| 399 r'`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\013\014\0' |
| 400 r'16-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./' |
| 401 r':-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/\*[' |
| 402 r'^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013' |
| 403 r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[' |
| 404 r'\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' |
| 405 r')+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]' |
| 406 r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' |
| 407 ) |
| 266 | 408 |
| 267 return _re.sub( | 409 def subber(match): |
| 268 r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?' | 410 """ Substitution callback """ |
| 269 r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|' | 411 groups = match.groups() |
| 270 r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?{};\r\n])(?' | 412 return ( |
| 271 r':[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*' | 413 groups[0] or |
| 272 r'(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*' | 414 groups[1] or |
| 273 r'[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(' | 415 (groups[3] and (groups[2] + '\n')) or |
| 274 r'?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[' | 416 groups[2] or |
| 275 r'\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[\000-#%-,./:-@\[-^`{-~-]return' | 417 (groups[5] and "%s%s%s" % ( |
| 276 r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/' | 418 groups[4] and '\n' or '', |
| 277 r'))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:' | 419 groups[5], |
| 278 r'/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?' | 420 groups[6] and '\n' or '', |
| 279 r':(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/' | 421 )) or |
| 280 r'\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\^`{|' | 422 (groups[7] and '\n') or |
| 281 r'~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)' | 423 (groups[8] and ' ') or |
| 282 r'*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]' | 424 (groups[9] and ' ') or |
| 283 r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,./' | 425 (groups[10] and ' ') or |
| 284 r':-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\013\01' | 426 '' |
| 285 r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./:' | 427 ) |
| 286 r'-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*' | 428 else: |
| 287 r'\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013\014\016-' | 429 rex = ( |
| 288 r'\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\000-\011\013' | 430 r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' |
| 289 r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^' | 431 r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' |
| 290 r'\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^' | 432 r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?' |
| 291 r'/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script).strip() | 433 r'{};\r\n])((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/' |
| 434 r'*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013' |
| 435 r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*)((?:/(?!' |
| 436 r'[\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^' |
| 437 r'\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011\013\01' |
| 438 r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^' |
| 439 r'\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(' |
| 440 r'?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;=?\]|}-]))?|(?<=[' |
| 441 r'\000-#%-,./:-@\[-^`{-~-]return)((?:[\000-\011\013\014\016-\040' |
| 442 r']|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[' |
| 443 r'\r\n]))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][' |
| 444 r'^*]*\*+)*/))*)*)((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|' |
| 445 r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*' |
| 446 r'/))((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]' |
| 447 r'*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\01' |
| 448 r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)' |
| 449 r'+,.:;=?\]|}-]))?|(?<=[^\000-!#%&(*,./:-@\[\\^`{|~])((?:[\000-' |
| 450 r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:' |
| 451 r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/' |
| 452 r'\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+)(?=[^\000-\040"#%-\047)*,./' |
| 453 r':-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\01' |
| 454 r'3\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=[^\000' |
| 455 r'-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|' |
| 456 r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=\+)|(?<=-)((?:[\000-\0' |
| 457 r'11\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=-' |
| 458 r')|((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*' |
| 459 r'\*+)*/))+)|((?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014' |
| 460 r'\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+)' |
| 461 ) |
| 462 |
| 463 keep = _re.compile(( |
| 464 r'[\000-\011\013\014\016-\040]+|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*' |
| 465 r'\*+)*/)+|(?:(?://[^\r\n]*)?[\r\n])+|((?:/\*![^*]*\*+(?:[^/*][^' |
| 466 r'*]*\*+)*/)+)' |
| 467 ) % locals()).sub |
| 468 keeper = lambda m: m.groups()[0] or '' |
| 469 |
| 470 def subber(match): |
| 471 """ Substitution callback """ |
| 472 groups = match.groups() |
| 473 return ( |
| 474 groups[0] or |
| 475 groups[1] or |
| 476 (groups[3] and "%s%s%s%s" % ( |
| 477 keep(keeper, groups[2]), |
| 478 groups[3], |
| 479 keep(keeper, groups[4] or ''), |
| 480 groups[4] and '\n' or '', |
| 481 )) or |
| 482 (groups[7] and "%s%s%s%s%s" % ( |
| 483 keep(keeper, groups[5]), |
| 484 groups[6] and '\n' or '', |
| 485 groups[7], |
| 486 keep(keeper, groups[8] or ''), |
| 487 groups[8] and '\n' or '', |
| 488 )) or |
| 489 (groups[9] and keep(keeper, groups[9] + '\n')) or |
| 490 (groups[10] and keep(keeper, groups[10]) or ' ') or |
| 491 (groups[11] and keep(keeper, groups[11]) or ' ') or |
| 492 (groups[12] and keep(keeper, groups[12]) or ' ') or |
| 493 keep(keeper, groups[13] or groups[14]) |
| 494 ) |
| 495 |
| 496 return _re.sub(rex, subber, '\n%s\n' % script).strip() |
| 292 | 497 |
| 293 | 498 |
| 294 if __name__ == '__main__': | 499 if __name__ == '__main__': |
| 295 import sys as _sys | 500 def main(): |
| 296 _sys.stdout.write(jsmin(_sys.stdin.read())) | 501 """ Main """ |
| 502 import sys as _sys |
| 503 |
| 504 argv = _sys.argv[1:] |
| 505 keep_bang_comments = '-b' in argv or '-bp' in argv or '-pb' in argv |
| 506 if '-p' in argv or '-bp' in argv or '-pb' in argv: |
| 507 xjsmin = _make_jsmin(python_only=True) |
| 508 else: |
| 509 xjsmin = jsmin |
| 510 |
| 511 _sys.stdout.write(xjsmin( |
| 512 _sys.stdin.read(), keep_bang_comments=keep_bang_comments |
| 513 )) |
| 514 |
| 515 main() |
| OLD | NEW |