rapper 0.0.1

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 (90) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +24 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.markdown +140 -0
  7. data/Rakefile +45 -0
  8. data/VERSION +1 -0
  9. data/lib/rapper.rb +66 -0
  10. data/lib/rapper/compressors.rb +47 -0
  11. data/lib/rapper/config.rb +95 -0
  12. data/lib/rapper/errors.rb +23 -0
  13. data/lib/rapper/logging.rb +35 -0
  14. data/lib/rapper/utils.rb +106 -0
  15. data/lib/rapper/versioning.rb +37 -0
  16. data/lib/yui/css_compressor.rb +279 -0
  17. data/rapper.gemspec +167 -0
  18. data/spec/fixtures/config/assets.yml +25 -0
  19. data/spec/fixtures/config/assets/base/javascripts.yml +12 -0
  20. data/spec/fixtures/config/assets/base/stylesheets.yml +10 -0
  21. data/spec/fixtures/config/assets/base/validators.yml +9 -0
  22. data/spec/fixtures/javascripts/simple_1.js +5 -0
  23. data/spec/fixtures/javascripts/simple_2.js +5 -0
  24. data/spec/fixtures/stylesheets/simple_1.css +4 -0
  25. data/spec/fixtures/stylesheets/simple_2.css +4 -0
  26. data/spec/fixtures/test_cases/concatenation/assets.yml +5 -0
  27. data/spec/fixtures/test_cases/concatenation/definitions/css.yml +15 -0
  28. data/spec/fixtures/test_cases/concatenation/definitions/js.yml +15 -0
  29. data/spec/fixtures/test_cases/concatenation/expected/base.css +8 -0
  30. data/spec/fixtures/test_cases/concatenation/expected/base.js +10 -0
  31. data/spec/fixtures/test_cases/concatenation/expected/base_reversed.css +8 -0
  32. data/spec/fixtures/test_cases/concatenation/expected/base_reversed.js +10 -0
  33. data/spec/fixtures/yui_css/background-position.css +2 -0
  34. data/spec/fixtures/yui_css/background-position.css.min +1 -0
  35. data/spec/fixtures/yui_css/box-model-hack.css +9 -0
  36. data/spec/fixtures/yui_css/box-model-hack.css.min +1 -0
  37. data/spec/fixtures/yui_css/bug2527974.css +9 -0
  38. data/spec/fixtures/yui_css/bug2527974.css.min +1 -0
  39. data/spec/fixtures/yui_css/bug2527991.css +19 -0
  40. data/spec/fixtures/yui_css/bug2527991.css.min +1 -0
  41. data/spec/fixtures/yui_css/bug2527998.css +4 -0
  42. data/spec/fixtures/yui_css/bug2527998.css.min +1 -0
  43. data/spec/fixtures/yui_css/bug2528034.css +5 -0
  44. data/spec/fixtures/yui_css/bug2528034.css.min +1 -0
  45. data/spec/fixtures/yui_css/charset-media.css +9 -0
  46. data/spec/fixtures/yui_css/charset-media.css.min +1 -0
  47. data/spec/fixtures/yui_css/color.css +7 -0
  48. data/spec/fixtures/yui_css/color.css.min +1 -0
  49. data/spec/fixtures/yui_css/comment.css +3 -0
  50. data/spec/fixtures/yui_css/comment.css.min +1 -0
  51. data/spec/fixtures/yui_css/concat-charset.css +15 -0
  52. data/spec/fixtures/yui_css/concat-charset.css.min +1 -0
  53. data/spec/fixtures/yui_css/decimals.css +3 -0
  54. data/spec/fixtures/yui_css/decimals.css.min +1 -0
  55. data/spec/fixtures/yui_css/dollar-header.css +7 -0
  56. data/spec/fixtures/yui_css/dollar-header.css.min +3 -0
  57. data/spec/fixtures/yui_css/font-face.css +6 -0
  58. data/spec/fixtures/yui_css/font-face.css.min +1 -0
  59. data/spec/fixtures/yui_css/ie5mac.css +5 -0
  60. data/spec/fixtures/yui_css/ie5mac.css.min +1 -0
  61. data/spec/fixtures/yui_css/media-empty-class.css +16 -0
  62. data/spec/fixtures/yui_css/media-empty-class.css.min +1 -0
  63. data/spec/fixtures/yui_css/media-multi.css +5 -0
  64. data/spec/fixtures/yui_css/media-multi.css.min +1 -0
  65. data/spec/fixtures/yui_css/media-test.css +5 -0
  66. data/spec/fixtures/yui_css/media-test.css.min +1 -0
  67. data/spec/fixtures/yui_css/opacity-filter.css +14 -0
  68. data/spec/fixtures/yui_css/opacity-filter.css.min +1 -0
  69. data/spec/fixtures/yui_css/preserve-new-line.css +6 -0
  70. data/spec/fixtures/yui_css/preserve-new-line.css.min +3 -0
  71. data/spec/fixtures/yui_css/preserve-strings.css +7 -0
  72. data/spec/fixtures/yui_css/preserve-strings.css.min +1 -0
  73. data/spec/fixtures/yui_css/preserve_string.css +7 -0
  74. data/spec/fixtures/yui_css/preserve_string.css.min +1 -0
  75. data/spec/fixtures/yui_css/pseudo-first.css +16 -0
  76. data/spec/fixtures/yui_css/pseudo-first.css.min +1 -0
  77. data/spec/fixtures/yui_css/pseudo.css +4 -0
  78. data/spec/fixtures/yui_css/pseudo.css.min +1 -0
  79. data/spec/fixtures/yui_css/special-comments.css +13 -0
  80. data/spec/fixtures/yui_css/special-comments.css.min +9 -0
  81. data/spec/fixtures/yui_css/star-underscore-hacks.css +5 -0
  82. data/spec/fixtures/yui_css/star-underscore-hacks.css.min +1 -0
  83. data/spec/fixtures/yui_css/string-in-comment.css +8 -0
  84. data/spec/fixtures/yui_css/string-in-comment.css.min +1 -0
  85. data/spec/fixtures/yui_css/zeros.css +6 -0
  86. data/spec/fixtures/yui_css/zeros.css.min +1 -0
  87. data/spec/rapper_spec.rb +139 -0
  88. data/spec/spec_helper.rb +57 -0
  89. data/spec/vendor_spec.rb +36 -0
  90. metadata +349 -0
