sass 3.4.0 → 3.4.25

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/CONTRIBUTING.md +148 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +26 -20
  7. data/Rakefile +103 -20
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/extra/sass-spec-ref.sh +32 -0
  11. data/extra/update_watch.rb +1 -1
  12. data/lib/sass/cache_stores/filesystem.rb +7 -7
  13. data/lib/sass/cache_stores/memory.rb +4 -5
  14. data/lib/sass/callbacks.rb +2 -2
  15. data/lib/sass/css.rb +11 -10
  16. data/lib/sass/deprecation.rb +55 -0
  17. data/lib/sass/engine.rb +83 -38
  18. data/lib/sass/environment.rb +26 -2
  19. data/lib/sass/error.rb +12 -12
  20. data/lib/sass/exec/base.rb +15 -3
  21. data/lib/sass/exec/sass_convert.rb +34 -15
  22. data/lib/sass/exec/sass_scss.rb +23 -7
  23. data/lib/sass/features.rb +2 -2
  24. data/lib/sass/importers/base.rb +1 -1
  25. data/lib/sass/importers/deprecated_path.rb +51 -0
  26. data/lib/sass/importers/filesystem.rb +24 -16
  27. data/lib/sass/importers.rb +1 -0
  28. data/lib/sass/logger/base.rb +8 -2
  29. data/lib/sass/logger/delayed.rb +50 -0
  30. data/lib/sass/logger.rb +8 -3
  31. data/lib/sass/plugin/compiler.rb +42 -25
  32. data/lib/sass/plugin/configuration.rb +38 -22
  33. data/lib/sass/plugin/merb.rb +2 -2
  34. data/lib/sass/plugin/rack.rb +3 -3
  35. data/lib/sass/plugin/rails.rb +1 -1
  36. data/lib/sass/plugin/staleness_checker.rb +3 -3
  37. data/lib/sass/plugin.rb +3 -2
  38. data/lib/sass/script/css_parser.rb +2 -3
  39. data/lib/sass/script/css_variable_warning.rb +52 -0
  40. data/lib/sass/script/functions.rb +140 -73
  41. data/lib/sass/script/lexer.rb +37 -22
  42. data/lib/sass/script/parser.rb +235 -40
  43. data/lib/sass/script/tree/funcall.rb +12 -5
  44. data/lib/sass/script/tree/interpolation.rb +109 -4
  45. data/lib/sass/script/tree/list_literal.rb +31 -4
  46. data/lib/sass/script/tree/literal.rb +4 -0
  47. data/lib/sass/script/tree/node.rb +21 -3
  48. data/lib/sass/script/tree/operation.rb +54 -1
  49. data/lib/sass/script/tree/string_interpolation.rb +58 -37
  50. data/lib/sass/script/tree/variable.rb +1 -1
  51. data/lib/sass/script/value/base.rb +10 -9
  52. data/lib/sass/script/value/color.rb +42 -24
  53. data/lib/sass/script/value/helpers.rb +16 -6
  54. data/lib/sass/script/value/map.rb +1 -1
  55. data/lib/sass/script/value/number.rb +52 -19
  56. data/lib/sass/script/value/string.rb +46 -5
  57. data/lib/sass/script.rb +3 -3
  58. data/lib/sass/scss/css_parser.rb +16 -2
  59. data/lib/sass/scss/parser.rb +120 -75
  60. data/lib/sass/scss/rx.rb +9 -10
  61. data/lib/sass/scss/static_parser.rb +19 -14
  62. data/lib/sass/scss.rb +0 -2
  63. data/lib/sass/selector/abstract_sequence.rb +8 -6
  64. data/lib/sass/selector/comma_sequence.rb +25 -9
  65. data/lib/sass/selector/pseudo.rb +45 -35
  66. data/lib/sass/selector/sequence.rb +54 -18
  67. data/lib/sass/selector/simple.rb +11 -11
  68. data/lib/sass/selector/simple_sequence.rb +34 -15
  69. data/lib/sass/selector.rb +7 -10
  70. data/lib/sass/shared.rb +1 -1
  71. data/lib/sass/source/map.rb +7 -4
  72. data/lib/sass/source/position.rb +4 -4
  73. data/lib/sass/stack.rb +2 -2
  74. data/lib/sass/supports.rb +8 -10
  75. data/lib/sass/tree/comment_node.rb +1 -1
  76. data/lib/sass/tree/css_import_node.rb +9 -1
  77. data/lib/sass/tree/function_node.rb +8 -3
  78. data/lib/sass/tree/import_node.rb +6 -5
  79. data/lib/sass/tree/node.rb +5 -3
  80. data/lib/sass/tree/prop_node.rb +5 -6
  81. data/lib/sass/tree/rule_node.rb +14 -4
  82. data/lib/sass/tree/visitors/check_nesting.rb +18 -22
  83. data/lib/sass/tree/visitors/convert.rb +43 -26
  84. data/lib/sass/tree/visitors/cssize.rb +5 -1
  85. data/lib/sass/tree/visitors/deep_copy.rb +1 -1
  86. data/lib/sass/tree/visitors/extend.rb +15 -13
  87. data/lib/sass/tree/visitors/perform.rb +42 -17
  88. data/lib/sass/tree/visitors/set_options.rb +1 -1
  89. data/lib/sass/tree/visitors/to_css.rb +58 -30
  90. data/lib/sass/util/multibyte_string_scanner.rb +0 -2
  91. data/lib/sass/util/normalized_map.rb +0 -1
  92. data/lib/sass/util/subset_map.rb +1 -2
  93. data/lib/sass/util.rb +125 -68
  94. data/lib/sass/version.rb +2 -2
  95. data/lib/sass.rb +10 -3
  96. data/test/sass/compiler_test.rb +6 -2
  97. data/test/sass/conversion_test.rb +187 -53
  98. data/test/sass/css2sass_test.rb +50 -1
  99. data/test/sass/css_variable_test.rb +132 -0
  100. data/test/sass/engine_test.rb +207 -61
  101. data/test/sass/exec_test.rb +10 -0
  102. data/test/sass/extend_test.rb +101 -29
  103. data/test/sass/functions_test.rb +60 -9
  104. data/test/sass/importer_test.rb +9 -0
  105. data/test/sass/more_templates/more1.sass +10 -10
  106. data/test/sass/more_templates/more_import.sass +2 -2
  107. data/test/sass/plugin_test.rb +10 -8
  108. data/test/sass/results/script.css +3 -3
  109. data/test/sass/script_conversion_test.rb +58 -29
  110. data/test/sass/script_test.rb +430 -53
  111. data/test/sass/scss/css_test.rb +73 -7
  112. data/test/sass/scss/rx_test.rb +4 -0
  113. data/test/sass/scss/scss_test.rb +309 -4
  114. data/test/sass/source_map_test.rb +152 -74
  115. data/test/sass/superselector_test.rb +19 -0
  116. data/test/sass/templates/_partial.sass +1 -1
  117. data/test/sass/templates/basic.sass +10 -10
  118. data/test/sass/templates/bork1.sass +1 -1
  119. data/test/sass/templates/bork5.sass +1 -1
  120. data/test/sass/templates/compact.sass +10 -10
  121. data/test/sass/templates/complex.sass +187 -187
  122. data/test/sass/templates/compressed.sass +10 -10
  123. data/test/sass/templates/expanded.sass +10 -10
  124. data/test/sass/templates/import.sass +2 -2
  125. data/test/sass/templates/importee.sass +3 -3
  126. data/test/sass/templates/mixins.sass +22 -22
  127. data/test/sass/templates/multiline.sass +4 -4
  128. data/test/sass/templates/nested.sass +13 -13
  129. data/test/sass/templates/parent_ref.sass +12 -12
  130. data/test/sass/templates/script.sass +70 -70
  131. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +1 -1
  132. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +2 -2
  133. data/test/sass/templates/subdir/subdir.sass +3 -3
  134. data/test/sass/templates/units.sass +10 -10
  135. data/test/sass/util/multibyte_string_scanner_test.rb +10 -2
  136. data/test/sass/util_test.rb +15 -44
  137. data/test/sass-spec.yml +3 -0
  138. data/test/test_helper.rb +5 -4
  139. metadata +302 -295
  140. data/CONTRIBUTING +0 -3
  141. data/lib/sass/scss/script_lexer.rb +0 -15
  142. data/lib/sass/scss/script_parser.rb +0 -25
