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
data/lib/sass/engine.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'set'
2
2
  require 'digest/sha1'
3
3
  require 'sass/cache_stores'
4
+ require 'sass/deprecation'
4
5
  require 'sass/source/position'
5
6
  require 'sass/source/range'
6
7
  require 'sass/source/map'
@@ -84,23 +85,25 @@ module Sass
84
85
  # This class handles the parsing and compilation of the Sass template.
85
86
  # Example usage:
86
87
  #
87
- # template = File.load('stylesheets/sassy.sass')
88
+ # template = File.read('stylesheets/sassy.sass')
88
89
  # sass_engine = Sass::Engine.new(template)
89
90
  # output = sass_engine.render
90
91
  # puts output
91
92
  class Engine
93
+ @@old_property_deprecation = Deprecation.new
94
+
92
95
  # A line of Sass code.
93
96
  #
94
97
  # `text`: `String`
95
98
  # : The text in the line, without any whitespace at the beginning or end.
96
99
  #
97
- # `tabs`: `Fixnum`
100
+ # `tabs`: `Integer`
98
101
  # : The level of indentation of the line.
99
102
  #
100
- # `index`: `Fixnum`
103
+ # `index`: `Integer`
101
104
  # : The line number in the original document.
102
105
  #
103
- # `offset`: `Fixnum`
106
+ # `offset`: `Integer`
104
107
  # : The number of bytes in on the line that the text begins.
105
108
  # This ends up being the number of bytes of leading whitespace.
106
109
  #
@@ -157,7 +160,7 @@ module Sass
157
160
  # @api public
