sass 3.3.0 → 3.4.0

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 (151) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +58 -50
  4. data/Rakefile +1 -4
  5. data/VERSION +1 -1
  6. data/VERSION_DATE +1 -1
  7. data/VERSION_NAME +1 -1
  8. data/bin/sass +1 -1
  9. data/bin/scss +1 -1
  10. data/lib/sass/cache_stores/filesystem.rb +6 -2
  11. data/lib/sass/css.rb +1 -3
  12. data/lib/sass/engine.rb +37 -46
  13. data/lib/sass/environment.rb +13 -17
  14. data/lib/sass/error.rb +6 -9
  15. data/lib/sass/exec/base.rb +187 -0
  16. data/lib/sass/exec/sass_convert.rb +264 -0
  17. data/lib/sass/exec/sass_scss.rb +424 -0
  18. data/lib/sass/exec.rb +5 -771
  19. data/lib/sass/features.rb +7 -0
  20. data/lib/sass/importers/base.rb +7 -2
  21. data/lib/sass/importers/filesystem.rb +9 -25
  22. data/lib/sass/importers.rb +0 -1
  23. data/lib/sass/media.rb +1 -4
  24. data/lib/sass/plugin/compiler.rb +200 -83
  25. data/lib/sass/plugin/staleness_checker.rb +1 -1
  26. data/lib/sass/plugin.rb +3 -3
  27. data/lib/sass/script/css_lexer.rb +1 -1
  28. data/lib/sass/script/functions.rb +622 -268
  29. data/lib/sass/script/lexer.rb +99 -34
  30. data/lib/sass/script/parser.rb +24 -23
  31. data/lib/sass/script/tree/funcall.rb +1 -1
  32. data/lib/sass/script/tree/interpolation.rb +20 -2
  33. data/lib/sass/script/tree/selector.rb +26 -0
  34. data/lib/sass/script/tree/string_interpolation.rb +1 -1
  35. data/lib/sass/script/tree.rb +1 -0
  36. data/lib/sass/script/value/base.rb +7 -5
  37. data/lib/sass/script/value/bool.rb +0 -5
  38. data/lib/sass/script/value/color.rb +39 -21
  39. data/lib/sass/script/value/helpers.rb +107 -0
  40. data/lib/sass/script/value/list.rb +0 -15
  41. data/lib/sass/script/value/null.rb +0 -5
  42. data/lib/sass/script/value/number.rb +62 -14
  43. data/lib/sass/script/value/string.rb +59 -11
  44. data/lib/sass/script/value.rb +0 -1
  45. data/lib/sass/scss/css_parser.rb +8 -2
  46. data/lib/sass/scss/parser.rb +190 -328
  47. data/lib/sass/scss/rx.rb +15 -6
  48. data/lib/sass/scss/static_parser.rb +298 -1
  49. data/lib/sass/selector/abstract_sequence.rb +28 -13
  50. data/lib/sass/selector/comma_sequence.rb +92 -13
  51. data/lib/sass/selector/pseudo.rb +256 -0
  52. data/lib/sass/selector/sequence.rb +94 -24
  53. data/lib/sass/selector/simple.rb +14 -25
  54. data/lib/sass/selector/simple_sequence.rb +97 -33
  55. data/lib/sass/selector.rb +57 -194
  56. data/lib/sass/shared.rb +1 -1
  57. data/lib/sass/source/map.rb +26 -12
  58. data/lib/sass/stack.rb +0 -6
  59. data/lib/sass/supports.rb +2 -3
  60. data/lib/sass/tree/at_root_node.rb +1 -0
  61. data/lib/sass/tree/charset_node.rb +1 -1
  62. data/lib/sass/tree/directive_node.rb +8 -2
  63. data/lib/sass/tree/error_node.rb +18 -0
  64. data/lib/sass/tree/extend_node.rb +1 -1
  65. data/lib/sass/tree/function_node.rb +4 -0
  66. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  67. data/lib/sass/tree/prop_node.rb +1 -1
  68. data/lib/sass/tree/rule_node.rb +12 -7
  69. data/lib/sass/tree/visitors/check_nesting.rb +38 -10
  70. data/lib/sass/tree/visitors/convert.rb +16 -18
  71. data/lib/sass/tree/visitors/cssize.rb +29 -29
  72. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  73. data/lib/sass/tree/visitors/perform.rb +45 -33
  74. data/lib/sass/tree/visitors/set_options.rb +14 -0
  75. data/lib/sass/tree/visitors/to_css.rb +15 -14
  76. data/lib/sass/util/subset_map.rb +1 -1
  77. data/lib/sass/util.rb +222 -99
  78. data/lib/sass/version.rb +5 -5
  79. data/lib/sass.rb +0 -5
  80. data/test/sass/cache_test.rb +62 -20
  81. data/test/sass/callbacks_test.rb +1 -1
  82. data/test/sass/compiler_test.rb +19 -10
  83. data/test/sass/conversion_test.rb +58 -1
  84. data/test/sass/css2sass_test.rb +23 -4
  85. data/test/sass/encoding_test.rb +219 -0
  86. data/test/sass/engine_test.rb +136 -199
  87. data/test/sass/exec_test.rb +2 -2
  88. data/test/sass/extend_test.rb +236 -19
  89. data/test/sass/functions_test.rb +295 -253
  90. data/test/sass/importer_test.rb +31 -21
  91. data/test/sass/logger_test.rb +1 -1
  92. data/test/sass/more_results/more_import.css +1 -1
  93. data/test/sass/plugin_test.rb +14 -13
  94. data/test/sass/results/compact.css +1 -1
  95. data/test/sass/results/complex.css +4 -4
  96. data/test/sass/results/expanded.css +1 -1
  97. data/test/sass/results/import.css +1 -1
  98. data/test/sass/results/import_charset_ibm866.css +2 -2
  99. data/test/sass/results/mixins.css +17 -17
  100. data/test/sass/results/nested.css +1 -1
  101. data/test/sass/results/parent_ref.css +2 -2
  102. data/test/sass/results/script.css +3 -3
  103. data/test/sass/results/scss_import.css +1 -1
  104. data/test/sass/script_conversion_test.rb +10 -7
  105. data/test/sass/script_test.rb +288 -74
  106. data/test/sass/scss/css_test.rb +141 -24
  107. data/test/sass/scss/rx_test.rb +4 -4
  108. data/test/sass/scss/scss_test.rb +457 -18
  109. data/test/sass/source_map_test.rb +115 -25
  110. data/test/sass/superselector_test.rb +191 -0
  111. data/test/sass/templates/scss_import.scss +2 -1
  112. data/test/sass/test_helper.rb +1 -1
  113. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  114. data/test/sass/util/normalized_map_test.rb +1 -1
  115. data/test/sass/util/subset_map_test.rb +2 -2
  116. data/test/sass/util_test.rb +31 -1
  117. data/test/sass/value_helpers_test.rb +5 -7
  118. data/test/test_helper.rb +2 -2
  119. data/vendor/listen/CHANGELOG.md +1 -228
  120. data/vendor/listen/Gemfile +5 -15
  121. data/vendor/listen/README.md +111 -77
  122. data/vendor/listen/Rakefile +0 -42
  123. data/vendor/listen/lib/listen/adapter.rb +195 -82
  124. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  125. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  126. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  127. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  128. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  129. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  130. data/vendor/listen/lib/listen/listener.rb +135 -37
  131. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  132. data/vendor/listen/lib/listen/version.rb +1 -1
  133. data/vendor/listen/lib/listen.rb +33 -19
  134. data/vendor/listen/listen.gemspec +6 -0
  135. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  136. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  137. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  138. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  139. data/vendor/listen/spec/listen_spec.rb +15 -21
  140. data/vendor/listen/spec/spec_helper.rb +4 -0
  141. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  142. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  143. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  144. metadata +25 -22
  145. data/ext/mkrf_conf.rb +0 -27
  146. data/lib/sass/importers/deprecated_path.rb +0 -51
  147. data/lib/sass/script/value/deprecated_false.rb +0 -55
  148. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  149. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  150. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  151. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
