ruby-clean-css 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. data/.gitmodules +3 -0
  2. data/EXAMPLE.md +25 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +30 -0
  5. data/LICENSE +19 -0
  6. data/README.md +106 -0
  7. data/lib/javascript/clean-css/.gitignore +4 -0
  8. data/lib/javascript/clean-css/.jshintignore +3 -0
  9. data/lib/javascript/clean-css/.jshintrc +14 -0
  10. data/lib/javascript/clean-css/.travis.yml +11 -0
  11. data/lib/javascript/clean-css/History.md +556 -0
  12. data/lib/javascript/clean-css/LICENSE +19 -0
  13. data/lib/javascript/clean-css/README.md +218 -0
  14. data/lib/javascript/clean-css/bin/cleancss +157 -0
  15. data/lib/javascript/clean-css/index.js +1 -0
  16. data/lib/javascript/clean-css/lib/clean.js +426 -0
  17. data/lib/javascript/clean-css/lib/colors/hsl-to-hex.js +64 -0
  18. data/lib/javascript/clean-css/lib/colors/long-to-short-hex.js +12 -0
  19. data/lib/javascript/clean-css/lib/colors/rgb-to-hex.js +14 -0
  20. data/lib/javascript/clean-css/lib/colors/shortener.js +174 -0
  21. data/lib/javascript/clean-css/lib/images/url-rebase.js +32 -0
  22. data/lib/javascript/clean-css/lib/images/url-rewriter.js +60 -0
  23. data/lib/javascript/clean-css/lib/imports/inliner.js +319 -0
  24. data/lib/javascript/clean-css/lib/properties/optimizer.js +276 -0
  25. data/lib/javascript/clean-css/lib/properties/override-compactor.js +116 -0
  26. data/lib/javascript/clean-css/lib/properties/processable.js +859 -0
  27. data/lib/javascript/clean-css/lib/properties/scanner.js +20 -0
  28. data/lib/javascript/clean-css/lib/properties/shorthand-compactor.js +244 -0
  29. data/lib/javascript/clean-css/lib/properties/token.js +184 -0
  30. data/lib/javascript/clean-css/lib/properties/validator.js +140 -0
  31. data/lib/javascript/clean-css/lib/selectors/empty-removal.js +30 -0
  32. data/lib/javascript/clean-css/lib/selectors/optimizer.js +341 -0
  33. data/lib/javascript/clean-css/lib/selectors/tokenizer.js +168 -0
  34. data/lib/javascript/clean-css/lib/text/comments.js +83 -0
  35. data/lib/javascript/clean-css/lib/text/escape-store.js +32 -0
  36. data/lib/javascript/clean-css/lib/text/expressions.js +73 -0
  37. data/lib/javascript/clean-css/lib/text/free.js +26 -0
  38. data/lib/javascript/clean-css/lib/text/name-quotes.js +37 -0
  39. data/lib/javascript/clean-css/lib/text/quote-scanner.js +91 -0
  40. data/lib/javascript/clean-css/lib/text/splitter.js +35 -0
  41. data/lib/javascript/clean-css/lib/text/urls.js +41 -0
  42. data/lib/javascript/clean-css/package.json +50 -0
  43. data/lib/javascript/clean-css/test/batch-test.js +56 -0
  44. data/lib/javascript/clean-css/test/bench.js +11 -0
  45. data/lib/javascript/clean-css/test/binary-test.js +323 -0
  46. data/lib/javascript/clean-css/test/data-bench/_partial.css +1 -0
  47. data/lib/javascript/clean-css/test/data-bench/complex.css +4 -0
  48. data/lib/javascript/clean-css/test/data/129-assets/assets/ui.css +2 -0
  49. data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/css/bootstrap.css +3 -0
  50. data/lib/javascript/clean-css/test/data/129-assets/components/bootstrap/images/glyphs.gif +0 -0
  51. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/css/style.css +6 -0
  52. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/next.gif +0 -0
  53. data/lib/javascript/clean-css/test/data/129-assets/components/jquery-ui/images/prev.gif +0 -0
  54. data/lib/javascript/clean-css/test/data/960-min.css +125 -0
  55. data/lib/javascript/clean-css/test/data/960.css +602 -0
  56. data/lib/javascript/clean-css/test/data/big-min.css +2984 -0
  57. data/lib/javascript/clean-css/test/data/big.css +13794 -0
  58. data/lib/javascript/clean-css/test/data/blueprint-min.css +245 -0
  59. data/lib/javascript/clean-css/test/data/blueprint.css +556 -0
  60. data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts-min.css +3 -0
  61. data/lib/javascript/clean-css/test/data/charset-mixed-with-fonts.css +14 -0
  62. data/lib/javascript/clean-css/test/data/font-awesome-ie7-min.css +392 -0
  63. data/lib/javascript/clean-css/test/data/font-awesome-ie7.css +1203 -0
  64. data/lib/javascript/clean-css/test/data/font-awesome-min.css +319 -0
  65. data/lib/javascript/clean-css/test/data/font-awesome.css +540 -0
  66. data/lib/javascript/clean-css/test/data/imports-min.css +5 -0
  67. data/lib/javascript/clean-css/test/data/imports.css +4 -0
  68. data/lib/javascript/clean-css/test/data/issue-117-snippet-min.css +3 -0
  69. data/lib/javascript/clean-css/test/data/issue-117-snippet.css +19 -0
  70. data/lib/javascript/clean-css/test/data/issue-159-snippet-min.css +7 -0
  71. data/lib/javascript/clean-css/test/data/issue-159-snippet.css +7 -0
  72. data/lib/javascript/clean-css/test/data/issue-192-min.css +1 -0
  73. data/lib/javascript/clean-css/test/data/issue-192.css +8 -0
  74. data/lib/javascript/clean-css/test/data/issue-198-min.css +3 -0
  75. data/lib/javascript/clean-css/test/data/issue-198.css +4 -0
  76. data/lib/javascript/clean-css/test/data/issue-232-min.css +2 -0
  77. data/lib/javascript/clean-css/test/data/issue-232.css +17 -0
  78. data/lib/javascript/clean-css/test/data/issue-241-min.css +2 -0
  79. data/lib/javascript/clean-css/test/data/issue-241.css +2 -0
  80. data/lib/javascript/clean-css/test/data/issue-304-min.css +1 -0
  81. data/lib/javascript/clean-css/test/data/issue-304.css +4 -0
  82. data/lib/javascript/clean-css/test/data/issue-305-min.css +1 -0
  83. data/lib/javascript/clean-css/test/data/issue-305.css +3 -0
  84. data/lib/javascript/clean-css/test/data/issue-308-min.css +1 -0
  85. data/lib/javascript/clean-css/test/data/issue-308.css +5 -0
  86. data/lib/javascript/clean-css/test/data/issue-312-min.css +1 -0
  87. data/lib/javascript/clean-css/test/data/issue-312.css +7 -0
  88. data/lib/javascript/clean-css/test/data/issue-337-min.css +1 -0
  89. data/lib/javascript/clean-css/test/data/issue-337.css +4 -0
  90. data/lib/javascript/clean-css/test/data/line-breaks-in-attributes-min.css +2 -0
  91. data/lib/javascript/clean-css/test/data/line-breaks-in-attributes.css +8 -0
  92. data/lib/javascript/clean-css/test/data/partials-absolute/base.css +3 -0
  93. data/lib/javascript/clean-css/test/data/partials-absolute/base2.css +1 -0
  94. data/lib/javascript/clean-css/test/data/partials-absolute/extra/sub.css +3 -0
  95. data/lib/javascript/clean-css/test/data/partials-relative/base.css +3 -0
  96. data/lib/javascript/clean-css/test/data/partials-relative/extra/included.css +1 -0
  97. data/lib/javascript/clean-css/test/data/partials/comment.css +2 -0
  98. data/lib/javascript/clean-css/test/data/partials/extra/down.gif +0 -0
  99. data/lib/javascript/clean-css/test/data/partials/extra/four.css +3 -0
  100. data/lib/javascript/clean-css/test/data/partials/extra/three.css +1 -0
  101. data/lib/javascript/clean-css/test/data/partials/five.css +1 -0
  102. data/lib/javascript/clean-css/test/data/partials/four.css +1 -0
  103. data/lib/javascript/clean-css/test/data/partials/one.css +1 -0
  104. data/lib/javascript/clean-css/test/data/partials/three.css +1 -0
  105. data/lib/javascript/clean-css/test/data/partials/two.css +5 -0
  106. data/lib/javascript/clean-css/test/data/reset-min.css +12 -0
  107. data/lib/javascript/clean-css/test/data/reset.css +64 -0
  108. data/lib/javascript/clean-css/test/data/sample1-min.css +4 -0
  109. data/lib/javascript/clean-css/test/data/sample1.css +12 -0
  110. data/lib/javascript/clean-css/test/data/unsupported/selectors-ie7.css +20 -0
  111. data/lib/javascript/clean-css/test/data/unsupported/selectors-ie8.css +17 -0
  112. data/lib/javascript/clean-css/test/module-test.js +185 -0
  113. data/lib/javascript/clean-css/test/protocol-imports-test.js +409 -0
  114. data/lib/javascript/clean-css/test/text/splitter-test.js +20 -0
  115. data/lib/javascript/clean-css/test/unit-test.js +2071 -0
  116. data/lib/ruby-clean-css.rb +6 -0
  117. data/lib/ruby-clean-css/compressor.rb +142 -0
  118. data/lib/ruby-clean-css/exports.rb +258 -0
  119. data/lib/ruby-clean-css/railtie.rb +27 -0
  120. data/lib/ruby-clean-css/sprockets.rb +19 -0
  121. data/lib/ruby-clean-css/version.rb +5 -0
  122. data/ruby-clean-css.gemspec +30 -0
  123. data/test/test_compressor.rb +84 -0
  124. metadata +203 -0
