Package tdi :: Package tools :: Module rjsmin
[frames] | no frames]

Source Code for Module tdi.tools.rjsmin

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