Commits

Simone Marzola committed d03204f Draft

first commit

Comments (0)

Files changed (9)

+syntax: glob
+
+.idea
+*.pyc
+*.egg-info
+About tgext.minify
+-------------------------
+
+tgext.minify is a Turbogears 2 middleware that minifies all css and javascript
+files in your public directory before serving them.
+Based on rCSSmin and rJSmin by André Malo
+The middleware is based on `tgext.scss <https://bitbucket.org/_amol_/tgext.scss>`_ by Alessandro Molina
+
+Installing
+-------------------------------
+
+tgext.scss can be installed both from pypi or from bitbucket::
+
+    easy_install tgext.minify
+
+should just work for most of the users
+
+Enabling tgext.minify
+----------------------------------
+
+If tgext.pluggable is available enabling tgext.minify is just a matter of appending to your `config/app_cfg.py`::
+
+    from tgext.pluggable import plug
+    plug(base_config, 'tgext.minify')
+
+Otherwise manually using tgext.minify is really simple, you edit your `config/middeware.py` and just after
+the `#Wrap your base TurboGears 2 application with custom middleware here` comment wrap
+`app` with `MinifyMiddleware`::
+
+    from tgext.minify import MinifyMiddleware
+
+    make_base_app = base_config.setup_tg_wsgi_app(load_environment)
+
+    def make_app(global_conf, full_stack=True, **app_conf):
+        app = make_base_app(global_conf, full_stack=True, **app_conf)
+
+        # Wrap your base TurboGears 2 application with custom middleware here
+        app = MinifyMiddleware(app)
+    
+        return app
+
+
+Now you just have to put your beautiful .css and .js files inside *public/* and they will be served as minified.
+
+Performance boost
+-----------------------------------
+
+Here is the report of a benchmark made on paster serving bootstrap.css with and without the middleware
+and then bootstrap.min.js already minified (without using the middleware)::
+
+    $ /usr/sbin/ab -n 1000 http://localhost:8080/css/bootstrap.css
+    Requests per second:    1135.50 [#/sec] (mean)
+
+    $ /usr/sbin/ab -n 1000 http://localhost:8080/css/bootstrap.scss
+    Requests per second:    714.99 [#/sec] (mean)
+
+    $ /usr/sbin/ab -n 1000 http://localhost:8080/css/bootstrap.scss
+    Requests per second:    806.26 [#/sec] (mean)
+
+In these case serving the unminified css using tgext.minify is even faster
+than directly serving the same css file as it is served from memory
+(due to caching performed by tgext.minify).
+[egg_info]
+from setuptools import setup, find_packages
+import sys, os
+
+version = '0.0.1'
+
+here = os.path.abspath(os.path.dirname(__file__))
+try:
+    README = open(os.path.join(here, 'README.rst')).read()
+except IOError:
+    README = ''
+
+setup(name='tgext.minify',
+      version=version,
+      description="CSS and JS minifier for TurboGears2",
+      long_description=README,
+      classifiers=[
+        "Environment :: Web Environment",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Framework :: TurboGears"
+        ],
+      keywords='turbogears2.extension CSS JS minify WSGI',
+      author='Simone Marzola',
+      author_email='simone.marzola@axant.it',
+      url='ssh://hg@bitbucket.org/simock85/tgext.minify',
+      license='MIT',
+      packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+      namespace_packages=['tgext'],
+      include_package_data=True,
+      package_data = {'':['*.html', '*.js', '*.css', '*.png', '*.gif']},
+      zip_safe=False,
+      install_requires=[
+        "TurboGears2 >= 2.0b7",
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      """,
+      )

tgext/__init__.py

+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

tgext/minify/__init__.py

+from middleware import MinifyMiddleware
+
+def plugme(app_config, options):
+    def mount_minify_middleware(app):
+        return MinifyMiddleware(app)
+    app_config.register_hook('after_config', mount_minify_middleware)
+    return dict(appid='tgext.minify')

tgext/minify/middleware.py

+import os
+from webob import Response
+from webob.exc import status_map
+from rcssmin import cssmin
+from rjsmin import jsmin
+from tg import config
+
+class MinifyMiddleware(object):
+    def __init__(self, app):
+        self.app = app
+        self.cache = {}
+        self.convert = {'js': self._convert_js,
+                        'css': self._convert_css}
+        try:
+            self.root_directory = os.path.normcase(os.path.abspath((config['paths']['static_files'])))
+        except KeyError:
+            self.root_directory = os.path.normcase(os.path.abspath((config['pylons.paths']['static_files'])))            
+
+    def _convert_js(self, text):
+        return jsmin(text)
+
+    def _convert_css(self, text):
+        return cssmin(text)
+
+    def get_pretty_content(self, path):
+        f = open(path)
+        try:
+            return f.read()
+        finally:
+            f.close()
+
+    def full_path(self, path):
+        if path[0] == '/':
+            path = path[1:]
+        return os.path.normcase(os.path.abspath((os.path.join(self.root_directory, path))))
+
+    def __call__(self, environ, start_response):
+        requested_path = environ['PATH_INFO']
+        try:
+            requested_ext = requested_path.rsplit('.', 1)[1]
+        except IndexError:
+            return self.app(environ, start_response)
+        if requested_ext not in self.convert.keys():
+            return self.app(environ, start_response)
+
+        full = self.full_path(requested_path)
+        if not os.path.exists(full):
+            return status_map[404]()(environ, start_response)
+
+        mtime = os.stat(full).st_mtime
+
+        etag_key = '"%s"' % mtime
+        if_none_match = environ.get('HTTP_IF_NONE_MATCH')
+        if if_none_match and etag_key == if_none_match:
+            start_response('304 Not Modified', [('ETag', etag_key)])
+            return ['']
+
+        cached_data = self.cache.get(requested_path)
+        if not cached_data or cached_data['etag_key'] != etag_key:
+            cached_data = {'content':self.convert[requested_ext](self.get_pretty_content(full)),
+                           'etag_key':etag_key}
+            self.cache[requested_path] = cached_data
+
+        response = Response()
+        response.content_type = 'text/css' if requested_ext == 'css' else 'text/javascript'
+        response.headers['ETag'] = etag_key
+        response.body = cached_data['content']
+        return response(environ, start_response)

tgext/minify/rcssmin.py

+#!/usr/bin/env python
+# -*- coding: ascii -*-
+#
+# Copyright 2011, 2012
+# Andr\xe9 Malo or his licensors, as applicable
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""
+==============
+ CSS Minifier
+==============
+
+CSS Minifier.
+
+The minifier is based on the semantics of the `YUI compressor`_\, which itself
+is based on `the rule list by Isaac Schlueter`_\.
+
+This module is a re-implementation aiming for speed instead of maximum
+compression, so it can be used at runtime (rather than during a preprocessing
+step). RCSSmin does syntactical compression only (removing spaces, comments
+and possibly semicolons). It does not provide semantic compression (like
+removing empty blocks, collapsing redundant properties etc). It does, however,
+support various CSS hacks (by keeping them working as intended).
+
+Here's a feature list:
+
+- Strings are kept, except that escaped newlines are stripped
+- Space/Comments before the very end or before various characters are
+  stripped: ``:{});=>+],!`` (The colon (``:``) is a special case, a single
+  space is kept if it's outside a ruleset.)
+- Space/Comments at the very beginning or after various characters are
+  stripped: ``{}(=:>+[,!``
+- Optional space after unicode escapes is kept, resp. replaced by a simple
+  space
+- whitespaces inside ``url()`` definitions are stripped
+- Comments starting with an exclamation mark (``!``) can be kept optionally.
+- All other comments and/or whitespace characters are replaced by a single
+  space.
+- Multiple consecutive semicolons are reduced to one
+- The last semicolon within a ruleset is stripped
+- CSS Hacks supported:
+
+  - IE7 hack (``>/**/``)
+  - Mac-IE5 hack (``/*\*/.../**/``)
+  - The boxmodelhack is supported naturally because it relies on valid CSS2
+    strings
+  - Between ``:first-line`` and the following comma or curly brace a space is
+    inserted. (apparently it's needed for IE6)
+  - Same for ``:first-letter``
+
+rcssmin.c is a reimplementation of rcssmin.py in C and improves runtime up to
+factor 50 or so (depending on the input).
+
+Both python 2 (>= 2.4) and python 3 are supported.
+
+.. _YUI compressor: https://github.com/yui/yuicompressor/
+
+.. _the rule list by Isaac Schlueter: https://github.com/isaacs/cssmin/tree/
+"""
+__author__ = "Andr\xe9 Malo"
+__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1')
+__docformat__ = "restructuredtext en"
+__license__ = "Apache License, Version 2.0"
+__version__ = '1.0.0'
+__all__ = ['cssmin']
+
+import re as _re
+
+
+def _make_cssmin(python_only=False):
+    """
+    Generate CSS minifier.
+
+    :Parameters:
+      `python_only` : ``bool``
+        Use only the python variant. If true, the c extension is not even
+        tried to be loaded.
+
+    :Return: Minifier
+    :Rtype: ``callable``
+    """
+    # pylint: disable = W0612
+    # ("unused" variables)
+
+    # pylint: disable = R0911, R0912, R0914, R0915
+    # (too many anything)
+
+    if not python_only:
+        try:
+            import _rcssmin
+        except ImportError:
+            pass
+        else:
+            return _rcssmin.cssmin
+
+    nl = r'(?:[\n\f]|\r\n?)' # pylint: disable = C0103
+    spacechar = r'[\r\n\f\040\t]'
+
+    unicoded = r'[0-9a-fA-F]{1,6}(?:[\040\n\t\f]|\r\n?)?'
+    escaped = r'[^\n\r\f0-9a-fA-F]'
+    escape = r'(?:\\(?:%(unicoded)s|%(escaped)s))' % locals()
+
+    nmchar = r'[^\000-\054\056\057\072-\100\133-\136\140\173-\177]'
+    #nmstart = r'[^\000-\100\133-\136\140\173-\177]'
+    #ident = (r'(?:'
+    #    r'-?(?:%(nmstart)s|%(escape)s)%(nmchar)s*(?:%(escape)s%(nmchar)s*)*'
+    #r')') % locals()
+
+    comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
+
+    # only for specific purposes. The bang is grouped:
+    _bang_comment = r'(?:/\*(!?)[^*]*\*+(?:[^/*][^*]*\*+)*/)'
+
+    string1 = \
+        r'(?:\047[^\047\\\r\n\f]*(?:\\[^\r\n\f][^\047\\\r\n\f]*)*\047)'
+    string2 = r'(?:"[^"\\\r\n\f]*(?:\\[^\r\n\f][^"\\\r\n\f]*)*")'
+    strings = r'(?:%s|%s)' % (string1, string2)
+
+    nl_string1 = \
+        r'(?:\047[^\047\\\r\n\f]*(?:\\(?:[^\r]|\r\n?)[^\047\\\r\n\f]*)*\047)'
+    nl_string2 = r'(?:"[^"\\\r\n\f]*(?:\\(?:[^\r]|\r\n?)[^"\\\r\n\f]*)*")'
+    nl_strings = r'(?:%s|%s)' % (nl_string1, nl_string2)
+
+    uri_nl_string1 = r'(?:\047[^\047\\]*(?:\\(?:[^\r]|\r\n?)[^\047\\]*)*\047)'
+    uri_nl_string2 = r'(?:"[^"\\]*(?:\\(?:[^\r]|\r\n?)[^"\\]*)*")'
+    uri_nl_strings = r'(?:%s|%s)' % (uri_nl_string1, uri_nl_string2)
+
+    nl_escaped = r'(?:\\%(nl)s)' % locals()
+
+    space = r'(?:%(spacechar)s|%(comment)s)' % locals()
+
+    ie7hack = r'(?:>/\*\*/)'
+
+    uri = (r'(?:'
+        r'(?:[^\000-\040"\047()\\\177]*'
+            r'(?:%(escape)s[^\000-\040"\047()\\\177]*)*)'
+        r'(?:'
+            r'(?:%(spacechar)s+|%(nl_escaped)s+)'
+            r'(?:'
+                r'(?:[^\000-\040"\047()\\\177]|%(escape)s|%(nl_escaped)s)'
+                r'[^\000-\040"\047()\\\177]*'
+                r'(?:%(escape)s[^\000-\040"\047()\\\177]*)*'
+            r')+'
+        r')*'
+    r')') % locals()
+
+    nl_unesc_sub = _re.compile(nl_escaped).sub
+
+    uri_space_sub = _re.compile((
+        r'(%(escape)s+)|%(spacechar)s+|%(nl_escaped)s+'
+    ) % locals()).sub
+    uri_space_subber = lambda m: m.groups()[0] or ''
+
+    space_sub_simple = _re.compile((
+        r'[\r\n\f\040\t;]+|(%(comment)s+)'
+    ) % locals()).sub
+    space_sub_banged = _re.compile((
+        r'[\r\n\f\040\t;]+|(%(_bang_comment)s+)'
+    ) % locals()).sub
+
+    post_esc_sub = _re.compile(r'[\r\n\f\t]+').sub
+
+    main_sub = _re.compile((
+        r'([^\\"\047u>@\r\n\f\040\t/;:{}]+)'
+        r'|(?<=[{}(=:>+[,!])(%(space)s+)'
+        r'|^(%(space)s+)'
+        r'|(%(space)s+)(?=(([:{});=>+\],!])|$)?)'
+        r'|;(%(space)s*(?:;%(space)s*)*)(?=(\})?)'
+        r'|(\{)'
+        r'|(\})'
+        r'|(%(strings)s)'
+        r'|(?<!%(nmchar)s)url\(%(spacechar)s*('
+                r'%(uri_nl_strings)s'
+                r'|%(uri)s'
+            r')%(spacechar)s*\)'
+        r'|(@[mM][eE][dD][iI][aA])(?!%(nmchar)s)'
+        r'|(%(ie7hack)s)(%(space)s*)'
+        r'|(:[fF][iI][rR][sS][tT]-[lL]'
+            r'(?:[iI][nN][eE]|[eE][tT][tT][eE][rR]))'
+            r'(%(space)s*)(?=[{,])'
+        r'|(%(nl_strings)s)'
+        r'|(%(escape)s[^\\"\047u>@\r\n\f\040\t/;:{}]*)'
+    ) % locals()).sub
+
+    #print main_sub.__self__.pattern
+
+    def main_subber(keep_bang_comments):
+        """ Make main subber """
+        in_macie5, in_rule, at_media = [0], [0], [0]
+
+        if keep_bang_comments:
+            space_sub = space_sub_banged
+            def space_subber(match):
+                """ Space|Comment subber """
+                if match.lastindex:
+                    group1, group2 = match.group(1, 2)
+                    if group2:
+                        if group1.endswith(r'\*/'):
+                            in_macie5[0] = 1
+                        else:
+                            in_macie5[0] = 0
+                        return group1
+                    elif group1:
+                        if group1.endswith(r'\*/'):
+                            if in_macie5[0]:
+                                return ''
+                            in_macie5[0] = 1
+                            return r'/*\*/'
+                        elif in_macie5[0]:
+                            in_macie5[0] = 0
+                            return '/**/'
+                return ''
+        else:
+            space_sub = space_sub_simple
+            def space_subber(match):
+                """ Space|Comment subber """
+                if match.lastindex:
+                    if match.group(1).endswith(r'\*/'):
+                        if in_macie5[0]:
+                            return ''
+                        in_macie5[0] = 1
+                        return r'/*\*/'
+                    elif in_macie5[0]:
+                        in_macie5[0] = 0
+                        return '/**/'
+                return ''
+
+        def fn_space_post(group):
+            """ space with token after """
+            if group(5) is None or (
+                    group(6) == ':' and not in_rule[0] and not at_media[0]):
+                return ' ' + space_sub(space_subber, group(4))
+            return space_sub(space_subber, group(4))
+
+        def fn_semicolon(group):
+            """ ; handler """
+            return ';' + space_sub(space_subber, group(7))
+
+        def fn_semicolon2(group):
+            """ ; handler """
+            if in_rule[0]:
+                return space_sub(space_subber, group(7))
+            return ';' + space_sub(space_subber, group(7))
+
+        def fn_open(group):
+            """ { handler """
+            # pylint: disable = W0613
+            if at_media[0]:
+                at_media[0] -= 1
+            else:
+                in_rule[0] = 1
+            return '{'
+
+        def fn_close(group):
+            """ } handler """
+            # pylint: disable = W0613
+            in_rule[0] = 0
+            return '}'
+
+        def fn_media(group):
+            """ @media handler """
+            at_media[0] += 1
+            return group(13)
+
+        def fn_ie7hack(group):
+            """ IE7 Hack handler """
+            if not in_rule[0] and not at_media[0]:
+                in_macie5[0] = 0
+                return group(14) + space_sub(space_subber, group(15))
+            return '>' + space_sub(space_subber, group(15))
+
+        table = (
+            None,
+            None,
+            None,
+            None,
+            fn_space_post,                      # space with token after
+            fn_space_post,                      # space with token after
+            fn_space_post,                      # space with token after
+            fn_semicolon,                       # semicolon
+            fn_semicolon2,                      # semicolon
+            fn_open,                            # {
+            fn_close,                           # }
+            lambda g: g(11),                    # string
+            lambda g: 'url(%s)' % uri_space_sub(uri_space_subber, g(12)),
+                                                # url(...)
+            fn_media,                           # @media
+            None,
+            fn_ie7hack,                         # ie7hack
+            None,
+            lambda g: g(16) + ' ' + space_sub(space_subber, g(17)),
+                                                # :first-line|letter followed
+                                                # by [{,] (apparently space
+                                                # needed for IE6)
+            lambda g: nl_unesc_sub('', g(18)),  # nl_string
+            lambda g: post_esc_sub(' ', g(19)), # escape
+        )
+
+        def func(match):
+            """ Main subber """
+            idx, group = match.lastindex, match.group
+            if idx > 3:
+                return table[idx](group)
+
+            # shortcuts for frequent operations below:
+            elif idx == 1:     # not interesting
+                return group(1)
+            #else: # space with token before or at the beginning
+            return space_sub(space_subber, group(idx))
+
+        return func
+
+    def cssmin(style, keep_bang_comments=False): # pylint: disable = W0621
+        """
+        Minify CSS.
+
+        :Parameters:
+          `style` : ``str``
+            CSS to minify
+
+          `keep_bang_comments` : ``bool``
+            Keep comments starting with an exclamation mark? (``/*!...*/``)
+
+        :Return: Minified style
+        :Rtype: ``str``
+        """
+        return main_sub(main_subber(keep_bang_comments), style)
+
+    return cssmin
+
+cssmin = _make_cssmin()
+
+
+if __name__ == '__main__':
+    def main():
+        """ Main """
+        import sys as _sys
+        keep_bang_comments = (
+            '-b' in _sys.argv[1:]
+            or '-bp' in _sys.argv[1:]
+            or '-pb' in _sys.argv[1:]
+        )
+        if '-p' in _sys.argv[1:] or '-bp' in _sys.argv[1:] \
+                or '-pb' in _sys.argv[1:]:
+            global cssmin # pylint: disable = W0603
+            cssmin = _make_cssmin(python_only=True)
+        _sys.stdout.write(cssmin(
+            _sys.stdin.read(), keep_bang_comments=keep_bang_comments
+        ))
+    main()

tgext/minify/rjsmin.py

+#!/usr/bin/env python
+# -*- coding: ascii -*-
+#
+# Copyright 2011, 2012
+# Andr\xe9 Malo or his licensors, as applicable
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+r"""
+=====================
+ Javascript Minifier
+=====================
+
+rJSmin is a javascript minifier written in python.
+
+The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\.
+
+The module is a re-implementation aiming for speed, so it can be used at
+runtime (rather than during a preprocessing step). Usually it produces the
+same results as the original ``jsmin.c``. It differs in the following ways:
+
+- there is no error detection: unterminated string, regex and comment
+  literals are treated as regular javascript code and minified as such.
+- Control characters inside string and regex literals are left untouched; they
+  are not converted to spaces (nor to \n)
+- Newline characters are not allowed inside string and regex literals, except
+  for line continuations in string literals (ECMA-5).
+- "return /regex/" is recognized correctly.
+- "+ ++" and "- --" sequences are not collapsed to '+++' or '---'
+- rJSmin does not handle streams, but only complete strings. (However, the
+  module provides a "streamy" interface).
+
+Since most parts of the logic are handled by the regex engine it's way
+faster than the original python port of ``jsmin.c`` by Baruch Even. The speed
+factor varies between about 6 and 55 depending on input and python version
+(it gets faster the more compressed the input already is). Compared to the
+speed-refactored python port by Dave St.Germain the performance gain is less
+dramatic but still between 1.2 and 7. See the docs/BENCHMARKS file for
+details.
+
+rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more.
+
+Both python 2 and python 3 are supported.
+
+.. _jsmin.c by Douglas Crockford:
+   http://www.crockford.com/javascript/jsmin.c
+"""
+__author__ = "Andr\xe9 Malo"
+__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1')
+__docformat__ = "restructuredtext en"
+__license__ = "Apache License, Version 2.0"
+__version__ = '1.0.3'
+__all__ = ['jsmin']
+
+import re as _re
+
+
+def _make_jsmin(python_only=False):
+    """
+    Generate JS minifier based on `jsmin.c by Douglas Crockford`_
+
+    .. _jsmin.c by Douglas Crockford:
+       http://www.crockford.com/javascript/jsmin.c
+
+    :Parameters:
+      `python_only` : ``bool``
+        Use only the python variant. If true, the c extension is not even
+        tried to be loaded.
+
+    :Return: Minifier
+    :Rtype: ``callable``
+    """
+    # pylint: disable = R0912, R0914, W0612
+    if not python_only:
+        try:
+            import _rjsmin
+        except ImportError:
+            pass
+        else:
+            return _rjsmin.jsmin
+    try:
+        xrange
+    except NameError:
+        xrange = range # pylint: disable = W0622
+
+    space_chars = r'[\000-\011\013\014\016-\040]'
+
+    line_comment = r'(?://[^\r\n]*)'
+    space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
+    string1 = \
+        r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)'
+    string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")'
+    strings = r'(?:%s|%s)' % (string1, string2)
+
+    charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])'
+    nospecial = r'[^/\\\[\r\n]'
+    regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % (
+        nospecial, charclass, nospecial
+    )
+    space = r'(?:%s|%s)' % (space_chars, space_comment)
+    newline = r'(?:%s?[\r\n])' % line_comment
+
+    def fix_charclass(result):
+        """ Fixup string of chars to fit into a regex char class """
+        pos = result.find('-')
+        if pos >= 0:
+            result = r'%s%s-' % (result[:pos], result[pos + 1:])
+
+        def sequentize(string):
+            """
+            Notate consecutive characters as sequence
+
+            (1-4 instead of 1234)
+            """
+            first, last, result = None, None, []
+            for char in map(ord, string):
+                if last is None:
+                    first = last = char
+                elif last + 1 == char:
+                    last = char
+                else:
+                    result.append((first, last))
+                    first = last = char
+            if last is not None:
+                result.append((first, last))
+            return ''.join(['%s%s%s' % (
+                chr(first),
+                last > first + 1 and '-' or '',
+                last != first and chr(last) or ''
+            ) for first, last in result])
+
+        return _re.sub(r'([\000-\040\047])', # for better portability
+            lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result)
+                .replace('\\', '\\\\')
+                .replace('[', '\\[')
+                .replace(']', '\\]')
+            )
+        )
+
+    def id_literal_(what):
+        """ Make id_literal like char class """
+        match = _re.compile(what).match
+        result = ''.join([
+            chr(c) for c in xrange(127) if not match(chr(c))
+        ])
+        return '[^%s]' % fix_charclass(result)
+
+    def not_id_literal_(keep):
+        """ Make negated id_literal like char class """
+        match = _re.compile(id_literal_(keep)).match
+        result = ''.join([
+            chr(c) for c in xrange(127) if not match(chr(c))
+        ])
+        return r'[%s]' % fix_charclass(result)
+
+    not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]')
+    preregex1 = r'[(,=:\[!&|?{};\r\n]'
+    preregex2 = r'%(not_id_literal)sreturn' % locals()
+
+    id_literal = id_literal_(r'[a-zA-Z0-9_$]')
+    id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(+-]')
+    id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
+
+    space_sub = _re.compile((
+        r'([^\047"/\000-\040]+)'
+        r'|(%(strings)s[^\047"/\000-\040]*)'
+        r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
+        r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
+        r'|(?<=%(id_literal_close)s)'
+            r'%(space)s*(?:(%(newline)s)%(space)s*)+'
+            r'(?=%(id_literal_open)s)'
+        r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
+        r'|(?<=\+)(%(space)s)+(?=\+\+)'
+        r'|(?<=-)(%(space)s)+(?=--)'
+        r'|%(space)s+'
+        r'|(?:%(newline)s%(space)s*)+'
+    ) % locals()).sub
+    #print space_sub.__self__.pattern
+
+    def space_subber(match):
+        """ Substitution callback """
+        # pylint: disable = C0321, R0911
+        groups = match.groups()
+        if groups[0]: return groups[0]
+        elif groups[1]: return groups[1]
+        elif groups[2]: return groups[2]
+        elif groups[3]: return groups[3]
+        elif groups[4]: return '\n'
+        elif groups[5] or groups[6] or groups[7]: return ' '
+        else: return ''
+
+    def jsmin(script): # pylint: disable = W0621
+        r"""
+        Minify javascript based on `jsmin.c by Douglas Crockford`_\.
+
+        Instead of parsing the stream char by char, it uses a regular
+        expression approach which minifies the whole script with one big
+        substitution regex.
+
+        .. _jsmin.c by Douglas Crockford:
+           http://www.crockford.com/javascript/jsmin.c
+
+        :Parameters:
+          `script` : ``str``
+            Script to minify
+
+        :Return: Minified script
+        :Rtype: ``str``
+        """
+        return space_sub(space_subber, '\n%s\n' % script).strip()
+
+    return jsmin
+
+jsmin = _make_jsmin()
+
+
+def jsmin_for_posers(script):
+    r"""
+    Minify javascript based on `jsmin.c by Douglas Crockford`_\.
+
+    Instead of parsing the stream char by char, it uses a regular
+    expression approach which minifies the whole script with one big
+    substitution regex.
+
+    .. _jsmin.c by Douglas Crockford:
+       http://www.crockford.com/javascript/jsmin.c
+
+    :Warning: This function is the digest of a _make_jsmin() call. It just
+              utilizes the resulting regex. It's just for fun here and may
+              vanish any time. Use the `jsmin` function instead.
+
+    :Parameters:
+      `script` : ``str``
+        Script to minify
+
+    :Return: Minified script
+    :Rtype: ``str``
+    """
+    def subber(match):
+        """ Substitution callback """
+        groups = match.groups()
+        return (
+            groups[0] or
+            groups[1] or
+            groups[2] or
+            groups[3] or
+            (groups[4] and '\n') or
+            (groups[5] and ' ') or
+            (groups[6] and ' ') or
+            (groups[7] and ' ') or
+            ''
+        )
+
+    return _re.sub(
+        r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
+        r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
+        r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
+        r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
+        r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
+        r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
+        r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
+        r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
+        r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
+        r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
+        r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
+        r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
+        r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-#%-\04'
+        r'7)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011'
+        r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-'
+        r'#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/'
+        r'\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+\+)|(?<=-)((?:[\000-\011\013'
+        r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=--)|(?:[\00'
+        r'0-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:('
+        r'?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]'
+        r'*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
+    ).strip()
+
+
+if __name__ == '__main__':
+    import sys as _sys
+    _sys.stdout.write(jsmin(_sys.stdin.read()))