yuicssmin 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ coverage
6
+ rdoc
7
+ doc
8
+ .yardoc
9
+ .rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in yuicssmin.gemspec
4
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Matthias Siegel (matthias.siegel@gmail.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # YUICSSMIN
2
+ Ruby wrapper for the Javascript port of YUI's CSS compressor.
3
+
4
+ The YUICSSMIN gem provides CSS compression using YUI compressor from Yahoo. Unlike other gems it doesn't use the Java applet YUI compressor but instead uses the Javascript port via ExecJS.
5
+
6
+ ## Installation
7
+ Install YUICSSMIN from RubyGems:
8
+
9
+ gem install yuicssmin
10
+
11
+ Or include it in your project's Gemfile:
12
+
13
+ gem 'yuicssmin'
14
+
15
+ ## Usage
16
+
17
+ require 'yuicssmin'
18
+
19
+ Yuicssmin.compress(File.read("path/to/styles.css")) # => minified CSS
20
+
21
+ # Alternatively use instance method...
22
+
23
+ compressor = Yuicssmin.new
24
+ compressor.compress(File.read("path/to/styles.css")) # => minified CSS
25
+
26
+ Files or strings are acceptable as input.
27
+
28
+ You can pass in a second argument to control the maximum output line length (default 5000 characters):
29
+
30
+ Yuicssmin.compress(File.read("path/to/styles.css"), 200)
31
+
32
+ Note: in most cases line length will only be approximated.
33
+
34
+ ## Rails asset pipeline
35
+ Rails 3.1 integrated [Sprockets](https://github.com/sstephenson/sprockets) to provide asset packaging and minimising out of the box. For CSS compression it relies on the [yui-compressor gem](https://github.com/sstephenson/ruby-yui-compressor) which requires Java. To use YUICSSMIN instead, edit your config/application.rb file:
36
+
37
+ config.assets.css_compressor = Yuicssmin.new
38
+
39
+ ## Credits
40
+ YUICSSMIN gem was inspired by Ville Lautanala's [Uglifier](https://github.com/lautis/uglifier) gem, released under MIT license.
41
+
42
+ ## Copyright
43
+
44
+ ### YUICSSMIN gem and documentation
45
+ Copyright (c) 2012 Matthias Siegel (matthias.siegel@gmail.com)
46
+ See [LICENSE](https://github.com/matthiassiegel/yuicssmin/blob/master/LICENSE.md) for details.
47
+
48
+ ### YUI compressor
49
+ See [file](https://github.com/matthiassiegel/yuicssmin/blob/master/lib/yuicssmin/cssmin.js).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/yuicssmin.rb ADDED
@@ -0,0 +1,52 @@
1
+ # coding: utf-8
2
+
3
+ require "execjs"
4
+ require "multi_json"
5
+ require "yuicssmin/version"
6
+
7
+ class Yuicssmin
8
+
9
+ #
10
+ # Source: https://github.com/yui/yuicompressor/blob/master/ports/js/cssmin.js
11
+ #
12
+ Yui = File.expand_path('../yuicssmin/cssmin.js', __FILE__)
13
+
14
+
15
+ #
16
+ # Read Javascript port of YUI CSS compressor
17
+ #
18
+ def initialize
19
+ @context = ExecJS.compile(File.open(Yui, 'r:UTF-8').read)
20
+ end
21
+
22
+
23
+ #
24
+ # Compress CSS with YUI
25
+ #
26
+ # @param [String, #read] String or IO-like object that supports #read
27
+ # @param [Integer] Maximum line length
28
+ # @return [String] Compressed CSS
29
+ def self.compress(source, length = 5000)
30
+ self.new.compress(source, length)
31
+ end
32
+
33
+
34
+ #
35
+ # Compress CSS with YUI
36
+ #
37
+ # @param [String, #read] String or IO-like object that supports #read
38
+ # @param [Integer] Maximum line length
39
+ # @return [String] Compressed CSS
40
+ def compress(source = '', length = 5000)
41
+ source = source.respond_to?(:read) ? source.read : source.to_s
42
+
43
+ js = []
44
+ js << "var result = '';"
45
+ js << "var length = #{length};"
46
+ js << "var source = #{MultiJson.dump(source)};"
47
+ js << "result = YAHOO.compressor.cssmin(source, length);"
48
+ js << "return result;"
49
+
50
+ @context.exec js.join("\n")
51
+ end
52
+ end
@@ -0,0 +1,353 @@
1
+ /**
2
+ * cssmin.js
3
+ * Author: Stoyan Stefanov - http://phpied.com/
4
+ * This is a JavaScript port of the CSS minification tool
5
+ * distributed with YUICompressor, itself a port
6
+ * of the cssmin utility by Isaac Schlueter - http://foohack.com/
7
+ * Permission is hereby granted to use the JavaScript version under the same
8
+ * conditions as the YUICompressor (original YUICompressor note below).
9
+ */
10
+
11
+ /*
12
+ * YUI Compressor
13
+ * http://developer.yahoo.com/yui/compressor/
14
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
15
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
16
+ * The copyrights embodied in the content of this file are licensed
17
+ * by Yahoo! Inc. under the BSD (revised) open source license.
18
+ */
19
+ var YAHOO = YAHOO || {};
20
+ YAHOO.compressor = YAHOO.compressor || {};
21
+
22
+ /**
23
+ * Utility method to replace all data urls with tokens before we start
24
+ * compressing, to avoid performance issues running some of the subsequent
25
+ * regexes against large strings chunks.
26
+ *
27
+ * @private
28
+ * @method _extractDataUrls
29
+ * @param {String} css The input css
30
+ * @param {Array} The global array of tokens to preserve
31
+ * @returns String The processed css
32
+ */
33
+ YAHOO.compressor._extractDataUrls = function (css, preservedTokens) {
34
+
35
+ // Leave data urls alone to increase parse performance.
36
+ var maxIndex = css.length - 1,
37
+ appendIndex = 0,
38
+ startIndex,
39
+ endIndex,
40
+ terminator,
41
+ foundTerminator,
42
+ sb = [],
43
+ m,
44
+ preserver,
45
+ token,
46
+ pattern = /url\(\s*(["']?)data\:/g;
47
+
48
+ // Since we need to account for non-base64 data urls, we need to handle
49
+ // ' and ) being part of the data string. Hence switching to indexOf,
50
+ // to determine whether or not we have matching string terminators and
51
+ // handling sb appends directly, instead of using matcher.append* methods.
52
+
53
+ while ((m = pattern.exec(css)) !== null) {
54
+
55
+ startIndex = m.index + 4; // "url(".length()
56
+ terminator = m[1]; // ', " or empty (not quoted)
57
+
58
+ if (terminator.length === 0) {
59
+ terminator = ")";
60
+ }
61
+
62
+ foundTerminator = false;
63
+
64
+ endIndex = pattern.lastIndex - 1;
65
+
66
+ while(foundTerminator === false && endIndex+1 <= maxIndex) {
67
+ endIndex = css.indexOf(terminator, endIndex + 1);
68
+
69
+ // endIndex == 0 doesn't really apply here
70
+ if ((endIndex > 0) && (css.charAt(endIndex - 1) !== '\\')) {
71
+ foundTerminator = true;
72
+ if (")" != terminator) {
73
+ endIndex = css.indexOf(")", endIndex);
74
+ }
75
+ }
76
+ }
77
+
78
+ // Enough searching, start moving stuff over to the buffer
79
+ sb.push(css.substring(appendIndex, m.index));
80
+
81
+ if (foundTerminator) {
82
+ token = css.substring(startIndex, endIndex);
83
+ token = token.replace(/\s+/g, "");
84
+ preservedTokens.push(token);
85
+
86
+ preserver = "url(___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___)";
87
+ sb.push(preserver);
88
+
89
+ appendIndex = endIndex + 1;
90
+ } else {
91
+ // No end terminator found, re-add the whole match. Should we throw/warn here?
92
+ sb.push(css.substring(m.index, pattern.lastIndex));
93
+ appendIndex = pattern.lastIndex;
94
+ }
95
+ }
96
+
97
+ sb.push(css.substring(appendIndex));
98
+
99
+ return sb.join("");
100
+ };
101
+
102
+ /**
103
+ * Utility method to compress hex color values of the form #AABBCC to #ABC.
104
+ *
105
+ * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
106
+ * e.g. #AddressForm { ... }
107
+ *
108
+ * DOES NOT compress IE filters, which have hex color values (which would break things).
109
+ * e.g. filter: chroma(color="#FFFFFF");
110
+ *
111
+ * DOES NOT compress invalid hex values.
112
+ * e.g. background-color: #aabbccdd
113
+ *
114
+ * @private
115
+ * @method _compressHexColors
116
+ * @param {String} css The input css
117
+ * @returns String The processed css
118
+ */
119
+ YAHOO.compressor._compressHexColors = function(css) {
120
+
121
+ // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
122
+ var pattern = /(\=\s*?["']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/gi,
123
+ m,
124
+ index = 0,
125
+ isFilter,
126
+ sb = [];
127
+
128
+ while ((m = pattern.exec(css)) !== null) {
129
+
130
+ sb.push(css.substring(index, m.index));
131
+
132
+ isFilter = m[1];
133
+
134
+ if (isFilter) {
135
+ // Restore, maintain case, otherwise filter will break
136
+ sb.push(m[1] + "#" + (m[2] + m[3] + m[4] + m[5] + m[6] + m[7]));
137
+ } else {
138
+ if (m[2].toLowerCase() == m[3].toLowerCase() &&
139
+ m[4].toLowerCase() == m[5].toLowerCase() &&
140
+ m[6].toLowerCase() == m[7].toLowerCase()) {
141
+
142
+ // Compress.
143
+ sb.push("#" + (m[3] + m[5] + m[7]).toLowerCase());
144
+ } else {
145
+ // Non compressible color, restore but lower case.
146
+ sb.push("#" + (m[2] + m[3] + m[4] + m[5] + m[6] + m[7]).toLowerCase());
147
+ }
148
+ }
149
+
150
+ index = pattern.lastIndex = pattern.lastIndex - m[8].length;
151
+ }
152
+
153
+ sb.push(css.substring(index));
154
+
155
+ return sb.join("");
156
+ };
157
+
158
+ YAHOO.compressor.cssmin = function (css, linebreakpos) {
159
+
160
+ var startIndex = 0,
161
+ endIndex = 0,
162
+ i = 0, max = 0,
163
+ preservedTokens = [],
164
+ comments = [],
165
+ token = '',
166
+ totallen = css.length,
167
+ placeholder = '';
168
+
169
+ css = this._extractDataUrls(css, preservedTokens);
170
+
171
+ // collect all comment blocks...
172
+ while ((startIndex = css.indexOf("/*", startIndex)) >= 0) {
173
+ endIndex = css.indexOf("*/", startIndex + 2);
174
+ if (endIndex < 0) {
175
+ endIndex = totallen;
176
+ }
177
+ token = css.slice(startIndex + 2, endIndex);
178
+ comments.push(token);
179
+ css = css.slice(0, startIndex + 2) + "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.length - 1) + "___" + css.slice(endIndex);
180
+ startIndex += 2;
181
+ }
182
+
183
+ // preserve strings so their content doesn't get accidentally minified
184
+ css = css.replace(/("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/g, function (match) {
185
+ var i, max, quote = match.substring(0, 1);
186
+
187
+ match = match.slice(1, -1);
188
+
189
+ // maybe the string contains a comment-like substring?
190
+ // one, maybe more? put'em back then
191
+ if (match.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
192
+ for (i = 0, max = comments.length; i < max; i = i + 1) {
193
+ match = match.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments[i]);
194
+ }
195
+ }
196
+
197
+ // minify alpha opacity in filter strings
198
+ match = match.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity=");
199
+
200
+ preservedTokens.push(match);
201
+ return quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___" + quote;
202
+ });
203
+
204
+ // strings are safe, now wrestle the comments
205
+ for (i = 0, max = comments.length; i < max; i = i + 1) {
206
+
207
+ token = comments[i];
208
+ placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
209
+
210
+ // ! in the first position of the comment means preserve
211
+ // so push to the preserved tokens keeping the !
212
+ if (token.charAt(0) === "!") {
213
+ preservedTokens.push(token);
214
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___");
215
+ continue;
216
+ }
217
+
218
+ // \ in the last position looks like hack for Mac/IE5
219
+ // shorten that to /*\*/ and the next one to /**/
220
+ if (token.charAt(token.length - 1) === "\\") {
221
+ preservedTokens.push("\\");
222
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___");
223
+ i = i + 1; // attn: advancing the loop
224
+ preservedTokens.push("");
225
+ css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___");
226
+ continue;
227
+ }
228
+
229
+ // keep empty comments after child selectors (IE7 hack)
230
+ // e.g. html >/**/ body
231
+ if (token.length === 0) {
232
+ startIndex = css.indexOf(placeholder);
233
+ if (startIndex > 2) {
234
+ if (css.charAt(startIndex - 3) === '>') {
235
+ preservedTokens.push("");
236
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.length - 1) + "___");
237
+ }
238
+ }
239
+ }
240
+
241
+ // in all other cases kill the comment
242
+ css = css.replace("/*" + placeholder + "*/", "");
243
+ }
244
+
245
+
246
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
247
+ css = css.replace(/\s+/g, " ");
248
+
249
+ // Remove the spaces before the things that should not have spaces before them.
250
+ // But, be careful not to turn "p :link {...}" into "p:link{...}"
251
+ // Swap out any pseudo-class colons with the token, and then swap back.
252
+ css = css.replace(/(^|\})(([^\{:])+:)+([^\{]*\{)/g, function (m) {
253
+ return m.replace(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
254
+ });
255
+ css = css.replace(/\s+([!{};:>+\(\)\],])/g, '$1');
256
+ css = css.replace(/___YUICSSMIN_PSEUDOCLASSCOLON___/g, ":");
257
+
258
+ // retain space for special IE6 cases
259
+ css = css.replace(/:first-(line|letter)(\{|,)/g, ":first-$1 $2");
260
+
261
+ // no space after the end of a preserved comment
262
+ css = css.replace(/\*\/ /g, '*/');
263
+
264
+
265
+ // If there is a @charset, then only allow one, and push to the top of the file.
266
+ css = css.replace(/^(.*)(@charset "[^"]*";)/gi, '$2$1');
267
+ css = css.replace(/^(\s*@charset [^;]+;\s*)+/gi, '$1');
268
+
269
+ // Put the space back in some cases, to support stuff like
270
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
271
+ css = css.replace(/\band\(/gi, "and (");
272
+
273
+
274
+ // Remove the spaces after the things that should not have spaces after them.
275
+ css = css.replace(/([!{}:;>+\(\[,])\s+/g, '$1');
276
+
277
+ // remove unnecessary semicolons
278
+ css = css.replace(/;+\}/g, "}");
279
+
280
+ // Replace 0(px,em,%) with 0.
281
+ css = css.replace(/([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)/gi, "$1$2");
282
+
283
+ // Replace 0 0 0 0; with 0.
284
+ css = css.replace(/:0 0 0 0(;|\})/g, ":0$1");
285
+ css = css.replace(/:0 0 0(;|\})/g, ":0$1");
286
+ css = css.replace(/:0 0(;|\})/g, ":0$1");
287
+
288
+ // Replace background-position:0; with background-position:0 0;
289
+ // same for transform-origin
290
+ css = css.replace(/(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|\})/gi, function(all, prop, tail) {
291
+ return prop.toLowerCase() + ":0 0" + tail;
292
+ });
293
+
294
+ // Replace 0.6 to .6, but only when preceded by : or a white-space
295
+ css = css.replace(/(:|\s)0+\.(\d+)/g, "$1.$2");
296
+
297
+ // Shorten colors from rgb(51,102,153) to #336699
298
+ // This makes it more likely that it'll get further compressed in the next step.
299
+ css = css.replace(/rgb\s*\(\s*([0-9,\s]+)\s*\)/gi, function () {
300
+ var i, rgbcolors = arguments[1].split(',');
301
+ for (i = 0; i < rgbcolors.length; i = i + 1) {
302
+ rgbcolors[i] = parseInt(rgbcolors[i], 10).toString(16);
303
+ if (rgbcolors[i].length === 1) {
304
+ rgbcolors[i] = '0' + rgbcolors[i];
305
+ }
306
+ }
307
+ return '#' + rgbcolors.join('');
308
+ });
309
+
310
+ // Shorten colors from #AABBCC to #ABC.
311
+ css = this._compressHexColors(css);
312
+
313
+ // border: none -> border:0
314
+ css = css.replace(/(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|\})/gi, function(all, prop, tail) {
315
+ return prop.toLowerCase() + ":0" + tail;
316
+ });
317
+
318
+ // shorter opacity IE filter
319
+ css = css.replace(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/gi, "alpha(opacity=");
320
+
321
+ // Remove empty rules.
322
+ css = css.replace(/[^\};\{\/]+\{\}/g, "");
323
+
324
+ if (linebreakpos >= 0) {
325
+ // Some source control tools don't like it when files containing lines longer
326
+ // than, say 8000 characters, are checked in. The linebreak option is used in
327
+ // that case to split long lines after a specific column.
328
+ startIndex = 0;
329
+ i = 0;
330
+ while (i < css.length) {
331
+ i = i + 1;
332
+ if (css[i - 1] === '}' && i - startIndex > linebreakpos) {
333
+ css = css.slice(0, i) + '\n' + css.slice(i);
334
+ startIndex = i;
335
+ }
336
+ }
337
+ }
338
+
339
+ // Replace multiple semi-colons in a row by a single one
340
+ // See SF bug #1980989
341
+ css = css.replace(/;;+/g, ";");
342
+
343
+ // restore preserved comments and strings
344
+ for (i = 0, max = preservedTokens.length; i < max; i = i + 1) {
345
+ css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens[i]);
346
+ }
347
+
348
+ // Trim the final string (for any leading or trailing white spaces)
349
+ css = css.replace(/^\s+|\s+$/g, "");
350
+
351
+ return css;
352
+
353
+ };
@@ -0,0 +1,5 @@
1
+ # coding: utf-8
2
+
3
+ class Yuicssmin
4
+ VERSION = "1.0.0"
5
+ end
data/spec/sample.css ADDED
@@ -0,0 +1,100 @@
1
+ /* Source http://developer.yahoo.com/yui/compressor/css.html */
2
+
3
+ /*****
4
+ Multi-line comment
5
+ before a new class name
6
+ *****/
7
+ .classname {
8
+ /* comment in declaration block */
9
+ font-weight: normal;
10
+ }
11
+
12
+ /*!
13
+ (c) Very Important Comment
14
+ */
15
+ .classname {
16
+ /* comment in declaration block */
17
+ font-weight: normal;
18
+ }
19
+
20
+ .classname {
21
+ border-top: 1px;
22
+ border-bottom: 2px;
23
+ }
24
+
25
+ .classname {
26
+ border-top: 1px; ;
27
+ border-bottom: 2px;;;
28
+ }
29
+
30
+ .empty { ;}
31
+ .nonempty {border: 0;}
32
+
33
+ a {
34
+ margin: 0px 0pt 0em 0%;
35
+ background-position: 0 0ex;
36
+ padding: 0in 0cm 0mm 0pc
37
+ }
38
+
39
+ .classname {
40
+ margin: 0.6px 0.333pt 1.2em 8.8cm;
41
+ }
42
+
43
+ .color-me {
44
+ color: rgb(123, 123, 123);
45
+ border-color: #ffeedd;
46
+ background: none repeat scroll 0 0 rgb(255, 0,0);
47
+ }
48
+
49
+ .cantouch {
50
+ color: rgba(1, 2, 3, 4);
51
+ filter: chroma(color="#FFFFFF");
52
+ }
53
+
54
+ @charset "utf-8";
55
+ #foo {
56
+ border-width: 1px;
57
+ }
58
+
59
+ /* second css, merged */
60
+ @charset "another one";
61
+ #bar {
62
+ border-width: 10px;
63
+ }
64
+
65
+ .classname {
66
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; /* IE 8 */
67
+ filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); /* IE < 8 */
68
+ }
69
+
70
+ .classname {
71
+ border: none;
72
+ background: none;
73
+ outline: none;
74
+ }
75
+
76
+ #element {
77
+ width: 1px;
78
+ *width: 2px;
79
+ _width: 3px;
80
+ }
81
+
82
+ html >/**/ body p {
83
+ color: blue;
84
+ }
85
+
86
+ /* Ignore the next rule in IE mac \*/
87
+ .selector {
88
+ color: khaki;
89
+ }
90
+ /* Stop ignoring in IE mac */
91
+
92
+ #elem {
93
+ width: 100px; /* IE */
94
+ voice-family: "\"}\"";
95
+ voice-family:inherit;
96
+ width: 200px; /* others */
97
+ }
98
+ html>body #elem {
99
+ width: 200px; /* others */
100
+ }
@@ -0,0 +1,4 @@
1
+ # coding: utf-8
2
+
3
+ require 'yuicssmin'
4
+ require 'rspec'
@@ -0,0 +1,261 @@
1
+ # coding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+
6
+ describe "Yuicssmin" do
7
+
8
+ context "application" do
9
+
10
+ it "minifies CSS" do
11
+ source = File.open(File.expand_path("../sample.css", __FILE__), "r:UTF-8").read
12
+ minified = Yuicssmin.compress(source)
13
+ minified.length.should < source.length
14
+ lambda {
15
+ Yuicssmin.compress(minified)
16
+ }.should_not raise_error
17
+ end
18
+
19
+ it "honors the specified maximum line length" do
20
+ source = <<-EOS
21
+ .classname1 {
22
+ border: none;
23
+ background: none;
24
+ outline: none;
25
+ }
26
+ .classname2 {
27
+ border: none;
28
+ background: none;
29
+ outline: none;
30
+ }
31
+ EOS
32
+ minified = Yuicssmin.compress(source, 30)
33
+ minified.split("\n").length.should eq(2)
34
+ minified.should eq(".classname1{border:0;background:0;outline:0}\n.classname2{border:0;background:0;outline:0}")
35
+ end
36
+
37
+ it "handles strings as input format" do
38
+ lambda {
39
+ Yuicssmin.compress(File.open(File.expand_path("../sample.css", __FILE__), "r:UTF-8").read).should_not be_empty
40
+ }.should_not raise_error
41
+ end
42
+
43
+ it "handles files as input format" do
44
+ lambda {
45
+ Yuicssmin.new.compress(File.open(File.expand_path("../sample.css", __FILE__), "r:UTF-8")).should_not be_empty
46
+ }.should_not raise_error
47
+ end
48
+
49
+ it "works as both class and class instance" do
50
+ lambda {
51
+ Yuicssmin.compress(File.open(File.expand_path("../sample.css", __FILE__), "r:UTF-8").read).should_not be_empty
52
+ Yuicssmin.new.compress(File.open(File.expand_path("../sample.css", __FILE__), "r:UTF-8")).should_not be_empty
53
+ }.should_not raise_error
54
+ end
55
+
56
+ end
57
+
58
+
59
+ context "compression" do
60
+
61
+ it "removes comments and white space" do
62
+ source = <<-EOS
63
+ /*****
64
+ Multi-line comment
65
+ before a new class name
66
+ *****/
67
+ .classname {
68
+ /* comment in declaration block */
69
+ font-weight: normal;
70
+ }
71
+ EOS
72
+ minified = Yuicssmin.compress(source)
73
+ minified.should eq('.classname{font-weight:normal}')
74
+ end
75
+
76
+ it "preserves special comments" do
77
+ source = <<-EOS
78
+ /*!
79
+ (c) Very Important Comment
80
+ */
81
+ .classname {
82
+ /* comment in declaration block */
83
+ font-weight: normal;
84
+ }
85
+ EOS
86
+ minified = Yuicssmin.compress(source)
87
+ result = <<-EOS
88
+ /*!
89
+ (c) Very Important Comment
90
+ */.classname{font-weight:normal}
91
+ EOS
92
+ (minified + "\n").should eq(result)
93
+ end
94
+
95
+ it "removes last semi-colon in a block" do
96
+ source = <<-EOS
97
+ .classname {
98
+ border-top: 1px;
99
+ border-bottom: 2px;
100
+ }
101
+ EOS
102
+ minified = Yuicssmin.compress(source)
103
+ minified.should eq('.classname{border-top:1px;border-bottom:2px}')
104
+ end
105
+
106
+ it "removes extra semi-colons" do
107
+ source = <<-EOS
108
+ .classname {
109
+ border-top: 1px; ;
110
+ border-bottom: 2px;;;
111
+ }
112
+ EOS
113
+ minified = Yuicssmin.compress(source)
114
+ minified.should eq('.classname{border-top:1px;border-bottom:2px}')
115
+ end
116
+
117
+ it "removes empty declarations" do
118
+ source = <<-EOS
119
+ .empty { ;}
120
+ .nonempty {border: 0;}
121
+ EOS
122
+ minified = Yuicssmin.compress(source)
123
+ minified.should eq('.nonempty{border:0}')
124
+ end
125
+
126
+ it "simplifies zero values" do
127
+ source = <<-EOS
128
+ a {
129
+ margin: 0px 0pt 0em 0%;
130
+ background-position: 0 0ex;
131
+ padding: 0in 0cm 0mm 0pc
132
+ }
133
+ EOS
134
+ minified = Yuicssmin.compress(source)
135
+ minified.should eq('a{margin:0;background-position:0 0;padding:0}')
136
+ end
137
+
138
+ it "removes leading zeros from floats" do
139
+ source = <<-EOS
140
+ .classname {
141
+ margin: 0.6px 0.333pt 1.2em 8.8cm;
142
+ }
143
+ EOS
144
+ minified = Yuicssmin.compress(source)
145
+ minified.should eq('.classname{margin:.6px .333pt 1.2em 8.8cm}')
146
+ end
147
+
148
+ it "simplifies colors values" do
149
+ source = <<-EOS
150
+ .color-me {
151
+ color: rgb(123, 123, 123);
152
+ border-color: #ffeedd;
153
+ background: none repeat scroll 0 0 rgb(255, 0,0);
154
+ }
155
+ EOS
156
+ minified = Yuicssmin.compress(source)
157
+ minified.should eq('.color-me{color:#7b7b7b;border-color:#fed;background:none repeat scroll 0 0 #f00}')
158
+
159
+ source = <<-EOS
160
+ .cantouch {
161
+ color: rgba(1, 2, 3, 4);
162
+ filter: chroma(color="#FFFFFF");
163
+ }
164
+ EOS
165
+ minified = Yuicssmin.compress(source)
166
+ minified.should eq('.cantouch{color:rgba(1,2,3,4);filter:chroma(color="#FFFFFF")}')
167
+ end
168
+
169
+ it "only keeps the first charset declaration" do
170
+ source = <<-EOS
171
+ @charset "utf-8";
172
+ #foo {
173
+ border-width: 1px;
174
+ }
175
+
176
+ /* second css, merged */
177
+ @charset "another one";
178
+ #bar {
179
+ border-width: 10px;
180
+ }
181
+ EOS
182
+ minified = Yuicssmin.compress(source)
183
+ minified.should eq('@charset "utf-8";#foo{border-width:1px}#bar{border-width:10px}')
184
+ end
185
+
186
+ it "simplifies the IE opacity filter syntax" do
187
+ source = <<-EOS
188
+ .classname {
189
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; /* IE 8 */
190
+ filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80); /* IE < 8 */
191
+ }
192
+ EOS
193
+ minified = Yuicssmin.compress(source)
194
+ minified.should eq('.classname{-ms-filter:"alpha(opacity=80)";filter:alpha(opacity=80)}')
195
+ end
196
+
197
+ it "replaces 'none' values with 0 where allowed" do
198
+ source = <<-EOS
199
+ .classname {
200
+ border: none;
201
+ background: none;
202
+ outline: none;
203
+ }
204
+ EOS
205
+ minified = Yuicssmin.compress(source)
206
+ minified.should eq('.classname{border:0;background:0;outline:0}')
207
+ end
208
+
209
+ it "tolerates underscore/star hacks" do
210
+ source = <<-EOS
211
+ #element {
212
+ width: 1px;
213
+ *width: 2px;
214
+ _width: 3px;
215
+ }
216
+ EOS
217
+ minified = Yuicssmin.compress(source)
218
+ minified.should eq('#element{width:1px;*width:2px;_width:3px}')
219
+ end
220
+
221
+ it "tolerates child selector hacks" do
222
+ source = <<-EOS
223
+ html >/**/ body p {
224
+ color: blue;
225
+ }
226
+ EOS
227
+ minified = Yuicssmin.compress(source)
228
+ minified.should eq('html>/**/body p{color:blue}')
229
+ end
230
+
231
+ it "tolerates IE5/Mac hacks" do
232
+ source = <<-EOS
233
+ /* Ignore the next rule in IE mac \\*/
234
+ .selector {
235
+ color: khaki;
236
+ }
237
+ /* Stop ignoring in IE mac */
238
+ EOS
239
+ minified = Yuicssmin.compress(source)
240
+ minified.should eq('/*\*/.selector{color:khaki}/**/')
241
+ end
242
+
243
+ it "tolerates box model hacks" do
244
+ source = <<-EOS
245
+ #elem {
246
+ width: 100px; /* IE */
247
+ voice-family: "\\"}\\"";
248
+ voice-family:inherit;
249
+ width: 200px; /* others */
250
+ }
251
+ html>body #elem {
252
+ width: 200px; /* others */
253
+ }
254
+ EOS
255
+ minified = Yuicssmin.compress(source)
256
+ minified.should eq('#elem{width:100px;voice-family:"\"}\"";voice-family:inherit;width:200px}html>body #elem{width:200px}')
257
+ end
258
+
259
+ end
260
+
261
+ end
data/yuicssmin.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "yuicssmin/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "yuicssmin"
7
+ s.version = Yuicssmin::VERSION
8
+ s.author = "Matthias Siegel"
9
+ s.email = "matthias.siegel@gmail.com"
10
+ s.homepage = "https://github.com/matthiassiegel/yuicssmin"
11
+ s.summary = "Ruby wrapper for the Javascript port of YUI's CSS compressor"
12
+ s.description = <<-EOF
13
+ The YUICSSMIN gem provides CSS compression using YUI compressor from Yahoo. Unlike other gems it doesn't use the Java applet YUI compressor but instead uses the Javascript port via ExecJS.
14
+ EOF
15
+
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.md",
18
+ "README.md"
19
+ ]
20
+
21
+ s.license = "MIT"
22
+ s.rubyforge_project = "yuicssmin"
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.require_paths = ["lib"]
28
+
29
+ s.add_development_dependency "rspec"
30
+
31
+ s.add_runtime_dependency "execjs", ">= 0.3.0"
32
+ s.add_runtime_dependency "multi_json", ">= 1.0.2"
33
+ s.add_runtime_dependency "bundler", "~> 1.0"
34
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yuicssmin
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matthias Siegel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &2153802240 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2153802240
25
+ - !ruby/object:Gem::Dependency
26
+ name: execjs
27
+ requirement: &2153801660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.3.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2153801660
36
+ - !ruby/object:Gem::Dependency
37
+ name: multi_json
38
+ requirement: &2153801120 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.0.2
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2153801120
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: &2153800460 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *2153800460
58
+ description: ! ' The YUICSSMIN gem provides CSS compression using YUI compressor
59
+ from Yahoo. Unlike other gems it doesn''t use the Java applet YUI compressor but
60
+ instead uses the Javascript port via ExecJS.
61
+
62
+ '
63
+ email: matthias.siegel@gmail.com
64
+ executables: []
65
+ extensions: []
66
+ extra_rdoc_files:
67
+ - LICENSE.md
68
+ - README.md
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - LICENSE.md
73
+ - README.md
74
+ - Rakefile
75
+ - lib/yuicssmin.rb
76
+ - lib/yuicssmin/cssmin.js
77
+ - lib/yuicssmin/version.rb
78
+ - spec/sample.css
79
+ - spec/spec_helper.rb
80
+ - spec/yuicssmin_spec.rb
81
+ - yuicssmin.gemspec
82
+ homepage: https://github.com/matthiassiegel/yuicssmin
83
+ licenses:
84
+ - MIT
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project: yuicssmin
103
+ rubygems_version: 1.8.10
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Ruby wrapper for the Javascript port of YUI's CSS compressor
107
+ test_files:
108
+ - spec/sample.css
109
+ - spec/spec_helper.rb
110
+ - spec/yuicssmin_spec.rb