@@ -15,11 +15,21 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
15
15
  super
16
16
  end
17
17
 
18
+ def visit_comment(node)
19
+ node.value.each {|c| c.options = @options if c.is_a?(Sass::Script::Tree::Node)}
20
+ yield
21
+ end
22
+
18
23
  def visit_debug(node)
19
24
  node.expr.options = @options
20
25
  yield
21
26
  end
22
27
 
28
+ def visit_error(node)
29
+ node.expr.options = @options
30
+ yield
31
+ end
32
+
23
33
  def visit_each(node)
24
34
  node.list.options = @options
25
35
  yield
@@ -41,6 +51,7 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
41
51
  k.options = @options
42
52
  v.options = @options if v
43
53
  end
54
+ node.splat.options = @options if node.splat
44
55
  yield
45
56
  end
46
57
 
@@ -63,12 +74,15 @@ class Sass::Tree::Visitors::SetOptions < Sass::Tree::Visitors::Base
63
74
  k.options = @options
64
75
  v.options = @options if v
65
76
  end
77
+ node.splat.options = @options if node.splat
66
78
  yield
67
79
  end
68
80
 
69
81
  def visit_mixin(node)
70
82
  node.args.each {|a| a.options = @options}
71
83
  node.keywords.each {|k, v| v.options = @options}
