rjsmin

Module rjsmin |

# Source Code for Module rjsmin

      1  #!/usr/bin/env python
      2  # -*- coding: ascii -*-
      3  #
      4  # Copyright 2011 - 2013
      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.7'
     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.
     79
     80      :Return: Minifier
     81      :Rtype: ``callable``
     82      """
     83      # pylint: disable = R0912, R0914, W0612
     84      if not python_only:
     85          try:
     86              import _rjsmin
     87          except ImportError:
     88              pass
     89          else:
     90              return _rjsmin.jsmin
     91      try:
     92          xrange
     93      except NameError:
     94          xrange = range # pylint: disable = W0622
     95
     96      space_chars = r'[\000-\011\013\014\016-\040]'
     97
     98      line_comment = r'(?://[^\r\n]*)'
     99      space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
    100      string1 = \
    101          r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)'
    102      string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")'
    103      strings = r'(?:%s|%s)' % (string1, string2)
    104
    105      charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])'
    106      nospecial = r'[^/\\\[\r\n]'
    107      regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % (
    108          nospecial, charclass, nospecial
    109      )
    110      space = r'(?:%s|%s)' % (space_chars, space_comment)
    111      newline = r'(?:%s?[\r\n])' % line_comment
    112
    113      def fix_charclass(result):
    114          """ Fixup string of chars to fit into a regex char class """
    115          pos = result.find('-')
    116          if pos >= 0:
    117              result = r'%s%s-' % (result[:pos], result[pos + 1:])
    118
    119          def sequentize(string):
    120              """
    121              Notate consecutive characters as sequence
    122
    123              (1-4 instead of 1234)
    124              """
    125              first, last, result = None, None, []
    126              for char in map(ord, string):
    127                  if last is None:
    128                      first = last = char
    129                  elif last + 1 == char:
    130                      last = char
    131                  else:
    132                      result.append((first, last))
    133                      first = last = char
    134              if last is not None:
    135                  result.append((first, last))
    136              return ''.join(['%s%s%s' % (
    137                  chr(first),
    138                  last > first + 1 and '-' or '',
    139                  last != first and chr(last) or ''
    140              ) for first, last in result])
    141
    142          return _re.sub(r'([\000-\040\047])', # for better portability
    143              lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result)
    144                  .replace('\\', '\\\\')
    145                  .replace('[', '\\[')
    146                  .replace(']', '\\]')
    147              )
    148          )
    149
    150      def id_literal_(what):
    151          """ Make id_literal like char class """
    152          match = _re.compile(what).match
    153          result = ''.join([
    154              chr(c) for c in xrange(127) if not match(chr(c))
    155          ])
    156          return '[^%s]' % fix_charclass(result)
    157
    158      def not_id_literal_(keep):
    159          """ Make negated id_literal like char class """
    160          match = _re.compile(id_literal_(keep)).match
    161          result = ''.join([
    162              chr(c) for c in xrange(127) if not match(chr(c))
    163          ])
    164          return r'[%s]' % fix_charclass(result)
    165
    166      not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]')
    167      preregex1 = r'[(,=:\[!&|?{};\r\n]'
    168      preregex2 = r'%(not_id_literal)sreturn' % locals()
    169
    170      id_literal = id_literal_(r'[a-zA-Z0-9_$]')
    171      id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]')
    172      id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
    173
    174      dull = r'[^\047"/\000-\040]'
    175
    176      space_sub = _re.compile((
    177          r'(%(dull)s+)'
    178          r'|(%(strings)s%(dull)s*)'
    179          r'|(?<=%(preregex1)s)'
    180              r'%(space)s*(?:%(newline)s%(space)s*)*'
    181              r'(%(regex)s%(dull)s*)'
    182          r'|(?<=%(preregex2)s)'
    183              r'%(space)s*(?:%(newline)s%(space)s)*'
    184              r'(%(regex)s%(dull)s*)'
    185          r'|(?<=%(id_literal_close)s)'
    186              r'%(space)s*(?:(%(newline)s)%(space)s*)+'
    187              r'(?=%(id_literal_open)s)'
    188          r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
    189          r'|(?<=\+)(%(space)s)+(?=\+)'
    190          r'|(?<=-)(%(space)s)+(?=-)'
    191          r'|%(space)s+'
    192          r'|(?:%(newline)s%(space)s*)+'
    193      ) % locals()).sub
    194      #print space_sub.__self__.pattern
    195
    196      def space_subber(match):
    197          """ Substitution callback """
    198          # pylint: disable = C0321, R0911
    199          groups = match.groups()
    200          if groups[0]: return groups[0]
    201          elif groups[1]: return groups[1]
    202          elif groups[2]: return groups[2]
    203          elif groups[3]: return groups[3]
    204          elif groups[4]: return '\n'
    205          elif groups[5] or groups[6] or groups[7]: return ' '
    206          else: return ''
    207
    208      def jsmin(script): # pylint: disable = W0621
    209          r"""
    210          Minify javascript based on `jsmin.c by Douglas Crockford`_\.
    211
    212          Instead of parsing the stream char by char, it uses a regular
    213          expression approach which minifies the whole script with one big
    214          substitution regex.
    215
    216          .. _jsmin.c by Douglas Crockford:
    217             http://www.crockford.com/javascript/jsmin.c
    218
    219          :Parameters:
    220            `script` : ``str``
    221              Script to minify
    222
    223          :Return: Minified script
    224          :Rtype: ``str``
    225          """
    226          return space_sub(space_subber, '\n%s\n' % script).strip()
    227
    228      return jsmin
    229
    230  jsmin = _make_jsmin()
    231
    232
    233 -def jsmin_for_posers(script):
    234      r"""
    235      Minify javascript based on `jsmin.c by Douglas Crockford`_\.
    236
    237      Instead of parsing the stream char by char, it uses a regular
    238      expression approach which minifies the whole script with one big
    239      substitution regex.
    240
    241      .. _jsmin.c by Douglas Crockford:
    242         http://www.crockford.com/javascript/jsmin.c
    243
    244      :Warning: This function is the digest of a _make_jsmin() call. It just
    245                utilizes the resulting regex. It's just for fun here and may
    246                vanish any time. Use the `jsmin` function instead.
    247
    248      :Parameters:
    249        `script` : ``str``
    250          Script to minify
    251
    252      :Return: Minified script
    253      :Rtype: ``str``
    254      """
    255      def subber(match):
    256          """ Substitution callback """
    257          groups = match.groups()
    258          return (
    259              groups[0] or
    260              groups[1] or
    261              groups[2] or
    262              groups[3] or
    263              (groups[4] and '\n') or
    264              (groups[5] and ' ') or
    265              (groups[6] and ' ') or
    266              (groups[7] and ' ') or
    267              ''
    268          )
    269
    270      return _re.sub(
    271          r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
    272          r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
    273          r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?{};\r\n])(?'
    274          r':[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*'
    275          r'(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*'
    276          r'[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:('
    277          r'?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\['
    278          r'\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[\000-#%-,./:-@\[-^`{-~-]return'
    279          r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
    280          r'))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:'
    281          r'/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?'
    282          r':(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/'
    283          r'\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\^`{|'
    284          r'~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)'
    285          r'*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]'
    286          r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,./'
    287          r':-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\013\01'
    288          r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./:'
    289          r'-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*'
    290          r'\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013\014\016-'
    291          r'\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\000-\011\013'
    292          r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^'
    293          r'\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^'
    294          r'/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
    295      ).strip()
    296
    297
    298  if __name__ == '__main__':
    299      import sys as _sys
    300      _sys.stdout.write(jsmin(_sys.stdin.read()))
    301