# Copyright © 2015-2017 STRG.AT GmbH, Vienna, Austria
#
# This file is part of the The SCORE Framework.
#
# The SCORE Framework and all its parts are free software: you can redistribute
# them and/or modify them under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation which is in the
# file named COPYING.LESSER.txt.
#
# The SCORE Framework and all its parts are distributed without any WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. For more details see the GNU Lesser General Public
# License.
#
# If you have not received a copy of the GNU Lesser General Public License see
# http://www.gnu.org/licenses/.
#
# The License-Agreement realised between you as Licensee and STRG.AT GmbH as
# Licenser including the issue of its valid conclusion and its pre- and
# post-contractual effects is governed by the laws of Austria. Any disputes
# concerning this License-Agreement including the issue of its valid conclusion
# and its pre- and post-contractual effects are exclusively decided by the
# competent court, in whose district STRG.AT GmbH has its registered seat, at
# the discretion of STRG.AT GmbH also the competent court, in whose district the
# Licensee has his registered seat, an establishment or assets.
"""
The minifier package provides means for reducing the size of javascript code
without altering its semantics. If one does not care, how the code is
minified, one can just use the functions :func:`minify_string` and
:func:`minify_file`.
Currently, the following minification backends are supported:
- slimit_: Provides good minification, but is quite slow. Does not preserve
licensing information.
- jsmin_: Very fast, but not the best minification. Does not preserve
licensing information.
- uglifyjs_: Moderate speed and good minification. Preserves licensing
information; dependends on node.js.
- `yui compressor`_: Fast but moderate compression. Preserves licensing
information; depends on java.
.. _slimit: https://pypi.python.org/pypi/slimit
.. _jsmin: https://pypi.python.org/pypi/jsmin
.. _uglifyjs: https://github.com/mishoo/UglifyJS
.. _yui compressor: http://yui.github.io/yuicompressor/
"""
from abc import ABCMeta, abstractmethod
import logging
import subprocess
log = logging.getLogger('score.js.minifier')
[docs]def minify_string(js, outfile=None):
"""
Minifies given *js* string using uglifyjs, as this is the only
configuration-free backend that preserves licensing information.
By default, this function returns the minified string. It is also possible
to provide an *outfile* to write the result to, instead of returning it.
"""
return Uglifyjs().minify_string(js, outfile)
[docs]def minify_file(file, outfile=None):
"""
Does the same as :func:`minify_string`, but operates on an input *file*,
instead of a string.
"""
return Uglifyjs().minify_file(file, outfile)
[docs]class MinifierBackend(metaclass=ABCMeta):
"""
Abstract base class for minifier backends.
"""
def __init__(self, shortname):
self.log = log.getChild(shortname)
[docs] @abstractmethod
def minify_file(self, file, outfile=None):
"""
Backend-specific implementation of the global function
:func:`minify_file`.
"""
return
[docs] @abstractmethod
def minify_string(self, string, outfile=None, *, path=None):
"""
Backend-specific implementation of the global function
:func:`minify_string`.
"""
return
[docs]class Slimit(MinifierBackend):
"""
:class:`.MinifierBackend` using slimit_.
.. _slimit: https://pypi.python.org/pypi/slimit
"""
def __init__(self):
"""
Initializes the slimit library. This will fix an error in current
version of ply.
See https://github.com/rspivak/slimit/issues/64#issuecomment-38801874
for details.
"""
MinifierBackend.__init__(self, 'slimit')
from ply import yacc
def __getitem__(self, n):
if isinstance(n, slice):
return self.__getslice__(n.start, n.stop)
if n >= 0:
return self.slice[n].value
else:
return self.stack[n].value
yacc.YaccProduction.__getitem__ = __getitem__
def minify_file(self, file, outfile=None):
return self.minify_string(open(file, 'r').read(), outfile)
def minify_string(self, string, outfile=None, *, path=None):
from slimit import minify
result = minify(string, mangle=True)
if outfile:
open(outfile, 'w').write(result)
else:
return result
[docs]class Jsmin(MinifierBackend):
"""
:class:`.MinifierBackend` using jsmin_.
.. _jsmin: https://pypi.python.org/pypi/jsmin
"""
def __init__(self):
MinifierBackend.__init__(self, 'jsmin')
def minify_file(self, file, outfile=None):
return self.jsmin_str(open(file, 'r').read())
def minify_string(self, js, outfile=None, *, path=None):
from jsmin import jsmin
result = jsmin(js)
if outfile:
open(outfile, 'w').write(result)
else:
return result
[docs]class Uglifyjs(MinifierBackend):
"""
:class:`.MinifierBackend` using uglifyjs_.
.. _uglifyjs: https://github.com/mishoo/UglifyJS
"""
def __init__(self, uglify_path='uglifyjs'):
MinifierBackend.__init__(self, 'uglifyjs')
self.uglify_path = uglify_path
def minify_file(self, file, outfile=None):
args = [self.uglify_path, '--mangle', '--compress', '--lint',
'--comments', '/^!|@license|@preserve/']
if outfile:
args += ['--output', outfile]
args.append(file)
process = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
output, error = process.communicate()
if process.returncode:
raise subprocess.CalledProcessError(
process.returncode,
' '.join(map(lambda x: repr(x), args)),
error)
if error:
try:
error = str(error, 'UTF-8').strip()
except UnicodeDecodeError:
pass
self.log.info('warnings for %s:\n%s' % (file, error))
if not outfile:
return str(output, 'UTF-8')
def minify_string(self, js, outfile=None, *, path=None):
args = [self.uglify_path, '--mangle', '--compress', '--lint',
'--comments', '/^!|@license|@preserve/']
if outfile:
args += ['--output', outfile]
process = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if isinstance(js, str):
js = js.encode('UTF-8')
output, error = process.communicate(js)
if process.returncode:
raise subprocess.CalledProcessError(
process.returncode,
' '.join(map(lambda x: repr(x), args)),
error)
if error:
try:
error = str(error, 'UTF-8').strip()
except UnicodeDecodeError:
pass
if path:
self.log.info('warnings for %s:\n%s' % (path, error))
else:
self.log.info('warnings:\n%s' % (error,))
if not outfile:
return str(output, 'UTF-8')
[docs]class YuiCompressor(MinifierBackend):
"""
:class:`.MinifierBackend` using `yui compressor`_. Constructor needs the
path to `yuicompressor's jar file`_.
.. _yui compressor: http://yui.github.io/yuicompressor/
.. _yuicompressor's jar file: https://github.com/yui/yuicompressor/releases
"""
def __init__(self, jar_path):
self.jar_path = jar_path
MinifierBackend.__init__(self, 'yui')
def minify_file(self, file, outfile=None):
args = ['java', '-jar', self.jar_path,
'--type', 'js', '--charset', 'UTF-8', '-v']
if outfile:
args += ['-o', outfile]
args.append(file)
process = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
output, error = process.communicate()
if process.returncode:
raise subprocess.CalledProcessError(
process.returncode,
' '.join(map(lambda x: repr(x), args)),
error)
if error:
try:
error = str(error, 'UTF-8')
except UnicodeDecodeError:
pass
self.log.info('warnings for %s:\n%s' % (file, error))
if not outfile:
return str(output, 'UTF-8')
def minify_string(self, js, outfile=None, *, path=None):
if not js:
# Yui seems to crash when trying to convert empty strings.
return ''
args = ['java', '-jar', self.jar_path,
'--type', 'js', '--charset', 'UTF-8', '-v']
if outfile:
args += ['-o', outfile]
process = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if isinstance(js, str):
js = js.encode('UTF-8')
output, error = process.communicate(js)
if process.returncode:
raise subprocess.CalledProcessError(process.returncode,
' '.join(args), error)
if error:
try:
error = str(error, 'UTF-8')
except UnicodeDecodeError:
pass
if path:
self.log.info('warnings for %s:\n%s' % (path, error))
else:
self.log.info('warnings:\n%s' % (error,))
if not outfile:
return str(output, 'UTF-8')