@@ -12,8 +12,10 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
12
12
  @tabs = 0
13
13
  @line = 1
14
14
  @offset = 1
15
- @result = ""
16
- @source_mapping = Sass::Source::Map.new if build_source_mapping
15
+ @result = String.new("")
16
+ @source_mapping = build_source_mapping ? Sass::Source::Map.new : nil
17
+ @lstrip = nil
18
+ @in_directive = false
17
19
  end
18
20
 
19
21
  # Runs the visitor on `node`.
@@ -51,6 +53,10 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
51
53
  @source_mapping.add(source_range, target_range)
52
54
  end
53
55
 
56
+ def trailing_semicolon?
57
+ @result.end_with?(";") && !@result.end_with?('\;')
58
+ end
59
+
54
60
  # Move the output cursor back `chars` characters.
55
61
  def erase!(chars)
56
62
  return if chars == 0
@@ -98,7 +104,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
98
104
  @lstrip = true
99
105
  yield
100
106
  ensure
101
- @lstrip = @lstrip && old_lstrip
107
+ @lstrip &&= old_lstrip
102
108
  end
103
109
 
104
110
  # Prepend `prefix` to the output string.
@@ -116,14 +122,15 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
116
122
  node.children.each do |child|
117
123
  next if child.invisible?
118
124
  visit(child)
