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 73importreas_re 74 75
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 94ifnotpython_only: 95try: 96import_rjsmin 97exceptImportError: 98pass 99else:100return_rjsmin.jsmin101try:102xrange103exceptNameError:104xrange=range# pylint: disable = redefined-builtin105106space_chars=r'[\\000-\\011\\013\\014\\016-\\040]'107108line_comment=r'(?://[^\\r\\n]*)'109space_comment=r'(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/)'110space_comment_nobang=r'(?:/\\*(?!!)[^*]*\\*+(?:[^/*][^*]*\\*+)*/)'111bang_comment=r'(?:/\\*![^*]*\\*+(?:[^/*][^*]*\\*+)*/)'112113string1= \\ 114r'(?:\\047[^\\047\\\\\\r\\n]*(?:\\\\(?:[^\\r\\n]|\\r?\\n|\\r)[^\\047\\\\\\r\\n]*)*\\047)'115string2=r'(?:"[^"\\\\\\r\\n]*(?:\\\\(?:[^\\r\\n]|\\r?\\n|\\r)[^"\\\\\\r\\n]*)*")'116strings=r'(?:%s|%s)'%(string1,string2)117118charclass=r'(?:\\[[^\\\\\\]\\r\\n]*(?:\\\\[^\\r\\n][^\\\\\\]\\r\\n]*)*\\])'119nospecial=r'[^/\\\\\\[\\r\\n]'120regex=r'(?:/(?![\\r\\n/*])%s*(?:(?:\\\\[^\\r\\n]|%s)%s*)*/)'%(121nospecial,charclass,nospecial122)123space=r'(?:%s|%s)'%(space_chars,space_comment)124newline=r'(?:%s?[\\r\\n])'%line_comment125126deffix_charclass(result):127""" Fixup string of chars to fit into a regex char class """128pos=result.find('-')129ifpos>=0:130result=r'%s%s-'%(result[:pos],result[pos+1:])131132defsequentize(string):133"""134 Notate consecutive characters as sequence135136 (1-4 instead of 1234)137 """138first,last,result=None,None,[]139forcharinmap(ord,string):140iflastisNone:141first=last=char142eliflast+1==char:143last=char144else:145result.append((first,last))146first=last=char147iflastisnotNone:148result.append((first,last))149return''.join(['%s%s%s'%(150chr(first),151last>first+1and'-'or'',152last!=firstandchr(last)or''153)forfirst,lastinresult])# noqa
154155return_re.sub(156r'([\\000-\\040\\047])',# \\047 for better portability157lambdam:'\\\\%03o'%ord(m.group(1)),(158sequentize(result)159.replace('\\\\','\\\\\\\\')160.replace('[','\\\\[')161.replace(']','\\\\]')162)163)164165defid_literal_(what):166""" Make id_literal like char class """167match=_re.compile(what).match168result=''.join([169chr(c)forcinxrange(127)ifnotmatch(chr(c))170])171return'[^%s]'%fix_charclass(result)172173defnot_id_literal_(keep):174""" Make negated id_literal like char class """175match=_re.compile(id_literal_(keep)).match176result=''.join([177chr(c)forcinxrange(127)ifnotmatch(chr(c))178])179returnr'[%s]'%fix_charclass(result)180181not_id_literal=not_id_literal_(r'[a-zA-Z0-9_$]')182preregex1=r'[(,=:\\[!&|?{};\\r\\n]'183preregex2=r'%(not_id_literal)sreturn'%locals()184185id_literal=id_literal_(r'[a-zA-Z0-9_$]')186id_literal_open=id_literal_(r'[a-zA-Z0-9_${\\[(!+-]')187id_literal_close=id_literal_(r'[a-zA-Z0-9_$}\\])"\\047+-]')188post_regex_off=id_literal_(r'[^\\000-\\040}\\])?:|,;.&=+-]')189190dull=r'[^\\047"/\\000-\\040]'191192space_sub_simple=_re.compile((193# noqa pylint: disable = bad-continuation194195r'(%(dull)s+)'# 0196r'|(%(strings)s%(dull)s*)'# 1197r'|(?<=%(preregex1)s)'198r'%(space)s*(?:%(newline)s%(space)s*)*'199r'(%(regex)s)'# 2200r'(%(space)s*(?:%(newline)s%(space)s*)+'# 3201r'(?=%(post_regex_off)s))?'202r'|(?<=%(preregex2)s)'203r'%(space)s*(?:(%(newline)s)%(space)s*)*'# 4204r'(%(regex)s)'# 5205r'(%(space)s*(?:%(newline)s%(space)s*)+'# 6206r'(?=%(post_regex_off)s))?'207r'|(?<=%(id_literal_close)s)'208r'%(space)s*(?:(%(newline)s)%(space)s*)+'# 7209r'(?=%(id_literal_open)s)'210r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'# 8211r'|(?<=\\+)(%(space)s)+(?=\\+)'# 9212r'|(?<=-)(%(space)s)+(?=-)'# 10213r'|%(space)s+'214r'|(?:%(newline)s%(space)s*)+'215)%locals()).sub216217# print space_sub_simple.__self__.pattern218219defspace_subber_simple(match):220""" Substitution callback """221# pylint: disable = too-many-return-statements222223groups=match.groups()224ifgroups[0]:225returngroups[0]226elifgroups[1]:227returngroups[1]228elifgroups[2]:229ifgroups[3]:230returngroups[2]+'\\n'231returngroups[2]232elifgroups[5]:233return"%s%s%s"%(234groups[4]and'\\n'or'',235groups[5],236groups[6]and'\\n'or'',237)238elifgroups[7]:239return'\\n'240elifgroups[8]orgroups[9]orgroups[10]:241return' '242else:243return''244245space_sub_banged=_re.compile((246# noqa pylint: disable = bad-continuation247248r'(%(dull)s+)'# 0249r'|(%(strings)s%(dull)s*)'# 1250r'|(?<=%(preregex1)s)'251r'(%(space)s*(?:%(newline)s%(space)s*)*)'# 2252r'(%(regex)s)'# 3253r'(%(space)s*(?:%(newline)s%(space)s*)+'# 4254r'(?=%(post_regex_off)s))?'255r'|(?<=%(preregex2)s)'256r'(%(space)s*(?:(%(newline)s)%(space)s*)*)'# 5, 6257r'(%(regex)s)'# 7258r'(%(space)s*(?:%(newline)s%(space)s*)+'# 8259r'(?=%(post_regex_off)s))?'260r'|(?<=%(id_literal_close)s)'261r'(%(space)s*(?:%(newline)s%(space)s*)+)'# 9262r'(?=%(id_literal_open)s)'263r'|(?<=%(id_literal)s)(%(space)s+)(?=%(id_literal)s)'# 10264r'|(?<=\\+)(%(space)s+)(?=\\+)'# 11265r'|(?<=-)(%(space)s+)(?=-)'# 12266r'|(%(space)s+)'# 13267r'|((?:%(newline)s%(space)s*)+)'# 14268)%locals()).sub269270# print space_sub_banged.__self__.pattern271272keep=_re.compile((273r'%(space_chars)s+|%(space_comment_nobang)s+|%(newline)s+'274r'|(%(bang_comment)s+)'275)%locals()).sub276keeper=lambdam:m.groups()[0]or''277278# print keep.__self__.pattern279280defspace_subber_banged(match):281""" Substitution callback """282# pylint: disable = too-many-return-statements283284groups=match.groups()285ifgroups[0]:286returngroups[0]287elifgroups[1]:288returngroups[1]289elifgroups[3]:290return"%s%s%s%s"%(291keep(keeper,groups[2]),292groups[3],293keep(keeper,groups[4]or''),294groups[4]and'\\n'or'',295)296elifgroups[7]:297return"%s%s%s%s%s"%(298keep(keeper,groups[5]),299groups[6]and'\\n'or'',300groups[7],301keep(keeper,groups[8]or''),302groups[8]and'\\n'or'',303)304elifgroups[9]:305returnkeep(keeper,groups[9])+'\\n'306elifgroups[10]orgroups[11]orgroups[12]:307returnkeep(keeper,groups[10]orgroups[11]orgroups[12])or' '308else:309returnkeep(keeper,groups[13]orgroups[14])310311defjsmin(script,keep_bang_comments=False):312r"""313 Minify javascript based on `jsmin.c by Douglas Crockford`_\\.314315 Instead of parsing the stream char by char, it uses a regular316 expression approach which minifies the whole script with one big317 substitution regex.318319 .. _jsmin.c by Douglas Crockford:320 http://www.crockford.com/javascript/jsmin.c321322 :Parameters:323 `script` : ``str``324 Script to minify325326 `keep_bang_comments` : ``bool``327 Keep comments starting with an exclamation mark? (``/*!...*/``)328329 :Return: Minified script330 :Rtype: ``str``331 """332# pylint: disable = redefined-outer-name333334ifkeep_bang_comments:335returnspace_sub_banged(336space_subber_banged,'\\n%s\\n'%script337).strip()338else:339returnspace_sub_simple(340space_subber_simple,'\\n%s\\n'%script341).strip()342343returnjsmin344345jsmin=_make_jsmin()346347
349r"""350 Minify javascript based on `jsmin.c by Douglas Crockford`_\\.351352 Instead of parsing the stream char by char, it uses a regular353 expression approach which minifies the whole script with one big354 substitution regex.355356 .. _jsmin.c by Douglas Crockford:357 http://www.crockford.com/javascript/jsmin.c358359 :Warning: This function is the digest of a _make_jsmin() call. It just360 utilizes the resulting regexes. It's here for fun and may361 vanish any time. Use the `jsmin` function instead.362363 :Parameters:364 `script` : ``str``365 Script to minify366367 `keep_bang_comments` : ``bool``368 Keep comments starting with an exclamation mark? (``/*!...*/``)369370 :Return: Minified script371 :Rtype: ``str``372 """373ifnotkeep_bang_comments:374rex=(375r'([^\\047"/\\000-\\040]+)|((?:(?:\\047[^\\047\\\\\\r\\n]*(?:\\\\(?:[^\\r\\n]'376r'|\\r?\\n|\\r)[^\\047\\\\\\r\\n]*)*\\047)|(?:"[^"\\\\\\r\\n]*(?:\\\\(?:[^\\r\\n]'377r'|\\r?\\n|\\r)[^"\\\\\\r\\n]*)*"))[^\\047"/\\000-\\040]*)|(?<=[(,=:\\[!&|?'378r'{};\\r\\n])(?:[\\000-\\011\\013\\014\\016-\\040]|(?:/\\*[^*]*\\*+(?:[^/*'379r'][^*]*\\*+)*/))*(?:(?:(?://[^\\r\\n]*)?[\\r\\n])(?:[\\000-\\011\\013\\0'380r'14\\016-\\040]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/))*)*((?:/(?![\\r'381r'\\n/*])[^/\\\\\\[\\r\\n]*(?:(?:\\\\[^\\r\\n]|(?:\\[[^\\\\\\]\\r\\n]*(?:\\\\[^\\r'382r'\\n][^\\\\\\]\\r\\n]*)*\\]))[^/\\\\\\[\\r\\n]*)*/))((?:[\\000-\\011\\013\\014'383r'\\016-\\040]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/))*(?:(?:(?://[^\\r'384r'\\n]*)?[\\r\\n])(?:[\\000-\\011\\013\\014\\016-\\040]|(?:/\\*[^*]*\\*+(?:'385r'[^/*][^*]*\\*+)*/))*)+(?=[^\\000-\\040&)+,.:;=?\\]|}-]))?|(?<=[\\00'386r'0-#%-,./:-@\\[-^`{-~-]return)(?:[\\000-\\011\\013\\014\\016-\\040]|(?'387r':/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/))*(?:((?:(?://[^\\r\\n]*)?[\\r\\n]'388r'))(?:[\\000-\\011\\013\\014\\016-\\040]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*'389r'\\*+)*/))*)*((?:/(?![\\r\\n/*])[^/\\\\\\[\\r\\n]*(?:(?:\\\\[^\\r\\n]|(?:\\['390r'[^\\\\\\]\\r\\n]*(?:\\\\[^\\r\\n][^\\\\\\]\\r\\n]*)*\\]))[^/\\\\\\[\\r\\n]*)*/))(('391r'?:[\\000-\\011\\013\\014\\016-\\040]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)'392r'*/))*(?:(?:(?://[^\\r\\n]*)?[\\r\\n])(?:[\\000-\\011\\013\\014\\016-\\04'393r'0]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/))*)+(?=[^\\000-\\040&)+,.:;'394r'=?\\]|}-]))?|(?<=[^\\000-!#%&(*,./:-@\\[\\\\^`{|~])(?:[\\000-\\011\\01'395r'3\\014\\016-\\040]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/))*(?:((?:(?:'396r'//[^\\r\\n]*)?[\\r\\n]))(?:[\\000-\\011\\013\\014\\016-\\040]|(?:/\\*[^*]'397r'*\\*+(?:[^/*][^*]*\\*+)*/))*)+(?=[^\\000-\\040"#%-\\047)*,./:-@\\\\-^'398r'`|-~])|(?<=[^\\000-#%-,./:-@\\[-^`{-~-])((?:[\\000-\\011\\013\\014\\0'399r'16-\\040]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/)))+(?=[^\\000-#%-,./'400r':-@\\[-^`{-~-])|(?<=\\+)((?:[\\000-\\011\\013\\014\\016-\\040]|(?:/\\*['401r'^*]*\\*+(?:[^/*][^*]*\\*+)*/)))+(?=\\+)|(?<=-)((?:[\\000-\\011\\013'402r'\\014\\016-\\040]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/)))+(?=-)|(?:['403r'\\000-\\011\\013\\014\\016-\\040]|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/)'404r')+|(?:(?:(?://[^\\r\\n]*)?[\\r\\n])(?:[\\000-\\011\\013\\014\\016-\\040]'405r'|(?:/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/))*)+'406)407408defsubber(match):409""" Substitution callback """410groups=match.groups()411return(412groups[0]or413groups[1]or414(groups[3]and(groups[2]+'\\n'))or415groups[2]or416(groups[5]and"%s%s%s"%(417groups[4]and'\\n'or'',418groups[5],419groups[6]and'\\n'or'',420))or421(groups[7]and'\\n')or422(groups[8]and' ')or423(groups[9]and' ')or424(groups[10]and' ')or425''426)
500""" Main """501importsysas_sys502503argv=_sys.argv[1:]504keep_bang_comments='-b'inargvor'-bp'inargvor'-pb'inargv505if'-p'inargvor'-bp'inargvor'-pb'inargv:506xjsmin=_make_jsmin(python_only=True)507else:508xjsmin=jsmin509510_sys.stdout.write(xjsmin(511_sys.stdin.read(),keep_bang_comments=keep_bang_comments512))