source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/contrib/minify/jsmin.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 14.0 KB
Line 
1#!/usr/bin/env python
2# -*- coding: ascii -*-
3#
4# Copyright 2011
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.
18r"""
19=====================
20 Javascript Minifier
21=====================
22
23Javascript Minifier based on `jsmin.c by Douglas Crockford`_\.
24
25This module is a re-implementation based on the semantics of jsmin.c. Usually
26it produces the same results. It differs in the following ways:
27
28- there is no error detection: unterminated string, regex and comment
29  literals are treated as regular javascript code and minified as such.
30- Control characters inside string and regex literals are left untouched; they
31  are not converted to spaces (nor to \n)
32- Newline characters are not allowed inside string and regex literals, except
33  for line continuations in string literals (ECMA-5).
34- "return /regex/" is recognized correctly.
35- rjsmin does not handle streams, but only complete strings. (However, the
36  module provides a "streamy" interface).
37
38Besides the list above it differs from direct python ports of jsmin.c in
39speed. Since most parts of the logic are handled by the regex engine it's way
40faster than the original python port by Baruch Even. The speed factor varies
41between about 6 and 55 depending on input and python version (it gets faster
42the more compressed the input already is). Compared to the speed-refactored
43python port by Dave St.Germain the performance gain is less dramatic but still
44between 1.2 and 7. See the docs/BENCHMARKS file for details.
45
46rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more.
47
48Both python 2 and python 3 are supported.
49
50.. _jsmin.c by Douglas Crockford:
51   http://www.crockford.com/javascript/jsmin.c
52
53Original author of Python version: Andr\xe9 Malo
54Home page: http://opensource.perlig.de/rjsmin/
55Modified by Ross Peoples <ross.peoples@gmail.com> for inclusion into web2py.
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.2'
62__all__ = ['jsmin', 'jsmin_for_posers']
63
64import re as _re
65
66
67def _make_jsmin(extended=True, python_only=True):
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      `extended` : ``bool``
76        Extended Regexps? (using lookahead and lookbehind). This is faster,
77        because it can be optimized way more. The regexps used with `extended`
78        being false are only left here to allow easier porting to platforms
79        without extended regex features (and for my own reference...)
80
81      `python_only` : ``bool``
82        Use only the python variant. If true, the c extension is not even
83        tried to be loaded.
84
85    :Return: Minifier
86    :Rtype: ``callable``
87    """
88    # pylint: disable = R0912, R0914, W0612
89    if not python_only:
90        try:
91            import _rjsmin
92        except ImportError:
93            pass
94        else:
95            return _rjsmin.jsmin
96    try:
97        xrange
98    except NameError:
99        xrange = range  # pylint: disable = W0622
100
101    space_chars = r'[\000-\011\013\014\016-\040]'
102
103    line_comment = r'(?://[^\r\n]*)'
104    space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
105    string1 = \
106        r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)'
107    string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")'
108    strings = r'(?:%s|%s)' % (string1, string2)
109
110    charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])'
111    nospecial = r'[^/\\\[\r\n]'
112    if extended:
113        regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % (
114            nospecial, charclass, nospecial
115        )
116    else:
117        regex = (
118            r'(?:/(?:[^*/\\\r\n\[]|%s|\\[^\r\n])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)'
119        )
120        regex = regex % (charclass, nospecial, charclass, nospecial)
121
122    space = r'(?:%s|%s)' % (space_chars, space_comment)
123    newline = r'(?:%s?[\r\n])' % line_comment
124
125    def fix_charclass(result):
126        """ Fixup string of chars to fit into a regex char class """
127        pos = result.find('-')
128        if pos >= 0:
129            result = r'%s%s-' % (result[:pos], result[pos + 1:])
130
131        def sequentize(string):
132            """
133            Notate consecutive characters as sequence
134
135            (1-4 instead of 1234)
136            """
137            first, last, result = None, None, []
138            for char in map(ord, string):
139                if last is None:
140                    first = last = char
141                elif last + 1 == char:
142                    last = char
143                else:
144                    result.append((first, last))
145                    first = last = char
146            if last is not None:
147                result.append((first, last))
148            return ''.join(['%s%s%s' % (
149                chr(first),
150                last > first + 1 and '-' or '',
151                last != first and chr(last) or ''
152            ) for first, last in result])
153
154        return _re.sub(r'([\000-\040\047])',  # for better portability
155                       lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result)
156                                                              .replace('\\', '\\\\')
157                                                              .replace('[', '\\[')
158                                                              .replace(']', '\\]')
159                                                              )
160                       )
161
162    def id_literal_(what):
163        """ Make id_literal like char class """
164        match = _re.compile(what).match
165        result = ''.join([
166            chr(c) for c in xrange(127) if not match(chr(c))
167        ])
168        return '[^%s]' % fix_charclass(result)
169
170    def not_id_literal_(keep):
171        """ Make negated id_literal like char class """
172        match = _re.compile(id_literal_(keep)).match
173        result = ''.join([
174            chr(c) for c in xrange(127) if not match(chr(c))
175        ])
176        return r'[%s]' % fix_charclass(result)
177
178    not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]')
179    preregex1 = r'[(,=:\[!&|?{};\r\n]'
180    preregex2 = r'%(not_id_literal)sreturn' % locals()
181
182    if extended:
183        id_literal = id_literal_(r'[a-zA-Z0-9_$]')
184        id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(+-]')
185        id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
186
187        space_sub = _re.compile((
188            r'([^\047"/\000-\040]+)'
189            r'|(%(strings)s[^\047"/\000-\040]*)'
190            r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
191            r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
192            r'|(?<=%(id_literal_close)s)'
193            r'%(space)s*(?:(%(newline)s)%(space)s*)+'
194            r'(?=%(id_literal_open)s)'
195            r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
196            r'|%(space)s+'
197            r'|(?:%(newline)s%(space)s*)+'
198        ) % locals()).sub
199
200        def space_subber(match):
201            """ Substitution callback """
202            # pylint: disable = C0321, R0911
203            groups = match.groups()
204            if groups[0]:
205                return groups[0]
206            elif groups[1]:
207                return groups[1]
208            elif groups[2]:
209                return groups[2]
210            elif groups[3]:
211                return groups[3]
212            elif groups[4]:
213                return '\n'
214            elif groups[5]:
215                return ' '
216            else:
217                return ''
218
219        def jsmin(script):  # pylint: disable = W0621
220            r"""
221            Minify javascript based on `jsmin.c by Douglas Crockford`_\.
222
223            Instead of parsing the stream char by char, it uses a regular
224            expression approach which minifies the whole script with one big
225            substitution regex.
226
227            .. _jsmin.c by Douglas Crockford:
228               http://www.crockford.com/javascript/jsmin.c
229
230            :Parameters:
231              `script` : ``str``
232                Script to minify
233
234            :Return: Minified script
235            :Rtype: ``str``
236            """
237            return space_sub(space_subber, '\n%s\n' % script).strip()
238
239    else:
240        pre_regex = r'(?:%(preregex1)s|%(preregex2)s)' % locals()
241        not_id_literal_open = not_id_literal_(r'[a-zA-Z0-9_${\[(+-]')
242        not_id_literal_close = not_id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
243
244        space_norm_sub = _re.compile((
245            r'(%(strings)s)'
246            r'|(?:(%(pre_regex)s)%(space)s*(%(regex)s))'
247            r'|(%(space)s)+'
248            r'|(?:(%(newline)s)%(space)s*)+'
249        ) % locals()).sub
250
251        def space_norm_subber(match):
252            """ Substitution callback """
253            # pylint: disable = C0321
254            groups = match.groups()
255            if groups[0]:
256                return groups[0]
257            elif groups[1]:
258                return groups[1].replace('\r', '\n') + groups[2]
259            elif groups[3]:
260                return ' '
261            elif groups[4]:
262                return '\n'
263
264        space_sub1 = _re.compile((
265            r'[\040\n]?(%(strings)s|%(pre_regex)s%(regex)s)'
266            r'|\040(%(not_id_literal)s)'
267            r'|\n(%(not_id_literal_open)s)'
268        ) % locals()).sub
269
270        def space_subber1(match):
271            """ Substitution callback """
272            groups = match.groups()
273            return groups[0] or groups[1] or groups[2]
274
275        space_sub2 = _re.compile((
276            r'(%(strings)s)\040?'
277            r'|(%(pre_regex)s%(regex)s)[\040\n]?'
278            r'|(%(not_id_literal)s)\040'
279            r'|(%(not_id_literal_close)s)\n'
280        ) % locals()).sub
281
282        def space_subber2(match):
283            """ Substitution callback """
284            groups = match.groups()
285            return groups[0] or groups[1] or groups[2] or groups[3]
286
287        def jsmin(script):
288            r"""
289            Minify javascript based on `jsmin.c by Douglas Crockford`_\.
290
291            Instead of parsing the stream char by char, it uses a regular
292            expression approach. The script is minified with three passes:
293
294            normalization
295                Control character are mapped to spaces, spaces and newlines
296                are squeezed and comments are stripped.
297            space removal 1
298                Spaces before certain tokens are removed
299            space removal 2
300                Spaces after certain tokens are remove
301
302            .. _jsmin.c by Douglas Crockford:
303               http://www.crockford.com/javascript/jsmin.c
304
305            :Parameters:
306              `script` : ``str``
307                Script to minify
308
309            :Return: Minified script
310            :Rtype: ``str``
311            """
312            return space_sub2(space_subber2,
313                              space_sub1(space_subber1,
314                                         space_norm_sub(space_norm_subber,
315                                                        '\n%s\n' % script)
316                                         )
317                              ).strip()
318    return jsmin
319
320jsmin = _make_jsmin()
321
322#####################
323#   EXAMPLE USAGE   #
324#####################
325#
326# import jsmin
327# jsmin.jsmin(script)
328#
329
330
331def jsmin_for_posers(script):
332    r"""
333    Minify javascript based on `jsmin.c by Douglas Crockford`_\.
334
335    Instead of parsing the stream char by char, it uses a regular
336    expression approach which minifies the whole script with one big
337    substitution regex.
338
339    .. _jsmin.c by Douglas Crockford:
340       http://www.crockford.com/javascript/jsmin.c
341
342    :Warning: This function is the digest of a _make_jsmin() call. It just
343              utilizes the resulting regex. It's just for fun here and may
344              vanish any time. Use the `jsmin` function instead.
345
346    :Parameters:
347      `script` : ``str``
348        Script to minify
349
350    :Return: Minified script
351    :Rtype: ``str``
352    """
353    def subber(match):
354        """ Substitution callback """
355        groups = match.groups()
356        return (
357            groups[0] or
358            groups[1] or
359            groups[2] or
360            groups[3] or
361            (groups[4] and '\n') or
362            (groups[5] and ' ') or
363            ''
364        )
365
366    return _re.sub(
367        r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
368        r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
369        r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
370        r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
371        r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
372        r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
373        r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
374        r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
375        r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
376        r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
377        r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
378        r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
379        r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-#%-\04'
380        r'7)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011'
381        r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-'
382        r'#%-,./:-@\[-^`{-~-])|(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*'
383        r'+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011'
384        r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+',
385        subber, '\n%s\n' % script
386    ).strip()
387
388
389if __name__ == '__main__':
390    import sys as _sys
391    _sys.stdout.write(jsmin(_sys.stdin.read()))
Note: See TracBrowser for help on using the repository browser.