From 53d39114deb80a30543a994969d8a98bfa20476c Mon Sep 17 00:00:00 2001 From: Johannes Linke Date: Mon, 12 Oct 2015 21:35:29 +0200 Subject: [PATCH 1/8] Remove stringformat.py, it's unused --- Makefile | 2 +- compressor/utils/stringformat.py | 260 ------------------------------- 2 files changed, 1 insertion(+), 261 deletions(-) delete mode 100644 compressor/utils/stringformat.py diff --git a/Makefile b/Makefile index 53b2b33..c342825 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ runtests: coverage run --branch --source=compressor `which django-admin.py` test --settings=compressor.test_settings compressor coveragereport: - coverage report --omit=compressor/test*,compressor/filters/jsmin/rjsmin*,compressor/filters/cssmin/cssmin*,compressor/utils/stringformat* + coverage report --omit=compressor/test*,compressor/filters/jsmin/rjsmin*,compressor/filters/cssmin/cssmin* test: flake8 runtests coveragereport diff --git a/compressor/utils/stringformat.py b/compressor/utils/stringformat.py deleted file mode 100644 index 9311e78..0000000 --- a/compressor/utils/stringformat.py +++ /dev/null @@ -1,260 +0,0 @@ -# -*- coding: utf-8 -*- -"""Advanced string formatting for Python >= 2.4. - -An implementation of the advanced string formatting (PEP 3101). - -Author: Florent Xicluna -""" - -from __future__ import unicode_literals - -import re - -from django.utils import six - -_format_str_re = re.compile( - r'((?=^])?)' # alignment - r'([-+ ]?)' # sign - r'(#?)' r'(\d*)' r'(,?)' # base prefix, minimal width, thousands sep - r'((?:\.\d+)?)' # precision - r'(.?)$' # type -) -_field_part_re = re.compile( - r'(?:(\[)|\.|^)' # start or '.' or '[' - r'((?(1)[^]]*|[^.[]*))' # part - r'(?(1)(?:\]|$)([^.[]+)?)' # ']' and invalid tail -) - -_format_str_sub = _format_str_re.sub - - -def _is_integer(value): - return hasattr(value, '__index__') - - -def _strformat(value, format_spec=""): - """Internal string formatter. - - It implements the Format Specification Mini-Language. - """ - m = _format_spec_re.match(str(format_spec)) - if not m: - raise ValueError('Invalid conversion specification') - align, sign, prefix, width, comma, precision, conversion = m.groups() - is_numeric = hasattr(value, '__float__') - is_integer = is_numeric and _is_integer(value) - if prefix and not is_integer: - raise ValueError('Alternate form (#) not allowed in %s format ' - 'specifier' % (is_numeric and 'float' or 'string')) - if is_numeric and conversion == 'n': - # Default to 'd' for ints and 'g' for floats - conversion = is_integer and 'd' or 'g' - elif sign: - if not is_numeric: - raise ValueError("Sign not allowed in string format specifier") - if conversion == 'c': - raise ValueError("Sign not allowed with integer " - "format specifier 'c'") - if comma: - # TODO: thousand separator - pass - try: - if ((is_numeric and conversion == 's') or (not is_integer and conversion in set('cdoxX'))): - raise ValueError - if conversion == 'c': - conversion = 's' - value = chr(value % 256) - rv = ('%' + prefix + precision + (conversion or 's')) % (value,) - except ValueError: - raise ValueError("Unknown format code %r for object of type %r" % - (conversion, value.__class__.__name__)) - if sign not in '-' and value >= 0: - # sign in (' ', '+') - rv = sign + rv - if width: - zero = (width[0] == '0') - width = int(width) - else: - zero = False - width = 0 - # Fastpath when alignment is not required - if width <= len(rv): - if not is_numeric and (align == '=' or (zero and not align)): - raise ValueError("'=' alignment not allowed in string format " - "specifier") - return rv - fill, align = align[:-1], align[-1:] - if not fill: - fill = zero and '0' or ' ' - if align == '^': - padding = width - len(rv) - # tweak the formatting if the padding is odd - if padding % 2: - rv += fill - rv = rv.center(width, fill) - elif align == '=' or (zero and not align): - if not is_numeric: - raise ValueError("'=' alignment not allowed in string format " - "specifier") - if value < 0 or sign not in '-': - rv = rv[0] + rv[1:].rjust(width - 1, fill) - else: - rv = rv.rjust(width, fill) - elif align in ('>', '=') or (is_numeric and not align): - # numeric value right aligned by default - rv = rv.rjust(width, fill) - else: - rv = rv.ljust(width, fill) - return rv - - -def _format_field(value, parts, conv, spec, want_bytes=False): - """Format a replacement field.""" - for k, part, _ in parts: - if k: - if part.isdigit(): - value = value[int(part)] - else: - value = value[part] - else: - value = getattr(value, part) - if conv: - value = ((conv == 'r') and '%r' or '%s') % (value,) - if hasattr(value, '__format__'): - value = value.__format__(spec) - elif hasattr(value, 'strftime') and spec: - value = value.strftime(str(spec)) - else: - value = _strformat(value, spec) - if want_bytes and isinstance(value, six.text_type): - return str(value) - return value - - -class FormattableString(object): - """Class which implements method format(). - - The method format() behaves like str.format() in python 2.6+. - - >>> FormattableString('{a:5}').format(a=42) - ... # Same as '{a:5}'.format(a=42) - ' 42' - - """ - - __slots__ = '_index', '_kwords', '_nested', '_string', 'format_string' - - def __init__(self, format_string): - self._index = 0 - self._kwords = {} - self._nested = {} - - self.format_string = format_string - self._string = _format_str_sub(self._prepare, format_string) - - def __eq__(self, other): - if isinstance(other, FormattableString): - return self.format_string == other.format_string - # Compare equal with the original string. - return self.format_string == other - - def _prepare(self, match): - # Called for each replacement field. - part = match.group(0) - if part[0] == part[-1]: - # '{{' or '}}' - assert part == part[0] * len(part) - return part[:len(part) // 2] - repl = part[1:-1] - field, _, format_spec = repl.partition(':') - literal, sep, conversion = field.partition('!') - if sep and not conversion: - raise ValueError("end of format while looking for " - "conversion specifier") - if len(conversion) > 1: - raise ValueError("expected ':' after format specifier") - if conversion not in 'rsa': - raise ValueError("Unknown conversion specifier %s" % - str(conversion)) - name_parts = _field_part_re.findall(literal) - if literal[:1] in '.[': - # Auto-numbering - if self._index is None: - raise ValueError("cannot switch from manual field " - "specification to automatic field numbering") - name = str(self._index) - self._index += 1 - if not literal: - del name_parts[0] - else: - name = name_parts.pop(0)[1] - if name.isdigit() and self._index is not None: - # Manual specification - if self._index: - raise ValueError("cannot switch from automatic field " - "numbering to manual field specification") - self._index = None - empty_attribute = False - for k, v, tail in name_parts: - if not v: - empty_attribute = True - if tail: - raise ValueError("Only '.' or '[' may follow ']' " - "in format field specifier") - if name_parts and k == '[' and not literal[-1] == ']': - raise ValueError("Missing ']' in format string") - if empty_attribute: - raise ValueError("Empty attribute in format string") - if '{' in format_spec: - format_spec = _format_sub_re.sub(self._prepare, format_spec) - rv = (name_parts, conversion, format_spec) - self._nested.setdefault(name, []).append(rv) - else: - rv = (name_parts, conversion, format_spec) - self._kwords.setdefault(name, []).append(rv) - return r'%%(%s)s' % id(rv) - - def format(self, *args, **kwargs): - """Same as str.format() and unicode.format() in Python 2.6+.""" - if args: - kwargs.update(dict((str(i), value) - for (i, value) in enumerate(args))) - # Encode arguments to ASCII, if format string is bytes - want_bytes = isinstance(self._string, str) - params = {} - for name, items in self._kwords.items(): - value = kwargs[name] - for item in items: - parts, conv, spec = item - params[str(id(item))] = _format_field(value, parts, conv, spec, - want_bytes) - for name, items in self._nested.items(): - value = kwargs[name] - for item in items: - parts, conv, spec = item - spec = spec % params - params[str(id(item))] = _format_field(value, parts, conv, spec, - want_bytes) - return self._string % params - - -def selftest(): - import datetime - F = FormattableString - - assert F("{0:{width}.{precision}s}").format('hello world', - width=8, precision=5) == 'hello ' - - d = datetime.date(2010, 9, 7) - assert F("The year is {0.year}").format(d) == "The year is 2010" - assert F("Tested on {0:%Y-%m-%d}").format(d) == "Tested on 2010-09-07" - print('Test successful') - -if __name__ == '__main__': - selftest() From 551c084727b5f9c3c4f0fd6988195dc832bc0a2b Mon Sep 17 00:00:00 2001 From: Johannes Linke Date: Fri, 4 Dec 2015 20:02:14 +0100 Subject: [PATCH 2/8] Remove support for CSSTidy. ref #664 --- README.rst | 4 +--- compressor/conf.py | 2 -- compressor/filters/csstidy.py | 10 ---------- compressor/tests/test_filters.py | 21 --------------------- docs/settings.txt | 21 ++++----------------- 5 files changed, 5 insertions(+), 53 deletions(-) delete mode 100644 compressor/filters/csstidy.py diff --git a/README.rst b/README.rst index 83c6893..a2b4400 100644 --- a/README.rst +++ b/README.rst @@ -50,7 +50,7 @@ default. As an alternative Django Compressor provides a BeautifulSoup_ and a html5lib_ based parser, as well as an abstract base class that makes it easy to write a custom parser. -Django Compressor also comes with built-in support for `CSS Tidy`_, +Django Compressor also comes with built-in support for `YUI CSS and JS`_ compressor, `yUglify CSS and JS`_ compressor, the Google's `Closure Compiler`_, a Python port of Douglas Crockford's JSmin_, a Python port of the YUI CSS Compressor cssmin_ and a filter to convert (some) images into @@ -72,7 +72,6 @@ The in-development version of Django Compressor can be installed with .. _BeautifulSoup: http://www.crummy.com/software/BeautifulSoup/ .. _lxml: http://lxml.de/ .. _html5lib: http://code.google.com/p/html5lib/ -.. _CSS Tidy: http://csstidy.sourceforge.net/ .. _YUI CSS and JS: http://developer.yahoo.com/yui/compressor/ .. _yUglify CSS and JS: https://github.com/yui/yuglify .. _Closure Compiler: http://code.google.com/closure/compiler/ @@ -81,4 +80,3 @@ The in-development version of Django Compressor can be installed with .. _data URIs: http://en.wikipedia.org/wiki/Data_URI_scheme .. _django-compressor.readthedocs.org: http://django-compressor.readthedocs.org/en/latest/ .. _github.com/django-compressor/django-compressor: https://github.com/django-compressor/django-compressor - diff --git a/compressor/conf.py b/compressor/conf.py index a546115..4f32e46 100644 --- a/compressor/conf.py +++ b/compressor/conf.py @@ -38,8 +38,6 @@ class CompressorConf(AppConf): CACHEABLE_PRECOMPILERS = () CLOSURE_COMPILER_BINARY = 'java -jar compiler.jar' CLOSURE_COMPILER_ARGUMENTS = '' - CSSTIDY_BINARY = 'csstidy' - CSSTIDY_ARGUMENTS = '--template=highest' YUI_BINARY = 'java -jar yuicompressor.jar' YUI_CSS_ARGUMENTS = '' YUI_JS_ARGUMENTS = '' diff --git a/compressor/filters/csstidy.py b/compressor/filters/csstidy.py deleted file mode 100644 index 4b7e4c7..0000000 --- a/compressor/filters/csstidy.py +++ /dev/null @@ -1,10 +0,0 @@ -from compressor.conf import settings -from compressor.filters import CompilerFilter - - -class CSSTidyFilter(CompilerFilter): - command = "{binary} {infile} {args} {outfile}" - options = ( - ("binary", settings.COMPRESS_CSSTIDY_BINARY), - ("args", settings.COMPRESS_CSSTIDY_ARGUMENTS), - ) diff --git a/compressor/tests/test_filters.py b/compressor/tests/test_filters.py index 9d40697..06bfedb 100644 --- a/compressor/tests/test_filters.py +++ b/compressor/tests/test_filters.py @@ -20,7 +20,6 @@ from compressor.filters.css_default import CssAbsoluteFilter from compressor.filters.jsmin import JSMinFilter from compressor.filters.template import TemplateFilter from compressor.filters.closure import ClosureCompilerFilter -from compressor.filters.csstidy import CSSTidyFilter from compressor.filters.yuglify import YUglifyCSSFilter, YUglifyJSFilter from compressor.filters.yui import YUICSSFilter, YUIJSFilter from compressor.filters.cleancss import CleanCSSFilter @@ -31,22 +30,6 @@ def blankdict(*args, **kwargs): return defaultdict(lambda: '', *args, **kwargs) -@unittest.skipIf(find_command(settings.COMPRESS_CSSTIDY_BINARY) is None, - 'CSStidy binary %r not found' % settings.COMPRESS_CSSTIDY_BINARY) -class CssTidyTestCase(TestCase): - def test_tidy(self): - content = textwrap.dedent("""\ - /* Some comment */ - font,th,td,p{ - color: black; - } - """) - ret = CSSTidyFilter(content).input() - self.assertIsInstance(ret, six.text_type) - self.assertEqual( - "font,th,td,p{color:#000;}", CSSTidyFilter(content).input()) - - @override_settings(COMPRESS_CACHEABLE_PRECOMPILERS=('text/css',)) class PrecompilerTestCase(TestCase): def setUp(self): @@ -440,10 +423,6 @@ class SpecializedFiltersTest(TestCase): filter = ClosureCompilerFilter('') self.assertEqual(filter.options, (('binary', six.text_type('java -jar compiler.jar')), ('args', six.text_type('')))) - def test_csstidy_filter(self): - filter = CSSTidyFilter('') - self.assertEqual(filter.options, (('binary', six.text_type('csstidy')), ('args', six.text_type('--template=highest')))) - def test_yuglify_filters(self): filter = YUglifyCSSFilter('') self.assertEqual(filter.command, '{binary} {args} --type=css') diff --git a/docs/settings.txt b/docs/settings.txt index e0cb6f2..c1e55e6 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -87,18 +87,6 @@ Backend settings feature, and the ``'content'`` in case you're using multiple servers to serve your content. - - ``compressor.filters.csstidy.CSSTidyFilter`` - - A filter that passes the CSS content to the CSSTidy_ tool. - - .. attribute:: COMPRESS_CSSTIDY_BINARY - - The CSSTidy binary filesystem path. - - .. attribute:: COMPRESS_CSSTIDY_ARGUMENTS - - The arguments passed to CSSTidy. - - ``compressor.filters.datauri.CssDataUriFilter`` A filter for embedding media as `data: URIs`_ in the CSS. @@ -155,7 +143,6 @@ Backend settings The arguments passed to clean-css. - .. _CSSTidy: http://csstidy.sourceforge.net/ .. _`data: URIs`: http://en.wikipedia.org/wiki/Data_URI_scheme .. _cssmin: http://pypi.python.org/pypi/cssmin/ .. _rCSSmin: http://opensource.perlig.de/rcssmin/ @@ -280,8 +267,8 @@ Backend settings .. note:: Depending on the implementation, some precompilers might not support outputting to something else than ``stdout``, so you'll need to omit the - ``{outfile}`` parameter when working with those. For instance, if you - are using the Ruby version of lessc, you'll need to set up the + ``{outfile}`` parameter when working with those. For instance, if you + are using the Ruby version of lessc, you'll need to set up the precompiler like this:: ('text/less', 'lessc {infile}'), @@ -453,11 +440,11 @@ Caching settings and the ``django.core.context_processors.request`` context processor. .. _RequestContext: http://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.RequestContext - + .. attribute:: COMPRESS_CACHE_KEY_FUNCTION :Default: ``'compressor.cache.simple_cachekey'`` - + The function to use when generating the cache key. The function must take one argument which is the partial key based on the source's hex digest. It must return the full key as a string. From 21d90f684b1a0d83cf15ec05671d2cd5cdbe0171 Mon Sep 17 00:00:00 2001 From: Johannes Linke Date: Fri, 4 Dec 2015 20:16:48 +0100 Subject: [PATCH 3/8] Remove support for beautifulsoup 3. ref #664 --- compressor/parser/beautifulsoup.py | 18 +++-------------- compressor/tests/test_base.py | 32 +++++++----------------------- tox.ini | 2 +- 3 files changed, 11 insertions(+), 41 deletions(-) diff --git a/compressor/parser/beautifulsoup.py b/compressor/parser/beautifulsoup.py index 68d542c..606a9e4 100644 --- a/compressor/parser/beautifulsoup.py +++ b/compressor/parser/beautifulsoup.py @@ -11,27 +11,15 @@ class BeautifulSoupParser(ParserBase): super(BeautifulSoupParser, self).__init__(content) try: from bs4 import BeautifulSoup - self.use_bs4 = True self.soup = BeautifulSoup(self.content, "html.parser") except ImportError: - try: - from BeautifulSoup import BeautifulSoup - self.use_bs4 = False - self.soup = BeautifulSoup(self.content) - except ImportError as err: - raise ImproperlyConfigured("Error while importing BeautifulSoup: %s" % err) + raise ImproperlyConfigured("Error while importing BeautifulSoup: %s" % err) def css_elems(self): - if self.use_bs4: - return self.soup.find_all({'link': True, 'style': True}) - else: - return self.soup.findAll({'link': True, 'style': True}) + return self.soup.find_all({'link': True, 'style': True}) def js_elems(self): - if self.use_bs4: - return self.soup.find_all('script') - else: - return self.soup.findAll('script') + return self.soup.find_all('script') def elem_attribs(self, elem): attrs = dict(elem.attrs) diff --git a/compressor/tests/test_base.py b/compressor/tests/test_base.py index ea86b7f..370bb7a 100644 --- a/compressor/tests/test_base.py +++ b/compressor/tests/test_base.py @@ -4,12 +4,7 @@ import re from tempfile import mkdtemp from shutil import rmtree, copytree -try: - from bs4 import BeautifulSoup - use_bs4 = True -except ImportError: - from BeautifulSoup import BeautifulSoup - use_bs4 = False +from bs4 import BeautifulSoup from django.core.cache.backends import locmem from django.test import SimpleTestCase @@ -26,17 +21,7 @@ from compressor.storage import DefaultStorage def make_soup(markup): - if use_bs4: - return BeautifulSoup(markup, "html.parser") - else: - return BeautifulSoup(markup) - - -def soup_find_all(markup, name): - if use_bs4: - return make_soup(markup).find_all(name) - else: - return make_soup(markup).findAll(name) + return BeautifulSoup(markup, "html.parser") def css_tag(href, **kwargs): @@ -302,7 +287,7 @@ class CssMediaTestCase(SimpleTestCase): def test_css_output(self): css_node = CssCompressor(self.css) - links = soup_find_all(css_node.output(), 'link') + links = make_soup(css_node.output()).find_all('link') media = ['screen', 'print', 'all', None] self.assertEqual(len(links), 4) self.assertEqual(media, [l.get('media', None) for l in links]) @@ -311,7 +296,7 @@ class CssMediaTestCase(SimpleTestCase): css = self.css + '' css_node = CssCompressor(css) media = ['screen', 'print', 'all', None, 'print'] - links = soup_find_all(css_node.output(), 'link') + links = make_soup(css_node.output()).find_all('link') self.assertEqual(media, [l.get('media', None) for l in links]) @override_settings(COMPRESS_PRECOMPILERS=( @@ -323,7 +308,7 @@ class CssMediaTestCase(SimpleTestCase): """ css_node = CssCompressor(css) - output = soup_find_all(css_node.output(), ['link', 'style']) + output = make_soup(css_node.output()).find_all(['link', 'style']) self.assertEqual(['/static/css/one.css', '/static/css/two.css', None], [l.get('href', None) for l in output]) self.assertEqual(['screen', 'screen', 'screen'], @@ -363,11 +348,8 @@ class JsAsyncDeferTestCase(SimpleTestCase): return 'defer' js_node = JsCompressor(self.js) output = [None, 'async', 'defer', None, 'async', None] - scripts = soup_find_all(js_node.output(), 'script') - if use_bs4: - attrs = [extract_attr(i) for i in scripts] - else: - attrs = [s.get('async') or s.get('defer') for s in scripts] + scripts = make_soup(js_node.output()).find_all('script') + attrs = [extract_attr(s) for s in scripts] self.assertEqual(output, attrs) diff --git a/tox.ini b/tox.ini index c25e80f..0b4156e 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ two_six = mock==1.0.1 Jinja2==2.7.3 lxml==3.4.2 - BeautifulSoup==3.2.1 + beautifulsoup4==4.4.0 unittest2==1.0.0 jingo==0.7 coffin==0.4.0 From 7eca92054872b1c85bd067ef2463ec0851c631c9 Mon Sep 17 00:00:00 2001 From: Johannes Linke Date: Fri, 4 Dec 2015 21:09:58 +0100 Subject: [PATCH 4/8] Replace cssmin by csscompressor. ref #664 --- README.rst | 4 +- compressor/filters/cssmin/__init__.py | 9 +- compressor/filters/cssmin/cssmin.py | 245 -------------------------- compressor/tests/test_filters.py | 13 +- docs/quickstart.txt | 9 +- docs/settings.txt | 10 +- requirements/tests.txt | 1 + tox.ini | 4 + 8 files changed, 34 insertions(+), 261 deletions(-) delete mode 100644 compressor/filters/cssmin/cssmin.py diff --git a/README.rst b/README.rst index a2b4400..cf84f58 100644 --- a/README.rst +++ b/README.rst @@ -53,7 +53,7 @@ write a custom parser. Django Compressor also comes with built-in support for `YUI CSS and JS`_ compressor, `yUglify CSS and JS`_ compressor, the Google's `Closure Compiler`_, a Python port of Douglas Crockford's JSmin_, a Python port -of the YUI CSS Compressor cssmin_ and a filter to convert (some) images into +of the YUI CSS Compressor csscompressor_ and a filter to convert (some) images into `data URIs`_. If your setup requires a different compressor or other post-processing @@ -76,7 +76,7 @@ The in-development version of Django Compressor can be installed with .. _yUglify CSS and JS: https://github.com/yui/yuglify .. _Closure Compiler: http://code.google.com/closure/compiler/ .. _JSMin: http://www.crockford.com/javascript/jsmin.html -.. _cssmin: https://github.com/zacharyvoase/cssmin +.. _csscompressor: https://github.com/sprymix/csscompressor .. _data URIs: http://en.wikipedia.org/wiki/Data_URI_scheme .. _django-compressor.readthedocs.org: http://django-compressor.readthedocs.org/en/latest/ .. _github.com/django-compressor/django-compressor: https://github.com/django-compressor/django-compressor diff --git a/compressor/filters/cssmin/__init__.py b/compressor/filters/cssmin/__init__.py index 93450cd..01a916d 100644 --- a/compressor/filters/cssmin/__init__.py +++ b/compressor/filters/cssmin/__init__.py @@ -1,12 +1,13 @@ from compressor.filters import CallbackOutputFilter -class CSSMinFilter(CallbackOutputFilter): +class CSSCompressorFilter(CallbackOutputFilter): """ - A filter that utilizes Zachary Voase's Python port of - the YUI CSS compression algorithm: http://pypi.python.org/pypi/cssmin/ + A filter that utilizes Yury Selivanov's Python port of + the YUI CSS compression algorithm: https://pypi.python.org/pypi/csscompressor """ - callback = "compressor.filters.cssmin.cssmin.cssmin" + callback = "csscompressor.compress" + dependencies = ["csscompressor"] class rCSSMinFilter(CallbackOutputFilter): diff --git a/compressor/filters/cssmin/cssmin.py b/compressor/filters/cssmin/cssmin.py deleted file mode 100644 index e8a02b0..0000000 --- a/compressor/filters/cssmin/cssmin.py +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# `cssmin.py` - A Python port of the YUI CSS compressor. -# -# Copyright (c) 2010 Zachary Voase -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation -# files (the "Software"), to deal in the Software without -# restriction, including without limitation the rights to use, -# copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following -# conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -# -"""`cssmin` - A Python port of the YUI CSS compressor.""" - -import re - -__version__ = '0.1.4' - - -def remove_comments(css): - """Remove all CSS comment blocks.""" - - iemac = False - preserve = False - comment_start = css.find("/*") - while comment_start >= 0: - # Preserve comments that look like `/*!...*/`. - # Slicing is used to make sure we don"t get an IndexError. - preserve = css[comment_start + 2:comment_start + 3] == "!" - - comment_end = css.find("*/", comment_start + 2) - if comment_end < 0: - if not preserve: - css = css[:comment_start] - break - elif comment_end >= (comment_start + 2): - if css[comment_end - 1] == "\\": - # This is an IE Mac-specific comment; leave this one and the - # following one alone. - comment_start = comment_end + 2 - iemac = True - elif iemac: - comment_start = comment_end + 2 - iemac = False - elif not preserve: - css = css[:comment_start] + css[comment_end + 2:] - else: - comment_start = comment_end + 2 - comment_start = css.find("/*", comment_start) - - return css - - -def remove_unnecessary_whitespace(css): - """Remove unnecessary whitespace characters.""" - - def pseudoclasscolon(css): - - """ - Prevents 'p :link' from becoming 'p:link'. - - Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is - translated back again later. - """ - - regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)") - match = regex.search(css) - while match: - css = ''.join([ - css[:match.start()], - match.group().replace(":", "___PSEUDOCLASSCOLON___"), - css[match.end():]]) - match = regex.search(css) - return css - - css = pseudoclasscolon(css) - # Remove spaces from before things. - css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css) - - # If there is a `@charset`, then only allow one, and move to the beginning. - css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css) - css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css) - - # Put the space back in for a few cases, such as `@media screen` and - # `(-webkit-min-device-pixel-ratio:0)`. - css = re.sub(r"\band\(", "and (", css) - - # Put the colons back. - css = css.replace('___PSEUDOCLASSCOLON___', ':') - - # Remove spaces from after things. - css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css) - - return css - - -def remove_unnecessary_semicolons(css): - """Remove unnecessary semicolons.""" - - return re.sub(r";+\}", "}", css) - - -def remove_empty_rules(css): - """Remove empty rules.""" - - return re.sub(r"[^\}\{]+\{\}", "", css) - - -def normalize_rgb_colors_to_hex(css): - """Convert `rgb(51,102,153)` to `#336699`.""" - - regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)") - match = regex.search(css) - while match: - colors = map(lambda s: s.strip(), match.group(1).split(",")) - hexcolor = '#%.2x%.2x%.2x' % tuple(map(int, colors)) - css = css.replace(match.group(), hexcolor) - match = regex.search(css) - return css - - -def condense_zero_units(css): - """Replace `0(px, em, %, etc)` with `0`.""" - - return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css) - - -def condense_multidimensional_zeros(css): - """Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`.""" - - css = css.replace(":0 0 0 0;", ":0;") - css = css.replace(":0 0 0;", ":0;") - css = css.replace(":0 0;", ":0;") - - # Revert `background-position:0;` to the valid `background-position:0 0;`. - css = css.replace("background-position:0;", "background-position:0 0;") - - return css - - -def condense_floating_points(css): - """Replace `0.6` with `.6` where possible.""" - - return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css) - - -def condense_hex_colors(css): - """Shorten colors from #AABBCC to #ABC where possible.""" - - regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])") - match = regex.search(css) - while match: - first = match.group(3) + match.group(5) + match.group(7) - second = match.group(4) + match.group(6) + match.group(8) - if first.lower() == second.lower(): - css = css.replace(match.group(), match.group(1) + match.group(2) + '#' + first) - match = regex.search(css, match.end() - 3) - else: - match = regex.search(css, match.end()) - return css - - -def condense_whitespace(css): - """Condense multiple adjacent whitespace characters into one.""" - - return re.sub(r"\s+", " ", css) - - -def condense_semicolons(css): - """Condense multiple adjacent semicolon characters into one.""" - - return re.sub(r";;+", ";", css) - - -def wrap_css_lines(css, line_length): - """Wrap the lines of the given CSS to an approximate length.""" - - lines = [] - line_start = 0 - for i, char in enumerate(css): - # It's safe to break after `}` characters. - if char == '}' and (i - line_start >= line_length): - lines.append(css[line_start:i + 1]) - line_start = i + 1 - - if line_start < len(css): - lines.append(css[line_start:]) - return '\n'.join(lines) - - -def cssmin(css, wrap=None): - css = remove_comments(css) - css = condense_whitespace(css) - # A pseudo class for the Box Model Hack - # (see http://tantek.com/CSS/Examples/boxmodelhack.html) - css = css.replace('"\\"}\\""', "___PSEUDOCLASSBMH___") - css = remove_unnecessary_whitespace(css) - css = remove_unnecessary_semicolons(css) - css = condense_zero_units(css) - css = condense_multidimensional_zeros(css) - css = condense_floating_points(css) - css = normalize_rgb_colors_to_hex(css) - css = condense_hex_colors(css) - if wrap is not None: - css = wrap_css_lines(css, wrap) - css = css.replace("___PSEUDOCLASSBMH___", '"\\"}\\""') - css = condense_semicolons(css) - return css.strip() - - -def main(): - import optparse - import sys - - p = optparse.OptionParser( - prog="cssmin", version=__version__, - usage="%prog [--wrap N]", - description="""Reads raw CSS from stdin, and writes compressed CSS to stdout.""") - - p.add_option( - '-w', '--wrap', type='int', default=None, metavar='N', - help="Wrap output to approximately N chars per line.") - - options, args = p.parse_args() - sys.stdout.write(cssmin(sys.stdin.read(), wrap=options.wrap)) - - -if __name__ == '__main__': - main() diff --git a/compressor/tests/test_filters.py b/compressor/tests/test_filters.py index 06bfedb..a63310b 100644 --- a/compressor/tests/test_filters.py +++ b/compressor/tests/test_filters.py @@ -15,7 +15,7 @@ from compressor.conf import settings from compressor.css import CssCompressor from compressor.utils import find_command from compressor.filters.base import CompilerFilter, CachedCompilerFilter -from compressor.filters.cssmin import CSSMinFilter, rCSSMinFilter +from compressor.filters.cssmin import CSSCompressorFilter, rCSSMinFilter from compressor.filters.css_default import CssAbsoluteFilter from compressor.filters.jsmin import JSMinFilter from compressor.filters.template import TemplateFilter @@ -127,8 +127,8 @@ class PrecompilerTestCase(TestCase): self.assertEqual("", compiler.input()) -class CssMinTestCase(TestCase): - def test_cssmin_filter(self): +class CSSCompressorTestCase(TestCase): + def test_csscompressor_filter(self): content = """/*! * django-compressor * Copyright (c) 2009-2014 Django Compressor authors @@ -141,8 +141,11 @@ class CssMinTestCase(TestCase): } """ - output = "/*!* django-compressor * Copyright(c) 2009-2014 Django Compressor authors */ p{background:#369 url('../../images/image.gif')}" - self.assertEqual(output, CSSMinFilter(content).output()) + output = """/*! + * django-compressor + * Copyright (c) 2009-2014 Django Compressor authors + */p{background:#369 url('../../images/image.gif')}""" + self.assertEqual(output, CSSCompressorFilter(content).output()) class rCssMinTestCase(TestCase): diff --git a/docs/quickstart.txt b/docs/quickstart.txt index 5c993a5..1169ab6 100644 --- a/docs/quickstart.txt +++ b/docs/quickstart.txt @@ -31,7 +31,7 @@ Installation ) * Define :attr:`COMPRESS_ROOT ` in settings - if you don't have already ``STATIC_ROOT`` or if you want it in a different + if you don't have already ``STATIC_ROOT`` or if you want it in a different folder. .. _staticfiles: http://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/ @@ -89,6 +89,13 @@ Optional pip install slimit +- `csscompressor`_ + + For the :ref:`csscompressor filter ` + ``compressor.filters.cssmin.CSSCompressorFilter``:: + + pip install csscompressor + .. _BeautifulSoup: http://www.crummy.com/software/BeautifulSoup/ .. _lxml: http://lxml.de/ .. _libxml2: http://xmlsoft.org/ diff --git a/docs/settings.txt b/docs/settings.txt index c1e55e6..34995d5 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -120,10 +120,12 @@ Backend settings The arguments passed to the compressor. Defaults to --terminal. - - ``compressor.filters.cssmin.CSSMinFilter`` + .. _csscompressor_filter: - A filter that uses Zachary Voase's Python port of the YUI CSS compression - algorithm cssmin_ (included, no external dependencies). + - ``compressor.filters.cssmin.CSSCompressorFilter`` + + A filter that uses Yury Selivanov's Python port of the YUI CSS compression + algorithm csscompressor_. - ``compressor.filters.cssmin.rCSSMinFilter`` @@ -144,7 +146,7 @@ Backend settings .. _`data: URIs`: http://en.wikipedia.org/wiki/Data_URI_scheme - .. _cssmin: http://pypi.python.org/pypi/cssmin/ + .. _csscompressor: http://pypi.python.org/pypi/csscompressor/ .. _rCSSmin: http://opensource.perlig.de/rcssmin/ .. _`clean-css`: https://github.com/GoalSmashers/clean-css/ diff --git a/requirements/tests.txt b/requirements/tests.txt index 4c5e008..3d42158 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -10,3 +10,4 @@ coffin==0.4.0 jingo==0.7 django-sekizai==0.8.2 django-overextends==0.4.0 +csscompressor==0.9.4 diff --git a/tox.ini b/tox.ini index 0b4156e..478adf0 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ two = coffin==0.4.0 django-sekizai==0.8.2 django-overextends==0.4.0 + csscompressor==0.9.4 two_six = flake8==2.4.0 coverage==3.7.1 @@ -25,6 +26,7 @@ two_six = coffin==0.4.0 django-sekizai==0.8.2 django-overextends==0.4.0 + csscompressor==0.9.4 three = flake8==2.4.0 coverage==3.7.1 @@ -37,6 +39,7 @@ three = coffin==0.4.0 django-sekizai==0.8.2 django-overextends==0.4.0 + csscompressor==0.9.4 three_two = flake8==2.4.0 coverage==3.7.1 @@ -49,6 +52,7 @@ three_two = coffin==0.4.0 django-sekizai==0.8.2 django-overextends==0.4.0 + csscompressor==0.9.4 [tox] envlist = {py26,py27}-1.4.X, From 6f4639e6b1cf7dcede4997eeabd8562da9366a9b Mon Sep 17 00:00:00 2001 From: Johannes Linke Date: Fri, 4 Dec 2015 21:52:32 +0100 Subject: [PATCH 5/8] Remove rcssmin and rjsmin from the repo and make them proper dependencies. ref #664 --- Makefile | 2 +- compressor/filters/cssmin/__init__.py | 3 +- compressor/filters/cssmin/rcssmin.py | 374 ------------------- compressor/filters/jsmin/__init__.py | 3 +- compressor/filters/jsmin/rjsmin.py | 514 -------------------------- docs/settings.txt | 4 +- requirements/tests.txt | 2 + setup.py | 2 + tox.ini | 8 + 9 files changed, 19 insertions(+), 893 deletions(-) delete mode 100755 compressor/filters/cssmin/rcssmin.py delete mode 100755 compressor/filters/jsmin/rjsmin.py diff --git a/Makefile b/Makefile index c342825..6d4b2bf 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ runtests: coverage run --branch --source=compressor `which django-admin.py` test --settings=compressor.test_settings compressor coveragereport: - coverage report --omit=compressor/test*,compressor/filters/jsmin/rjsmin*,compressor/filters/cssmin/cssmin* + coverage report --omit=compressor/test* test: flake8 runtests coveragereport diff --git a/compressor/filters/cssmin/__init__.py b/compressor/filters/cssmin/__init__.py index 01a916d..3b127a9 100644 --- a/compressor/filters/cssmin/__init__.py +++ b/compressor/filters/cssmin/__init__.py @@ -11,7 +11,8 @@ class CSSCompressorFilter(CallbackOutputFilter): class rCSSMinFilter(CallbackOutputFilter): - callback = "compressor.filters.cssmin.rcssmin.cssmin" + callback = "rcssmin.cssmin" + dependencies = ["rcssmin"] kwargs = { "keep_bang_comments": True } diff --git a/compressor/filters/cssmin/rcssmin.py b/compressor/filters/cssmin/rcssmin.py deleted file mode 100755 index 5f590bc..0000000 --- a/compressor/filters/cssmin/rcssmin.py +++ /dev/null @@ -1,374 +0,0 @@ -#!/usr/bin/env python -# -*- coding: ascii -*- -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`_\\. - -:Copyright: - - Copyright 2011 - 2015 - Andr\xe9 Malo or his licensors, as applicable - -:License: - - 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. - -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 100 or so (depending on the input). docs/BENCHMARKS in the source -distribution contains the details. - -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/ -""" -if __doc__: - # pylint: disable = W0622 - __doc__ = __doc__.encode('ascii').decode('unicode_escape') -__author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') -__docformat__ = "restructuredtext en" -__license__ = "Apache License, Version 2.0" -__version__ = '1.0.6' -__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 = R0912, R0914, W0612 - - 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'(?:' - # noqa pylint: disable = C0330 - 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(( - # noqa pylint: disable = C0330 - r'([^\\"\047u>@\r\n\f\040\t/;:{}+]+)' # 1 - r'|(?<=[{}(=:>[,!])(%(space)s+)' # 2 - r'|^(%(space)s+)' # 3 - r'|(%(space)s+)(?=(([:{});=>\],!])|$)?)' # 4, 5, 6 - r'|;(%(space)s*(?:;%(space)s*)*)(?=(\})?)' # 7, 8 - r'|(\{)' # 9 - r'|(\})' # 10 - r'|(%(strings)s)' # 11 - r'|(?@\r\n\f\040\t/;:{}+]*)' # 19 - ) % locals()).sub - - # print main_sub.__self__.pattern - - def main_subber(keep_bang_comments): - """ Make main subber """ - in_macie5, in_rule, at_group = [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_group[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(_): - """ { handler """ - if at_group[0]: - at_group[0] -= 1 - else: - in_rule[0] = 1 - return '{' - - def fn_close(_): - """ } handler """ - in_rule[0] = 0 - return '}' - - def fn_at_group(group): - """ @xxx group handler """ - at_group[0] += 1 - return group(13) - - def fn_ie7hack(group): - """ IE7 Hack handler """ - if not in_rule[0] and not at_group[0]: - in_macie5[0] = 0 - return group(14) + space_sub(space_subber, group(15)) - return '>' + space_sub(space_subber, group(15)) - - table = ( - # noqa pylint: disable = C0330 - 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_at_group, # @xxx expecting {...} - 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() diff --git a/compressor/filters/jsmin/__init__.py b/compressor/filters/jsmin/__init__.py index f8fbbdf..ceca6ac 100644 --- a/compressor/filters/jsmin/__init__.py +++ b/compressor/filters/jsmin/__init__.py @@ -4,7 +4,8 @@ from compressor.filters.jsmin.slimit import SlimItFilter # noqa class rJSMinFilter(CallbackOutputFilter): - callback = "compressor.filters.jsmin.rjsmin.jsmin" + callback = "rjsmin.jsmin" + dependencies = ["rjsmin"] kwargs = { "keep_bang_comments": True } diff --git a/compressor/filters/jsmin/rjsmin.py b/compressor/filters/jsmin/rjsmin.py deleted file mode 100755 index 4d24b46..0000000 --- a/compressor/filters/jsmin/rjsmin.py +++ /dev/null @@ -1,514 +0,0 @@ -#!/usr/bin/env python -# -*- coding: ascii -*- -r""" -===================== - Javascript Minifier -===================== - -rJSmin is a javascript minifier written in python. - -The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\\. - -:Copyright: - - Copyright 2011 - 2015 - Andr\xe9 Malo or his licensors, as applicable - -:License: - - 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. - -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. -- Line terminators after regex literals are handled more sensibly -- "+ +" and "- -" sequences are not collapsed to '++' or '--' -- Newlines before ! operators are removed more sensibly -- Comments starting with an exclamation mark (``!``) can be kept optionally -- 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 3 and 50 (for huge inputs). 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 -""" -if __doc__: - # pylint: disable = redefined-builtin - __doc__ = __doc__.encode('ascii').decode('unicode_escape') -__author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') -__docformat__ = "restructuredtext en" -__license__ = "Apache License, Version 2.0" -__version__ = '1.0.12' -__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 = unused-variable - # pylint: disable = too-many-locals - - if not python_only: - try: - import _rjsmin - except ImportError: - pass - else: - return _rjsmin.jsmin - try: - xrange - except NameError: - xrange = range # pylint: disable = redefined-builtin - - space_chars = r'[\000-\011\013\014\016-\040]' - - line_comment = r'(?://[^\r\n]*)' - space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' - space_comment_nobang = r'(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*\*+)*/)' - bang_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]) # noqa - - return _re.sub( - r'([\000-\040\047])', # \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+-]') - post_regex_off = id_literal_(r'[^\000-\040}\])?:|,;.&=+-]') - - dull = r'[^\047"/\000-\040]' - - space_sub_simple = _re.compile(( - # noqa pylint: disable = bad-continuation - - r'(%(dull)s+)' # 0 - r'|(%(strings)s%(dull)s*)' # 1 - r'|(?<=%(preregex1)s)' - r'%(space)s*(?:%(newline)s%(space)s*)*' - r'(%(regex)s)' # 2 - r'(%(space)s*(?:%(newline)s%(space)s*)+' # 3 - r'(?=%(post_regex_off)s))?' - r'|(?<=%(preregex2)s)' - r'%(space)s*(?:(%(newline)s)%(space)s*)*' # 4 - r'(%(regex)s)' # 5 - r'(%(space)s*(?:%(newline)s%(space)s*)+' # 6 - r'(?=%(post_regex_off)s))?' - r'|(?<=%(id_literal_close)s)' - r'%(space)s*(?:(%(newline)s)%(space)s*)+' # 7 - r'(?=%(id_literal_open)s)' - r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)' # 8 - r'|(?<=\+)(%(space)s)+(?=\+)' # 9 - r'|(?<=-)(%(space)s)+(?=-)' # 10 - r'|%(space)s+' - r'|(?:%(newline)s%(space)s*)+' - ) % locals()).sub - - # print space_sub_simple.__self__.pattern - - def space_subber_simple(match): - """ Substitution callback """ - # pylint: disable = too-many-return-statements - - groups = match.groups() - if groups[0]: - return groups[0] - elif groups[1]: - return groups[1] - elif groups[2]: - if groups[3]: - return groups[2] + '\n' - return groups[2] - elif groups[5]: - return "%s%s%s" % ( - groups[4] and '\n' or '', - groups[5], - groups[6] and '\n' or '', - ) - elif groups[7]: - return '\n' - elif groups[8] or groups[9] or groups[10]: - return ' ' - else: - return '' - - space_sub_banged = _re.compile(( - # noqa pylint: disable = bad-continuation - - r'(%(dull)s+)' # 0 - r'|(%(strings)s%(dull)s*)' # 1 - r'|(?<=%(preregex1)s)' - r'(%(space)s*(?:%(newline)s%(space)s*)*)' # 2 - r'(%(regex)s)' # 3 - r'(%(space)s*(?:%(newline)s%(space)s*)+' # 4 - r'(?=%(post_regex_off)s))?' - r'|(?<=%(preregex2)s)' - r'(%(space)s*(?:(%(newline)s)%(space)s*)*)' # 5, 6 - r'(%(regex)s)' # 7 - r'(%(space)s*(?:%(newline)s%(space)s*)+' # 8 - r'(?=%(post_regex_off)s))?' - r'|(?<=%(id_literal_close)s)' - r'(%(space)s*(?:%(newline)s%(space)s*)+)' # 9 - r'(?=%(id_literal_open)s)' - r'|(?<=%(id_literal)s)(%(space)s+)(?=%(id_literal)s)' # 10 - r'|(?<=\+)(%(space)s+)(?=\+)' # 11 - r'|(?<=-)(%(space)s+)(?=-)' # 12 - r'|(%(space)s+)' # 13 - r'|((?:%(newline)s%(space)s*)+)' # 14 - ) % locals()).sub - - # print space_sub_banged.__self__.pattern - - keep = _re.compile(( - r'%(space_chars)s+|%(space_comment_nobang)s+|%(newline)s+' - r'|(%(bang_comment)s+)' - ) % locals()).sub - keeper = lambda m: m.groups()[0] or '' - - # print keep.__self__.pattern - - def space_subber_banged(match): - """ Substitution callback """ - # pylint: disable = too-many-return-statements - - groups = match.groups() - if groups[0]: - return groups[0] - elif groups[1]: - return groups[1] - elif groups[3]: - return "%s%s%s%s" % ( - keep(keeper, groups[2]), - groups[3], - keep(keeper, groups[4] or ''), - groups[4] and '\n' or '', - ) - elif groups[7]: - return "%s%s%s%s%s" % ( - keep(keeper, groups[5]), - groups[6] and '\n' or '', - groups[7], - keep(keeper, groups[8] or ''), - groups[8] and '\n' or '', - ) - elif groups[9]: - return keep(keeper, groups[9]) + '\n' - elif groups[10] or groups[11] or groups[12]: - return keep(keeper, groups[10] or groups[11] or groups[12]) or ' ' - else: - return keep(keeper, groups[13] or groups[14]) - - def jsmin(script, keep_bang_comments=False): - 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 - - `keep_bang_comments` : ``bool`` - Keep comments starting with an exclamation mark? (``/*!...*/``) - - :Return: Minified script - :Rtype: ``str`` - """ - # pylint: disable = redefined-outer-name - - if keep_bang_comments: - return space_sub_banged( - space_subber_banged, '\n%s\n' % script - ).strip() - else: - return space_sub_simple( - space_subber_simple, '\n%s\n' % script - ).strip() - - return jsmin - -jsmin = _make_jsmin() - - -def jsmin_for_posers(script, keep_bang_comments=False): - 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 regexes. It's here for fun and may - vanish any time. Use the `jsmin` function instead. - - :Parameters: - `script` : ``str`` - Script to minify - - `keep_bang_comments` : ``bool`` - Keep comments starting with an exclamation mark? (``/*!...*/``) - - :Return: Minified script - :Rtype: ``str`` - """ - if not keep_bang_comments: - rex = ( - r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' - r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' - r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?' - r'{};\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*' - r'][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\0' - r'14\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r' - r'\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r' - r'\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011\013\014' - r'\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^\r' - r'\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:' - r'[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;=?\]|}-]))?|(?<=[\00' - r'0-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\016-\040]|(?' - r':/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]' - r'))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*' - r'\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[' - r'[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((' - r'?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)' - r'*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\04' - r'0]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;' - r'=?\]|}-]))?|(?<=[^\000-!#%&(*,./:-@\[\\^`{|~])(?:[\000-\011\01' - r'3\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?:' - r'//[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]' - r'*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,./:-@\\-^' - r'`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\013\014\0' - r'16-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./' - r':-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/\*[' - r'^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013' - r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[' - r'\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)' - r')+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]' - r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+' - ) - - def subber(match): - """ Substitution callback """ - groups = match.groups() - return ( - groups[0] or - groups[1] or - (groups[3] and (groups[2] + '\n')) or - groups[2] or - (groups[5] and "%s%s%s" % ( - groups[4] and '\n' or '', - groups[5], - groups[6] and '\n' or '', - )) or - (groups[7] and '\n') or - (groups[8] and ' ') or - (groups[9] and ' ') or - (groups[10] and ' ') or - '' - ) - else: - rex = ( - r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]' - r'|\r?\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]' - r'|\r?\n|\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?' - r'{};\r\n])((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/' - r'*][^*]*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013' - r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*)((?:/(?!' - r'[\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^' - r'\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/))((?:[\000-\011\013\01' - r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:(?:(?://[^' - r'\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(' - r'?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)+,.:;=?\]|}-]))?|(?<=[' - r'\000-#%-,./:-@\[-^`{-~-]return)((?:[\000-\011\013\014\016-\040' - r']|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[' - r'\r\n]))(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][' - r'^*]*\*+)*/))*)*)((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|' - r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*' - r'/))((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]' - r'*\*+)*/))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\01' - r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040&)' - r'+,.:;=?\]|}-]))?|(?<=[^\000-!#%&(*,./:-@\[\\^`{|~])((?:[\000-' - r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*(?:' - r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/' - r'\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+)(?=[^\000-\040"#%-\047)*,./' - r':-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\01' - r'3\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=[^\000' - r'-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|' - r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=\+)|(?<=-)((?:[\000-\0' - r'11\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+)(?=-' - r')|((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*' - r'\*+)*/))+)|((?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014' - r'\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+)' - ) - - keep = _re.compile(( - r'[\000-\011\013\014\016-\040]+|(?:/\*(?!!)[^*]*\*+(?:[^/*][^*]*' - r'\*+)*/)+|(?:(?://[^\r\n]*)?[\r\n])+|((?:/\*![^*]*\*+(?:[^/*][^' - r'*]*\*+)*/)+)' - ) % locals()).sub - keeper = lambda m: m.groups()[0] or '' - - def subber(match): - """ Substitution callback """ - groups = match.groups() - return ( - groups[0] or - groups[1] or - (groups[3] and "%s%s%s%s" % ( - keep(keeper, groups[2]), - groups[3], - keep(keeper, groups[4] or ''), - groups[4] and '\n' or '', - )) or - (groups[7] and "%s%s%s%s%s" % ( - keep(keeper, groups[5]), - groups[6] and '\n' or '', - groups[7], - keep(keeper, groups[8] or ''), - groups[8] and '\n' or '', - )) or - (groups[9] and keep(keeper, groups[9] + '\n')) or - (groups[10] and keep(keeper, groups[10]) or ' ') or - (groups[11] and keep(keeper, groups[11]) or ' ') or - (groups[12] and keep(keeper, groups[12]) or ' ') or - keep(keeper, groups[13] or groups[14]) - ) - - return _re.sub(rex, subber, '\n%s\n' % script).strip() - - -if __name__ == '__main__': - def main(): - """ Main """ - import sys as _sys - - argv = _sys.argv[1:] - keep_bang_comments = '-b' in argv or '-bp' in argv or '-pb' in argv - if '-p' in argv or '-bp' in argv or '-pb' in argv: - xjsmin = _make_jsmin(python_only=True) - else: - xjsmin = jsmin - - _sys.stdout.write(xjsmin( - _sys.stdin.read(), keep_bang_comments=keep_bang_comments - )) - - main() diff --git a/docs/settings.txt b/docs/settings.txt index 34995d5..b175bff 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -130,7 +130,7 @@ Backend settings - ``compressor.filters.cssmin.rCSSMinFilter`` A filter that uses the cssmin implementation rCSSmin_ to compress CSS - (included, no external dependencies). + (installed by default). - ``compressor.filters.cleancss.CleanCSSFilter`` @@ -173,7 +173,7 @@ Backend settings - ``compressor.filters.jsmin.JSMinFilter`` A filter that uses the jsmin implementation rJSmin_ to compress - JavaScript code (included, no external dependencies). + JavaScript code (installed by default). .. _slimit_filter: diff --git a/requirements/tests.txt b/requirements/tests.txt index 3d42158..1c6a3ff 100644 --- a/requirements/tests.txt +++ b/requirements/tests.txt @@ -11,3 +11,5 @@ jingo==0.7 django-sekizai==0.8.2 django-overextends==0.4.0 csscompressor==0.9.4 +rcssmin==1.0.6 +rjsmin==1.0.12 diff --git a/setup.py b/setup.py index 5271a00..49f9dc2 100644 --- a/setup.py +++ b/setup.py @@ -142,5 +142,7 @@ setup( zip_safe=False, install_requires=[ 'django-appconf >= 0.4', + 'rcssmin == 1.0.6', + 'rjsmin == 1.0.12', ], ) diff --git a/tox.ini b/tox.ini index 478adf0..8767697 100644 --- a/tox.ini +++ b/tox.ini @@ -13,6 +13,8 @@ two = django-sekizai==0.8.2 django-overextends==0.4.0 csscompressor==0.9.4 + rcssmin==1.0.6 + rjsmin==1.0.12 two_six = flake8==2.4.0 coverage==3.7.1 @@ -27,6 +29,8 @@ two_six = django-sekizai==0.8.2 django-overextends==0.4.0 csscompressor==0.9.4 + rcssmin==1.0.6 + rjsmin==1.0.12 three = flake8==2.4.0 coverage==3.7.1 @@ -40,6 +44,8 @@ three = django-sekizai==0.8.2 django-overextends==0.4.0 csscompressor==0.9.4 + rcssmin==1.0.6 + rjsmin==1.0.12 three_two = flake8==2.4.0 coverage==3.7.1 @@ -53,6 +59,8 @@ three_two = django-sekizai==0.8.2 django-overextends==0.4.0 csscompressor==0.9.4 + rcssmin==1.0.6 + rjsmin==1.0.12 [tox] envlist = {py26,py27}-1.4.X, From 5fee8cfd9f0ce8753e5126beac198b3960751c6a Mon Sep 17 00:00:00 2001 From: Johannes Linke Date: Fri, 4 Dec 2015 22:10:17 +0100 Subject: [PATCH 6/8] Remove installation instructions for dependencies listed in setup.py. ref #664 --- docs/quickstart.txt | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/docs/quickstart.txt b/docs/quickstart.txt index 1169ab6..9dc58ee 100644 --- a/docs/quickstart.txt +++ b/docs/quickstart.txt @@ -39,27 +39,9 @@ Installation .. _dependencies: -Dependencies +Optional Dependencies ------------ -Required -^^^^^^^^ - -In case you're installing Django Compressor differently -(e.g. from the Git repo), make sure to install the following -dependencies. - -- django-appconf_ - - Used internally to handle Django's settings, this is - automatically installed when following the above - installation instructions. - - pip install django-appconf - -Optional -^^^^^^^^ - - BeautifulSoup_ For the :attr:`parser ` From 5e7fa5c6079faf8bb337f8a3c06eb26482e5472b Mon Sep 17 00:00:00 2001 From: Johannes Linke Date: Fri, 4 Dec 2015 22:10:57 +0100 Subject: [PATCH 7/8] Move slimit.py into __init__.py for consistency --- compressor/filters/jsmin/__init__.py | 9 ++++++++- compressor/filters/jsmin/slimit.py | 10 ---------- 2 files changed, 8 insertions(+), 11 deletions(-) delete mode 100644 compressor/filters/jsmin/slimit.py diff --git a/compressor/filters/jsmin/__init__.py b/compressor/filters/jsmin/__init__.py index ceca6ac..f32e6a4 100644 --- a/compressor/filters/jsmin/__init__.py +++ b/compressor/filters/jsmin/__init__.py @@ -1,6 +1,5 @@ from __future__ import absolute_import from compressor.filters import CallbackOutputFilter -from compressor.filters.jsmin.slimit import SlimItFilter # noqa class rJSMinFilter(CallbackOutputFilter): @@ -12,3 +11,11 @@ class rJSMinFilter(CallbackOutputFilter): # This is for backwards compatibility JSMinFilter = rJSMinFilter + + +class SlimItFilter(CallbackOutputFilter): + dependencies = ["slimit"] + callback = "slimit.minify" + kwargs = { + "mangle": True, + } diff --git a/compressor/filters/jsmin/slimit.py b/compressor/filters/jsmin/slimit.py deleted file mode 100644 index 9ffc7f4..0000000 --- a/compressor/filters/jsmin/slimit.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import absolute_import -from compressor.filters import CallbackOutputFilter - - -class SlimItFilter(CallbackOutputFilter): - dependencies = ["slimit"] - callback = "slimit.minify" - kwargs = { - "mangle": True, - } From a695e0eb8519aecfda1d8e25d1a76e0f05686fb0 Mon Sep 17 00:00:00 2001 From: Johannes Linke Date: Fri, 4 Dec 2015 23:01:30 +0100 Subject: [PATCH 8/8] Fix flake8 errors --- compressor/parser/beautifulsoup.py | 2 +- compressor/tests/test_filters.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/compressor/parser/beautifulsoup.py b/compressor/parser/beautifulsoup.py index 606a9e4..1068abe 100644 --- a/compressor/parser/beautifulsoup.py +++ b/compressor/parser/beautifulsoup.py @@ -12,7 +12,7 @@ class BeautifulSoupParser(ParserBase): try: from bs4 import BeautifulSoup self.soup = BeautifulSoup(self.content, "html.parser") - except ImportError: + except ImportError as err: raise ImproperlyConfigured("Error while importing BeautifulSoup: %s" % err) def css_elems(self): diff --git a/compressor/tests/test_filters.py b/compressor/tests/test_filters.py index a63310b..8264072 100644 --- a/compressor/tests/test_filters.py +++ b/compressor/tests/test_filters.py @@ -3,17 +3,14 @@ from collections import defaultdict import io import os import sys -import textwrap from django.utils import six from django.test import TestCase -from django.utils import unittest from django.test.utils import override_settings from compressor.cache import cache, get_hashed_mtime, get_hashed_content from compressor.conf import settings from compressor.css import CssCompressor -from compressor.utils import find_command from compressor.filters.base import CompilerFilter, CachedCompilerFilter from compressor.filters.cssmin import CSSCompressorFilter, rCSSMinFilter from compressor.filters.css_default import CssAbsoluteFilter