84
+ node.splat.options = @options if node.splat
85
+ node.kwarg_splat.options = @options if node.kwarg_splat
72
86
  yield
73
87
  end
74
88
 
@@ -127,21 +127,18 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
127
127
  return "" if @result.empty?
128
128
 
129
129
  output "\n"
130
- return @result if Sass::Util.ruby1_8? || @result.ascii_only?
131
-
132
- if node.children.first.is_a?(Sass::Tree::CharsetNode)
133
- begin
134
- encoding = node.children.first.name
135
- # Default to big-endian encoding, because we have to decide somehow
136
- encoding << 'BE' if encoding =~ /\Autf-(16|32)\Z/i
137
- @result = @result.encode(Encoding.find(encoding))
138
- rescue EncodingError
130
+
131
+ unless Sass::Util.ruby1_8? || @result.ascii_only?
132
+ if node.style == :compressed
133
+ # A byte order mark is sufficient to tell browsers that this
134
+ # file is UTF-8 encoded, and will override any other detection
135
+ # methods as per http://encoding.spec.whatwg.org/#decode-and-encode.
136
+ prepend! "\uFEFF"
137
+ else
138
+ prepend! "@charset \"UTF-8\";\n"
139
139
  end
140
140
  end
141
141
 
142
- prepend! "@charset \"#{@result.encoding.name}\";#{
143
- node.style == :compressed ? '' : "\n"
144
- }".encode(@result.encoding)
145
142
  @result
146
143
  rescue Sass::SyntaxError => e
147
144
  e.sass_template ||= node.template
@@ -235,7 +232,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
235
232
 
236
233
  def visit_media(node)
237
234
  with_tabs(@tabs + node.tabs) {visit_directive(node)}
238
- output("\n") if node.group_end
235
+ output("\n") if node.style != :compressed && node.group_end
239
236
  end
240
237
 
241
238
  def visit_supports(node)
@@ -281,7 +278,7 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
281
278
 
282
279
  joined_rules = node.resolved_rules.members.map do |seq|
283
280
  next if seq.has_placeholder?
284
- rule_part = seq.to_a.join
281
+ rule_part = seq.to_s
285
282
  if node.style == :compressed
286
283
  rule_part.gsub!(/([^,])\s*\n\s*/m, '\1 ')
287
284
  rule_part.gsub!(/\s*([,+>])\s*/m, '\1')
@@ -353,6 +350,10 @@ class Sass::Tree::Visitors::ToCss < Sass::Tree::Visitors::Base
353
350
  # @comment
354
351
  # rubocop:enable MethodLength
355
352
 
353
+ def visit_keyframerule(node)
354
+ visit_directive(node)
355
+ end
356
+
356
357
  private
357
358
 
358
359
  def debug_info_rule(debug_info, options)
@@ -59,7 +59,7 @@ module Sass
59
59
  #
60
60
  # In the worst case, this runs in `O(m*max(n, log m))` time,
61
61
  # where `n` is the size of `set`
62
- # and `m` is the number of assocations in the map.
62
+ # and `m` is the number of associations in the map.
63
63
  # However, unless many keys in the map overlap with `set`,
64
64
  # `m` will typically be much smaller.
65
65
  #
data/lib/sass/util.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # -*- coding: utf-8 -*-
1
2
  require 'erb'
2
3
  require 'set'
3
4
  require 'enumerator'
@@ -17,7 +18,7 @@ module Sass
17
18
 
18
19
  # An array of ints representing the Ruby version number.
19
20
  # @api public
20
- RUBY_VERSION = ::RUBY_VERSION.split(".").map {|s| s.to_i}
21
+ RUBY_VERSION_COMPONENTS = RUBY_VERSION.split(".").map {|s| s.to_i}
21
22
 
22
23
  # The Ruby engine we're running under. Defaults to `"ruby"`
23
24
  # if the top-level constant is undefined.
@@ -162,6 +163,43 @@ module Sass
162
163
  end