@@ -0,0 +1,23 @@
1
+ module Rapper
2
+ # Common Rapper errors.
3
+ module Errors
4
+
5
+ # Raised when an invalid environment param is used. An invalid environment
6
+ # is one not defined in the current rapper config.
7
+ class InvalidEnvironment < StandardError; end
8
+
9
+ # Raised when no "definition_root" setting is given for the current
10
+ # environment.
11
+ class NoDefinitionRoot < StandardError; end
12
+
13
+ # Raised when an invalid definition type param is used. An invalid
14
+ # definition type is one that doesn't have a definition YAML file in the
15
+ # "definition_root" folder (set in the config).
16
+ class InvalidDefinitionType < StandardError; end
17
+
18
+ # Raised when an invalid asset name param is used. An invalid asset name
19
+ # is one not defined in a given definition file.
20
+ class InvalidAssetName < StandardError; end
21
+
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ module Rapper
2
+ # Basic logging support.
3
+ module Logging
4
+
5
+ protected
6
+
7
+ PATHY_STRING = /\.|\//
8
+
9
+ # Outputs all arguments (joined with spaces) to:
10
+ # * <code>stdout</code> if "log" is set to "stdout" in the environment
11
+ # configuration.
12
+ # * a text file if "log" appears to be a file path (i.e. has a slash or
13
+ # period in it).
14
+ #
15
+ # @param [Symbol] level Log level. :info or :verbose. :verbose level log
16
+ # messages are only emitted if the "verbose_logging" setting is truthy.
17
+ #
18
+ # @param [String] message Message to be logged.
19
+ def log( level, message )
20
+ return unless destination = get_config( "log" )
21
+ return if !get_config( "log_verbose" ) && level == :verbose
22
+
23
+ case destination
24
+ when PATHY_STRING
25
+ open( destination, "a" ) do |file|
26
+ file.puts "#{message}\n"
27
+ end
28
+ when "stdout"
29
+ puts message
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,106 @@
1
+ module Rapper
2
+ # Rapper-wide utility methods for working with paths, files, etc.
3
+ module Utils
4
+
5
+ protected
6
+
7
+ # =========
8
+ # = Paths =
9
+ # =========
10
+
11
+ # @param [String] type Asset type.
12
+ #
13
+ # @return [String] Path to the definition file for the given asset type.
14
+ def definition_path( type )
15
+ File.join( env_config["definition_root"], "#{type}.yml")
16
+ end
17
+
18
+ # @param [String] type Asset type.
19
+ #
20
+ # @param [String] name Name of the asset.
21
+ #
22
+ # @return [String] Path to the packaged asset file for the given type and
23
+ # name.
24
+ def asset_path( type, name )
25
+ if @definitions[type].nil?
26
+ raise Rapper::Errors::InvalidDefinitionType,
27
+ "'#{type}' is not a defined asset type."
28
+ end
29
+
30
+ file_name = "#{name}.#{@definitions[type]["suffix"]}"
31
+
32
+ File.join( @definitions[type]["destination_root"], file_name )
33
+ end
34
+
35
+ # @param [String] type Asset type.
36
+ #
37
+ # @param [String] name Name of the asset.
38
+ #
39
+ # @return [Array<String>] Ordered list of asset component file paths.
40
+ def asset_component_paths( type, name )
41
+ if @definitions[type].nil?
42
+ raise Rapper::Errors::InvalidDefinitionType,
43
+ "'#{type}' is not a defined asset type."
44
+ end
45
+
46
+ spec = first_hash_with_key( name, @definitions[type]["assets"] )
47
+
48
+ if spec.nil?
49
+ raise Rapper::Errors::InvalidAssetName,
50
+ "'#{name}' is not a valid #{type} asset. Make sure it is defined in the definition file."
51
+ end
52
+
53
+ source_root = @definitions[type]["source_root"]
54
+ suffix = @definitions[type]["suffix"]
55
+ files = first_hash_with_key( "files", spec.values.first )["files"] || []
56
+
57
+ files.map do |file|
58
+ file_name = "#{file}.#{suffix}"
59
+ File.join( source_root, file_name )
60
+ end
61
+ end
62
+
63
+ # =========
64
+ # = Files =
65
+ # =========
66
+
67
+ # @param [String] path Path to the desired file.
68
+ #
69
+ # @return [File] Readable File instance.
70
+ def readable_file( path )
71
+ File.new( path, 'r' )
72
+ end
73
+
74
+ # @param [String] path Path to the desired file.
75
+ #
76
+ # @return [File] Writable file instance with 0644 permissions.
77
+ def writable_file( path )
78
+ File.new( path, 'w', 0644 )
79
+ end
80
+
81
+ # Contatenate one or more files. Uses <code>cat</code>.
82
+ #
83
+ # @param [Array<String>,String] source_files A path or array of paths to
84
+ # files to concatenate.
85
+ #
86
+ # @param [String] destination_file Destination for concatenated output.
87
+ def join_files( source_files, destination_file )
88
+ source_files = Array( source_files ).uniq.join( " " )
89
+ system "cat #{source_files} > #{destination_file}"
90
+ end
91
+
92
+ # =========
93
+ # = Misc. =
94
+ # =========
95
+
96
+ # @param [Object] key Key to search for.
97
+ #
98
+ # @param [Array<Hash>] array Array of Hash object to search in.
99
+ #
100
+ # @return [Hash] First hash with the given key.
101
+ def first_hash_with_key( key, array )
102
+ array.find { |h| h.keys.include? key }
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,37 @@
1
+ require 'digest/md5'
2
+
3
+ module Rapper
4
+ # Asset versioning methods.
5
+ module Versioning
6
+
7
+ protected
8
+
9
+ # Refresh the version hashes for the given asset types. If no arguments are
10
+ # passed, version hashes for all asset types will be updated.
11
+ #
12
+ # @param [<String>] types Asset types to refresh versions for.
13
+ def refresh_versions( *types )
14
+ types = types.empty? ? asset_types : types
15
+
16
+ log :info, "Refreshing versions for #{types.join( ', ' )}"
17
+
18
+ types.each do |type|
19
+ @definitions[type]["assets"].each do |asset|
20
+ name = asset.keys.first
21
+ spec = asset.values.first
22
+ path = asset_path( type, name )
23
+ version = version( path )
24
+ first_hash_with_key( "version", spec )["version"] = version
25
+ end
26
+ end
27
+ end
28
+
29
+ # @param [String] file_path The path to a file to generate a version for.
30
+ #
31
+ # @return [String] A four-character MD5 hash of the contents of the file.
32
+ def version( file_path )
33
+ Digest::MD5.file( file_path ).to_s[0,4]
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,279 @@
1
+ # Source: https://github.com/rhulse/ruby-css-toolkit
2
+ #
3
+ # Only minor modifications to module structure, code style, and comments have
4
+ # been made.
5
+ #
6
+ # Original license:
7
+ # =================
8
+ #
9
+ # Ruby CSS Compressor Copyright License Agreement (BSD License)
10
+ #
11
+ # Copyright (c) 2010, Richard Hulse
12
+ # All rights reserved.
13
+ #
14
+ # YUI CSS files in the test suite are Copyright (c) 2009, Yahoo! Inc. All rights reserved.
15
+ #
16
+ # Redistribution and use of this software in source and binary forms,
17
+ # with or without modification, are permitted provided that the following
18
+ # conditions are met:
19
+ #
20
+ # * Redistributions of source code must retain the above
21
+ # copyright notice, this list of conditions and the
22
+ # following disclaimer.
23
+ #
24
+ # * Redistributions in binary form must reproduce the above
25
+ # copyright notice, this list of conditions and the
26
+ # following disclaimer in the documentation and/or other
27
+ # materials provided with the distribution.
28
+ #
29
+ # * Neither the name of Yahoo! Inc. nor the names of its
30
+ # contributors may be used to endorse or promote products
31
+ # derived from this software without specific prior
32
+ # written permission of Yahoo! Inc.
33
+ #
34
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
35
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
36
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
37
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
38
+ # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
39
+ # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
40
+ # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
41
+ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
42
+ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
43
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44
+
45
+ module YUI
46
+ # Methods for working with CSS.
47
+ class CSS
48
+
49
+ class << self
50
+
51
+ attr_reader :input_size, :output_size
52
+
53
+ # Compress CSS using the Ruby port of the YUI Compressor (by
54
+ # <a href="https://github.com/rhulse">Richard Hulse</a>).
55
+ #
56
+ # @param [String] css The CSS to be compressed.
57
+ #
58
+ # @param [Integer] line_length (optional) Number of columns to wrap to.
59
+ # Default is no wrapping.
60
+ #
61
+ # @return [String] Compressed CSS.
62
+ def compress(css, line_length=0)
63
+ @preservedTokens = []
64
+ @comments = []
65
+ @output_size = 0
66
+ @input_size = css.length
67
+
68
+ css = process_comments_and_strings(css)
69
+
70
+ # Normalize all whitespace strings to single spaces. Easier to work with that way.
71
+ css.gsub!(/\s+/, ' ')
72
+
73
+ # Remove the spaces before the things that should not have spaces before them.
74
+ # But, be careful not to turn "p :link {...}" into "p:link{...}"
75
+ # Swap out any pseudo-class colons with the token, and then swap back.
76
+ css.gsub!(/(?:^|\})[^\{:]+\s+:+[^\{]*\{/) do |match|
77
+ match.gsub(':', '___PSEUDOCLASSCOLON___')
78
+ end
79
+ css.gsub!(/\s+([!\{\};:>+\(\)\],])/, '\1')
80
+ css.gsub!(/([!\{\}:;>+\(\[,])\s+/, '\1')
81
+ css.gsub!('___PSEUDOCLASSCOLON___', ':')
82
+
83
+ # special case for IE
84
+ css.gsub!(/:first-(line|letter)(\{|,)/, ':first-\1 \2')
85
+
86
+ # no space after the end of a preserved comment
87
+ css.gsub!(/\*\/ /, '*/')
88
+
89
+ # If there is a @charset, then only allow one, and push to the top of the file.
90
+ css.gsub!(/^(.*)(@charset "[^"]*";)/i, '\2\1')
91
+ css.gsub!(/^(\s*@charset [^;]+;\s*)+/i, '\1')
92
+
93
+ # Put the space back in some cases, to support stuff like
94
+ # @media screen and (-webkit-min-device-pixel-ratio:0){
95
+ css.gsub!(/\band\(/i, "and (")
96
+
97
+ # remove unnecessary semicolons
98
+ css.gsub!(/;+\}/, '}')
99
+
100
+ # Replace 0(%, em, ex, px, in, cm, mm, pt, pc) with just 0.
101
+ css.gsub!(/([\s:])([+-]?0)(?:%|em|ex|px|in|cm|mm|pt|pc)/i, '\1\2')
102
+
103
+ # Replace 0 0 0 0; with 0.
104
+ css.gsub!(/:(?:0 )+0(;|\})/, ':0\1')
105
+
106
+ # Restore background-position:0 0; if required
107
+ css.gsub!(/background-position:0(;|\})/i, 'background-position:0 0\1')
108
+
109
+ # Replace 0.6 with .6, but only when preceded by : or a space.
110
+ css.gsub!(/(:|\s)0+\.(\d+)/, '\1.\2')
111
+
112
+ # Shorten colors from rgb(51,102,153) to #336699
113
+ # This makes it more likely that it'll get further compressed in the next step.
114
+ css.gsub!(/rgb\s*\(\s*([0-9,\s]+)\s*\)/) do |match|
115
+ '#' << $1.scan(/\d+/).map{|n| n.to_i.to_s(16).rjust(2, '0') }.join
116
+ end
117
+
118
+ # Shorten colors from #AABBCC to #ABC. Note that we want to make sure
119
+ # the color is not preceded by either ", " or =. Indeed, the property
120
+ # filter: chroma(color="#FFFFFF");
121
+ # would become
122
+ # filter: chroma(color="#FFF");
123
+ # which makes the filter break in IE.
124
+ css.gsub!(/([^"'=\s])(\s?)\s*#([0-9a-f])\3([0-9a-f])\4([0-9a-f])\5/i, '\1\2#\3\4\5')
125
+
126
+ # shorter opacity IE filter
127
+ css.gsub!(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i, "alpha(opacity=")
128
+
129
+ # Remove empty rules.
130
+ css.gsub!(/[^\};\{\/]+\{\}/, '')
131
+
132
+ if (line_length > 0)
133
+ # Some source control tools don't like it when files containing lines longer
134
+ # than, say 8000 characters, are checked in. The linebreak option is used in
135
+ # that case to split long lines after a specific column.
136
+ startIndex = 0
137
+ index = 0
138
+ length = css.length
139
+ while (index < length)
140
+ index += 1
141
+ if (css[index - 1,1] === '}' && index - startIndex > line_length)
142
+ css = css.slice(0, index) + "\n" + css.slice(index, length)
143
+ startIndex = index
144
+ end
145
+ end
146
+ end
147
+
148
+ # Replace multiple semi-colons in a row by a single one
149
+ # See SF bug #1980989
150
+ css.gsub!(/[;]+/, ';')
151
+
152
+ #restore preserved comments and strings
153
+ css = restore_preserved_comments_and_strings(css)
154
+
155
+ # top and tail whitespace
156
+ css.strip!
157
+
158
+ @output_size = css.length
159
+ css
160
+ end
161
+
162
+ private
163
+
164
+ # Process comments (preserve special comments, nuke the rest) and strings
165
+ # (preserve them).
166
+ def process_comments_and_strings(css_text)
167
+ css = css_text.clone
168
+
169
+ startIndex = 0
170
+ endIndex = 0
171
+ i = 0
172
+ max = 0
173
+ token = ''
174
+ totallen = css.length
175
+ placeholder = ''
176
+
177
+ # collect all comment blocks
178
+ while (startIndex = css.index(/\/\*/, startIndex))
179
+ endIndex = css.index(/\*\//, startIndex + 2)
180
+ unless endIndex
181
+ endIndex = totallen
182
+ end
183
+ token = css.slice(startIndex+2..endIndex-1)
184
+ @comments.push(token)
185
+ css = css.slice(0..startIndex+1).to_s + "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (@comments.length - 1).to_s + "___" + css.slice(endIndex, totallen).to_s
186
+ startIndex += 2
187
+ end
188
+
189
+ # preserve strings so their content doesn't get accidentally minified
190
+ css.gsub!(/("([^\\"]|\\.|\\)*")|('([^\\']|\\.|\\)*')/) do |match|
191
+ quote = match[0,1]
192
+ string = match.slice(1..-2)
193
+
194
+ # maybe the string contains a comment-like substring?
195
+ # one, maybe more? put'em back then
196
+ if string =~ /___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_/
197
+ @comments.each_index do |index|
198
+ string.gsub!(/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_#{index.to_s}___/, @comments[index])
199
+ end
200
+ end
201
+
202
+ # minify alpha opacity in filter strings
203
+ string.gsub!(/progid:DXImageTransform\.Microsoft\.Alpha\(Opacity=/i, "alpha(opacity=")
204
+ @preservedTokens.push(string)
205
+
206
+ quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___" + quote
207
+ end
208
+
209
+ # used to jump one index in loop
210
+ ie5_hack = false
211
+ # strings are safe, now wrestle the comments
212
+ @comments.each_index do |index|
213
+ if ie5_hack
214
+ ie5_hack = false
215
+ next
216
+ end
217
+
218
+ token = @comments[index]
219
+ placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + index.to_s + "___"
220
+
221
+ # ! in the first position of the comment means preserve
222
+ # so push to the preserved tokens keeping the !
223
+ if (token[0,1] === "!")
224
+ @preservedTokens.push(token)
225
+ css.gsub!( /#{placeholder}/i, "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___")
226
+ next
227
+ end
228
+
229
+ # \ in the last position looks like hack for Mac/IE5
230
+ # shorten that to /*\*/ and the next one to /**/
231
+ if (token[-1,1] === "\\")
232
+ @preservedTokens.push("\\")
233
+ css.gsub!( /#{placeholder}/, "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___")
234
+ # keep the next comment but remove its content
235
+ @preservedTokens.push("")
236
+ css.gsub!(/___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_#{index+1}___/, "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___")
237
+ ie5_hack = true
238
+ next
239
+ end
240
+
241
+ # keep empty comments after child selectors (IE7 hack)
242
+ # e.g. html >/**/ body
243
+ if ((token.length === 0) && (startIndex = css.index( /#{placeholder}/)))
244
+ if (startIndex > 2)
245
+ if (css[startIndex - 3,1] === '>')
246
+ @preservedTokens.push("")
247
+ css.gsub!(/#{placeholder}/, "___YUICSSMIN_PRESERVED_TOKEN_" + (@preservedTokens.length - 1).to_s + "___")
248
+ end
249
+ end
250
+ end
251
+
252
+ # in all other cases kill the comment
253
+ css.gsub!( /\/\*#{placeholder}\*\//, "")
254
+ end
255
+
256
+ css
257
+ end
258
+
259
+ # Restore @preservedTokens back in to the css.
260
+ def restore_preserved_comments_and_strings(clean_css)
261
+ css = clean_css.clone
262
+ css_length = css.length
263
+ @preservedTokens.each_index do |index|
264
+ # slice these back into place rather than regex, because
265
+ # complex nested strings cause the replacement to fail
266
+ placeholder = "___YUICSSMIN_PRESERVED_TOKEN_#{index}___"
267
+ startIndex = css.index(placeholder, 0)
268
+ next unless startIndex # skip if nil
269
+ endIndex = startIndex + placeholder.length
270
+
271
+ css = css.slice(0..startIndex-1).to_s + @preservedTokens[index] + css.slice(endIndex, css_length).to_s
272
+ end
273
+
274
+ css
275
+ end
276
+ end
277
+ end
278
+
279
+ end