119
- unless node.style == :compressed
120
- output "\n"
121
- if child.is_a?(Sass::Tree::DirectiveNode) && child.has_children && !child.bubbles?
122
- output "\n"
123
- end
124
- end
125
+ next if node.style == :compressed
126
+ output "\n"
127
+ next unless child.is_a?(Sass::Tree::DirectiveNode) && child.has_children && !child.bubbles?
128
+ output "\n"
125
129
  end
126
130
  rstrip!
131
+ if node.style == :compressed && trailing_semicolon?
132
+ erase! 1
133
+ end
127
134
  return "" if @result.empty?
128
135
 
129
136
  output "\n"
@@ -152,13 +159,14 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
152
159
  def visit_comment(node)
153
160
  return if node.invisible?
154
161
  spaces = (' ' * [@tabs - node.resolved_value[/^ */].size, 0].max)
162
+ output(spaces)
155
163
 
156
- content = node.resolved_value.gsub(/^/, spaces)
164
+ content = node.resolved_value.split("\n").join("\n" + spaces)
157
165
  if node.type == :silent
158
- content.gsub!(%r{^(\s*)//(.*)$}) {|md| "#{$1}/*#{$2} */"}
166
+ content.gsub!(%r{^(\s*)//(.*)$}) {"#{$1}/*#{$2} */"}
159
167
  end
160
168
  if (node.style == :compact || node.style == :compressed) && node.type != :loud
161
- content.gsub!(/\n +(\* *(?!\/))?/, ' ')
169
+ content.gsub!(%r{\n +(\* *(?!/))?}, ' ')
162
170
  end
163
171
  for_node(node) {output(content)}
164
172
  end
@@ -171,28 +179,32 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
171
179
  if !node.has_children || node.children.empty?
172
180
  output(tab_str)
173
181
  for_node(node) {output(node.resolved_value)}
174
- output(!node.has_children ? ";" : " {}")
182
+ if node.has_children
183
+ output("#{' ' unless node.style == :compressed}{}")
184
+ elsif node.children.empty?
185
+ output(";")
186
+ end
175
187
  return
176
188
  end
177
189
 
178
- @in_directive = @in_directive || !node.is_a?(Sass::Tree::MediaNode)
190
+ @in_directive ||= !node.is_a?(Sass::Tree::MediaNode)
179
191
  output(tab_str) if node.style != :compressed
180
192
  for_node(node) {output(node.resolved_value)}
181
193
  output(node.style == :compressed ? "{" : " {")
182
194
  output(node.style == :compact ? ' ' : "\n") if node.style != :compressed
183
195
 
184
- was_prop = false
196
+ had_children = true
185
197
  first = true
186
198
  node.children.each do |child|
187
199
  next if child.invisible?
188
200
  if node.style == :compact
189
201
  if child.is_a?(Sass::Tree::PropNode)
190
- with_tabs(first || was_prop ? 0 : @tabs + 1) do
202
+ with_tabs(first || !had_children ? 0 : @tabs + 1) do
191
203
  visit(child)
192
204
  output(' ')
193
205
  end
194
206
  else
195
- if was_prop
207
+ unless had_children
196
208
  erase! 1
197
209
  output "\n"
198
210
  end
@@ -206,18 +218,23 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
206
218
  rstrip!
207
219
  output "\n"
208
220
  end
209
- was_prop = child.is_a?(Sass::Tree::PropNode)
221
+ had_children = child.has_children
210
222
  first = false
211
223
  elsif node.style == :compressed
212
- output(was_prop ? ";" : "")
224
+ unless had_children
225
+ output(";") unless trailing_semicolon?
226
+ end
213
227
  with_tabs(0) {visit(child)}
214
- was_prop = child.is_a?(Sass::Tree::PropNode)
228
+ had_children = child.has_children
215
229
  else
216
230
  with_tabs(@tabs + 1) {visit(child)}
217
231
  output "\n"
218
232
  end
219
233
  end
220
234
  rstrip!
235
+ if node.style == :compressed && trailing_semicolon?
236
+ erase! 1
237
+ end
221
238
  if node.style == :expanded
222
239
  output("\n#{tab_str}")
223
240
  elsif node.style != :compressed
@@ -278,10 +295,13 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
278
295
 
279
296
  joined_rules = node.resolved_rules.members.map do |seq|
280
297
  next if seq.has_placeholder?
281
- rule_part = seq.to_s
298
+ rule_part = seq.to_s(:style => node.style)
282
299
  if node.style == :compressed
283
300
  rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
284
- rule_part.gsub!(/\s*([,+>])\s*/m, '\1')
301
+ rule_part.gsub!(/\s*([+>])\s*/m, '\1')
302
+ rule_part.gsub!(/nth([^( ]*)\(([^)]*)\)/m) do |match|
303
+ match.tr(" \t\n", "")
304
+ end
285
305
  rule_part.strip!
286
306
  end
287
307
  rule_part
@@ -306,8 +326,8 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
306
326
  relative_filename =
307
327
  if node.options[:css_filename]
308
328
  begin
309
- Sass::Util.pathname(node.filename).relative_path_from(
310
- Sass::Util.pathname(File.dirname(node.options[:css_filename]))).to_s
329
+ Sass::Util.relative_path_from(
330
+ node.filename, File.dirname(node.options[:css_filename])).to_s
311
331
  rescue ArgumentError
312
332
  nil
313
333
  end
@@ -320,7 +340,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
320
340
  end
321
341
  end
322
342
 
323
- end_props, trailer, tabs = '', '', 0
343
+ end_props, trailer, tabs = '', '', 0
324
344
  if node.style == :compact
325
345
  separator, end_props, bracket = ' ', ' ', ' { '
326
346
  trailer = "\n" if node.group_end
@@ -338,10 +358,18 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
338
358
 
339
359
  with_tabs(tabs) do
340
360
  node.children.each_with_index do |child, i|
341
- output(separator) if i > 0
361
+ if i > 0
362
+ if separator.start_with?(";") && trailing_semicolon?
363
+ erase! 1
364
+ end
365
+ output(separator)
366
+ end
342
367
  visit(child)
343
368
  end
344
369
  end
370
+ if node.style == :compressed && trailing_semicolon?
371
+ erase! 1
372
+ end
345
373
 
346
374
  output(end_props)
347
375
  output("}" + trailer)
@@ -362,10 +390,10 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
362
390
  rule = Sass::Tree::RuleNode.new([""])
363
391
  rule.resolved_rules = Sass::Selector::CommaSequence.new(
364
392
  [Sass::Selector::Sequence.new(
365
- [Sass::Selector::SimpleSequence.new(
366
- [Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)],
367
- false)
368
- ])
393
+ [Sass::Selector::SimpleSequence.new(
394
+ [Sass::Selector::Element.new(k.to_s.gsub(/[^\w-]/, "\\\\\\0"), nil)],
395
+ false)
396
+ ])
369
397
  ])
370
398
  prop = Sass::Tree::PropNode.new([""], Sass::Script::Value::String.new(''), :new)
371
399
  prop.resolved_name = "font-family"
@@ -1,9 +1,7 @@
1
1
  require 'strscan'
2
2
 
3
3
  if Sass::Util.ruby1_8?
4
- # rubocop:disable ConstantName
5
4
  Sass::Util::MultibyteStringScanner = StringScanner
6
- # rubocop:enable ConstantName
7
5
  else
8
6
  if Sass::Util.rbx?
9
7
  # Rubinius's StringScanner class implements some of its methods in terms of
@@ -1,5 +1,4 @@
1
1
  require 'delegate'
2
- require 'sass/util'
3
2
 
4
3
  module Sass
5
4
  module Util
@@ -78,8 +78,7 @@ module Sass
78
78
  next unless subset.subset?(set)
79
79
  [index, subenum]
80
80
  end
81
- end
82
- res = Sass::Util.flatten(res, 1)
81
+ end.flatten(1)
83
82
  res.compact!
84
83
  res.uniq!
85
84
  res.sort!
data/lib/sass/util.rb CHANGED
@@ -13,6 +13,8 @@ require 'sass/util/subset_map'
13
13
 
14
14
  module Sass
15
15
  # A module containing various useful functions.
16
+ # @comment
17
+ # rubocop:disable ModuleLength
16
18
  module Util
17
19
  extend self
18
20
 
@@ -138,6 +140,18 @@ module Sass
138
140
  [[value, range.first].max, range.last].min
139
141
  end
140
142
 
143
+ # Like [Fixnum.round], but leaves rooms for slight floating-point
144
+ # differences.
145
+ #
146
+ # @param value [Numeric]
147
+ # @return [Numeric]
148
+ def round(value)
149
+ # If the number is within epsilon of X.5, round up (or down for negative
150
+ # numbers).
151
+ return value.round if (value % 1) - 0.5 <= -1 * Script::Value::Number.epsilon
152
+ value > 0 ? value.ceil : value.floor
153
+ end
154
+
141
155
  # Concatenates all strings that are adjacent in an array,
142
156
  # while leaving other elements as they are.
143
157
  #
@@ -265,7 +279,7 @@ module Sass
265
279
  # # [2, 4, 5]]
266
280
  def paths(arrs)
267
281
  arrs.inject([[]]) do |paths, arr|
268
- flatten(arr.map {|e| paths.map {|path| path + [e]}}, 1)
282
+ arr.map {|e| paths.map {|path| path + [e]}}.flatten(1)
269
283
  end
270
284
  end
271
285
 
@@ -298,7 +312,7 @@ module Sass
298
312
  # @return [Array]
299
313
  def hash_to_a(hash)
300
314
  return hash.to_a unless ruby1_8? || defined?(Test::Unit)
301
- hash.sort_by {|k, v| k}
315
+ hash.sort_by {|k, _v| k}
302
316
  end
303
317
 
304
318
  # Performs the equivalent of `enum.group_by.to_a`, but with a guaranteed
@@ -325,6 +339,18 @@ module Sass
325
339
  arr
326
340
  end
327
341
 
342
+ # Like `String.upcase`, but only ever upcases ASCII letters.
343
+ def upcase(string)
344
+ return string.upcase unless ruby2_4?
345
+ string.upcase(:ascii)
346
+ end
347
+
348
+ # Like `String.downcase`, but only ever downcases ASCII letters.
349
+ def downcase(string)
350
+ return string.downcase unless ruby2_4?
351
+ string.downcase(:ascii)
352
+ end
353
+
328
354
  # Returns a sub-array of `minuend` containing only elements that are also in
329
355
  # `subtrahend`. Ensures that the return value has the same order as
330
356
  # `minuend`, even on Rubinius where that's not guaranteed by `Array#-`.
@@ -404,13 +430,13 @@ module Sass
404
430
  # Returns information about the caller of the previous method.
405
431
  #
406
432
  # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
407
- # @return [[String, Fixnum, (String, nil)]]
433
+ # @return [[String, Integer, (String, nil)]]
408
434
  # An array containing the filename, line, and method name of the caller.
409
435
  # The method name may be nil
410
436
  def caller_info(entry = nil)
411
437
  # JRuby evaluates `caller` incorrectly when it's in an actual default argument.
412
438
  entry ||= caller[1]
413
- info = entry.scan(/^(.*?):(-?.*?)(?::.*`(.+)')?$/).first
439
+ info = entry.scan(/^((?:[A-Za-z]:)?.*?):(-?.*?)(?::.*`(.+)')?$/).first
414
440
  info[1] = info[1].to_i
415
441
  # This is added by Rubinius to designate a block, but we don't care about it.
416
442
  info[2].sub!(/ \{\}\Z/, '') if info[2]
@@ -498,7 +524,7 @@ module Sass
498
524
  #
499
525
  # @param msg [String]
500
526
  def sass_warn(msg)
501
- msg = msg + "\n" unless ruby1?
527
+ msg += "\n" unless ruby1?
502
528
  Sass.logger.warn(msg)
503
529
  end
504
530
 
@@ -557,9 +583,12 @@ module Sass
557
583
  #
558
584
  # @return [Boolean]
559
585
  def listen_geq_2?
560
- return @listen_geq_2 unless @listen_geq_2.nil?
586
+ return @listen_geq_2 if defined?(@listen_geq_2)
561
587
  @listen_geq_2 =
562
588
  begin
589
+ # Make sure we're loading listen/version from the same place that
590
+ # we're loading listen itself.
591
+ load_listen!
563
592
  require 'listen/version'
564
593
  version_geq(::Listen::VERSION, '2.0.0')
565
594
  rescue LoadError
@@ -619,7 +648,7 @@ module Sass
619
648
 
620
649
  # Returns an array of ints representing the JRuby version number.
621
650
  #
622
- # @return [Array<Fixnum>]
651
+ # @return [Array<Integer>]
623
652
  def jruby_version
624
653
  @jruby_version ||= ::JRUBY_VERSION.split(".").map {|s| s.to_i}
625
654
  end
@@ -628,7 +657,7 @@ module Sass
628
657
  #
629
658
  # @param path [String]
630
659
  def glob(path)
631
- path = path.gsub('\\', '/') if windows?
660
+ path = path.tr('\\', '/') if windows?
632
661
  if block_given?
633
662
  Dir.glob(path) {|f| yield(f)}
634
663
  else
@@ -661,19 +690,77 @@ module Sass
661
690
  pathname(path.cleanpath.to_s)
662
691
  end
663
692
 
693
+ # Returns `path` with all symlinks resolved.
694
+ #
695
+ # @param path [String, Pathname]
696
+ # @return [Pathname]
697
+ def realpath(path)
698
+ path = Pathname.new(path) unless path.is_a?(Pathname)
699
+
700
+ # Explicitly DON'T run #pathname here. We don't want to convert
701
+ # to Windows directory separators because we're comparing these
702
+ # against the paths returned by Listen, which use forward
703
+ # slashes everywhere.
704
+ begin
705
+ path.realpath
706
+ rescue SystemCallError
707
+ # If [path] doesn't actually exist, don't bail, just
708
+ # return the original.
709
+ path
710
+ end
711
+ end
712
+
713
+ # Returns `path` relative to `from`.
714
+ #
715
+ # This is like `Pathname#relative_path_from` except it accepts both strings
716
+ # and pathnames, it handles Windows path separators correctly, and it throws
717
+ # an error rather than crashing if the paths use different encodings
718
+ # (https://github.com/ruby/ruby/pull/713).
719
+ #
720
+ # @param path [String, Pathname]
721
+ # @param from [String, Pathname]
722
+ # @return [Pathname?]
723
+ def relative_path_from(path, from)
724
+ pathname(path.to_s).relative_path_from(pathname(from.to_s))
725
+ rescue NoMethodError => e
726
+ raise e unless e.name == :zero?
727
+
728
+ # Work around https://github.com/ruby/ruby/pull/713.
729
+ path = path.to_s
730
+ from = from.to_s
731
+ raise ArgumentError("Incompatible path encodings: #{path.inspect} is #{path.encoding}, " +
732
+ "#{from.inspect} is #{from.encoding}")
733
+ end
734
+
664
735
  # Converts `path` to a "file:" URI. This handles Windows paths correctly.
665
736
  #
666
737
  # @param path [String, Pathname]
667
738
  # @return [String]
668
739
  def file_uri_from_path(path)
669
740
  path = path.to_s if path.is_a?(Pathname)
741
+ path = path.tr('\\', '/') if windows?
670
742
  path = Sass::Util.escape_uri(path)
671
743
  return path.start_with?('/') ? "file://" + path : path unless windows?
672
- return "file:///" + path.tr("\\", "/") if path =~ /^[a-zA-Z]:[\/\\]/
673
- return "file://" + path.tr("\\", "/") if path =~ /\\\\[^\\]+\\[^\\\/]+/
744
+ return "file:///" + path.tr("\\", "/") if path =~ %r{^[a-zA-Z]:[/\\]}
745
+ return "file:" + path.tr("\\", "/") if path =~ %r{\\\\[^\\]+\\[^\\/]+}
674
746
  path.tr("\\", "/")
675
747
  end
676
748
 
749
+ # Retries a filesystem operation if it fails on Windows. Windows
750
+ # has weird and flaky locking rules that can cause operations to fail.
751
+ #
752
+ # @yield [] The filesystem operation.
753
+ def retry_on_windows
754
+ return yield unless windows?
755
+
756
+ begin
757
+ yield
758
+ rescue SystemCallError
759
+ sleep 0.1
760
+ yield
761
+ end
762
+ end
763
+
677
764
  # Prepare a value for a destructuring assignment (e.g. `a, b =
678
765
  # val`). This works around a performance bug when using
679
766
  # ActiveSupport, and only needs to be called when `val` is likely
@@ -711,15 +798,6 @@ module Sass
711
798
  (RUBY_VERSION_COMPONENTS[0] == 1 && RUBY_VERSION_COMPONENTS[1] < 9)
712
799
  end
713
800
 
714
- # Whether or not this is running under Ruby 1.8.6 or lower.
715
- # Note that lower versions are not officially supported.
716
- #
717
- # @return [Boolean]
718
- def ruby1_8_6?
719
- return @ruby1_8_6 if defined?(@ruby1_8_6)
720
- @ruby1_8_6 = ruby1_8? && RUBY_VERSION_COMPONENTS[2] < 7
721
- end
722
-
723
801
  # Whether or not this is running under Ruby 1.9.2 exactly.
724
802
  #
725
803
  # @return [Boolean]
@@ -728,6 +806,19 @@ module Sass
728
806
  @ruby1_9_2 = RUBY_VERSION_COMPONENTS == [1, 9, 2]
729
807
  end
730
808
 
809
+ # Whether or not this is running under Ruby 2.4 or higher.
810
+ #
811
+ # @return [Boolean]
812
+ def ruby2_4?
813
+ return @ruby2_4 if defined?(@ruby2_4)
814
+ @ruby2_4 =
815
+ if RUBY_VERSION_COMPONENTS[0] == 2
816
+ RUBY_VERSION_COMPONENTS[1] >= 4
817
+ else
818
+ RUBY_VERSION_COMPONENTS[0] > 2
819
+ end
820
+ end
821
+
731
822
  # Wehter or not this is running under JRuby 1.6 or lower.
732
823
  def jruby1_6?
733
824
  return @jruby1_6 if defined?(@jruby1_6)
@@ -769,14 +860,15 @@ module Sass
769
860
  end
770
861
 
771
862
  return Hash[pairs_or_hash] unless ruby1_8?
772
- (pairs_or_hash.is_a?(NormalizedMap) ? NormalizedMap : OrderedHash)[*flatten(pairs_or_hash, 1)]
863
+ (pairs_or_hash.is_a?(NormalizedMap) ? NormalizedMap : OrderedHash)[*pairs_or_hash.flatten(1)]
773
864
  end
774
865
 
775
866
  unless ruby1_8?
776
867
  CHARSET_REGEXP = /\A@charset "([^"]+)"/
777
- UTF_8_BOM = "\xEF\xBB\xBF".force_encoding('BINARY')
778
- UTF_16BE_BOM = "\xFE\xFF".force_encoding('BINARY')
779
- UTF_16LE_BOM = "\xFF\xFE".force_encoding('BINARY')
868
+ bom = "\uFEFF"
869
+ UTF_8_BOM = bom.encode("UTF-8").force_encoding('BINARY')
870
+ UTF_16BE_BOM = bom.encode("UTF-16BE").force_encoding('BINARY')
871
+ UTF_16LE_BOM = bom.encode("UTF-16LE").force_encoding('BINARY')
780
872
  end
781
873
 
782
874
  # Like {\#check\_encoding}, but also checks for a `@charset` declaration
@@ -874,7 +966,7 @@ module Sass
874
966
  # A version of `Enumerable#enum_cons` that works in Ruby 1.8 and 1.9.
875
967
  #
876
968
  # @param enum [Enumerable] The enumerable to get the enumerator for
877
- # @param n [Fixnum] The size of each cons
969
+ # @param n [Integer] The size of each cons
878
970
  # @return [Enumerator] The consed enumerator
879
971
  def enum_cons(enum, n)
880
972
  ruby1_8? ? enum.enum_cons(n) : enum.each_cons(n)
@@ -883,7 +975,7 @@ module Sass
883
975
  # A version of `Enumerable#enum_slice` that works in Ruby 1.8 and 1.9.
884
976
  #
885
977
  # @param enum [Enumerable] The enumerable to get the enumerator for
886
- # @param n [Fixnum] The size of each slice
978
+ # @param n [Integer] The size of each slice
887
979
  # @return [Enumerator] The consed enumerator
888
980
  def enum_slice(enum, n)
889
981
  ruby1_8? ? enum.enum_slice(n) : enum.each_slice(n)
@@ -910,22 +1002,11 @@ module Sass
910
1002
  # Returns the ASCII code of the given character.
911
1003
  #
912
1004
  # @param c [String] All characters but the first are ignored.
913
- # @return [Fixnum] The ASCII code of `c`.
1005
+ # @return [Integer] The ASCII code of `c`.
914
1006
  def ord(c)
915
1007
  ruby1_8? ? c[0] : c.ord
916
1008
  end
917
1009
 
918
- # Flattens the first `n` nested arrays in a cross-version manner.
919
- #
920
- # @param arr [Array] The array to flatten
921
- # @param n [Fixnum] The number of levels to flatten
922
- # @return [Array] The flattened array
923
- def flatten(arr, n)
924
- return arr.flatten(n) unless ruby1_8_6?
925
- return arr if n == 0
926
- arr.inject([]) {|res, e| e.is_a?(Array) ? res.concat(flatten(e, n - 1)) : res << e}
927
- end
928
-
929
1010
  # Flattens the first level of nested arrays in `arrs`. Unlike
930
1011
  # `Array#flatten`, this orders the result by taking the first
931
1012
  # values from each array in order, then the second, and so on.
@@ -944,27 +1025,6 @@ module Sass
944
1025
  result
945
1026
  end
946
1027
 
947
- # Returns the hash code for a set in a cross-version manner.
948
- # Aggravatingly, this is order-dependent in Ruby 1.8.6.
949
- #
950
- # @param set [Set]
951
- # @return [Fixnum] The order-independent hashcode of `set`
952
- def set_hash(set)
953
- return set.hash unless ruby1_8_6?
954
- set.map {|e| e.hash}.uniq.sort.hash
955
- end
956
-
957
- # Tests the hash-equality of two sets in a cross-version manner.
958
- # Aggravatingly, this is order-dependent in Ruby 1.8.6.
959
- #
960
- # @param set1 [Set]
961
- # @param set2 [Set]
962
- # @return [Boolean] Whether or not the sets are hashcode equal
963
- def set_eql?(set1, set2)
964
- return set1.eql?(set2) unless ruby1_8_6?
965
- set1.to_a.uniq.sort_by {|e| e.hash}.eql?(set2.to_a.uniq.sort_by {|e| e.hash})
966
- end
967
-
968
1028
  # Like `Object#inspect`, but preserves non-ASCII characters rather than
969
1029
  # escaping them under Ruby 1.9.2. This is necessary so that the
970
1030
  # precompiled Haml template can be `#encode`d into `@options[:encoding]`
@@ -1067,11 +1127,11 @@ module Sass
1067
1127
 
1068
1128
  # Converts the argument into a valid JSON value.
1069
1129
  #
1070
- # @param v [Fixnum, String, Array, Boolean, nil]
1130
+ # @param v [Integer, String, Array, Boolean, nil]
1071
1131
  # @return [String]
1072
1132
  def json_value_of(v)
1073
1133
  case v
1074
- when Fixnum
1134
+ when Integer
1075
1135
  v.to_s
1076
1136
  when String
1077
1137
  "\"" + json_escape_string(v) + "\""
@@ -1093,7 +1153,7 @@ module Sass
1093
1153
  VLQ_BASE_MASK = VLQ_BASE - 1
1094
1154
  VLQ_CONTINUATION_BIT = VLQ_BASE
1095
1155
 
1096
- BASE64_DIGITS = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['+', '/']
1156
+ BASE64_DIGITS = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['+', '/']
1097
1157
  BASE64_DIGIT_MAP = begin
1098
1158
  map = {}
1099
1159
  Sass::Util.enum_with_index(BASE64_DIGITS).map do |digit, i|
@@ -1104,7 +1164,7 @@ module Sass
1104
1164
 
1105
1165
  # Encodes `value` as VLQ (http://en.wikipedia.org/wiki/VLQ).
1106
1166
  #
1107
- # @param value [Fixnum]
1167
+ # @param value [Integer]
1108
1168
  # @return [String] The encoded value
1109
1169
  def encode_vlq(value)
1110
1170
  if value < 0
@@ -1223,7 +1283,7 @@ module Sass
1223
1283
  require 'listen'
1224
1284
  rescue LoadError => e
1225
1285
  if version_geq(RUBY_VERSION, "1.9.3")
1226
- version_constraint = "~> 2.7"
1286
+ version_constraint = "~> 3.0"
1227
1287
  else
1228
1288
  version_constraint = "~> 1.1"
1229
1289
  end
@@ -1272,13 +1332,10 @@ module Sass
1272
1332
  return str, str.encoding
1273
1333
  end
1274
1334
 
1275
- # rubocop:disable LineLength
1276
-
1277
1335
  # Calculates the memoization table for the Least Common Subsequence algorithm.
1278
1336
  # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Computing_the_length_of_the_LCS)
1279
1337
  def lcs_table(x, y)
1280
1338
  # This method does not take a block as an explicit parameter for performance reasons.
1281
- # rubocop:enable LineLength
1282
1339
  c = Array.new(x.size) {[]}
1283
1340
  x.size.times {|i| c[i][0] = 0}
1284
1341
  y.size.times {|j| c[0][j] = 0}
@@ -1294,12 +1351,12 @@ module Sass
1294
1351
  end
1295
1352
  c
1296
1353
  end
1297
- # rubocop:disable ParameterLists, LineLength
1354
+ # rubocop:disable ParameterLists
1298
1355
 
1299
1356
  # Computes a single longest common subsequence for arrays x and y.
1300
1357
  # Algorithm from [Wikipedia](http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Reading_out_an_LCS)
1301
1358
  def lcs_backtrace(c, x, y, i, j, &block)
1302
- # rubocop:enable ParameterList, LineLengths
1359
+ # rubocop:enable ParameterList
1303
1360
  return [] if i == 0 || j == 0
1304
1361
  if (v = yield(x[i], y[j]))
1305
1362
  return lcs_backtrace(c, x, y, i - 1, j - 1, &block) << v
data/lib/sass/version.rb CHANGED
@@ -8,7 +8,7 @@ module Sass
8
8
  # if it was installed from Git.
9
9
  module Version
10
10
  # Returns a hash representing the version of Sass.
11
- # The `:major`, `:minor`, and `:teeny` keys have their respective numbers as Fixnums.
11
+ # The `:major`, `:minor`, and `:teeny` keys have their respective numbers as Integers.
12
12
  # The `:name` key has the name of the version.
13
13
  # The `:string` key contains a human-readable string representation of the version.
14
14
  # The `:number` key is the major, minor, and teeny keys separated by periods.
@@ -41,7 +41,7 @@ module Sass
41
41
  # :prerelease_number => 1
42
42
  # }
43
43
  #
44
- # @return [{Symbol => String/Fixnum}] The version hash
44
+ # @return [{Symbol => String/Integer}] The version hash
45
45
  # @comment
46
46
  # rubocop:disable ClassVars
47
47
  def version
data/lib/sass.rb CHANGED
@@ -47,7 +47,7 @@ module Sass
47
47
  #
48
48
  # @param contents [String] The contents of the Sass file.
49
49
  # @param options [{Symbol => Object}] An options hash;
50
- # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
50
+ # see {file:SASS_REFERENCE.md#Options the Sass options documentation}
51
51
  # @raise [Sass::SyntaxError] if there's an error in the document
52
52
  # @raise [Encoding::UndefinedConversionError] if the source encoding
53
53
  # cannot be converted to UTF-8
@@ -69,7 +69,7 @@ module Sass
69
69
  #
70
70
  # @param filename [String] The path to the Sass, SCSS, or CSS file on disk.
71
71
  # @param options [{Symbol => Object}] An options hash;
72
- # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
72
+ # see {file:SASS_REFERENCE.md#Options the Sass options documentation}
73
73
  # @return [String] The compiled CSS.
74
74
  #
75
75
  # @overload compile_file(filename, css_filename, options = {})
@@ -77,7 +77,7 @@ module Sass
77
77
  #
78
78
  # @param filename [String] The path to the Sass, SCSS, or CSS file on disk.
79
79
  # @param options [{Symbol => Object}] An options hash;
80
- # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
80
+ # see {file:SASS_REFERENCE.md#Options the Sass options documentation}
81
81
  # @param css_filename [String] The location to which to write the compiled CSS.
82
82
  def self.compile_file(filename, *args)
83
83
  options = args.last.is_a?(Hash) ? args.pop : {}
@@ -100,3 +100,10 @@ require 'sass/engine'
100
100
  require 'sass/plugin' if defined?(Merb::Plugins)
101
101
  require 'sass/railtie'
102
102
  require 'sass/features'
103
+
104
+ if Sass::Util.ruby1?
105
+ Sass::Util.sass_warn(
106
+ "DEPRECATION WARNING:\n" +
107
+ "Sass 3.5 will no longer support Ruby #{RUBY_VERSION}.\n" +
108
+ "Please upgrade to Ruby 2.0.0 or greater as soon as possible.\n")
109
+ end