163
164
  end
164
165
 
166
+ # Non-destructively replaces all occurrences of a subsequence in an array
167
+ # with another subsequence.
168
+ #
169
+ # @example
170
+ # replace_subseq([1, 2, 3, 4, 5], [2, 3], [:a, :b])
171
+ # #=> [1, :a, :b, 4, 5]
172
+ #
173
+ # @param arr [Array] The array whose subsequences will be replaced.
174
+ # @param subseq [Array] The subsequence to find and replace.
175
+ # @param replacement [Array] The sequence that `subseq` will be replaced with.
176
+ # @return [Array] `arr` with `subseq` replaced with `replacement`.
177
+ def replace_subseq(arr, subseq, replacement)
178
+ new = []
179
+ matched = []
180
+ i = 0
181
+ arr.each do |elem|
182
+ if elem != subseq[i]
183
+ new.push(*matched)
184
+ matched = []
185
+ i = 0
186
+ new << elem
187
+ next
188
+ end
189
+
190
+ if i == subseq.length - 1
191
+ matched = []
192
+ i = 0
193
+ new.push(*replacement)
194
+ else
195
+ matched << elem
196
+ i += 1
197
+ end
198
+ end
199
+ new.push(*matched)
200
+ new
201
+ end
202
+
165
203
  # Intersperses a value in an enumerable, as would be done with `Array#join`
166
204
  # but without concatenating the array together afterwards.
167
205
  #
@@ -300,6 +338,18 @@ module Sass
300
338
  minuend.select {|e| set.include?(e)}
301
339
  end
302
340
 
341
+ # Returns the maximum of `val1` and `val2`. We use this over \{Array.max} to
342
+ # avoid unnecessary garbage collection.
343
+ def max(val1, val2)
344
+ val1 > val2 ? val1 : val2
345
+ end
346
+
347
+ # Returns the minimum of `val1` and `val2`. We use this over \{Array.min} to
348
+ # avoid unnecessary garbage collection.
349
+ def min(val1, val2)
350
+ val1 <= val2 ? val1 : val2
351
+ end
352
+
303
353
  # Returns a string description of the character that caused an
304
354
  # `Encoding::UndefinedConversionError`.
305
355
  #
@@ -507,8 +557,14 @@ module Sass
507
557
  #
508
558
  # @return [Boolean]
509
559
  def listen_geq_2?
510
- require 'listen/version'
511
- version_geq(::Listen::VERSION, '2.0.0')
560
+ return @listen_geq_2 unless @listen_geq_2.nil?
561
+ @listen_geq_2 =
562
+ begin
563
+ require 'listen/version'
564
+ version_geq(::Listen::VERSION, '2.0.0')
565
+ rescue LoadError
566
+ false
567
+ end
512
568
  end
513
569
 
514
570
  # Returns an ActionView::Template* class.
@@ -583,13 +639,41 @@ module Sass
583
639
  # Like `Pathname.new`, but normalizes Windows paths to always use backslash
584
640
  # separators.
585
641
  #
586
- # `Pathname.relative_path_from` can break if the two pathnames aren't
642
+ # `Pathname#relative_path_from` can break if the two pathnames aren't
587
643
  # consistent in their slash style.
644
+ #
645
+ # @param path [String]
646
+ # @return [Pathname]
588
647
  def pathname(path)
589
648
  path = path.tr("/", "\\") if windows?
590
649
  Pathname.new(path)
591
650
  end
592
651
 
652
+ # Like `Pathname#cleanpath`, but normalizes Windows paths to always use
653
+ # backslash separators. Normally, `Pathname#cleanpath` actually does the
654
+ # reverse -- it will convert backslashes to forward slashes, which can break
655
+ # `Pathname#relative_path_from`.
656
+ #
657
+ # @param path [String, Pathname]
658
+ # @return [Pathname]
659
+ def cleanpath(path)
660
+ path = Pathname.new(path) unless path.is_a?(Pathname)
661
+ pathname(path.cleanpath.to_s)
662
+ end
663
+
664
+ # Converts `path` to a "file:" URI. This handles Windows paths correctly.
665
+ #
666
+ # @param path [String, Pathname]
667
+ # @return [String]
668
+ def file_uri_from_path(path)
669
+ path = path.to_s if path.is_a?(Pathname)
670
+ path = Sass::Util.escape_uri(path)
671
+ 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 =~ /\\\\[^\\]+\\[^\\\/]+/
674
+ path.tr("\\", "/")
675
+ end
676
+
593
677
  # Prepare a value for a destructuring assignment (e.g. `a, b =
