source: OpenRLabs-Git/deploy/rlabs-docker/web2py-rlabs/gluon/contrib/minify/cssmin.py

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100755
File size: 6.8 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""`cssmin` - A Python port of the YUI CSS compressor."""
5
6"""
7Home page: https://github.com/zacharyvoase/cssmin
8License: BSD: https://github.com/zacharyvoase/cssmin/blob/master/LICENSE
9Original author: Zachary Voase
10Modified for inclusion into web2py by: Ross Peoples <ross.peoples@gmail.com>
11"""
12
13try:
14    from StringIO import StringIO
15except ImportError:
16    from io import StringIO
17
18import re
19
20
21__version__ = '0.1.4'
22
23
24def remove_comments(css):
25    """Remove all CSS comment blocks."""
26
27    iemac = False
28    preserve = False
29    comment_start = css.find("/*")
30    while comment_start >= 0:
31        # Preserve comments that look like `/*!...*/`.
32        # Slicing is used to make sure we don"t get an IndexError.
33        preserve = css[comment_start + 2:comment_start + 3] == "!"
34
35        comment_end = css.find("*/", comment_start + 2)
36        if comment_end < 0:
37            if not preserve:
38                css = css[:comment_start]
39                break
40        elif comment_end >= (comment_start + 2):
41            if css[comment_end - 1] == "\\":
42                # This is an IE Mac-specific comment; leave this one and the
43                # following one alone.
44                comment_start = comment_end + 2
45                iemac = True
46            elif iemac:
47                comment_start = comment_end + 2
48                iemac = False
49            elif not preserve:
50                css = css[:comment_start] + css[comment_end + 2:]
51            else:
52                comment_start = comment_end + 2
53        comment_start = css.find("/*", comment_start)
54
55    return css
56
57
58def remove_unnecessary_whitespace(css):
59    """Remove unnecessary whitespace characters."""
60
61    def pseudoclasscolon(css):
62
63        """
64        Prevents 'p :link' from becoming 'p:link'.
65
66        Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is
67        translated back again later.
68        """
69
70        regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)")
71        match = regex.search(css)
72        while match:
73            css = ''.join([
74                css[:match.start()],
75                match.group().replace(":", "___PSEUDOCLASSCOLON___"),
76                css[match.end():]])
77            match = regex.search(css)
78        return css
79
80    css = pseudoclasscolon(css)
81    # Remove spaces from before things.
82    css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css)
83
84    # If there is a `@charset`, then only allow one, and move to the beginning.
85    css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css)
86    css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css)
87
88    # Put the space back in for a few cases, such as `@media screen` and
89    # `(-webkit-min-device-pixel-ratio:0)`.
90    css = re.sub(r"\band\(", "and (", css)
91
92    # Put the colons back.
93    css = css.replace('___PSEUDOCLASSCOLON___', ':')
94
95    # Remove spaces from after things.
96    css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css)
97
98    return css
99
100
101def remove_unnecessary_semicolons(css):
102    """Remove unnecessary semicolons."""
103
104    return re.sub(r";+\}", "}", css)
105
106
107def remove_empty_rules(css):
108    """Remove empty rules."""
109
110    return re.sub(r"[^\}\{]+\{\}", "", css)
111
112
113def normalize_rgb_colors_to_hex(css):
114    """Convert `rgb(51,102,153)` to `#336699`."""
115
116    regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)")
117    match = regex.search(css)
118    while match:
119        colors = map(lambda s: s.strip(), match.group(1).split(","))
120        hexcolor = '#%.2x%.2x%.2x' % tuple(map(int, colors))
121        css = css.replace(match.group(), hexcolor)
122        match = regex.search(css)
123    return css
124
125
126def condense_zero_units(css):
127    """Replace `0(px, em, %, etc)` with `0`."""
128
129    return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css)
130
131
132def condense_multidimensional_zeros(css):
133    """Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`."""
134
135    css = css.replace(":0 0 0 0;", ":0;")
136    css = css.replace(":0 0 0;", ":0;")
137    css = css.replace(":0 0;", ":0;")
138
139    # Revert `background-position:0;` to the valid `background-position:0 0;`.
140    css = css.replace("background-position:0;", "background-position:0 0;")
141
142    return css
143
144
145def condense_floating_points(css):
146    """Replace `0.6` with `.6` where possible."""
147
148    return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css)
149
150
151def condense_hex_colors(css):
152    """Shorten colors from #AABBCC to #ABC where possible."""
153
154    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])")
155    match = regex.search(css)
156    while match:
157        first = match.group(3) + match.group(5) + match.group(7)
158        second = match.group(4) + match.group(6) + match.group(8)
159        if first.lower() == second.lower():
160            css = css.replace(
161                match.group(), match.group(1) + match.group(2) + '#' + first)
162            match = regex.search(css, match.end() - 3)
163        else:
164            match = regex.search(css, match.end())
165    return css
166
167
168def condense_whitespace(css):
169    """Condense multiple adjacent whitespace characters into one."""
170
171    return re.sub(r"\s+", " ", css)
172
173
174def condense_semicolons(css):
175    """Condense multiple adjacent semicolon characters into one."""
176
177    return re.sub(r";;+", ";", css)
178
179
180def wrap_css_lines(css, line_length):
181    """Wrap the lines of the given CSS to an approximate length."""
182
183    lines = []
184    line_start = 0
185    for i, char in enumerate(css):
186        # It's safe to break after `}` characters.
187        if char == '}' and (i - line_start >= line_length):
188            lines.append(css[line_start:i + 1])
189            line_start = i + 1
190
191    if line_start < len(css):
192        lines.append(css[line_start:])
193    return '\n'.join(lines)
194
195
196def cssmin(css, wrap=None):
197    css = remove_comments(css)
198    css = condense_whitespace(css)
199    # A pseudo class for the Box Model Hack
200    # (see http://tantek.com/CSS/Examples/boxmodelhack.html)
201    css = css.replace('"\\"}\\""', "___PSEUDOCLASSBMH___")
202    css = remove_unnecessary_whitespace(css)
203    css = remove_unnecessary_semicolons(css)
204    css = condense_zero_units(css)
205    css = condense_multidimensional_zeros(css)
206    css = condense_floating_points(css)
207    css = normalize_rgb_colors_to_hex(css)
208    css = condense_hex_colors(css)
209    if wrap is not None:
210        css = wrap_css_lines(css, wrap)
211    css = css.replace("___PSEUDOCLASSBMH___", '"\\"}\\""')
212    css = condense_semicolons(css)
213    return css.strip()
214
215
216def main():
217    import optparse
218    import sys
219
220    p = optparse.OptionParser(
221        prog="cssmin", version=__version__,
222        usage="%prog [--wrap N]",
223        description="""Reads raw CSS from stdin, and writes compressed CSS to stdout.""")
224
225    p.add_option(
226        '-w', '--wrap', type='int', default=None, metavar='N',
227        help="Wrap output to approximately N chars per line.")
228
229    options, args = p.parse_args()
230    sys.stdout.write(cssmin(sys.stdin.read(), wrap=options.wrap))
231
232
233if __name__ == '__main__':
234    main()
Note: See TracBrowser for help on using the repository browser.