@@ -0,0 +1,30 @@
1
+ module.exports = function EmptyRemoval(data) {
2
+ var stripEmpty = function(cssData) {
3
+ var tempData = [];
4
+ var nextEmpty = 0;
5
+ var cursor = 0;
6
+
7
+ for (; nextEmpty < cssData.length;) {
8
+ nextEmpty = cssData.indexOf('{}', cursor);
9
+ if (nextEmpty == -1)
10
+ break;
11
+
12
+ var startsAt = nextEmpty - 1;
13
+ while (cssData[startsAt] && cssData[startsAt] != '}' && cssData[startsAt] != '{' && cssData[startsAt] != ';')
14
+ startsAt--;
15
+
16
+ tempData.push(cssData.substring(cursor, startsAt + 1));
17
+ cursor = nextEmpty + 2;
18
+ }
19
+
20
+ return tempData.length > 0 ?
21
+ stripEmpty(tempData.join('') + cssData.substring(cursor, cssData.length)) :
22
+ cssData;
23
+ };
24
+
25
+ return {
26
+ process: function() {
27
+ return stripEmpty(data);
28
+ }
29
+ };
30
+ };
@@ -0,0 +1,341 @@
1
+ var Tokenizer = require('./tokenizer');
2
+ var PropertyOptimizer = require('../properties/optimizer');
3
+
4
+ module.exports = function Optimizer(data, context, options) {
5
+ var specialSelectors = {
6
+ '*': /\-(moz|ms|o|webkit)\-/,
7
+ 'ie8': /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/,
8
+ 'ie7': /(\-moz\-|\-ms\-|\-o\-|\-webkit\-|:focus|:before|:after|:root|:nth|:first\-of|:last|:only|:empty|:target|:checked|::selection|:enabled|:disabled|:not)/
9
+ };
10
+
11
+ var minificationsMade = [];
12
+
13
+ var propertyOptimizer = new PropertyOptimizer(options.compatibility, options.aggressiveMerging);
14
+
15
+ var cleanUpSelector = function(selectors) {
16
+ if (selectors.indexOf(',') == -1)
17
+ return selectors;
18
+
19
+ var plain = [];
20
+ var cursor = 0;
21
+ var lastComma = 0;
22
+ var noBrackets = selectors.indexOf('(') == -1;
23
+ var withinBrackets = function(idx) {
24
+ if (noBrackets)
25
+ return false;
26
+
27
+ var previousOpening = selectors.lastIndexOf('(', idx);
28
+ var previousClosing = selectors.lastIndexOf(')', idx);
29
+
30
+ if (previousOpening == -1)
31
+ return false;
32
+ if (previousClosing > 0 && previousClosing < idx)
33
+ return false;
34
+
35
+ return true;
36
+ };
37
+
38
+ while (true) {
39
+ var nextComma = selectors.indexOf(',', cursor + 1);
40
+ var selector;
41
+
42
+ if (nextComma === -1) {
43
+ nextComma = selectors.length;
44
+ } else if (withinBrackets(nextComma)) {
45
+ cursor = nextComma + 1;
46
+ continue;
47
+ }
48
+ selector = selectors.substring(lastComma, nextComma);
49
+ lastComma = cursor = nextComma + 1;
50
+
51
+ if (plain.indexOf(selector) == -1)
52
+ plain.push(selector);
53
+
54
+ if (nextComma === selectors.length)
55
+ break;
56
+ }
57
+
58
+ return plain.sort().join(',');
59
+ };
60
+
61
+ var isSpecial = function(selector) {
62
+ return specialSelectors[options.compatibility || '*'].test(selector);
63
+ };
64
+
65
+ var removeDuplicates = function(tokens) {
66
+ var matched = {};
67
+ var forRemoval = [];
68
+
69
+ for (var i = 0, l = tokens.length; i < l; i++) {
70
+ var token = tokens[i];
71
+ if (typeof token == 'string' || token.block)
72
+ continue;
73
+
74
+ var id = token.body + '@' + token.selector;
75
+ var alreadyMatched = matched[id];
76
+
77
+ if (alreadyMatched) {
78
+ forRemoval.push(alreadyMatched[0]);
79
+ alreadyMatched.unshift(i);
80
+ } else {
81
+ matched[id] = [i];
82
+ }
83
+ }
84
+
85
+ forRemoval = forRemoval.sort(function(a, b) {
86
+ return a > b ? 1 : -1;
87
+ });
88
+
89
+ for (var j = 0, n = forRemoval.length; j < n; j++) {
90
+ tokens.splice(forRemoval[j] - j, 1);
91
+ }
92
+
93
+ minificationsMade.unshift(forRemoval.length > 0);
94
+ };
95
+
96
+ var mergeAdjacent = function(tokens) {
97
+ var forRemoval = [];
98
+ var lastToken = { selector: null, body: null };
99
+
100
+ for (var i = 0, l = tokens.length; i < l; i++) {
101
+ var token = tokens[i];
102
+
103
+ if (typeof token == 'string' || token.block)
104
+ continue;
105
+
106
+ if (token.selector == lastToken.selector) {
107
+ var joinAt = [lastToken.body.split(';').length];
108
+ lastToken.body = propertyOptimizer.process(lastToken.body + ';' + token.body, joinAt);
109
+ forRemoval.push(i);
110
+ } else if (token.body == lastToken.body && !isSpecial(token.selector) && !isSpecial(lastToken.selector)) {
111
+ lastToken.selector = cleanUpSelector(lastToken.selector + ',' + token.selector);
112
+ forRemoval.push(i);
113
+ } else {
114
+ lastToken = token;
115
+ }
116
+ }
117
+
118
+ for (var j = 0, m = forRemoval.length; j < m; j++) {
119
+ tokens.splice(forRemoval[j] - j, 1);
120
+ }
121
+
122
+ minificationsMade.unshift(forRemoval.length > 0);
123
+ };
124
+
125
+ var reduceNonAdjacent = function(tokens) {
126
+ var candidates = {};
127
+ var moreThanOnce = [];
128
+
129
+ for (var i = tokens.length - 1; i >= 0; i--) {
130
+ var token = tokens[i];
131
+
132
+ if (typeof token == 'string' || token.block)
133
+ continue;
134
+
135
+ var complexSelector = token.selector;
136
+ var selectors = complexSelector.indexOf(',') > -1 && !isSpecial(complexSelector) ?
137
+ complexSelector.split(',').concat(complexSelector) : // simplification, as :not() can have commas too
138
+ [complexSelector];
139
+
140
+ for (var j = 0, m = selectors.length; j < m; j++) {
141
+ var selector = selectors[j];
142
+
143
+ if (!candidates[selector])
144
+ candidates[selector] = [];
145
+ else
146
+ moreThanOnce.push(selector);
147
+
148
+ candidates[selector].push({
149
+ where: i,
150
+ partial: selector != complexSelector
151
+ });
152
+ }
153
+ }
154
+
155
+ var reducedInSimple = _reduceSimpleNonAdjacentCases(tokens, moreThanOnce, candidates);
156
+ var reducedInComplex = _reduceComplexNonAdjacentCases(tokens, candidates);
157
+
158
+ minificationsMade.unshift(reducedInSimple || reducedInComplex);
159
+ };
160
+
161
+ var _reduceSimpleNonAdjacentCases = function(tokens, matches, positions) {
162
+ var reduced = false;
163
+
164
+ for (var i = 0, l = matches.length; i < l; i++) {
165
+ var selector = matches[i];
166
+ var data = positions[selector];
167
+
168
+ if (data.length < 2)
169
+ continue;
170
+
171
+ /* jshint loopfunc: true */
172
+ _reduceSelector(tokens, selector, data, {
173
+ filterOut: function(idx, bodies) {
174
+ return data[idx].partial && bodies.length === 0;
175
+ },
176
+ callback: function(token, newBody, processedCount, tokenIdx) {
177
+ if (!data[processedCount - tokenIdx - 1].partial) {
178
+ token.body = newBody.join(';');
179
+ reduced = true;
180
+ }
181
+ }
182
+ });
183
+ }
184
+
185
+ return reduced;
186
+ };
187
+
188
+ var _reduceComplexNonAdjacentCases = function(tokens, positions) {
189
+ var reduced = false;
190
+
191
+ allSelectors:
192
+ for (var complexSelector in positions) {
193
+ if (complexSelector.indexOf(',') == -1) // simplification, as :not() can have commas too
194
+ continue;
195
+
196
+ var intoPosition = positions[complexSelector].pop().where;
197
+ var intoToken = tokens[intoPosition];
198
+
199
+ var selectors = isSpecial(complexSelector) ?
200
+ [complexSelector] :
201
+ complexSelector.split(',');
202
+ var reducedBodies = [];
203
+
204
+ for (var j = 0, m = selectors.length; j < m; j++) {
205
+ var selector = selectors[j];
206
+ var data = positions[selector];
207
+
208
+ if (data.length < 2)
209
+ continue allSelectors;
210
+
211
+ /* jshint loopfunc: true */
212
+ _reduceSelector(tokens, selector, data, {
213
+ filterOut: function(idx) {
214
+ return data[idx].where < intoPosition;
215
+ },
216
+ callback: function(token, newBody, processedCount, tokenIdx) {
217
+ if (tokenIdx === 0)
218
+ reducedBodies.push(newBody.join(';'));
219
+ }
220
+ });
221
+
222
+ if (reducedBodies[reducedBodies.length - 1] != reducedBodies[0])
223
+ continue allSelectors;
224
+ }
225
+
226
+ intoToken.body = reducedBodies[0];
227
+ reduced = true;
228
+ }
229
+
230
+ return reduced;
231
+ };
232
+
233
+ var _reduceSelector = function(tokens, selector, data, options) {
234
+ var bodies = [];
235
+ var joinsAt = [];
236
+ var splitBodies = [];
237
+ var processedTokens = [];
238
+
239
+ for (var j = data.length - 1, m = 0; j >= 0; j--) {
240
+ if (options.filterOut(j, bodies))
241
+ continue;
242
+
243
+ var where = data[j].where;
244
+ var token = tokens[where];
245
+ var body = token.body;
246
+ bodies.push(body);
247
+ splitBodies.push(body.split(';'));
248
+ processedTokens.push(where);
249
+ }
250
+
251
+ for (j = 0, m = bodies.length; j < m; j++) {
252
+ if (bodies[j].length > 0)
253
+ joinsAt.push((joinsAt[j - 1] || 0) + splitBodies[j].length);
254
+ }
255
+
256
+ var optimizedBody = propertyOptimizer.process(bodies.join(';'), joinsAt, true);
257
+ var optimizedProperties = optimizedBody.split(';');
258
+
259
+ var processedCount = processedTokens.length;
260
+ var propertyIdx = optimizedProperties.length - 1;
261
+ var tokenIdx = processedCount - 1;
262
+
263
+ while (tokenIdx >= 0) {
264
+ if ((tokenIdx === 0 || splitBodies[tokenIdx].indexOf(optimizedProperties[propertyIdx]) > -1) && propertyIdx > -1) {
265
+ propertyIdx--;
266
+ continue;
267
+ }
268
+
269
+ var newBody = optimizedProperties.splice(propertyIdx + 1);
270
+ options.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
271
+
272
+ tokenIdx--;
273
+ }
274
+ };
275
+
276
+ var optimize = function(tokens) {
277
+ var noChanges = function() {
278
+ return minificationsMade.length > 4 &&
279
+ minificationsMade[0] === false &&
280
+ minificationsMade[1] === false;
281
+ };
282
+
283
+ tokens = Array.isArray(tokens) ? tokens : [tokens];
284
+ for (var i = 0, l = tokens.length; i < l; i++) {
285
+ var token = tokens[i];
286
+
287
+ if (token.selector) {
288
+ token.selector = cleanUpSelector(token.selector);
289
+ token.body = propertyOptimizer.process(token.body, false);
290
+ } else if (token.block) {
291
+ optimize(token.body);
292
+ }
293
+ }
294
+
295
+ // Run until 2 last operations do not yield any changes
296
+ minificationsMade = [];
297
+ while (true) {
298
+ if (noChanges())
299
+ break;
300
+ removeDuplicates(tokens);
301
+
302
+ if (noChanges())
303
+ break;
304
+ mergeAdjacent(tokens);
305
+
306
+ if (noChanges())
307
+ break;
308
+ reduceNonAdjacent(tokens);
309
+ }
310
+ };
311
+
312
+ var rebuild = function(tokens) {
313
+ var rebuilt = [];
314
+
315
+ tokens = Array.isArray(tokens) ? tokens : [tokens];
316
+ for (var i = 0, l = tokens.length; i < l; i++) {
317
+ var token = tokens[i];
318
+
319
+ if (typeof token == 'string') {
320
+ rebuilt.push(token);
321
+ continue;
322
+ }
323
+
324
+ var name = token.block || token.selector;
325
+ var body = token.block ? rebuild(token.body) : token.body;
326
+
327
+ if (body.length > 0)
328
+ rebuilt.push(name + '{' + body + '}');
329
+ }
330
+
331
+ return rebuilt.join(options.keepBreaks ? options.lineBreak : '');
332
+ };
333
+
334
+ return {
335
+ process: function() {
336
+ var tokenized = new Tokenizer(data, context).process();
337
+ optimize(tokenized);
338
+ return rebuild(tokenized);
339
+ }
340
+ };
341
+ };
@@ -0,0 +1,168 @@
1
+ module.exports = function Tokenizer(data, minifyContext) {
2
+ var chunker = new Chunker(data, 128);
3
+ var chunk = chunker.next();
4
+ var flatBlock = /(^@(font\-face|page|\-ms\-viewport|\-o\-viewport|viewport)|\\@.+?)/;
5
+
6
+ var whatsNext = function(context) {
7
+ var cursor = context.cursor;
8
+ var mode = context.mode;
9
+ var closest;
10
+
11
+ if (chunk.length == context.cursor) {
12
+ if (chunker.isEmpty())
13
+ return null;
14
+
15
+ chunk = chunker.next();
16
+ context.cursor = 0;
17
+ }
18
+
19
+ if (mode == 'body') {
20
+ closest = chunk.indexOf('}', cursor);
21
+ return closest > -1 ?
22
+ [closest, 'bodyEnd'] :
23
+ null;
24
+ }
25
+
26
+ var nextSpecial = chunk.indexOf('@', context.cursor);
27
+ var nextEscape = mode == 'top' ? chunk.indexOf('__ESCAPED_COMMENT_CLEAN_CSS', context.cursor) : -1;
28
+ var nextBodyStart = chunk.indexOf('{', context.cursor);
29
+ var nextBodyEnd = chunk.indexOf('}', context.cursor);
30
+
31
+ closest = nextSpecial;
32
+ if (closest == -1 || (nextEscape > -1 && nextEscape < closest))
33
+ closest = nextEscape;
34
+ if (closest == -1 || (nextBodyStart > -1 && nextBodyStart < closest))
35
+ closest = nextBodyStart;
36
+ if (closest == -1 || (nextBodyEnd > -1 && nextBodyEnd < closest))
37
+ closest = nextBodyEnd;
38
+
39
+ if (closest == -1)
40
+ return;
41
+ if (nextEscape === closest)
42
+ return [closest, 'escape'];
43
+ if (nextBodyStart === closest)
44
+ return [closest, 'bodyStart'];
45
+ if (nextBodyEnd === closest)
46
+ return [closest, 'bodyEnd'];
47
+ if (nextSpecial === closest)
48
+ return [closest, 'special'];
49
+ };
50
+
51
+ var tokenize = function(context) {
52
+ var tokenized = [];
53
+
54
+ context = context || { cursor: 0, mode: 'top' };
55
+
56
+ while (true) {
57
+ var next = whatsNext(context);
58
+ if (!next) {
59
+ var whatsLeft = chunk.substring(context.cursor);
60
+ if (whatsLeft.length > 0) {
61
+ tokenized.push(whatsLeft);
62
+ context.cursor += whatsLeft.length;
63
+ }
64
+ break;
65
+ }
66
+
67
+ var nextSpecial = next[0];
68
+ var what = next[1];
69
+ var nextEnd;
70
+ var oldMode;
71
+
72
+ if (what == 'special') {
73
+ var firstOpenBraceAt = chunk.indexOf('{', nextSpecial);
74
+ var firstSemicolonAt = chunk.indexOf(';', nextSpecial);
75
+ var isSingle = firstSemicolonAt > -1 && (firstOpenBraceAt == -1 || firstSemicolonAt < firstOpenBraceAt);
76
+ if (isSingle) {
77
+ nextEnd = chunk.indexOf(';', nextSpecial + 1);
78
+ tokenized.push(chunk.substring(context.cursor, nextEnd + 1));
79
+
80
+ context.cursor = nextEnd + 1;
81
+ } else {
82
+ nextEnd = chunk.indexOf('{', nextSpecial + 1);
83
+ var block = chunk.substring(context.cursor, nextEnd).trim();
84
+
85
+ var isFlat = flatBlock.test(block);
86
+ oldMode = context.mode;
87
+ context.cursor = nextEnd + 1;
88
+ context.mode = isFlat ? 'body' : 'block';
89
+ var specialBody = tokenize(context);
90
+ context.mode = oldMode;
91
+
92
+ tokenized.push({ block: block, body: specialBody });
93
+ }
94
+ } else if (what == 'escape') {
95
+ nextEnd = chunk.indexOf('__', nextSpecial + 1);
96
+ var escaped = chunk.substring(context.cursor, nextEnd + 2);
97
+ tokenized.push(escaped);
98
+
99
+ context.cursor = nextEnd + 2;
100
+ } else if (what == 'bodyStart') {
101
+ var selector = chunk.substring(context.cursor, nextSpecial).trim();
102
+
103
+ oldMode = context.mode;
104
+ context.cursor = nextSpecial + 1;
105
+ context.mode = 'body';
106
+ var body = tokenize(context);
107
+ context.mode = oldMode;
108
+
109
+ tokenized.push({ selector: selector, body: body });
110
+ } else if (what == 'bodyEnd') {
111
+ // extra closing brace at the top level can be safely ignored
112
+ if (context.mode == 'top') {
113
+ var at = context.cursor;
114
+ var warning = chunk[context.cursor] == '}' ?
115
+ 'Unexpected \'}\' in \'' + chunk.substring(at - 20, at + 20) + '\'. Ignoring.' :
116
+ 'Unexpected content: \'' + chunk.substring(at, nextSpecial + 1) + '\'. Ignoring.';
117
+
118
+ minifyContext.warnings.push(warning);
119
+ context.cursor = nextSpecial + 1;
120
+ continue;
121
+ }
122
+
123
+ if (context.mode != 'block')
124
+ tokenized = chunk.substring(context.cursor, nextSpecial);
125
+
126
+ context.cursor = nextSpecial + 1;
127
+
128
+ break;
129
+ }
130
+ }
131
+
132
+ return tokenized;
133
+ };
134
+
135
+ return {
136
+ process: function() {
137
+ return tokenize();
138
+ }
139
+ };
140
+ };
141
+
142
+ // Divides `data` into chunks of `chunkSize` for faster processing
143
+ var Chunker = function(data, chunkSize) {
144
+ var chunks = [];
145
+ for (var cursor = 0, dataSize = data.length; cursor < dataSize;) {
146
+ var nextCursor = cursor + chunkSize > dataSize ?
147
+ dataSize - 1 :
148
+ cursor + chunkSize;
149
+
150
+ if (data[nextCursor] != '}')
151
+ nextCursor = data.indexOf('}', nextCursor);
152
+ if (nextCursor == -1)
153
+ nextCursor = data.length - 1;
154
+
155
+ chunks.push(data.substring(cursor, nextCursor + 1));
156
+ cursor = nextCursor + 1;
157
+ }
158
+
159
+ return {
160
+ isEmpty: function() {
161
+ return chunks.length === 0;
162
+ },
163
+
164
+ next: function() {
165
+ return chunks.shift() || '';
166
+ }
167
+ };
168
+ };