594
678
  # val`). This works around a performance bug when using
595
679
  # ActiveSupport, and only needs to be called when `val` is likely
@@ -610,7 +694,7 @@ module Sass
610
694
  # @return [Boolean]
611
695
  def ruby1?
612
696
  return @ruby1 if defined?(@ruby1)
613
- @ruby1 = Sass::Util::RUBY_VERSION[0] <= 1
697
+ @ruby1 = RUBY_VERSION_COMPONENTS[0] <= 1
614
698
  end
615
699
 
616
700
  # Whether or not this is running under Ruby 1.8 or lower.
@@ -624,7 +708,7 @@ module Sass
624
708
  # We have to fall back to 1.8 behavior.
625
709
  return @ruby1_8 if defined?(@ruby1_8)
626
710
  @ruby1_8 = ironruby? ||
627
- (Sass::Util::RUBY_VERSION[0] == 1 && Sass::Util::RUBY_VERSION[1] < 9)
711
+ (RUBY_VERSION_COMPONENTS[0] == 1 && RUBY_VERSION_COMPONENTS[1] < 9)
628
712
  end
629
713
 
630
714
  # Whether or not this is running under Ruby 1.8.6 or lower.
@@ -633,7 +717,15 @@ module Sass
633
717
  # @return [Boolean]
634
718
  def ruby1_8_6?
635
719
  return @ruby1_8_6 if defined?(@ruby1_8_6)
636
- @ruby1_8_6 = ruby1_8? && Sass::Util::RUBY_VERSION[2] < 7
720
+ @ruby1_8_6 = ruby1_8? && RUBY_VERSION_COMPONENTS[2] < 7
721
+ end
722
+
723
+ # Whether or not this is running under Ruby 1.9.2 exactly.
724
+ #
725
+ # @return [Boolean]
726
+ def ruby1_9_2?
727
+ return @ruby1_9_2 if defined?(@ruby1_9_2)
728
+ @ruby1_9_2 = RUBY_VERSION_COMPONENTS == [1, 9, 2]
637
729
  end
638
730
 
639
731
  # Wehter or not this is running under JRuby 1.6 or lower.
@@ -680,114 +772,76 @@ module Sass
680
772
  (pairs_or_hash.is_a?(NormalizedMap) ? NormalizedMap : OrderedHash)[*flatten(pairs_or_hash, 1)]
681
773
  end
682
774
 
683
- # Checks that the encoding of a string is valid in Ruby 1.9
684
- # and cleans up potential encoding gotchas like the UTF-8 BOM.
685
- # If it's not, yields an error string describing the invalid character
686
- # and the line on which it occurrs.
687
- #
688
- # @param str [String] The string of which to check the encoding
689
- # @yield [msg] A block in which an encoding error can be raised.
690
- # Only yields if there is an encoding error
691
- # @yieldparam msg [String] The error message to be raised
692
- # @return [String] `str`, potentially with encoding gotchas like BOMs removed
693
- def check_encoding(str)
694
- if ruby1_8?
695
- return str.gsub(/\A\xEF\xBB\xBF/, '') # Get rid of the UTF-8 BOM
696
- elsif str.valid_encoding?
697
- # Get rid of the Unicode BOM if possible
698
- if str.encoding.name =~ /^UTF-(8|16|32)(BE|LE)?$/
699
- return str.gsub(Regexp.new("\\A\uFEFF".encode(str.encoding.name)), '')
700
- else
701
- return str
702
- end
703
- end
704
-
705
- encoding = str.encoding
706
- newlines = Regexp.new("\r\n|\r|\n".encode(encoding).force_encoding("binary"))
707
- str.force_encoding("binary").split(newlines).each_with_index do |line, i|
708
- begin
709
- line.encode(encoding)
710
- rescue Encoding::UndefinedConversionError => e
711
- yield <<MSG.rstrip, i + 1
712
- Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}
713
- MSG
714
- end
715
- end
716
- str
775
+ unless ruby1_8?
776
+ 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')
717
780
  end
718
781
 
719
782
  # Like {\#check\_encoding}, but also checks for a `@charset` declaration
720
783
  # at the beginning of the file and uses that encoding if it exists.
721
784
  #
