ruby-clean-css 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,64 @@
1
+ module.exports = function HSLToHex(data) {
2
+ // HSL to RGB converter. Both methods adapted from:
3
+ // http://mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript
4
+ var hslToRgb = function(h, s, l) {
5
+ var r, g, b;
6
+
7
+ // normalize hue orientation b/w 0 and 360 degrees
8
+ h = h % 360;
9
+ if (h < 0)
10
+ h += 360;
11
+ h = ~~h / 360;
12
+
13
+ if (s < 0)
14
+ s = 0;
15
+ else if (s > 100)
16
+ s = 100;
17
+ s = ~~s / 100;
18
+
19
+ if (l < 0)
20
+ l = 0;
21
+ else if (l > 100)
22
+ l = 100;
23
+ l = ~~l / 100;
24
+
25
+ if (s === 0) {
26
+ r = g = b = l; // achromatic
27
+ } else {
28
+ var q = l < 0.5 ?
29
+ l * (1 + s) :
30
+ l + s - l * s;
31
+ var p = 2 * l - q;
32
+ r = hueToRgb(p, q, h + 1/3);
33
+ g = hueToRgb(p, q, h);
34
+ b = hueToRgb(p, q, h - 1/3);
35
+ }
36
+
37
+ return [~~(r * 255), ~~(g * 255), ~~(b * 255)];
38
+ };
39
+
40
+ var hueToRgb = function(p, q, t) {
41
+ if (t < 0) t += 1;
42
+ if (t > 1) t -= 1;
43
+ if (t < 1/6) return p + (q - p) * 6 * t;
44
+ if (t < 1/2) return q;
45
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
46
+ return p;
47
+ };
48
+
49
+ return {
50
+ process: function() {
51
+ return data.replace(/hsl\((-?\d+),(-?\d+)%?,(-?\d+)%?\)/g, function(match, hue, saturation, lightness) {
52
+ var asRgb = hslToRgb(hue, saturation, lightness);
53
+ var redAsHex = asRgb[0].toString(16);
54
+ var greenAsHex = asRgb[1].toString(16);
55
+ var blueAsHex = asRgb[2].toString(16);
56
+
57
+ return '#' +
58
+ ((redAsHex.length == 1 ? '0' : '') + redAsHex) +
59
+ ((greenAsHex.length == 1 ? '0' : '') + greenAsHex) +
60
+ ((blueAsHex.length == 1 ? '0' : '') + blueAsHex);
61
+ });
62
+ }
63
+ };
64
+ };
@@ -0,0 +1,12 @@
1
+ module.exports = function LongToShortHex(data) {
2
+ return {
3
+ process: function() {
4
+ return data.replace(/([,: \(])#([0-9a-f]{6})/gi, function(match, prefix, color) {
5
+ if (color[0] == color[1] && color[2] == color[3] && color[4] == color[5])
6
+ return prefix + '#' + color[0] + color[2] + color[4];
7
+ else
8
+ return prefix + '#' + color;
9
+ });
10
+ }
11
+ };
12
+ };
@@ -0,0 +1,14 @@
1
+ module.exports = function RGBToHex(data) {
2
+ return {
3
+ process: function() {
4
+ return data.replace(/rgb\((\-?\d+),(\-?\d+),(\-?\d+)\)/g, function(match, red, green, blue) {
5
+ red = Math.max(0, Math.min(~~red, 255));
6
+ green = Math.max(0, Math.min(~~green, 255));
7
+ blue = Math.max(0, Math.min(~~blue, 255));
8
+
9
+ // Credit: Asen http://jsbin.com/UPUmaGOc/2/edit?js,console
10
+ return '#' + ('00000' + (red << 16 | green << 8 | blue).toString(16)).slice(-6);
11
+ });
12
+ }
13
+ };
14
+ };
@@ -0,0 +1,174 @@
1
+ module.exports = function Shortener(data) {
2
+ var COLORS = {
3
+ aliceblue: '#f0f8ff',
4
+ antiquewhite: '#faebd7',
5
+ aqua: '#0ff',
6
+ aquamarine: '#7fffd4',
7
+ azure: '#f0ffff',
8
+ beige: '#f5f5dc',
9
+ bisque: '#ffe4c4',
10
+ black: '#000',
11
+ blanchedalmond: '#ffebcd',
12
+ blue: '#00f',
13
+ blueviolet: '#8a2be2',
14
+ brown: '#a52a2a',
15
+ burlywood: '#deb887',
16
+ cadetblue: '#5f9ea0',
17
+ chartreuse: '#7fff00',
18
+ chocolate: '#d2691e',
19
+ coral: '#ff7f50',
20
+ cornflowerblue: '#6495ed',
21
+ cornsilk: '#fff8dc',
22
+ crimson: '#dc143c',
23
+ cyan: '#0ff',
24
+ darkblue: '#00008b',
25
+ darkcyan: '#008b8b',
26
+ darkgoldenrod: '#b8860b',
27
+ darkgray: '#a9a9a9',
28
+ darkgreen: '#006400',
29
+ darkkhaki: '#bdb76b',
30
+ darkmagenta: '#8b008b',
31
+ darkolivegreen: '#556b2f',
32
+ darkorange: '#ff8c00',
33
+ darkorchid: '#9932cc',
34
+ darkred: '#8b0000',
35
+ darksalmon: '#e9967a',
36
+ darkseagreen: '#8fbc8f',
37
+ darkslateblue: '#483d8b',
38
+ darkslategray: '#2f4f4f',
39
+ darkturquoise: '#00ced1',
40
+ darkviolet: '#9400d3',
41
+ deeppink: '#ff1493',
42
+ deepskyblue: '#00bfff',
43
+ dimgray: '#696969',
44
+ dodgerblue: '#1e90ff',
45
+ firebrick: '#b22222',
46
+ floralwhite: '#fffaf0',
47
+ forestgreen: '#228b22',
48
+ fuchsia: '#f0f',
49
+ gainsboro: '#dcdcdc',
50
+ ghostwhite: '#f8f8ff',
51
+ gold: '#ffd700',
52
+ goldenrod: '#daa520',
53
+ gray: '#808080',
54
+ green: '#008000',
55
+ greenyellow: '#adff2f',
56
+ honeydew: '#f0fff0',
57
+ hotpink: '#ff69b4',
58
+ indianred: '#cd5c5c',
59
+ indigo: '#4b0082',
60
+ ivory: '#fffff0',
61
+ khaki: '#f0e68c',
62
+ lavender: '#e6e6fa',
63
+ lavenderblush: '#fff0f5',
64
+ lawngreen: '#7cfc00',
65
+ lemonchiffon: '#fffacd',
66
+ lightblue: '#add8e6',
67
+ lightcoral: '#f08080',
68
+ lightcyan: '#e0ffff',
69
+ lightgoldenrodyellow: '#fafad2',
70
+ lightgray: '#d3d3d3',
71
+ lightgreen: '#90ee90',
72
+ lightpink: '#ffb6c1',
73
+ lightsalmon: '#ffa07a',
74
+ lightseagreen: '#20b2aa',
75
+ lightskyblue: '#87cefa',
76
+ lightslategray: '#778899',
77
+ lightsteelblue: '#b0c4de',
78
+ lightyellow: '#ffffe0',
79
+ lime: '#0f0',
80
+ limegreen: '#32cd32',
81
+ linen: '#faf0e6',
82
+ magenta: '#ff00ff',
83
+ maroon: '#800000',
84
+ mediumaquamarine: '#66cdaa',
85
+ mediumblue: '#0000cd',
86
+ mediumorchid: '#ba55d3',
87
+ mediumpurple: '#9370db',
88
+ mediumseagreen: '#3cb371',
89
+ mediumslateblue: '#7b68ee',
90
+ mediumspringgreen: '#00fa9a',
91
+ mediumturquoise: '#48d1cc',
92
+ mediumvioletred: '#c71585',
93
+ midnightblue: '#191970',
94
+ mintcream: '#f5fffa',
95
+ mistyrose: '#ffe4e1',
96
+ moccasin: '#ffe4b5',
97
+ navajowhite: '#ffdead',
98
+ navy: '#000080',
99
+ oldlace: '#fdf5e6',
100
+ olive: '#808000',
101
+ olivedrab: '#6b8e23',
102
+ orange: '#ffa500',
103
+ orangered: '#ff4500',
104
+ orchid: '#da70d6',
105
+ palegoldenrod: '#eee8aa',
106
+ palegreen: '#98fb98',
107
+ paleturquoise: '#afeeee',
108
+ palevioletred: '#db7093',
109
+ papayawhip: '#ffefd5',
110
+ peachpuff: '#ffdab9',
111
+ peru: '#cd853f',
112
+ pink: '#ffc0cb',
113
+ plum: '#dda0dd',
114
+ powderblue: '#b0e0e6',
115
+ purple: '#800080',
116
+ red: '#f00',
117
+ rosybrown: '#bc8f8f',
118
+ royalblue: '#4169e1',
119
+ saddlebrown: '#8b4513',
120
+ salmon: '#fa8072',
121
+ sandybrown: '#f4a460',
122
+ seagreen: '#2e8b57',
123
+ seashell: '#fff5ee',
124
+ sienna: '#a0522d',
125
+ silver: '#c0c0c0',
126
+ skyblue: '#87ceeb',
127
+ slateblue: '#6a5acd',
128
+ slategray: '#708090',
129
+ snow: '#fffafa',
130
+ springgreen: '#00ff7f',
131
+ steelblue: '#4682b4',
132
+ tan: '#d2b48c',
133
+ teal: '#008080',
134
+ thistle: '#d8bfd8',
135
+ tomato: '#ff6347',
136
+ turquoise: '#40e0d0',
137
+ violet: '#ee82ee',
138
+ wheat: '#f5deb3',
139
+ white: '#fff',
140
+ whitesmoke: '#f5f5f5',
141
+ yellow: '#ff0',
142
+ yellowgreen: '#9acd32'
143
+ };
144
+
145
+ var toHex = {};
146
+ var toName = {};
147
+
148
+ for (var name in COLORS) {
149
+ var color = COLORS[name];
150
+ if (name.length < color.length)
151
+ toName[color] = name;
152
+ else
153
+ toHex[name] = color;
154
+ }
155
+
156
+ return {
157
+ toHex: toHex,
158
+ toName: toName,
159
+
160
+ // replace color name with hex values if shorter (or the other way around)
161
+ process: function() {
162
+ [toHex, toName].forEach(function(conversion) {
163
+ var pattern = '(' + Object.keys(conversion).join('|') + ')';
164
+ var colorSwitcher = function(match, prefix, colorValue, suffix) {
165
+ return prefix + conversion[colorValue.toLowerCase()] + suffix;
166
+ };
167
+ data = data.replace(new RegExp('([ :,\\(])' + pattern + '([;\\}!\\) ])', 'ig'), colorSwitcher);
168
+ data = data.replace(new RegExp('(,)' + pattern + '(,)', 'ig'), colorSwitcher);
169
+ });
170
+
171
+ return data;
172
+ }
173
+ };
174
+ };
@@ -0,0 +1,32 @@
1
+ var path = require('path');
2
+
3
+ var UrlRewriter = require('./url-rewriter');
4
+
5
+ module.exports = function UrlRebase(options, context) {
6
+ var process = function(data) {
7
+ var rebaseOpts = {
8
+ absolute: !!options.root,
9
+ relative: !options.root && !!options.target,
10
+ fromBase: options.relativeTo
11
+ };
12
+
13
+ if (!rebaseOpts.absolute && !rebaseOpts.relative)
14
+ return data;
15
+
16
+ if (rebaseOpts.absolute && !!options.target)
17
+ context.warnings.push('Both \'root\' and output file given so rebasing URLs as absolute paths');
18
+
19
+ if (rebaseOpts.absolute)
20
+ rebaseOpts.toBase = path.resolve(options.root);
21
+
22
+ if (rebaseOpts.relative)
23
+ rebaseOpts.toBase = path.resolve(path.dirname(options.target));
24
+
25
+ if (!rebaseOpts.fromBase || !rebaseOpts.toBase)
26
+ return data;
27
+
28
+ return UrlRewriter.process(data, rebaseOpts);
29
+ };
30
+
31
+ return { process: process };
32
+ };
@@ -0,0 +1,60 @@
1
+ var path = require('path');
2
+ var url = require('url');
3
+
4
+ module.exports = {
5
+ process: function(data, options) {
6
+ var tempData = [];
7
+ var nextStart = 0;
8
+ var nextEnd = 0;
9
+ var cursor = 0;
10
+
11
+ for (; nextEnd < data.length;) {
12
+ nextStart = data.indexOf('url(', nextEnd);
13
+ if (nextStart == -1)
14
+ break;
15
+
16
+ nextEnd = data.indexOf(')', nextStart + 4);
17
+ if (nextEnd == -1)
18
+ break;
19
+
20
+ tempData.push(data.substring(cursor, nextStart));
21
+ var url = data.substring(nextStart + 4, nextEnd);
22
+ if (!/\/\*|\*\//.test(url))
23
+ url = url.replace(/['"]/g, '');
24
+
25
+ tempData.push('url(' + this._rebased(url, options) + ')');
26
+ cursor = nextEnd + 1;
27
+ }
28
+
29
+ return tempData.length > 0 ?
30
+ tempData.join('') + data.substring(cursor, data.length) :
31
+ data;
32
+ },
33
+
34
+ _rebased: function(resource, options) {
35
+ var specialUrl = resource[0] == '/' ||
36
+ resource.substring(resource.length - 4) == '.css' ||
37
+ resource.indexOf('data:') === 0 ||
38
+ /^https?:\/\//.exec(resource) !== null ||
39
+ /__\w+__/.exec(resource) !== null;
40
+ var rebased;
41
+
42
+ if (specialUrl)
43
+ return resource;
44
+
45
+ if (/https?:\/\//.test(options.toBase))
46
+ return url.resolve(options.toBase, resource);
47
+
48
+ if (options.absolute) {
49
+ rebased = path
50
+ .resolve(path.join(options.fromBase, resource))
51
+ .replace(options.toBase, '');
52
+ } else {
53
+ rebased = path.relative(options.toBase, path.join(options.fromBase, resource));
54
+ }
55
+
56
+ return process.platform == 'win32' ?
57
+ rebased.replace(/\\/g, '/') :
58
+ rebased;
59
+ }
60
+ };
@@ -0,0 +1,319 @@
1
+ var fs = require('fs');
2
+ var path = require('path');
3
+ var http = require('http');
4
+ var https = require('https');
5
+ var url = require('url');
6
+
7
+ var UrlRewriter = require('../images/url-rewriter');
8
+
9
+ var merge = function(source1, source2) {
10
+ var target = {};
11
+ for (var key1 in source1)
12
+ target[key1] = source1[key1];
13
+ for (var key2 in source2)
14
+ target[key2] = source2[key2];
15
+
16
+ return target;
17
+ };
18
+
19
+ module.exports = function Inliner(context, options) {
20
+ var defaultOptions = {
21
+ timeout: 5000,
22
+ request: {}
23
+ };
24
+ var inlinerOptions = merge(defaultOptions, options || {});
25
+
26
+ var process = function(data, options) {
27
+ if (options.shallow) {
28
+ options.shallow = false;
29
+ options._shared.done.push(data);
30
+ return processNext(options);
31
+ }
32
+
33
+ options._shared = options._shared || {
34
+ done: [],
35
+ left: []
36
+ };
37
+ var shared = options._shared;
38
+
39
+ var nextStart = 0;
40
+ var nextEnd = 0;
41
+ var cursor = 0;
42
+ var isComment = commentScanner(data);
43
+ var afterContent = contentScanner(data);
44
+
45
+ options.relativeTo = options.relativeTo || options.root;
46
+ options._baseRelativeTo = options._baseRelativeTo || options.relativeTo;
47
+ options.visited = options.visited || [];
48
+
49
+ for (; nextEnd < data.length;) {
50
+ nextStart = data.indexOf('@import', cursor);
51
+ if (nextStart == -1)
52
+ break;
53
+
54
+ if (isComment(nextStart)) {
55
+ cursor = nextStart + 1;
56
+ continue;
57
+ }
58
+
59
+ nextEnd = data.indexOf(';', nextStart);
60
+ if (nextEnd == -1) {
61
+ cursor = data.length;
62
+ data = '';
63
+ break;
64
+ }
65
+
66
+ shared.done.push(data.substring(0, nextStart));
67
+ shared.left.unshift([data.substring(nextEnd + 1), options]);
68
+
69
+ return afterContent(nextStart) ?
70
+ processNext(options) :
71
+ inline(data, nextStart, nextEnd, options);
72
+ }
73
+
74
+ // no @import matched in current data
75
+ shared.done.push(data);
76
+ return processNext(options);
77
+ };
78
+
79
+ var processNext = function(options) {
80
+ if (options._shared.left.length > 0)
81
+ return process.apply(null, options._shared.left.shift());
82
+ else
83
+ return options.whenDone(options._shared.done.join(''));
84
+ };
85
+
86
+ var commentScanner = function(data) {
87
+ var commentRegex = /(\/\*(?!\*\/)[\s\S]*?\*\/)/;
88
+ var lastStartIndex = 0;
89
+ var lastEndIndex = 0;
90
+ var noComments = false;
91
+
92
+ // test whether an index is located within a comment
93
+ var scanner = function(idx) {
94
+ var comment;
95
+ var localStartIndex = 0;
96
+ var localEndIndex = 0;
97
+ var globalStartIndex = 0;
98
+ var globalEndIndex = 0;
99
+
100
+ // return if we know there are no more comments
101
+ if (noComments)
102
+ return false;
103
+
104
+ // idx can be still within last matched comment (many @import statements inside one comment)
105
+ if (idx > lastStartIndex && idx < lastEndIndex)
106
+ return true;
107
+
108
+ comment = data.match(commentRegex);
109
+
110
+ if (!comment) {
111
+ noComments = true;
112
+ return false;
113
+ }
114
+
115
+ // get the indexes relative to the current data chunk
116
+ lastStartIndex = localStartIndex = comment.index;
117
+ localEndIndex = localStartIndex + comment[0].length;
118
+
119
+ // calculate the indexes relative to the full original data
120
+ globalEndIndex = localEndIndex + lastEndIndex;
121
+ globalStartIndex = globalEndIndex - comment[0].length;
122
+
123
+ // chop off data up to and including current comment block
124
+ data = data.substring(localEndIndex);
125
+ lastEndIndex = globalEndIndex;
126
+
127
+ // re-run scan if comment ended before the idx
128
+ if (globalEndIndex < idx)
129
+ return scanner(idx);
130
+
131
+ return globalEndIndex > idx && idx > globalStartIndex;
132
+ };
133
+
134
+ return scanner;
135
+ };
136
+
137
+ var contentScanner = function(data) {
138
+ var isComment = commentScanner(data);
139
+ var firstContentIdx = -1;
140
+ while (true) {
141
+ firstContentIdx = data.indexOf('{', firstContentIdx + 1);
142
+ if (firstContentIdx == -1 || !isComment(firstContentIdx))
143
+ break;
144
+ }
145
+
146
+ return function(idx) {
147
+ return firstContentIdx > -1 ?
148
+ idx > firstContentIdx :
149
+ false;
150
+ };
151
+ };
152
+
153
+ var inline = function(data, nextStart, nextEnd, options) {
154
+ options.shallow = data.indexOf('@shallow') > 0;
155
+
156
+ var importDeclaration = data
157
+ .substring(data.indexOf(' ', nextStart) + 1, nextEnd)
158
+ .replace(/@shallow\)$/, ')')
159
+ .trim();
160
+
161
+ var viaUrl = importDeclaration.indexOf('url(') === 0;
162
+ var urlStartsAt = viaUrl ? 4 : 0;
163
+ var isQuoted = /^['"]/.exec(importDeclaration.substring(urlStartsAt, urlStartsAt + 2));
164
+ var urlEndsAt = isQuoted ?
165
+ importDeclaration.indexOf(isQuoted[0], urlStartsAt + 1) :
166
+ importDeclaration.split(' ')[0].length;
167
+
168
+ var importedFile = importDeclaration
169
+ .substring(urlStartsAt, urlEndsAt)
170
+ .replace(/['"]/g, '')
171
+ .replace(/\)$/, '')
172
+ .trim();
173
+
174
+ var mediaQuery = importDeclaration
175
+ .substring(urlEndsAt + 1)
176
+ .replace(/^\)/, '')
177
+ .trim();
178
+
179
+ var isRemote = options.isRemote ||
180
+ /^(http|https):\/\//.test(importedFile) ||
181
+ /^\/\//.test(importedFile);
182
+
183
+ if (options.localOnly && isRemote) {
184
+ context.warnings.push('Ignoring remote @import declaration of "' + importedFile + '" as no callback given.');
185
+ restoreImport(importedFile, mediaQuery, options);
186
+
187
+ return processNext(options);
188
+ }
189
+
190
+ var method = isRemote ? inlineRemoteResource : inlineLocalResource;
191
+ return method(importedFile, mediaQuery, options);
192
+ };
193
+
194
+ var inlineRemoteResource = function(importedFile, mediaQuery, options) {
195
+ var importedUrl = /^https?:\/\//.test(importedFile) ?
196
+ importedFile :
197
+ url.resolve(options.relativeTo, importedFile);
198
+
199
+ if (importedUrl.indexOf('//') === 0)
200
+ importedUrl = 'http:' + importedUrl;
201
+
202
+ if (options.visited.indexOf(importedUrl) > -1)
203
+ return processNext(options);
204
+
205
+
206
+ if (context.debug)
207
+ console.error('Inlining remote stylesheet: ' + importedUrl);
208
+
209
+ options.visited.push(importedUrl);
210
+
211
+ var get = importedUrl.indexOf('http://') === 0 ?
212
+ http.get :
213
+ https.get;
214
+
215
+ var timedOut = false;
216
+ var handleError = function(message) {
217
+ context.errors.push('Broken @import declaration of "' + importedUrl + '" - ' + message);
218
+ restoreImport(importedUrl, mediaQuery, options);
219
+
220
+ processNext(options);
221
+ };
222
+ var requestOptions = merge(url.parse(importedUrl), inlinerOptions.request);
223
+
224
+ get(requestOptions, function(res) {
225
+ if (res.statusCode < 200 || res.statusCode > 399) {
226
+ return handleError('error ' + res.statusCode);
227
+ } else if (res.statusCode > 299) {
228
+ var movedUrl = url.resolve(importedUrl, res.headers.location);
229
+ return inlineRemoteResource(movedUrl, mediaQuery, options);
230
+ }
231
+
232
+ var chunks = [];
233
+ var parsedUrl = url.parse(importedUrl);
234
+ res.on('data', function(chunk) {
235
+ chunks.push(chunk.toString());
236
+ });
237
+ res.on('end', function() {
238
+ var importedData = chunks.join('');
239
+ importedData = UrlRewriter.process(importedData, { toBase: importedUrl });
240
+
241
+ if (mediaQuery.length > 0)
242
+ importedData = '@media ' + mediaQuery + '{' + importedData + '}';
243
+
244
+ process(importedData, {
245
+ isRemote: true,
246
+ relativeTo: parsedUrl.protocol + '//' + parsedUrl.host,
247
+ _shared: options._shared,
248
+ whenDone: options.whenDone,
249
+ visited: options.visited,
250
+ shallow: options.shallow
251
+ });
252
+ });
253
+ })
254
+ .on('error', function(res) {
255
+ handleError(res.message);
256
+ })
257
+ .on('timeout', function() {
258
+ // FIX: node 0.8 fires this event twice
259
+ if (timedOut)
260
+ return;
261
+
262
+ handleError('timeout');
263
+ timedOut = true;
264
+ })
265
+ .setTimeout(inlinerOptions.timeout);
266
+ };
267
+
268
+ var inlineLocalResource = function(importedFile, mediaQuery, options) {
269
+ var relativeTo = importedFile[0] == '/' ?
270
+ options.root :
271
+ options.relativeTo;
272
+
273
+ var fullPath = path.resolve(path.join(relativeTo, importedFile));
274
+
275
+ if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
276
+ context.errors.push('Broken @import declaration of "' + importedFile + '"');
277
+ return processNext(options);
278
+ }
279
+
280
+ if (options.visited.indexOf(fullPath) > -1)
281
+ return processNext(options);
282
+
283
+
284
+ if (context.debug)
285
+ console.error('Inlining local stylesheet: ' + fullPath);
286
+
287
+ options.visited.push(fullPath);
288
+
289
+ var importedData = fs.readFileSync(fullPath, 'utf8');
290
+ var importRelativeTo = path.dirname(fullPath);
291
+ importedData = UrlRewriter.process(importedData, {
292
+ relative: true,
293
+ fromBase: importRelativeTo,
294
+ toBase: options._baseRelativeTo
295
+ });
296
+
297
+ if (mediaQuery.length > 0)
298
+ importedData = '@media ' + mediaQuery + '{' + importedData + '}';
299
+
300
+ return process(importedData, {
301
+ root: options.root,
302
+ relativeTo: importRelativeTo,
303
+ _baseRelativeTo: options._baseRelativeTo,
304
+ _shared: options._shared,
305
+ visited: options.visited,
306
+ whenDone: options.whenDone,
307
+ localOnly: options.localOnly,
308
+ shallow: options.shallow
309
+ });
310
+ };
311
+
312
+ var restoreImport = function(importedUrl, mediaQuery, options) {
313
+ var restoredImport = '@import url(' + importedUrl + ')' + (mediaQuery.length > 0 ? ' ' + mediaQuery : '') + ';';
314
+ options._shared.done.push(restoredImport);
315
+ };
316
+
317
+ // Inlines all imports taking care of repetitions, unknown files, and circular dependencies
318
+ return { process: process };
319
+ };