158
161
  DEFAULT_OPTIONS = {
159
162
  :style => :nested,
160
- :load_paths => ['.'],
163
+ :load_paths => [],
161
164
  :cache => true,
162
165
  :cache_location => './.sass-cache',
163
166
  :syntax => :sass,
@@ -168,11 +171,11 @@ module Sass
168
171
  # default values and resolving aliases.
169
172
  #
170
173
  # @param options [{Symbol => Object}] The options hash;
171
- # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
174
+ # see {file:SASS_REFERENCE.md#Options the Sass options documentation}
172
175
  # @return [{Symbol => Object}] The normalized options hash.
173
176
  # @private
174
177
  def self.normalize_options(options)
175
- options = DEFAULT_OPTIONS.merge(options.reject {|k, v| v.nil?})
178
+ options = DEFAULT_OPTIONS.merge(options.reject {|_k, v| v.nil?})
176
179
 
177
180
  # If the `:filename` option is passed in without an importer,
178
181
  # assume it's using the default filesystem importer.
@@ -192,6 +195,16 @@ module Sass
192
195
  options[:filesystem_importer].new(p.to_s)
193
196
  end
194
197
 
198
+ # Remove any deprecated importers if the location is imported explicitly
199
+ options[:load_paths].reject! do |importer|
200
+ importer.is_a?(Sass::Importers::DeprecatedPath) &&
201
+ options[:load_paths].find do |other_importer|
202
+ other_importer.is_a?(Sass::Importers::Filesystem) &&
203
+ other_importer != importer &&
204
+ other_importer.root == importer.root
205
+ end
206
+ end
207
+
195
208
  # Backwards compatibility
196
209
  options[:property_syntax] ||= options[:attribute_syntax]
197
210
  case options[:property_syntax]
@@ -212,14 +225,14 @@ module Sass
212
225
  #
213
226
  # @param filename [String] The path to the Sass or SCSS file
214
227
  # @param options [{Symbol => Object}] The options hash;
215
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
228
+ # See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
216
229
  # @return [Sass::Engine] The Engine for the given Sass or SCSS file.
217
230
  # @raise [Sass::SyntaxError] if there's an error in the document.
218
231
  def self.for_file(filename, options)
219
232
  had_syntax = options[:syntax]
220
233
 
221
234
  if had_syntax
222
- # Use what was explicitly specificed
235
+ # Use what was explicitly specified
223
236
  elsif filename =~ /\.scss$/
224
237
  options.merge!(:syntax => :scss)
225
238
  elsif filename =~ /\.sass$/
@@ -230,7 +243,7 @@ module Sass
230
243
  end
231
244
 
232
245
  # The options for the Sass engine.
233
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
246
+ # See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
234
247
  #
235
248
  # @return [{Symbol => Object}]
236
249
  attr_reader :options
@@ -247,14 +260,17 @@ module Sass
247
260
  # that can be converted to Unicode.
248
261
  # If the template contains an `@charset` declaration,
249
262
  # that overrides the Ruby encoding
250
- # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
263
+ # (see {file:SASS_REFERENCE.md#Encodings the encoding documentation})
251
264
  # @param options [{Symbol => Object}] An options hash.
252
- # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
265
+ # See {file:SASS_REFERENCE.md#Options the Sass options documentation}.
253
266
  # @see {Sass::Engine.for_file}
254
267
  # @see {Sass::Plugin}
255
268
  def initialize(template, options = {})
256
269
  @options = self.class.normalize_options(options)
257
270
  @template = template
271
+ @checked_encoding = false
272
+ @filename = nil
273
+ @line = nil
258
274
  end
259
275
 
260
276
  # Render the template to CSS.
@@ -466,7 +482,7 @@ ERR
466
482
 
467
483
  line_tabs = line_tab_str.scan(tab_str).size
468
484
  if tab_str * line_tabs != line_tab_str
469
- message = <<END.strip.gsub("\n", ' ')
485
+ message = <<END.strip.tr("\n", ' ')
470
486
  Inconsistent indentation: #{Sass::Shared.human_indentation line_tab_str, true} used for indentation,
471
487
  but the rest of the document was indented using #{Sass::Shared.human_indentation tab_str}.
472
488
  END
@@ -479,15 +495,13 @@ END
479
495
  end
480
496
 
481
497
  # @comment
482
- # rubocop:disable ParameterLists
483
498
  def try_comment(line, last, tab_str, comment_tab_str, index)
484
- # rubocop:enable ParameterLists
485
499
  return unless last && last.comment?
486
500
  # Nested comment stuff must be at least one whitespace char deeper
487
501
  # than the normal indentation
488
502
  return unless line =~ /^#{tab_str}\s/
489
503
  unless line =~ /^(?:#{comment_tab_str})(.*)$/
490
- raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), :line => index)
504
+ raise SyntaxError.new(<<MSG.strip.tr("\n", " "), :line => index)
491
505
  Inconsistent indentation:
492
506
  previous line was indented by #{Sass::Shared.human_indentation comment_tab_str},
493
507
  but this line was indented by #{Sass::Shared.human_indentation line[/^\s*/]}.
@@ -564,8 +578,8 @@ MSG
564
578
  if continued_comment &&
565
579
  child.line == continued_comment.line +
566
580
  continued_comment.lines + 1
567
- continued_comment.value.last.sub!(/ \*\/\Z/, '')
568
- child.value.first.gsub!(/\A\/\*/, ' *')
581
+ continued_comment.value.last.sub!(%r{ \*/\Z}, '')
582
+ child.value.first.gsub!(%r{\A/\*}, ' *')
569
583
  continued_comment.value += ["\n"] + child.value
570
584
  next
571
585
  end
@@ -614,6 +628,11 @@ WARNING
614
628
  raise SyntaxError.new("Invalid property: \"#{line.text}\".",
615
629
  :line => @line) if name.nil? || value.nil?
616
630
 
631
+ @@old_property_deprecation.warn(@options[:filename], @line, <<WARNING)
632
+ Old-style properties like "#{line.text}" are deprecated and will be an error in future versions of Sass.
633
+ Use "#{name}: #{value}" instead.
634
+ WARNING
635
+
617
636
  value_start_offset = name_end_offset = name_start_offset + name.length