722
- # The Sass encoding rules are simple.
723
- # If a `@charset` declaration exists,
724
- # we assume that that's the original encoding of the document.
725
- # Otherwise, we use whatever encoding Ruby has.
726
- # Then we convert that to UTF-8 to process internally.
727
- # The UTF-8 end result is what's returned by this method.
785
+ # Sass follows CSS's decoding rules.
728
786
  #
729
787
  # @param str [String] The string of which to check the encoding
730
- # @yield [msg] A block in which an encoding error can be raised.
731
- # Only yields if there is an encoding error
732
- # @yieldparam msg [String] The error message to be raised
733
788
  # @return [(String, Encoding)] The original string encoded as UTF-8,
734
789
  # and the source encoding of the string (or `nil` under Ruby 1.8)
735
790
  # @raise [Encoding::UndefinedConversionError] if the source encoding
736
791
  # cannot be converted to UTF-8
737
792
  # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
738
- def check_sass_encoding(str, &block)
739
- return check_encoding(str, &block), nil if ruby1_8?
740
- # We allow any printable ASCII characters but double quotes in the charset decl
741
- bin = str.dup.force_encoding("BINARY")
742
- encoding = Sass::Util::ENCODINGS_TO_CHECK.find do |enc|
743
- re = Sass::Util::CHARSET_REGEXPS[enc]
744
- re && bin =~ re
793
+ # @raise [Sass::SyntaxError] If the document declares an encoding that
794
+ # doesn't match its contents, or it doesn't declare an encoding and its
795
+ # contents are invalid in the native encoding.
796
+ def check_sass_encoding(str)
797
+ # On Ruby 1.8 we can't do anything complicated with encodings.
798
+ # Instead, we just strip out a UTF-8 BOM if it exists and
799
+ # sanitize according to Section 3.3 of CSS Syntax Level 3. We
800
+ # don't sanitize null characters since they might be components
801
+ # of other characters.
802
+ if ruby1_8?
803
+ return str.gsub(/\A\xEF\xBB\xBF/, '').gsub(/\r\n?|\f/, "\n"), nil
745
804
  end
746
- charset, bom = $1, $2
747
- if charset
748
- charset = charset.force_encoding(encoding).encode("UTF-8")
749
- if (endianness = encoding[/[BL]E$/])
750
- begin
751
- Encoding.find(charset + endianness)
752
- charset << endianness
753
- rescue ArgumentError # Encoding charset + endianness doesn't exist
805
+
806
+ # Determine the fallback encoding following section 3.2 of CSS Syntax Level 3 and Encodings:
807
+ # http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#determine-the-fallback-encoding
808
+ # http://encoding.spec.whatwg.org/#decode
809
+ binary = str.dup.force_encoding("BINARY")
810
+ if binary.start_with?(UTF_8_BOM)
811
+ binary.slice! 0, UTF_8_BOM.length
812
+ str = binary.force_encoding('UTF-8')
813
+ elsif binary.start_with?(UTF_16BE_BOM)
814
+ binary.slice! 0, UTF_16BE_BOM.length
815
+ str = binary.force_encoding('UTF-16BE')
816
+ elsif binary.start_with?(UTF_16LE_BOM)
817
+ binary.slice! 0, UTF_16LE_BOM.length
818
+ str = binary.force_encoding('UTF-16LE')
819
+ elsif binary =~ CHARSET_REGEXP
820
+ charset = $1.force_encoding('US-ASCII')
821
+ # Ruby 1.9.2 doesn't recognize a UTF-16 encoding without an endian marker.
822
+ if ruby1_9_2? && charset.downcase == 'utf-16'
823
+ encoding = Encoding.find('UTF-8')
824
+ else
825
+ encoding = Encoding.find(charset)
826
+ if encoding.name == 'UTF-16' || encoding.name == 'UTF-16BE'
827
+ encoding = Encoding.find('UTF-8')
754
828
  end
755
829
  end
756
- str.force_encoding(charset)
757
- elsif bom
758
- str.force_encoding(encoding)
759
- end
760
-
761
- str = check_encoding(str, &block)
762
- return str.encode("UTF-8"), str.encoding
763
- end
764
-
765
- unless ruby1_8?
766
- # @private
767
- def _enc(string, encoding)
768
- string.encode(encoding).force_encoding("BINARY")
830
+ str = binary.force_encoding(encoding)
831
+ elsif str.encoding.name == "ASCII-8BIT"
832
+ # Normally we want to fall back on believing the Ruby string
833
+ # encoding, but if that's just binary we want to make sure
834
+ # it's valid UTF-8.
835
+ str = str.force_encoding('utf-8')
769
836
  end
