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,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
+ };