618
637
  unless value.empty?
619
638
  # +1 and -1 both compensate for the leading ':', which is part of line.text
@@ -707,9 +726,12 @@ WARNING
707
726
  expr = Sass::Script::Tree::Literal.new(Sass::Script::Value::String.new(""))
708
727
  end_offset = start_offset
709
728
  else
710
- expr = parse_script(value, :offset => to_parser_offset(start_offset))
729
+ expr = parse_script(value,
730
+ :offset => to_parser_offset(start_offset),
731
+ :css_variable => name.start_with?("--"))
711
732
  end_offset = expr.source_range.end_pos.offset - 1
712
733
  end
734
+
713
735
  node = Tree::PropNode.new(parse_interp(name), expr, prop)
714
736
  node.value_source_range = Sass::Source::Range.new(
715
737
  Sass::Source::Position.new(line.index, to_parser_offset(start_offset)),
@@ -766,7 +788,19 @@ WARNING
766
788
  else
767
789
  :normal
768
790
  end
769
- Tree::CommentNode.new(value, type)
791
+ comment = Tree::CommentNode.new(value, type)
792
+ comment.line = line.index
793
+ text = line.text.rstrip
794
+ if text.include?("\n")
795
+ end_offset = text.length - text.rindex("\n")
796
+ else
797
+ end_offset = to_parser_offset(line.offset + text.length)
798
+ end
799
+ comment.source_range = Sass::Source::Range.new(
800
+ Sass::Source::Position.new(@line, to_parser_offset(line.offset)),
801
+ Sass::Source::Position.new(@line + text.count("\n"), end_offset),
802
+ @options[:filename])
803
+ comment
770
804
  else
771
805
  Tree::RuleNode.new(parse_interp(line.text), full_line_range(line))
772
806
  end
@@ -776,14 +810,12 @@ WARNING
776
810
  :each, :while, :if, :else, :extend, :import, :media, :charset, :content,
777
811
  :at_root, :error]
778
812
 
779
- # @comment
780
- # rubocop:disable MethodLength
781
813
  def parse_directive(parent, line, root)
782
814
  directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
783
815
  raise SyntaxError.new("Invalid directive: '@'.") unless directive
784
816
  offset = directive.size + whitespace.size + 1 if whitespace
785
817
 
786
- directive_name = directive.gsub('-', '_').to_sym
818
+ directive_name = directive.tr('-', '_').to_sym
787
819
  if DIRECTIVES.include?(directive_name)
788
820
  return send("parse_#{directive_name}_directive", parent, line, root, value, offset)
789
821
  end
@@ -838,8 +870,6 @@ WARNING
838
870
  )
839
871
  Tree::ExtendNode.new(interp_parsed, optional, selector_range)
840
872
  end
841
- # @comment
842
- # rubocop:enable MethodLength
843
873
 
844
874
  def parse_warn_directive(parent, line, root, value, offset)
845
875
  raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
@@ -1003,12 +1033,21 @@ WARNING
1003
1033
  end_pos = str.source_range.end_pos
1004
1034
  node = Tree::CssImportNode.new(str)
1005
1035
  else
