initial commit
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""`cssmin` - A Python port of the YUI CSS compressor."""
|
||||
|
||||
"""
|
||||
Home page: https://github.com/zacharyvoase/cssmin
|
||||
License: BSD: https://github.com/zacharyvoase/cssmin/blob/master/LICENSE
|
||||
Original author: Zachary Voase
|
||||
Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
|
||||
"""
|
||||
|
||||
|
||||
from StringIO import StringIO # The pure-Python StringIO supports unicode.
|
||||
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()
|
||||
@@ -0,0 +1,374 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: ascii -*-
|
||||
#
|
||||
# Copyright 2011
|
||||
# Andr\xe9 Malo or his licensors, as applicable
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
r"""
|
||||
=====================
|
||||
Javascript Minifier
|
||||
=====================
|
||||
|
||||
Javascript Minifier based on `jsmin.c by Douglas Crockford`_\.
|
||||
|
||||
This module is a re-implementation based on the semantics of jsmin.c. Usually
|
||||
it produces the same results. 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.
|
||||
- rjsmin does not handle streams, but only complete strings. (However, the
|
||||
module provides a "streamy" interface).
|
||||
|
||||
Besides the list above it differs from direct python ports of jsmin.c in
|
||||
speed. Since most parts of the logic are handled by the regex engine it's way
|
||||
faster than the original python port by Baruch Even. The speed factor varies
|
||||
between about 6 and 55 depending on input and python version (it gets faster
|
||||
the more compressed the input already is). Compared to the speed-refactored
|
||||
python port by Dave St.Germain the performance gain is less dramatic but still
|
||||
between 1.2 and 7. See the docs/BENCHMARKS file for details.
|
||||
|
||||
rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more.
|
||||
|
||||
Both python 2 and python 3 are supported.
|
||||
|
||||
.. _jsmin.c by Douglas Crockford:
|
||||
http://www.crockford.com/javascript/jsmin.c
|
||||
|
||||
Original author of Python version: Andr\xe9 Malo
|
||||
Home page: http://opensource.perlig.de/rjsmin/
|
||||
Modified by Ross Peoples <ross.peoples@gmail.com> for inclusion into web2py.
|
||||
"""
|
||||
__author__ = "Andr\xe9 Malo"
|
||||
__author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1')
|
||||
__docformat__ = "restructuredtext en"
|
||||
__license__ = "Apache License, Version 2.0"
|
||||
__version__ = '1.0.2'
|
||||
__all__ = ['jsmin', 'jsmin_for_posers']
|
||||
|
||||
import re as _re
|
||||
|
||||
|
||||
def _make_jsmin(extended=True, python_only=True):
|
||||
"""
|
||||
Generate JS minifier based on `jsmin.c by Douglas Crockford`_
|
||||
|
||||
.. _jsmin.c by Douglas Crockford:
|
||||
http://www.crockford.com/javascript/jsmin.c
|
||||
|
||||
:Parameters:
|
||||
`extended` : ``bool``
|
||||
Extended Regexps? (using lookahead and lookbehind). This is faster,
|
||||
because it can be optimized way more. The regexps used with `extended`
|
||||
being false are only left here to allow easier porting to platforms
|
||||
without extended regex features (and for my own reference...)
|
||||
|
||||
`python_only` : ``bool``
|
||||
Use only the python variant. If true, the c extension is not even
|
||||
tried to be loaded.
|
||||
|
||||
:Return: Minifier
|
||||
:Rtype: ``callable``
|
||||
"""
|
||||
# pylint: disable = R0912, R0914, W0612
|
||||
if not python_only:
|
||||
try:
|
||||
import _rjsmin
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
return _rjsmin.jsmin
|
||||
try:
|
||||
xrange
|
||||
except NameError:
|
||||
xrange = range # pylint: disable = W0622
|
||||
|
||||
space_chars = r'[\000-\011\013\014\016-\040]'
|
||||
|
||||
line_comment = r'(?://[^\r\n]*)'
|
||||
space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
|
||||
string1 = \
|
||||
r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)'
|
||||
string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")'
|
||||
strings = r'(?:%s|%s)' % (string1, string2)
|
||||
|
||||
charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])'
|
||||
nospecial = r'[^/\\\[\r\n]'
|
||||
if extended:
|
||||
regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % (
|
||||
nospecial, charclass, nospecial
|
||||
)
|
||||
else:
|
||||
regex = (
|
||||
r'(?:/(?:[^*/\\\r\n\[]|%s|\\[^\r\n])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)'
|
||||
)
|
||||
regex = regex % (charclass, nospecial, charclass, nospecial)
|
||||
|
||||
space = r'(?:%s|%s)' % (space_chars, space_comment)
|
||||
newline = r'(?:%s?[\r\n])' % line_comment
|
||||
|
||||
def fix_charclass(result):
|
||||
""" Fixup string of chars to fit into a regex char class """
|
||||
pos = result.find('-')
|
||||
if pos >= 0:
|
||||
result = r'%s%s-' % (result[:pos], result[pos + 1:])
|
||||
|
||||
def sequentize(string):
|
||||
"""
|
||||
Notate consecutive characters as sequence
|
||||
|
||||
(1-4 instead of 1234)
|
||||
"""
|
||||
first, last, result = None, None, []
|
||||
for char in map(ord, string):
|
||||
if last is None:
|
||||
first = last = char
|
||||
elif last + 1 == char:
|
||||
last = char
|
||||
else:
|
||||
result.append((first, last))
|
||||
first = last = char
|
||||
if last is not None:
|
||||
result.append((first, last))
|
||||
return ''.join(['%s%s%s' % (
|
||||
chr(first),
|
||||
last > first + 1 and '-' or '',
|
||||
last != first and chr(last) or ''
|
||||
) for first, last in result])
|
||||
|
||||
return _re.sub(r'([\000-\040\047])', # for better portability
|
||||
lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result)
|
||||
.replace('\\', '\\\\')
|
||||
.replace('[', '\\[')
|
||||
.replace(']', '\\]')
|
||||
)
|
||||
)
|
||||
|
||||
def id_literal_(what):
|
||||
""" Make id_literal like char class """
|
||||
match = _re.compile(what).match
|
||||
result = ''.join([
|
||||
chr(c) for c in xrange(127) if not match(chr(c))
|
||||
])
|
||||
return '[^%s]' % fix_charclass(result)
|
||||
|
||||
def not_id_literal_(keep):
|
||||
""" Make negated id_literal like char class """
|
||||
match = _re.compile(id_literal_(keep)).match
|
||||
result = ''.join([
|
||||
chr(c) for c in xrange(127) if not match(chr(c))
|
||||
])
|
||||
return r'[%s]' % fix_charclass(result)
|
||||
|
||||
not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]')
|
||||
preregex1 = r'[(,=:\[!&|?{};\r\n]'
|
||||
preregex2 = r'%(not_id_literal)sreturn' % locals()
|
||||
|
||||
if extended:
|
||||
id_literal = id_literal_(r'[a-zA-Z0-9_$]')
|
||||
id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(+-]')
|
||||
id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
|
||||
|
||||
space_sub = _re.compile((
|
||||
r'([^\047"/\000-\040]+)'
|
||||
r'|(%(strings)s[^\047"/\000-\040]*)'
|
||||
r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
|
||||
r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
|
||||
r'|(?<=%(id_literal_close)s)'
|
||||
r'%(space)s*(?:(%(newline)s)%(space)s*)+'
|
||||
r'(?=%(id_literal_open)s)'
|
||||
r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
|
||||
r'|%(space)s+'
|
||||
r'|(?:%(newline)s%(space)s*)+'
|
||||
) % locals()).sub
|
||||
def space_subber(match):
|
||||
""" Substitution callback """
|
||||
# pylint: disable = C0321, R0911
|
||||
groups = match.groups()
|
||||
if groups[0]: return groups[0]
|
||||
elif groups[1]: return groups[1]
|
||||
elif groups[2]: return groups[2]
|
||||
elif groups[3]: return groups[3]
|
||||
elif groups[4]: return '\n'
|
||||
elif groups[5]: return ' '
|
||||
else: return ''
|
||||
|
||||
def jsmin(script): # pylint: disable = W0621
|
||||
r"""
|
||||
Minify javascript based on `jsmin.c by Douglas Crockford`_\.
|
||||
|
||||
Instead of parsing the stream char by char, it uses a regular
|
||||
expression approach which minifies the whole script with one big
|
||||
substitution regex.
|
||||
|
||||
.. _jsmin.c by Douglas Crockford:
|
||||
http://www.crockford.com/javascript/jsmin.c
|
||||
|
||||
:Parameters:
|
||||
`script` : ``str``
|
||||
Script to minify
|
||||
|
||||
:Return: Minified script
|
||||
:Rtype: ``str``
|
||||
"""
|
||||
return space_sub(space_subber, '\n%s\n' % script).strip()
|
||||
|
||||
else:
|
||||
pre_regex = r'(?:%(preregex1)s|%(preregex2)s)' % locals()
|
||||
not_id_literal_open = not_id_literal_(r'[a-zA-Z0-9_${\[(+-]')
|
||||
not_id_literal_close = not_id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
|
||||
|
||||
space_norm_sub = _re.compile((
|
||||
r'(%(strings)s)'
|
||||
r'|(?:(%(pre_regex)s)%(space)s*(%(regex)s))'
|
||||
r'|(%(space)s)+'
|
||||
r'|(?:(%(newline)s)%(space)s*)+'
|
||||
) % locals()).sub
|
||||
def space_norm_subber(match):
|
||||
""" Substitution callback """
|
||||
# pylint: disable = C0321
|
||||
groups = match.groups()
|
||||
if groups[0]: return groups[0]
|
||||
elif groups[1]: return groups[1].replace('\r', '\n') + groups[2]
|
||||
elif groups[3]: return ' '
|
||||
elif groups[4]: return '\n'
|
||||
|
||||
space_sub1 = _re.compile((
|
||||
r'[\040\n]?(%(strings)s|%(pre_regex)s%(regex)s)'
|
||||
r'|\040(%(not_id_literal)s)'
|
||||
r'|\n(%(not_id_literal_open)s)'
|
||||
) % locals()).sub
|
||||
def space_subber1(match):
|
||||
""" Substitution callback """
|
||||
groups = match.groups()
|
||||
return groups[0] or groups[1] or groups[2]
|
||||
|
||||
space_sub2 = _re.compile((
|
||||
r'(%(strings)s)\040?'
|
||||
r'|(%(pre_regex)s%(regex)s)[\040\n]?'
|
||||
r'|(%(not_id_literal)s)\040'
|
||||
r'|(%(not_id_literal_close)s)\n'
|
||||
) % locals()).sub
|
||||
def space_subber2(match):
|
||||
""" Substitution callback """
|
||||
groups = match.groups()
|
||||
return groups[0] or groups[1] or groups[2] or groups[3]
|
||||
|
||||
def jsmin(script):
|
||||
r"""
|
||||
Minify javascript based on `jsmin.c by Douglas Crockford`_\.
|
||||
|
||||
Instead of parsing the stream char by char, it uses a regular
|
||||
expression approach. The script is minified with three passes:
|
||||
|
||||
normalization
|
||||
Control character are mapped to spaces, spaces and newlines
|
||||
are squeezed and comments are stripped.
|
||||
space removal 1
|
||||
Spaces before certain tokens are removed
|
||||
space removal 2
|
||||
Spaces after certain tokens are remove
|
||||
|
||||
.. _jsmin.c by Douglas Crockford:
|
||||
http://www.crockford.com/javascript/jsmin.c
|
||||
|
||||
:Parameters:
|
||||
`script` : ``str``
|
||||
Script to minify
|
||||
|
||||
:Return: Minified script
|
||||
:Rtype: ``str``
|
||||
"""
|
||||
return space_sub2(space_subber2,
|
||||
space_sub1(space_subber1,
|
||||
space_norm_sub(space_norm_subber, '\n%s\n' % script)
|
||||
)
|
||||
).strip()
|
||||
return jsmin
|
||||
|
||||
jsmin = _make_jsmin()
|
||||
|
||||
#####################
|
||||
# EXAMPLE USAGE #
|
||||
#####################
|
||||
#
|
||||
# import jsmin
|
||||
# jsmin.jsmin(script)
|
||||
#
|
||||
|
||||
def jsmin_for_posers(script):
|
||||
r"""
|
||||
Minify javascript based on `jsmin.c by Douglas Crockford`_\.
|
||||
|
||||
Instead of parsing the stream char by char, it uses a regular
|
||||
expression approach which minifies the whole script with one big
|
||||
substitution regex.
|
||||
|
||||
.. _jsmin.c by Douglas Crockford:
|
||||
http://www.crockford.com/javascript/jsmin.c
|
||||
|
||||
:Warning: This function is the digest of a _make_jsmin() call. It just
|
||||
utilizes the resulting regex. It's just for fun here and may
|
||||
vanish any time. Use the `jsmin` function instead.
|
||||
|
||||
:Parameters:
|
||||
`script` : ``str``
|
||||
Script to minify
|
||||
|
||||
:Return: Minified script
|
||||
:Rtype: ``str``
|
||||
"""
|
||||
def subber(match):
|
||||
""" Substitution callback """
|
||||
groups = match.groups()
|
||||
return (
|
||||
groups[0] or
|
||||
groups[1] or
|
||||
groups[2] or
|
||||
groups[3] or
|
||||
(groups[4] and '\n') or
|
||||
(groups[5] and ' ') or
|
||||
''
|
||||
)
|
||||
|
||||
return _re.sub(
|
||||
r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
|
||||
r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
|
||||
r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
|
||||
r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
|
||||
r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
|
||||
r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
|
||||
r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
|
||||
r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
|
||||
r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
|
||||
r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
|
||||
r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
|
||||
r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
|
||||
r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-#%-\04'
|
||||
r'7)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011'
|
||||
r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-'
|
||||
r'#%-,./:-@\[-^`{-~-])|(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*'
|
||||
r'+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011'
|
||||
r'\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+',
|
||||
subber, '\n%s\n' % script
|
||||
).strip()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys as _sys
|
||||
_sys.stdout.write(jsmin(_sys.stdin.read()))
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
High-level CSS and JS minification class for web2py.
|
||||
Called by response.include_files()
|
||||
Created by: Ross Peoples <ross.peoples@gmail.com>
|
||||
Modified by: Massimo Di Pierro <massimo.dipierro@gmail.com>
|
||||
"""
|
||||
|
||||
import cssmin
|
||||
import jsmin
|
||||
import os
|
||||
|
||||
def read_binary_file(filename):
|
||||
f = open(filename,'rb')
|
||||
data = f.read()
|
||||
f.close()
|
||||
return data
|
||||
|
||||
def write_binary_file(filename,data):
|
||||
f =open(filename,'wb')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
def fix_links(css,static_path):
|
||||
return css.replace('../',static_path+'/')
|
||||
|
||||
def minify(files, path_info, folder, optimize_css, optimize_js,
|
||||
ignore_concat = [],
|
||||
ignore_minity = ['/jquery.js', '/anytime.js']):
|
||||
|
||||
"""
|
||||
Input:
|
||||
files: is a list of URLs to JS and CSS files (not repeated)
|
||||
path_info: is the URL of a temp static folder
|
||||
folder: is the application folder
|
||||
optimize_css: is a string of the form 'concat|minify|inline'
|
||||
optimize_js: is a string of the form 'concat|minify|inline'
|
||||
(minify requires concat, inline requires concat also)
|
||||
|
||||
Returns a new list of:
|
||||
- filename (absolute or relative, css or js, actual or temporary) or
|
||||
- ('css:inline','...css..')
|
||||
- ('js:inline','...js..')
|
||||
"""
|
||||
concat_css = 'concat' in optimize_css
|
||||
minify_css = 'minify' in optimize_css
|
||||
inline_css = 'inline' in optimize_css
|
||||
concat_js = 'concat' in optimize_js
|
||||
minify_js = 'minify' in optimize_js
|
||||
inline_js = 'inline' in optimize_js
|
||||
static_path,temp = path_info.rsplit('/',1)
|
||||
new_files = []
|
||||
css = []
|
||||
js = []
|
||||
for k,filename in enumerate(files):
|
||||
if not filename.startswith('/') or \
|
||||
any(filename.endswith(x) for x in ignore_concat):
|
||||
new_files.append(filename)
|
||||
continue
|
||||
|
||||
abs_filename = os.path.join(folder,'static',
|
||||
filename[len(static_path)+1:])
|
||||
|
||||
if filename.lower().endswith('.css'):
|
||||
if concat_css:
|
||||
contents = read_binary_file(abs_filename)
|
||||
if minify_css:
|
||||
css.append(cssmin.cssmin(contents))
|
||||
else:
|
||||
css.append(contents)
|
||||
else:
|
||||
css.append(filename)
|
||||
elif filename.lower().endswith('.js'):
|
||||
if concat_js:
|
||||
contents = read_binary_file(abs_filename)
|
||||
if minify_js and not filename.endswith('.min.js') and \
|
||||
not any(filename.endswith(x) for x in ignore_minify):
|
||||
js.append(jsmin.jsmin(contents))
|
||||
else:
|
||||
js.append(contents)
|
||||
else:
|
||||
js.append(filename)
|
||||
if css and concat_css:
|
||||
css = '\n\n'.join(contents for contents in css)
|
||||
if inline_css:
|
||||
css = ('css:inline',fix_links(css,static_path))
|
||||
else:
|
||||
temppath = os.path.join(folder,'static',temp)
|
||||
if not os.path.exists(temppath): os.mkdir(temppath)
|
||||
tempfile = os.path.join(temppath,'compressed.css')
|
||||
write_binary_file(tempfile,css)
|
||||
css = path_info+'/compressed.css'
|
||||
new_files.append(css)
|
||||
else:
|
||||
new_files += css
|
||||
if js and concat_js:
|
||||
js = '\n'.join(contents for contents in js)
|
||||
if inline_js:
|
||||
js = ('js:inline',js)
|
||||
else:
|
||||
temppath = os.path.join(folder,'static',temp)
|
||||
if not os.path.exists(temppath): os.mkdir(temppath)
|
||||
tempfile = os.path.join(folder,'static',temp,'compressed.js')
|
||||
write_binary_file(tempfile,js)
|
||||
js = path_info+'/compressed.js'
|
||||
new_files.append(js)
|
||||
else:
|
||||
new_files += js
|
||||
return new_files
|
||||
|
||||
Reference in New Issue
Block a user