Merge pull request #700 from karyon/deal_with_dependencies
Deal with dependencies
This commit is contained in:
2
Makefile
2
Makefile
@@ -10,7 +10,7 @@ runtests:
|
|||||||
coverage run --branch --source=compressor `which django-admin.py` test --settings=compressor.test_settings compressor
|
coverage run --branch --source=compressor `which django-admin.py` test --settings=compressor.test_settings compressor
|
||||||
|
|
||||||
coveragereport:
|
coveragereport:
|
||||||
coverage report --omit=compressor/test*,compressor/filters/jsmin/rjsmin*,compressor/filters/cssmin/cssmin*,compressor/utils/stringformat*
|
coverage report --omit=compressor/test*
|
||||||
|
|
||||||
test: flake8 runtests coveragereport
|
test: flake8 runtests coveragereport
|
||||||
|
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ 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
|
html5lib_ based parser, as well as an abstract base class that makes it easy to
|
||||||
write a custom parser.
|
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
|
`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
|
`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`_.
|
`data URIs`_.
|
||||||
|
|
||||||
If your setup requires a different compressor or other post-processing
|
If your setup requires a different compressor or other post-processing
|
||||||
@@ -72,13 +72,11 @@ The in-development version of Django Compressor can be installed with
|
|||||||
.. _BeautifulSoup: http://www.crummy.com/software/BeautifulSoup/
|
.. _BeautifulSoup: http://www.crummy.com/software/BeautifulSoup/
|
||||||
.. _lxml: http://lxml.de/
|
.. _lxml: http://lxml.de/
|
||||||
.. _html5lib: http://code.google.com/p/html5lib/
|
.. _html5lib: http://code.google.com/p/html5lib/
|
||||||
.. _CSS Tidy: http://csstidy.sourceforge.net/
|
|
||||||
.. _YUI CSS and JS: http://developer.yahoo.com/yui/compressor/
|
.. _YUI CSS and JS: http://developer.yahoo.com/yui/compressor/
|
||||||
.. _yUglify CSS and JS: https://github.com/yui/yuglify
|
.. _yUglify CSS and JS: https://github.com/yui/yuglify
|
||||||
.. _Closure Compiler: http://code.google.com/closure/compiler/
|
.. _Closure Compiler: http://code.google.com/closure/compiler/
|
||||||
.. _JSMin: http://www.crockford.com/javascript/jsmin.html
|
.. _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
|
.. _data URIs: http://en.wikipedia.org/wiki/Data_URI_scheme
|
||||||
.. _django-compressor.readthedocs.org: http://django-compressor.readthedocs.org/en/latest/
|
.. _django-compressor.readthedocs.org: http://django-compressor.readthedocs.org/en/latest/
|
||||||
.. _github.com/django-compressor/django-compressor: https://github.com/django-compressor/django-compressor
|
.. _github.com/django-compressor/django-compressor: https://github.com/django-compressor/django-compressor
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,6 @@ class CompressorConf(AppConf):
|
|||||||
CACHEABLE_PRECOMPILERS = ()
|
CACHEABLE_PRECOMPILERS = ()
|
||||||
CLOSURE_COMPILER_BINARY = 'java -jar compiler.jar'
|
CLOSURE_COMPILER_BINARY = 'java -jar compiler.jar'
|
||||||
CLOSURE_COMPILER_ARGUMENTS = ''
|
CLOSURE_COMPILER_ARGUMENTS = ''
|
||||||
CSSTIDY_BINARY = 'csstidy'
|
|
||||||
CSSTIDY_ARGUMENTS = '--template=highest'
|
|
||||||
YUI_BINARY = 'java -jar yuicompressor.jar'
|
YUI_BINARY = 'java -jar yuicompressor.jar'
|
||||||
YUI_CSS_ARGUMENTS = ''
|
YUI_CSS_ARGUMENTS = ''
|
||||||
YUI_JS_ARGUMENTS = ''
|
YUI_JS_ARGUMENTS = ''
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
from compressor.filters import CallbackOutputFilter
|
from compressor.filters import CallbackOutputFilter
|
||||||
|
|
||||||
|
|
||||||
class CSSMinFilter(CallbackOutputFilter):
|
class CSSCompressorFilter(CallbackOutputFilter):
|
||||||
"""
|
"""
|
||||||
A filter that utilizes Zachary Voase's Python port of
|
A filter that utilizes Yury Selivanov's Python port of
|
||||||
the YUI CSS compression algorithm: http://pypi.python.org/pypi/cssmin/
|
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):
|
class rCSSMinFilter(CallbackOutputFilter):
|
||||||
callback = "compressor.filters.cssmin.rcssmin.cssmin"
|
callback = "rcssmin.cssmin"
|
||||||
|
dependencies = ["rcssmin"]
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"keep_bang_comments": True
|
"keep_bang_comments": True
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -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'|(?<!%(nmchar)s)url\(%(spacechar)s*(' # 12
|
|
||||||
r'%(uri_nl_strings)s'
|
|
||||||
r'|%(uri)s'
|
|
||||||
r')%(spacechar)s*\)'
|
|
||||||
r'|(@(?:' # 13
|
|
||||||
r'[mM][eE][dD][iI][aA]'
|
|
||||||
r'|[sS][uU][pP][pP][oO][rR][tT][sS]'
|
|
||||||
r'|[dD][oO][cC][uU][mM][eE][nN][tT]'
|
|
||||||
r'|(?:-(?:'
|
|
||||||
r'[wW][eE][bB][kK][iI][tT]|[mM][oO][zZ]|[oO]|[mM][sS]'
|
|
||||||
r')-)?'
|
|
||||||
r'[kK][eE][yY][fF][rR][aA][mM][eE][sS]'
|
|
||||||
r'))(?!%(nmchar)s)'
|
|
||||||
r'|(%(ie7hack)s)(%(space)s*)' # 14, 15
|
|
||||||
r'|(:[fF][iI][rR][sS][tT]-[lL]' # 16
|
|
||||||
r'(?:[iI][nN][eE]|[eE][tT][tT][eE][rR]))'
|
|
||||||
r'(%(space)s*)(?=[{,])' # 17
|
|
||||||
r'|(%(nl_strings)s)' # 18
|
|
||||||
r'|(%(escape)s[^\\"\047u>@\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()
|
|
||||||
@@ -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),
|
|
||||||
)
|
|
||||||
@@ -1,13 +1,21 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from compressor.filters import CallbackOutputFilter
|
from compressor.filters import CallbackOutputFilter
|
||||||
from compressor.filters.jsmin.slimit import SlimItFilter # noqa
|
|
||||||
|
|
||||||
|
|
||||||
class rJSMinFilter(CallbackOutputFilter):
|
class rJSMinFilter(CallbackOutputFilter):
|
||||||
callback = "compressor.filters.jsmin.rjsmin.jsmin"
|
callback = "rjsmin.jsmin"
|
||||||
|
dependencies = ["rjsmin"]
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"keep_bang_comments": True
|
"keep_bang_comments": True
|
||||||
}
|
}
|
||||||
|
|
||||||
# This is for backwards compatibility
|
# This is for backwards compatibility
|
||||||
JSMinFilter = rJSMinFilter
|
JSMinFilter = rJSMinFilter
|
||||||
|
|
||||||
|
|
||||||
|
class SlimItFilter(CallbackOutputFilter):
|
||||||
|
dependencies = ["slimit"]
|
||||||
|
callback = "slimit.minify"
|
||||||
|
kwargs = {
|
||||||
|
"mangle": True,
|
||||||
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -11,27 +11,15 @@ class BeautifulSoupParser(ParserBase):
|
|||||||
super(BeautifulSoupParser, self).__init__(content)
|
super(BeautifulSoupParser, self).__init__(content)
|
||||||
try:
|
try:
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
self.use_bs4 = True
|
|
||||||
self.soup = BeautifulSoup(self.content, "html.parser")
|
self.soup = BeautifulSoup(self.content, "html.parser")
|
||||||
except ImportError:
|
except ImportError as err:
|
||||||
try:
|
raise ImproperlyConfigured("Error while importing BeautifulSoup: %s" % err)
|
||||||
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)
|
|
||||||
|
|
||||||
def css_elems(self):
|
def css_elems(self):
|
||||||
if self.use_bs4:
|
return self.soup.find_all({'link': True, 'style': True})
|
||||||
return self.soup.find_all({'link': True, 'style': True})
|
|
||||||
else:
|
|
||||||
return self.soup.findAll({'link': True, 'style': True})
|
|
||||||
|
|
||||||
def js_elems(self):
|
def js_elems(self):
|
||||||
if self.use_bs4:
|
return self.soup.find_all('script')
|
||||||
return self.soup.find_all('script')
|
|
||||||
else:
|
|
||||||
return self.soup.findAll('script')
|
|
||||||
|
|
||||||
def elem_attribs(self, elem):
|
def elem_attribs(self, elem):
|
||||||
attrs = dict(elem.attrs)
|
attrs = dict(elem.attrs)
|
||||||
|
|||||||
@@ -4,12 +4,7 @@ import re
|
|||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from shutil import rmtree, copytree
|
from shutil import rmtree, copytree
|
||||||
|
|
||||||
try:
|
from bs4 import BeautifulSoup
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
use_bs4 = True
|
|
||||||
except ImportError:
|
|
||||||
from BeautifulSoup import BeautifulSoup
|
|
||||||
use_bs4 = False
|
|
||||||
|
|
||||||
from django.core.cache.backends import locmem
|
from django.core.cache.backends import locmem
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
@@ -26,17 +21,7 @@ from compressor.storage import DefaultStorage
|
|||||||
|
|
||||||
|
|
||||||
def make_soup(markup):
|
def make_soup(markup):
|
||||||
if use_bs4:
|
return BeautifulSoup(markup, "html.parser")
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def css_tag(href, **kwargs):
|
def css_tag(href, **kwargs):
|
||||||
@@ -302,7 +287,7 @@ class CssMediaTestCase(SimpleTestCase):
|
|||||||
|
|
||||||
def test_css_output(self):
|
def test_css_output(self):
|
||||||
css_node = CssCompressor(self.css)
|
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]
|
media = ['screen', 'print', 'all', None]
|
||||||
self.assertEqual(len(links), 4)
|
self.assertEqual(len(links), 4)
|
||||||
self.assertEqual(media, [l.get('media', None) for l in links])
|
self.assertEqual(media, [l.get('media', None) for l in links])
|
||||||
@@ -311,7 +296,7 @@ class CssMediaTestCase(SimpleTestCase):
|
|||||||
css = self.css + '<style type="text/css" media="print">p { border:10px solid red;}</style>'
|
css = self.css + '<style type="text/css" media="print">p { border:10px solid red;}</style>'
|
||||||
css_node = CssCompressor(css)
|
css_node = CssCompressor(css)
|
||||||
media = ['screen', 'print', 'all', None, 'print']
|
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])
|
self.assertEqual(media, [l.get('media', None) for l in links])
|
||||||
|
|
||||||
@override_settings(COMPRESS_PRECOMPILERS=(
|
@override_settings(COMPRESS_PRECOMPILERS=(
|
||||||
@@ -323,7 +308,7 @@ class CssMediaTestCase(SimpleTestCase):
|
|||||||
<link rel="stylesheet" href="/static/css/two.css" type="text/css" media="screen">
|
<link rel="stylesheet" href="/static/css/two.css" type="text/css" media="screen">
|
||||||
<style type="text/foobar" media="screen">h1 { border:5px solid green;}</style>"""
|
<style type="text/foobar" media="screen">h1 { border:5px solid green;}</style>"""
|
||||||
css_node = CssCompressor(css)
|
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],
|
self.assertEqual(['/static/css/one.css', '/static/css/two.css', None],
|
||||||
[l.get('href', None) for l in output])
|
[l.get('href', None) for l in output])
|
||||||
self.assertEqual(['screen', 'screen', 'screen'],
|
self.assertEqual(['screen', 'screen', 'screen'],
|
||||||
@@ -363,11 +348,8 @@ class JsAsyncDeferTestCase(SimpleTestCase):
|
|||||||
return 'defer'
|
return 'defer'
|
||||||
js_node = JsCompressor(self.js)
|
js_node = JsCompressor(self.js)
|
||||||
output = [None, 'async', 'defer', None, 'async', None]
|
output = [None, 'async', 'defer', None, 'async', None]
|
||||||
scripts = soup_find_all(js_node.output(), 'script')
|
scripts = make_soup(js_node.output()).find_all('script')
|
||||||
if use_bs4:
|
attrs = [extract_attr(s) for s in scripts]
|
||||||
attrs = [extract_attr(i) for i in scripts]
|
|
||||||
else:
|
|
||||||
attrs = [s.get('async') or s.get('defer') for s in scripts]
|
|
||||||
self.assertEqual(output, attrs)
|
self.assertEqual(output, attrs)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,24 +3,20 @@ from collections import defaultdict
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
|
||||||
|
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.utils import unittest
|
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
from compressor.cache import cache, get_hashed_mtime, get_hashed_content
|
from compressor.cache import cache, get_hashed_mtime, get_hashed_content
|
||||||
from compressor.conf import settings
|
from compressor.conf import settings
|
||||||
from compressor.css import CssCompressor
|
from compressor.css import CssCompressor
|
||||||
from compressor.utils import find_command
|
|
||||||
from compressor.filters.base import CompilerFilter, CachedCompilerFilter
|
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.css_default import CssAbsoluteFilter
|
||||||
from compressor.filters.jsmin import JSMinFilter
|
from compressor.filters.jsmin import JSMinFilter
|
||||||
from compressor.filters.template import TemplateFilter
|
from compressor.filters.template import TemplateFilter
|
||||||
from compressor.filters.closure import ClosureCompilerFilter
|
from compressor.filters.closure import ClosureCompilerFilter
|
||||||
from compressor.filters.csstidy import CSSTidyFilter
|
|
||||||
from compressor.filters.yuglify import YUglifyCSSFilter, YUglifyJSFilter
|
from compressor.filters.yuglify import YUglifyCSSFilter, YUglifyJSFilter
|
||||||
from compressor.filters.yui import YUICSSFilter, YUIJSFilter
|
from compressor.filters.yui import YUICSSFilter, YUIJSFilter
|
||||||
from compressor.filters.cleancss import CleanCSSFilter
|
from compressor.filters.cleancss import CleanCSSFilter
|
||||||
@@ -31,22 +27,6 @@ def blankdict(*args, **kwargs):
|
|||||||
return defaultdict(lambda: '', *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',))
|
@override_settings(COMPRESS_CACHEABLE_PRECOMPILERS=('text/css',))
|
||||||
class PrecompilerTestCase(TestCase):
|
class PrecompilerTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -144,8 +124,8 @@ class PrecompilerTestCase(TestCase):
|
|||||||
self.assertEqual("", compiler.input())
|
self.assertEqual("", compiler.input())
|
||||||
|
|
||||||
|
|
||||||
class CssMinTestCase(TestCase):
|
class CSSCompressorTestCase(TestCase):
|
||||||
def test_cssmin_filter(self):
|
def test_csscompressor_filter(self):
|
||||||
content = """/*!
|
content = """/*!
|
||||||
* django-compressor
|
* django-compressor
|
||||||
* Copyright (c) 2009-2014 Django Compressor authors
|
* Copyright (c) 2009-2014 Django Compressor authors
|
||||||
@@ -158,8 +138,11 @@ class CssMinTestCase(TestCase):
|
|||||||
|
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
output = "/*!* django-compressor * Copyright(c) 2009-2014 Django Compressor authors */ p{background:#369 url('../../images/image.gif')}"
|
output = """/*!
|
||||||
self.assertEqual(output, CSSMinFilter(content).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):
|
class rCssMinTestCase(TestCase):
|
||||||
@@ -440,10 +423,6 @@ class SpecializedFiltersTest(TestCase):
|
|||||||
filter = ClosureCompilerFilter('')
|
filter = ClosureCompilerFilter('')
|
||||||
self.assertEqual(filter.options, (('binary', six.text_type('java -jar compiler.jar')), ('args', six.text_type(''))))
|
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):
|
def test_yuglify_filters(self):
|
||||||
filter = YUglifyCSSFilter('')
|
filter = YUglifyCSSFilter('')
|
||||||
self.assertEqual(filter.command, '{binary} {args} --type=css')
|
self.assertEqual(filter.command, '{binary} {args} --type=css')
|
||||||
|
|||||||
@@ -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'((?<!{)(?:{{)+' # '{{'
|
|
||||||
r'|(?:}})+(?!})' # '}}
|
|
||||||
r'|{(?:[^{](?:[^{}]+|{[^{}]*})*)?})' # replacement field
|
|
||||||
)
|
|
||||||
_format_sub_re = re.compile(r'({[^{}]*})') # nested replacement field
|
|
||||||
_format_spec_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()
|
|
||||||
@@ -39,27 +39,9 @@ Installation
|
|||||||
|
|
||||||
.. _dependencies:
|
.. _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_
|
- BeautifulSoup_
|
||||||
|
|
||||||
For the :attr:`parser <django.conf.settings.COMPRESS_PARSER>`
|
For the :attr:`parser <django.conf.settings.COMPRESS_PARSER>`
|
||||||
@@ -89,6 +71,13 @@ Optional
|
|||||||
|
|
||||||
pip install slimit
|
pip install slimit
|
||||||
|
|
||||||
|
- `csscompressor`_
|
||||||
|
|
||||||
|
For the :ref:`csscompressor filter <csscompressor_filter>`
|
||||||
|
``compressor.filters.cssmin.CSSCompressorFilter``::
|
||||||
|
|
||||||
|
pip install csscompressor
|
||||||
|
|
||||||
.. _BeautifulSoup: http://www.crummy.com/software/BeautifulSoup/
|
.. _BeautifulSoup: http://www.crummy.com/software/BeautifulSoup/
|
||||||
.. _lxml: http://lxml.de/
|
.. _lxml: http://lxml.de/
|
||||||
.. _libxml2: http://xmlsoft.org/
|
.. _libxml2: http://xmlsoft.org/
|
||||||
|
|||||||
@@ -87,18 +87,6 @@ Backend settings
|
|||||||
feature, and the ``'content'`` in case you're using multiple servers
|
feature, and the ``'content'`` in case you're using multiple servers
|
||||||
to serve your content.
|
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``
|
- ``compressor.filters.datauri.CssDataUriFilter``
|
||||||
|
|
||||||
A filter for embedding media as `data: URIs`_ in the CSS.
|
A filter for embedding media as `data: URIs`_ in the CSS.
|
||||||
@@ -132,15 +120,17 @@ Backend settings
|
|||||||
|
|
||||||
The arguments passed to the compressor. Defaults to --terminal.
|
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
|
- ``compressor.filters.cssmin.CSSCompressorFilter``
|
||||||
algorithm cssmin_ (included, no external dependencies).
|
|
||||||
|
A filter that uses Yury Selivanov's Python port of the YUI CSS compression
|
||||||
|
algorithm csscompressor_.
|
||||||
|
|
||||||
- ``compressor.filters.cssmin.rCSSMinFilter``
|
- ``compressor.filters.cssmin.rCSSMinFilter``
|
||||||
|
|
||||||
A filter that uses the cssmin implementation rCSSmin_ to compress CSS
|
A filter that uses the cssmin implementation rCSSmin_ to compress CSS
|
||||||
(included, no external dependencies).
|
(installed by default).
|
||||||
|
|
||||||
- ``compressor.filters.cleancss.CleanCSSFilter``
|
- ``compressor.filters.cleancss.CleanCSSFilter``
|
||||||
|
|
||||||
@@ -155,9 +145,8 @@ Backend settings
|
|||||||
The arguments passed to clean-css.
|
The arguments passed to clean-css.
|
||||||
|
|
||||||
|
|
||||||
.. _CSSTidy: http://csstidy.sourceforge.net/
|
|
||||||
.. _`data: URIs`: http://en.wikipedia.org/wiki/Data_URI_scheme
|
.. _`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/
|
.. _rCSSmin: http://opensource.perlig.de/rcssmin/
|
||||||
.. _`clean-css`: https://github.com/GoalSmashers/clean-css/
|
.. _`clean-css`: https://github.com/GoalSmashers/clean-css/
|
||||||
|
|
||||||
@@ -184,7 +173,7 @@ Backend settings
|
|||||||
- ``compressor.filters.jsmin.JSMinFilter``
|
- ``compressor.filters.jsmin.JSMinFilter``
|
||||||
|
|
||||||
A filter that uses the jsmin implementation rJSmin_ to compress
|
A filter that uses the jsmin implementation rJSmin_ to compress
|
||||||
JavaScript code (included, no external dependencies).
|
JavaScript code (installed by default).
|
||||||
|
|
||||||
.. _slimit_filter:
|
.. _slimit_filter:
|
||||||
|
|
||||||
|
|||||||
@@ -10,3 +10,6 @@ coffin==0.4.0
|
|||||||
jingo==0.7
|
jingo==0.7
|
||||||
django-sekizai==0.8.2
|
django-sekizai==0.8.2
|
||||||
django-overextends==0.4.0
|
django-overextends==0.4.0
|
||||||
|
csscompressor==0.9.4
|
||||||
|
rcssmin==1.0.6
|
||||||
|
rjsmin==1.0.12
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -142,5 +142,7 @@ setup(
|
|||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'django-appconf >= 0.4',
|
'django-appconf >= 0.4',
|
||||||
|
'rcssmin == 1.0.6',
|
||||||
|
'rjsmin == 1.0.12',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
14
tox.ini
14
tox.ini
@@ -12,6 +12,9 @@ two =
|
|||||||
coffin==0.4.0
|
coffin==0.4.0
|
||||||
django-sekizai==0.8.2
|
django-sekizai==0.8.2
|
||||||
django-overextends==0.4.0
|
django-overextends==0.4.0
|
||||||
|
csscompressor==0.9.4
|
||||||
|
rcssmin==1.0.6
|
||||||
|
rjsmin==1.0.12
|
||||||
two_six =
|
two_six =
|
||||||
flake8==2.4.0
|
flake8==2.4.0
|
||||||
coverage==3.7.1
|
coverage==3.7.1
|
||||||
@@ -19,12 +22,15 @@ two_six =
|
|||||||
mock==1.0.1
|
mock==1.0.1
|
||||||
Jinja2==2.7.3
|
Jinja2==2.7.3
|
||||||
lxml==3.4.2
|
lxml==3.4.2
|
||||||
BeautifulSoup==3.2.1
|
beautifulsoup4==4.4.0
|
||||||
unittest2==1.0.0
|
unittest2==1.0.0
|
||||||
jingo==0.7
|
jingo==0.7
|
||||||
coffin==0.4.0
|
coffin==0.4.0
|
||||||
django-sekizai==0.8.2
|
django-sekizai==0.8.2
|
||||||
django-overextends==0.4.0
|
django-overextends==0.4.0
|
||||||
|
csscompressor==0.9.4
|
||||||
|
rcssmin==1.0.6
|
||||||
|
rjsmin==1.0.12
|
||||||
three =
|
three =
|
||||||
flake8==2.4.0
|
flake8==2.4.0
|
||||||
coverage==3.7.1
|
coverage==3.7.1
|
||||||
@@ -37,6 +43,9 @@ three =
|
|||||||
coffin==0.4.0
|
coffin==0.4.0
|
||||||
django-sekizai==0.8.2
|
django-sekizai==0.8.2
|
||||||
django-overextends==0.4.0
|
django-overextends==0.4.0
|
||||||
|
csscompressor==0.9.4
|
||||||
|
rcssmin==1.0.6
|
||||||
|
rjsmin==1.0.12
|
||||||
three_two =
|
three_two =
|
||||||
flake8==2.4.0
|
flake8==2.4.0
|
||||||
coverage==3.7.1
|
coverage==3.7.1
|
||||||
@@ -49,6 +58,9 @@ three_two =
|
|||||||
coffin==0.4.0
|
coffin==0.4.0
|
||||||
django-sekizai==0.8.2
|
django-sekizai==0.8.2
|
||||||
django-overextends==0.4.0
|
django-overextends==0.4.0
|
||||||
|
csscompressor==0.9.4
|
||||||
|
rcssmin==1.0.6
|
||||||
|
rjsmin==1.0.12
|
||||||
[tox]
|
[tox]
|
||||||
envlist =
|
envlist =
|
||||||
{py26,py27}-1.4.X,
|
{py26,py27}-1.4.X,
|
||||||
|
|||||||
Reference in New Issue
Block a user