770
837
 
771
- # We could automatically add in any non-ASCII-compatible encodings here,
772
- # but there's not really a good way to do that
773
- # without manually checking that each encoding
774
- # encodes all ASCII characters properly,
775
- # which takes long enough to affect the startup time of the CLI.
776
- ENCODINGS_TO_CHECK = %w[UTF-8 UTF-16BE UTF-16LE UTF-32BE UTF-32LE]
838
+ find_encoding_error(str) unless str.valid_encoding?
777
839
 
778
- CHARSET_REGEXPS = Hash.new do |h, e|
779
- h[e] =
780
- begin
781
- # /\A(?:\uFEFF)?@charset "(.*?)"|\A(\uFEFF)/
782
- Regexp.new(/\A(?:#{_enc("\uFEFF", e)})?#{
783
- _enc('@charset "', e)}(.*?)#{_enc('"', e)}|\A(#{
784
- _enc("\uFEFF", e)})/)
785
- rescue Encoding::ConverterNotFoundError => _
786
- nil # JRuby on Java 5 doesn't support UTF-32
787
- rescue
788
- # /\A@charset "(.*?)"/
789
- Regexp.new(/\A#{_enc('@charset "', e)}(.*?)#{_enc('"', e)}/)
790
- end
840
+ begin
841
+ # If the string is valid, preprocess it according to section 3.3 of CSS Syntax Level 3.
842
+ return str.encode("UTF-8").gsub(/\r\n?|\f/, "\n").tr("\u0000", "�"), str.encoding
843
+ rescue EncodingError
844
+ find_encoding_error(str)
791
845
  end
792
846
  end
793
847
 
@@ -919,7 +973,7 @@ MSG
919
973
  # @param obj {Object}
920
974
  # @return {String}
921
975
  def inspect_obj(obj)
922
- return obj.inspect unless version_geq(::RUBY_VERSION, "1.9.2")
976
+ return obj.inspect unless version_geq(RUBY_VERSION, "1.9.2")
923
977
  return ':' + inspect_obj(obj.to_s) if obj.is_a?(Symbol)
924
978
  return obj.inspect unless obj.is_a?(String)
925
979
  '"' + obj.gsub(/[\x00-\x7F]+/) {|s| s.inspect[1...-1]} + '"'
@@ -1128,15 +1182,25 @@ MSG
1128
1182
  # rename operation.
1129
1183
  #
1130
1184
  # @param filename [String] The file to write to.
1185
+ # @param perms [Integer] The permissions used for creating this file.
1186
+ # Will be masked by the process umask. Defaults to readable/writeable
1187
+ # by all users however the umask usually changes this to only be writable
1188
+ # by the process's user.
1131
1189
  # @yieldparam tmpfile [Tempfile] The temp file that can be written to.
1132
1190
  # @return The value returned by the block.
1133
- def atomic_create_and_write_file(filename)
1191
+ def atomic_create_and_write_file(filename, perms = 0666)
1134
1192
  require 'tempfile'
1135
1193
  tmpfile = Tempfile.new(File.basename(filename), File.dirname(filename))
1136
1194
  tmpfile.binmode if tmpfile.respond_to?(:binmode)
1137
1195
  result = yield tmpfile
1138
1196
  tmpfile.close
1139
1197
  ATOMIC_WRITE_MUTEX.synchronize do
1198
+ begin
1199
+ File.chmod(perms & ~File.umask, tmpfile.path)
1200
+ rescue Errno::EPERM
1201
+ # If we don't have permissions to chmod the file, don't let that crash
1202
+ # the compilation. See issue 1215.
1203
+ end
1140
1204
  File.rename tmpfile.path, filename
1141
1205
  end
1142
1206
  result
@@ -1147,8 +1211,67 @@ MSG
1147
1211
  tmpfile.unlink if tmpfile
1148
1212
  end
1149
1213
 
1214
+ def load_listen!
1215
+ if defined?(gem)
1216
+ begin
1217
+ gem 'listen', '>= 1.1.0', '< 3.0.0'
1218
+ require 'listen'
1219
+ rescue Gem::LoadError
1220
+ dir = scope("vendor/listen/lib")
1221
+ $LOAD_PATH.unshift dir
1222
+ begin
1223
+ require 'listen'
1224
+ rescue LoadError => e
1225
+ if version_geq(RUBY_VERSION, "1.9.3")
1226
+ version_constraint = "~> 2.7"
1227
+ else
1228
+ version_constraint = "~> 1.1"
1229
+ end
1230
+ e.message << "\n" <<
1231
+ "Run \"gem install listen --version '#{version_constraint}'\" to get it."
1232
+ raise e
1233
+ end
1234
+ end
1235
+ else
1236
+ begin
1237
+ require 'listen'
1238
+ rescue LoadError => e
1239
+ dir = scope("vendor/listen/lib")
1240
+ if $LOAD_PATH.include?(dir)
1241
+ raise e unless File.exist?(scope(".git"))
1242
+ e.message << "\n" <<
1243
+ 'Run "git submodule update --init" to get the bundled version.'
1244
+ else
1245
+ $LOAD_PATH.unshift dir
1246
+ retry
1247
+ end
1248
+ end
1249
+ end
1250
+ end
1251
+
1150
1252
  private
1151
1253
 
1254
+ def find_encoding_error(str)
1255
+ encoding = str.encoding
1256
+ cr = Regexp.quote("\r".encode(encoding).force_encoding('BINARY'))
1257
+ lf = Regexp.quote("\n".encode(encoding).force_encoding('BINARY'))
1258
+ ff = Regexp.quote("\f".encode(encoding).force_encoding('BINARY'))
1259
+ line_break = /#{cr}#{lf}?|#{ff}|#{lf}/
1260
+
1261
+ str.force_encoding("binary").split(line_break).each_with_index do |line, i|
1262
+ begin
1263
+ line.encode(encoding)
1264
+ rescue Encoding::UndefinedConversionError => e
1265
+ raise Sass::SyntaxError.new(
1266
+ "Invalid #{encoding.name} character #{undefined_conversion_error_char(e)}",
1267
+ :line => i + 1)
1268
+ end
1269
+ end
1270
+
1271
+ # We shouldn't get here, but it's possible some weird encoding stuff causes it.
1272
+ return str, str.encoding
1273
+ end
1274
+
1152
1275
  # rubocop:disable LineLength
1153
1276
 
1154
1277
  # Calculates the memoization table for the Least Common Subsequence algorithm.
data/lib/sass/version.rb CHANGED
@@ -85,20 +85,20 @@ module Sass
85
85
  private
86
86
 
87
87
  def revision_number
88
- if File.exists?(Sass::Util.scope('REVISION'))
88
+ if File.exist?(Sass::Util.scope('REVISION'))
89
89
  rev = File.read(Sass::Util.scope('REVISION')).strip
90
90
  return rev unless rev =~ /^([a-f0-9]+|\(.*\))$/ || rev == '(unknown)'
91
91
  end
92
92
 
93
- return unless File.exists?(Sass::Util.scope('.git/HEAD'))
93
+ return unless File.exist?(Sass::Util.scope('.git/HEAD'))
94
94
  rev = File.read(Sass::Util.scope('.git/HEAD')).strip
95
95
  return rev unless rev =~ /^ref: (.*)$/
96
96
 
97
97
  ref_name = $1
98
98
  ref_file = Sass::Util.scope(".git/#{ref_name}")
99
99
  info_file = Sass::Util.scope(".git/info/refs")
100
- return File.read(ref_file).strip if File.exists?(ref_file)
101
- return unless File.exists?(info_file)
100
+ return File.read(ref_file).strip if File.exist?(ref_file)
101
+ return unless File.exist?(info_file)
102
102
  File.open(info_file) do |f|
103
103
  f.each do |l|
104
104
  sha, ref = l.strip.split("\t", 2)
@@ -110,7 +110,7 @@ module Sass
110
110
  end
111
111
 
112
112
  def version_date
113
- return unless File.exists?(Sass::Util.scope('VERSION_DATE'))
113
+ return unless File.exist?(Sass::Util.scope('VERSION_DATE'))
114
114
  DateTime.parse(File.read(Sass::Util.scope('VERSION_DATE')).strip)
115
115
  end
116
116
  end
data/lib/sass.rb CHANGED
@@ -1,11 +1,6 @@
1
1
  dir = File.dirname(__FILE__)
2
2
  $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
3
3
 
4
- # This is necessary to set so that the Haml code that tries to load Sass
5
- # knows that Sass is indeed loading,
6
- # even if there's some crazy autoload stuff going on.
7
- SASS_BEGUN_TO_LOAD = true unless defined?(SASS_BEGUN_TO_LOAD)
8
-
9
4
  require 'sass/version'
10
5
 
11
6
  # The module that contains everything Sass-related: