• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# -*- coding: ascii -*-
3r"""
4=====================
5 Javascript Minifier
6=====================
7
8rJSmin is a javascript minifier written in python.
9
10The 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.
30
31The module is a re-implementation aiming for speed, so it can be used at
32runtime (rather than during a preprocessing step). Usually it produces the
33same results as the original ``jsmin.c``. It differs in the following ways:
34
35- there is no error detection: unterminated string, regex and comment
36  literals are treated as regular javascript code and minified as such.
37- Control characters inside string and regex literals are left untouched; they
38  are not converted to spaces (nor to \\n)
39- Newline characters are not allowed inside string and regex literals, except
40  for line continuations in string literals (ECMA-5).
41- "return /regex/" is recognized correctly.
42- Line terminators after regex literals are handled more sensibly
43- "+ +" and "- -" sequences are not collapsed to '++' or '--'
44- Newlines before ! operators are removed more sensibly
45- Comments starting with an exclamation mark (``!``) can be kept optionally
46- rJSmin does not handle streams, but only complete strings. (However, the
47  module provides a "streamy" interface).
48
49Since most parts of the logic are handled by the regex engine it's way faster
50than the original python port of ``jsmin.c`` by Baruch Even. The speed factor
51varies between about 6 and 55 depending on input and python version (it gets
52faster the more compressed the input already is). Compared to the
53speed-refactored python port by Dave St.Germain the performance gain is less
54dramatic but still between 3 and 50 (for huge inputs). See the docs/BENCHMARKS
55file for details.
56
57rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more.
58
59Both python 2 and python 3 are supported.
60
61.. _jsmin.c by Douglas Crockford:
62   http://www.crockford.com/javascript/jsmin.c
63"""
64if __doc__:
65    # pylint: disable = redefined-builtin
66    __doc__ = __doc__.encode('ascii').decode('unicode_escape')
67__author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape')
68__docformat__ = "restructuredtext en"
69__license__ = "Apache License, Version 2.0"
70__version__ = '1.0.12'
71__all__ = ['jsmin']
72
73import re as _re
74
75
76def _make_jsmin(python_only=False):
77    """
78    Generate JS minifier based on `jsmin.c by Douglas Crockford`_
79
80    .. _jsmin.c by Douglas Crockford:
81       http://www.crockford.com/javascript/jsmin.c
82
83    :Parameters:
84      `python_only` : ``bool``
85        Use only the python variant. If true, the c extension is not even
86        tried to be loaded.
87
88    :Return: Minifier
89    :Rtype: ``callable``
90    """
91    # pylint: disable = unused-variable
92    # pylint: disable = too-many-locals
93
94    if not python_only:
95        try:
96            import _rjsmin
97        except ImportError:
98            pass
99        else:
100            return _rjsmin.jsmin
101    try:
102        xrange
103    except NameError:
104        xrange = range  # pylint: disable = redefined-builtin
105
106    space_chars = r'[\000-\011\013\014\016-\040]'
107
108    line_comment = r'(?://[^\r\n]*)'
109    space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
110    space_comment_nobang = r'(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/)'
111    bang_comment = r'(?:/\*![^*]*\*+(?:[^/*][^*]*\*+)*/)'
112
113    string1 = \
114        r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)'
115    string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")'
116    string3 = r'(?:`(?:[^`\\]|\\.)*`)'
117    strings = r'(?:%s|%s|%s)' % (string1, string2, string3)
118
119    charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])'
120    nospecial = r'[^/\\\[\r\n]'
121    regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % (
122        nospecial, charclass, nospecial
123    )
124    space = r'(?:%s|%s)' % (space_chars, space_comment)
125    newline = r'(?:%s?[\r\n])' % line_comment
126
127    def fix_charclass(result):
128        """ Fixup string of chars to fit into a regex char class """
129        pos = result.find('-')
130        if pos >= 0:
131            result = r'%s%s-' % (result[:pos], result[pos + 1:])
132
133        def sequentize(string):
134            """
135            Notate consecutive characters as sequence
136
137            (1-4 instead of 1234)
138            """
139            first, last, result = None, None, []
140            for char in map(ord, string):
141                if last is None:
142                    first = last = char
143                elif last + 1 == char:
144                    last = char
145                else:
146                    result.append((first, last))
147                    first = last = char
148            if last is not None:
149                result.append((first, last))
150            return ''.join(['%s%s%s' % (
151                chr(first),
152                last > first + 1 and '-' or '',
153                last != first and chr(last) or ''
154            ) for first, last in result])  # noqa
155
156        return _re.sub(
157            r'([\000-\040\047])',  # \047 for better portability
158            lambda m: '\\%03o' % ord(m.group(1)), (
159                sequentize(result)
160                .replace('\\', '\\\\')
161                .replace('[', '\\[')
162                .replace(']', '\\]')
163            )
164        )
165
166    def id_literal_(what):
167        """ Make id_literal like char class """
168        match = _re.compile(what).match
169        result = ''.join([
170            chr(c) for c in xrange(127) if not match(chr(c))
171        ])
172        return '[^%s]' % fix_charclass(result)
173
174    def not_id_literal_(keep):
175        """ Make negated id_literal like char class """
176        match = _re.compile(id_literal_(keep)).match
177        result = ''.join([
178            chr(c) for c in xrange(127) if not match(chr(c))
179        ])
180        return r'[%s]' % fix_charclass(result)
181
182    not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]')
183    preregex1 = r'[(,=:\[!&|?{};\r\n]'
184    preregex2 = r'%(not_id_literal)sreturn' % locals()
185
186    id_literal = id_literal_(r'[a-zA-Z0-9_$]')
187    id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]')
188    id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
189    post_regex_off = id_literal_(r'[^\000-\040}\])?:|,;.&=+-]')
190
191    dull = r'[^\047"`/\000-\040]'
192
193    space_sub_simple = _re.compile((
194        # noqa pylint: disable = bad-continuation
195
196        r'(%(dull)s+)'                                         # 0
197        r'|(%(strings)s%(dull)s*)'                             # 1
198        r'|(?<=%(preregex1)s)'
199            r'%(space)s*(?:%(newline)s%(space)s*)*'
200            r'(%(regex)s)'                                     # 2
201            r'(%(space)s*(?:%(newline)s%(space)s*)+'           # 3
202                r'(?=%(post_regex_off)s))?'
203        r'|(?<=%(preregex2)s)'
204            r'%(space)s*(?:(%(newline)s)%(space)s*)*'          # 4
205            r'(%(regex)s)'                                     # 5
206            r'(%(space)s*(?:%(newline)s%(space)s*)+'           # 6
207                r'(?=%(post_regex_off)s))?'
208        r'|(?<=%(id_literal_close)s)'
209            r'%(space)s*(?:(%(newline)s)%(space)s*)+'          # 7
210            r'(?=%(id_literal_open)s)'
211        r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'  # 8
212        r'|(?<=\+)(%(space)s)+(?=\+)'                          # 9
213        r'|(?<=-)(%(space)s)+(?=-)'                            # 10
214        r'|%(space)s+'
215        r'|(?:%(newline)s%(space)s*)+'
216    ) % locals()).sub
217
218    # print space_sub_simple.__self__.pattern
219
220    def space_subber_simple(match):
221        """ Substitution callback """
222        # pylint: disable = too-many-return-statements
223
224        groups = match.groups()
225        if groups[0]:
226            return groups[0]
227        elif groups[1]:
228            return groups[1]
229        elif groups[2]:
230            if groups[3]:
231                return groups[2] + '\n'
232            return groups[2]
233        elif groups[5]:
234            return "%s%s%s" % (
235                groups[4] and '\n' or '',
236                groups[5],
237                groups[6] and '\n' or '',
238            )
239        elif groups[7]:
240            return '\n'
241        elif groups[8] or groups[9] or groups[10]:
242            return ' '
243        else:
244            return ''
245
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):
313        r"""
314        Minify javascript based on `jsmin.c by Douglas Crockford`_\.
315
316        Instead of parsing the stream char by char, it uses a regular
317        expression approach which minifies the whole script with one big
318        substitution regex.
319
320        .. _jsmin.c by Douglas Crockford:
321           http://www.crockford.com/javascript/jsmin.c
322
323        :Parameters:
324          `script` : ``str``
325            Script to minify
326
327          `keep_bang_comments` : ``bool``
328            Keep comments starting with an exclamation mark? (``/*!...*/``)
329
330        :Return: Minified script
331        :Rtype: ``str``
332        """
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()
343
344    return jsmin
345
346jsmin = _make_jsmin()
347
348
349def jsmin_for_posers(script, keep_bang_comments=False):
350    r"""
351    Minify javascript based on `jsmin.c by Douglas Crockford`_\.
352
353    Instead of parsing the stream char by char, it uses a regular
354    expression approach which minifies the whole script with one big
355    substitution regex.
356
357    .. _jsmin.c by Douglas Crockford:
358       http://www.crockford.com/javascript/jsmin.c
359
360    :Warning: This function is the digest of a _make_jsmin() call. It just
361              utilizes the resulting regexes. It's here for fun and may
362              vanish any time. Use the `jsmin` function instead.
363
364    :Parameters:
365      `script` : ``str``
366        Script to minify
367
368      `keep_bang_comments` : ``bool``
369        Keep comments starting with an exclamation mark? (``/*!...*/``)
370
371    :Return: Minified script
372    :Rtype: ``str``
373    """
374    if not keep_bang_comments:
375        rex = (
376            r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]'
377            r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]'
378            r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?'
379            r'{};\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*'
380            r'][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\0'
381            r'14\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r'
382            r'\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r'
383            r'\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011\013\014'
384            r'\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^\r'
385            r'\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:'
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        )
408
409        def subber(match):
410            """ Substitution callback """
411            groups = match.groups()
412            return (
413                groups[0] or
414                groups[1] or
415                (groups[3] and (groups[2] + '\n')) or
416                groups[2] or
417                (groups[5] and "%s%s%s" % (
418                    groups[4] and '\n' or '',
419                    groups[5],
420                    groups[6] and '\n' or '',
421                )) or
422                (groups[7] and '\n') or
423                (groups[8] and ' ') or
424                (groups[9] and ' ') or
425                (groups[10] and ' ') or
426                ''
427            )
428    else:
429        rex = (
430            r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]'
431            r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]'
432            r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?'
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()
497
498
499if __name__ == '__main__':
500    def main():
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()
516