1006
- media_parser = Sass::SCSS::Parser.new(scanner,
1036
+ supports_parser = Sass::SCSS::Parser.new(scanner,
1007
1037
  @options[:filename], @options[:importer],
1008
1038
  @line, str.source_range.end_pos.offset)
1009
- media = media_parser.parse_media_query_list
1010
- end_pos = Sass::Source::Position.new(@line, media_parser.offset + 1)
1011
- node = Tree::CssImportNode.new(str, media.to_a)
1039
+ supports_condition = supports_parser.parse_supports_clause
1040
+
1041
+ if scanner.eos?
1042
+ node = Tree::CssImportNode.new(str, [], supports_condition)
1043
+ else
1044
+ media_parser = Sass::SCSS::Parser.new(scanner,
1045
+ @options[:filename], @options[:importer],
1046
+ @line, str.source_range.end_pos.offset)
1047
+ media = media_parser.parse_media_query_list
1048
+ end_pos = Sass::Source::Position.new(@line, media_parser.offset + 1)
1049
+ node = Tree::CssImportNode.new(str, media.to_a, supports_condition)
1050
+ end
1012
1051
  end
1013
1052
 
1014
1053
  node.source_range = Sass::Source::Range.new(
@@ -1115,9 +1154,9 @@ WARNING
1115
1154
  end
1116
1155
 
1117
1156
  def parse_script(script, options = {})
1118
- line = options[:line] || @line
1119
- offset = options[:offset] || @offset + 1
1120
- Script.parse(script, line, offset, @options)
1157
+ line = options.delete(:line) || @line
1158
+ offset = options.delete(:offset) || @offset + 1
1159
+ Script.parse(script, line, offset, @options.merge(options))
1121
1160
  end
1122
1161
 
1123
1162
  def format_comment_text(text, silent)
@@ -1129,9 +1168,10 @@ WARNING
1129
1168
  end
1130
1169
 
1131
1170
  return "/* */" if content.empty?
1132
- content.last.gsub!(/ ?\*\/ *$/, '')
1171
+ content.last.gsub!(%r{ ?\*/ *$}, '')
1172
+ first = content.shift unless removed_first
1133
1173
  content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
1134
- content.first.gsub!(/^ /, '') unless removed_first
1174
+ content.unshift first unless removed_first
1135
1175
  if silent
1136
1176
  "/*" + content.join("\n *") + " */"
1137
1177
  else
@@ -1169,10 +1209,15 @@ WARNING
1169
1209
  res << "\\" * (escapes - 1) << '#{'
1170
1210
  else
1171
1211
  res << "\\" * [0, escapes - 1].max
1172
- # Add 1 to emulate to_parser_offset.
1173
- res << Script::Parser.new(
1174
- scan, line, offset + scan.pos - scan.matched_size + 1, options).
1175
- parse_interpolated
1212
+ if scan[1].include?("\n")
1213
+ line += scan[1].count("\n")
1214
+ offset = scan.matched_size - scan[1].rindex("\n")
1215
+ else
1216
+ offset += scan.matched_size
1217
+ end
1218
+ node = Script::Parser.new(scan, line, offset, options).parse_interpolated
1219
+ offset = node.source_range.end_pos.offset
1220
+ res << node
1176
1221
  end
1177
1222
  end
1178
1223
  res << rest
@@ -81,12 +81,19 @@ module Sass
81
81
  inherited_hash_reader :function
82
82
 
83
83
  # @param options [{Symbol => Object}] The options hash. See
84
- # {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
84
+ # {file:SASS_REFERENCE.md#Options the Sass options documentation}.
85
85
  # @param parent [Environment] See \{#parent}
86
86
  def initialize(parent = nil, options = nil)
87
87
  @parent = parent
88
88
  @options = options || (parent && parent.options) || {}
89
- @stack = Sass::Stack.new if @parent.nil?
89
+ @stack = @parent.nil? ? Sass::Stack.new : nil
90
+ @caller = nil
91
+ @content = nil
92
+ @filename = nil
93
+ @functions = nil
94
+ @mixins = nil
95
+ @selector = nil
96
+ @vars = nil
90
97
  end
91
98
 
92
99
  # Returns whether this is the global environment.
@@ -188,4 +195,21 @@ module Sass
188
195
  @content ||= env.is_a?(ReadOnlyEnvironment) ? env : ReadOnlyEnvironment.new(env, env.options)
189
196
  end
190
197
  end
198
+
199
+ # An environment that can write to in-scope global variables, but doesn't
200
+ # create new variables in the global scope. Useful for top-level control
201
+ # directives.
202
+ class SemiGlobalEnvironment < Environment
203
+ def try_set_var(name, value)
204
+ @vars ||= {}
205
+ if @vars.include?(name)
206
+ @vars[name] = value
207
+ true
208
+ elsif @parent
209
+ @parent.try_set_var(name, value)
210
+ else
211
+ false
212
+ end
213
+ end
214
+ end
191
215
  end
data/lib/sass/error.rb CHANGED
@@ -69,14 +69,14 @@ module Sass
69
69
  # The name of the mixin in which the error occurred.
70
70
  # This could be `nil` if the error occurred outside a mixin.
71
71
  #
72
- # @return [Fixnum]
72
+ # @return [String]
73
73
  def sass_mixin
74
74
  sass_backtrace.first[:mixin]
75
75
  end
76
76
 
77
77
  # The line of the Sass template on which the error occurred.
78
78
  #
79
- # @return [Fixnum]
79
+ # @return [Integer]
80
80
  def sass_line
81
81
  sass_backtrace.first[:line]
82
82
  end
@@ -86,7 +86,7 @@ module Sass
86
86
  # @param attrs [{Symbol => Object}] The information in the backtrace entry.
87
87
  # See \{#sass\_backtrace}
88
88
  def add_backtrace(attrs)
89
- sass_backtrace << attrs.reject {|k, v| v.nil?}
89
+ sass_backtrace << attrs.reject {|_k, v| v.nil?}
90
90
  end
91
91
 
92
92
  # Modify the top Sass backtrace entries
@@ -104,12 +104,12 @@ module Sass
104
104
  # @param attrs [{Symbol => Object}] The information to add to the backtrace entry.
105
105
  # See \{#sass\_backtrace}
106
106
  def modify_backtrace(attrs)
107
- attrs = attrs.reject {|k, v| v.nil?}
107
+ attrs = attrs.reject {|_k, v| v.nil?}
108
108
  # Move backwards through the backtrace
109
- (0...sass_backtrace.size).to_a.reverse.each do |i|
109
+ (0...sass_backtrace.size).to_a.reverse_each do |i|
110
110
  entry = sass_backtrace[i]
111
111
  sass_backtrace[i] = attrs.merge(entry)
112
- attrs.reject! {|k, v| entry.include?(k)}
112
+ attrs.reject! {|k, _v| entry.include?(k)}
113
113
  break if attrs.empty?
114
114
  end
115
115
  end
@@ -127,7 +127,7 @@ module Sass
127
127
  return nil if super.nil?
128
128
  return super if sass_backtrace.all? {|h| h.empty?}
129
129
  sass_backtrace.map do |h|
130
- "#{h[:filename] || "(sass)"}:#{h[:line]}" +
130
+ "#{h[:filename] || '(sass)'}:#{h[:line]}" +
131
131
  (h[:mixin] ? ":in `#{h[:mixin]}'" : "")
132
132
  end + super
133
133
  end
@@ -143,7 +143,7 @@ module Sass
143
143
  map {|l| "\n" + (" " * "Error: ".size) + l}.join
144
144
  "Error: #{msg}" +
145
145
  Sass::Util.enum_with_index(sass_backtrace).map do |entry, i|
146
- "\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
146
+ "\n #{i == 0 ? 'on' : 'from'} line #{entry[:line]}" +
147
147
  " of #{entry[:filename] || default_filename}" +
148
148
  (entry[:mixin] ? ", in `#{entry[:mixin]}'" : "")
149
149
  end.join
@@ -153,7 +153,7 @@ module Sass
153
153
  # Returns an error report for an exception in CSS format.
154
154
  #
155
155
  # @param e [Exception]
156
- # @param line_offset [Fixnum] The number of the first line of the Sass template.
156
+ # @param line_offset [Integer] The number of the first line of the Sass template.
157
157
  # @return [String] The error report
158
158
  # @raise [Exception] `e`, if the
159
159
  # {file:SASS_REFERENCE.md#full_exception-option `:full_exception`} option
@@ -163,9 +163,9 @@ module Sass
163
163
 
164
164
  <<END
165
165
  /*
166
- #{header.gsub("*/", "*\\/")}
166
+ #{header.gsub('*/', '*\\/')}
167
167
 
168
- Backtrace:\n#{e.backtrace.join("\n").gsub("*/", "*\\/")}
168
+ Backtrace:\n#{e.backtrace.join("\n").gsub('*/', '*\\/')}
169
169
  */
170
170
  body:before {
171
171
  white-space: pre;
@@ -183,7 +183,7 @@ END
183
183
 
184
184
  line_num = e.sass_line + 1 - line_offset
185
185
  min = [line_num - 6, 0].max
186
- section = e.sass_template.rstrip.split("\n")[min ... line_num + 5]
186
+ section = e.sass_template.rstrip.split("\n")[min...line_num + 5]
187
187
  return e.sass_backtrace_str if section.nil? || section.empty?
188
188
 
189
189
  e.sass_backtrace_str + "\n\n" + Sass::Util.enum_with_index(section).
@@ -18,11 +18,23 @@ module Sass::Exec
18
18
  begin
19
19
  parse
20
20
  rescue Exception => e
21
+ # Exit code 65 indicates invalid data per
22
+ # http://www.freebsd.org/cgi/man.cgi?query=sysexits. Setting it via
23
+ # at_exit is a bit of a hack, but it allows us to rethrow when --trace
24
+ # is active and get both the built-in exception formatting and the
25
+ # correct exit code.
26
+ at_exit {exit Sass::Util.windows? ? 13 : 65} if e.is_a?(Sass::SyntaxError)
27
+
21
28
  raise e if @options[:trace] || e.is_a?(SystemExit)
22
29
 
23
- $stderr.print "#{e.class}: " unless e.class == RuntimeError
24
- $stderr.puts "#{e.message}"
30
+ if e.is_a?(Sass::SyntaxError)
31
+ $stderr.puts e.sass_backtrace_str("standard input")
32
+ else
33
+ $stderr.print "#{e.class}: " unless e.class == RuntimeError
34
+ $stderr.puts e.message.to_s
35
+ end
25
36
  $stderr.puts " Use --trace for backtrace."
37
+
26
38
  exit 1
27
39
  end
28
40
  exit 0
@@ -137,7 +149,7 @@ module Sass::Exec
137
149
 
138
150
  # Wraps the given string in terminal escapes
139
151
  # causing it to have the given color.
140
- # If terminal esapes aren't supported on this platform,
152
+ # If terminal escapes aren't supported on this platform,
141
153
  # just returns the string instead.
142
154
  #
143
155
  # @param color [Symbol] The name of the color to use.
@@ -108,10 +108,11 @@ END
108
108
  @options[:for_tree][:dasherize] = true
109
109
  end
110
110
 
111
- opts.on('--indent NUM',
111
+ opts.on(
112
+ '--indent NUM',
112
113
  'How many spaces to use for each level of indentation. Defaults to 2.',
113
- '"t" means use hard tabs.') do |indent|
114
-
114
+ '"t" means use hard tabs.'
115
+ ) do |indent|
115
116
  if indent == 't'
116
117
  @options[:for_tree][:indent] = "\t"
117
118
  else
@@ -156,13 +157,18 @@ END
156
157
  @options[:for_engine][:read_cache] = false
157
158
  end
158
159
 
160
+ opts.on('-q', '--quiet', 'Silence warnings and status messages during conversion.') do |bool|
161
+ @options[:for_engine][:quiet] = bool
162
+ end
163
+
159
164
  opts.on('--trace', :NONE, 'Show a full Ruby stack trace on error') do
160
165
  @options[:trace] = true
161
166
  end
162
167
  end
163
168
 
164
169
  def process_directory
165
- unless (input = @options[:input] = @args.shift)
170
+ @options[:input] = @args.shift
171
+ unless @options[:input]
166
172
  raise "Error: directory required when using --recursive."
167
173
  end
168
174
 
@@ -207,15 +213,15 @@ END
207
213
  puts_action :create, :green, output
208
214
  end
209
215
 
210
- input = open_file(f)
211
- process_file(input, output)
216
+ process_file(f, output)
212
217
  end
213
218
  end
214
219
 
215
220
  def process_file(input, output)
216
- if input.is_a?(File)
221
+ input_path, output_path = path_for(input), path_for(output)
222
+ if input_path
217
223
  @options[:from] ||=
218
- case input.path
224
+ case input_path
219
225
  when /\.scss$/; :scss
220
226
  when /\.sass$/; :sass
221
227
  when /\.less$/; raise "sass-convert no longer supports LessCSS."
@@ -225,9 +231,9 @@ END
225
231
  raise "Error: the --in-place option requires a filename."
226
232
  end
227
233
 
228
- if output.is_a?(File)
234
+ if output_path
229
235
  @options[:to] ||=
230
- case output.path
236
+ case output_path
231
237
  when /\.scss$/; :scss
232
238
  when /\.sass$/; :sass
233
239
  end
@@ -241,17 +247,17 @@ END
241
247
  Sass::Util.silence_sass_warnings do
242
248
  if @options[:from] == :css
243
249
  require 'sass/css'
244
- Sass::CSS.new(input.read, @options[:for_tree]).render(@options[:to])
250
+ Sass::CSS.new(read(input), @options[:for_tree]).render(@options[:to])
245
251
  else
246
- if input.is_a?(File)
247
- Sass::Engine.for_file(input.path, @options[:for_engine])
252
+ if input_path
253
+ Sass::Engine.for_file(input_path, @options[:for_engine])
248
254
  else
249
- Sass::Engine.new(input.read, @options[:for_engine])
255
+ Sass::Engine.new(read(input), @options[:for_engine])
250
256
  end.to_tree.send("to_#{@options[:to]}", @options[:for_tree])
251
257
  end
252
258
  end
253
259
 
254
- output = input.path if @options[:in_place]
260
+ output = input_path if @options[:in_place]
255
261
  write_output(out, output)
256
262
  rescue Sass::SyntaxError => e
257
263
  raise e if @options[:trace]
@@ -260,5 +266,18 @@ END
260
266
  rescue LoadError => err
261
267
  handle_load_error(err)
262
268
  end
269
+
270
+ def path_for(file)
271
+ return file.path if file.is_a?(File)
272
+ return file if file.is_a?(String)
273
+ end
274
+
275
+ def read(file)
276
+ if file.respond_to?(:read)
277
+ file.read
278
+ else
279
+ open(file, 'rb') {|f| f.read}
280
+ end
281
+ end
263
282
  end
264
283
  end
@@ -42,6 +42,7 @@ END
42
42
  if @args.size == 1
43
43
  @args = split_colon_path(@args.first)
44
44
  else
45
+ @fake_update = true
45
46
  @options[:update] = true
46
47
  end
47
48
  end
@@ -155,7 +156,7 @@ END
155
156
  ' file: always absolute file URIs',
156
157
  ' inline: include the source text in the sourcemap',
157
158
  ' none: no sourcemaps') do |type|
158
- if type && !%w[auto file inline none].include?(type)
159
+ if type && !%w(auto file inline none).include?(type)
159
160
  $stderr.puts "Unknown sourcemap type #{type}.\n\n"
160
161
  $stderr.puts opts
161
162
  exit
@@ -291,6 +292,16 @@ MSG
291
292
 
292
293
  dirs, files = @args.map {|name| split_colon_path(name)}.
293
294
  partition {|i, _| File.directory? i}
295
+
296
+ if @fake_update && !dirs.empty?
297
+ # Issue 1602.
298
+ Sass::Util.sass_warn <<WARNING.strip
299
+ DEPRECATION WARNING: Compiling directories without --update or --watch is
300
+ deprecated and won't work in future versions of Sass. Instead use:
301
+ #{@default_syntax} --update #{@args}
302
+ WARNING
303
+ end
304
+
294
305
  files.map! do |from, to|
295
306
  to ||= from.gsub(/\.[^.]*?$/, '.css')
296
307
  sourcemap = Sass::Util.sourcemap_name(to) if @options[:sourcemap]
@@ -355,6 +366,11 @@ MSG
355
366
  input = @options[:input]
356
367
  output = @options[:output]
357
368
 
369
+ if input == $stdin
370
+ # See issue 1745
371
+ (@options[:for_engine][:load_paths] ||= []) << ::Sass::Importers::DeprecatedPath.new(".")
372
+ end
373
+
358
374
  @options[:for_engine][:syntax] ||= :scss if input.is_a?(File) && input.path =~ /\.scss$/
359
375
  @options[:for_engine][:syntax] ||= @default_syntax
360
376
  engine =
@@ -370,11 +386,12 @@ MSG
370
386
  input.close if input.is_a?(File)
371
387
 
372
388
  if @options[:sourcemap] != :none && @options[:sourcemap_filename]
373
- relative_sourcemap_path = Sass::Util.pathname(@options[:sourcemap_filename]).
374
- relative_path_from(Sass::Util.pathname(@options[:output_filename]).dirname)
389
+ relative_sourcemap_path = Sass::Util.relative_path_from(
390
+ @options[:sourcemap_filename], Sass::Util.pathname(@options[:output_filename]).dirname)
375
391
  rendered, mapping = engine.render_with_sourcemap(relative_sourcemap_path.to_s)
376
392
  write_output(rendered, output)
377
- write_output(mapping.to_json(
393
+ write_output(
394
+ mapping.to_json(
378
395
  :type => @options[:sourcemap],
379
396
  :css_path => @options[:output_filename],
380
397
  :sourcemap_path => @options[:sourcemap_filename]) + "\n",
@@ -384,8 +401,7 @@ MSG
384
401
  end
385
402
  rescue Sass::SyntaxError => e
386
403
  write_output(Sass::SyntaxError.exception_to_css(e), output) if output.is_a?(String)
387
- raise e if @options[:trace]
388
- raise e.sass_backtrace_str("standard input")
404
+ raise e
389
405
  ensure
390
406
  output.close if output.is_a? File
391
407
  end
@@ -397,7 +413,7 @@ MSG
397
413
  def split_colon_path(path)
398
414
  one, two = path.split(':', 2)
399
415
  if one && two && Sass::Util.windows? &&
400
- one =~ /\A[A-Za-z]\Z/ && two =~ /\A[\/\\]/
416
+ one =~ /\A[A-Za-z]\Z/ && two =~ %r{\A[/\\]}
401
417
  # If we're on Windows and we were passed a drive letter path,
402
418
  # don't split on that colon.
403
419
  one2, two = two.split(':', 2)
data/lib/sass/features.rb CHANGED
@@ -7,12 +7,12 @@ module Sass
7
7
  #
8
8
  # When this is updated, the documentation of `feature-exists()` should be
9
9
  # updated as well.
10
- KNOWN_FEATURES = Set[*%w{
10
+ KNOWN_FEATURES = Set[*%w(
11
11
  global-variable-shadowing
12
12
  extend-selector-pseudoclass
13
13
  units-level-3
14
14
  at-error
15
- }]
15
+ )]
16
16
 
17
17
  # Check if a feature exists by name. This is used to implement
18
18
  # the Sass function `feature-exists($feature)`
@@ -90,7 +90,7 @@ module Sass
90
90
  #
91
91
  # @param uri [String] The URI of the file to check.
92
92
  # Comes from a `:filename` option set on an engine returned by this importer.
93
- # @param options [{Symbol => Objet}] Options for the Sass file
93
+ # @param options [{Symbol => Object}] Options for the Sass file
94
94
  # containing the `@import` currently being checked.
95
95
  # @return [Time, nil]
96
96
  def mtime(uri, options)