sass 3.3.14 → 3.4.0.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +5 -5
  4. data/VERSION +1 -1
  5. data/VERSION_DATE +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/sass +1 -1
  8. data/bin/scss +1 -1
  9. data/lib/sass.rb +0 -5
  10. data/lib/sass/css.rb +1 -3
  11. data/lib/sass/engine.rb +28 -39
  12. data/lib/sass/environment.rb +13 -17
  13. data/lib/sass/error.rb +6 -9
  14. data/lib/sass/exec.rb +5 -771
  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 +419 -0
  18. data/lib/sass/features.rb +6 -0
  19. data/lib/sass/importers.rb +0 -1
  20. data/lib/sass/importers/base.rb +5 -1
  21. data/lib/sass/importers/filesystem.rb +4 -21
  22. data/lib/sass/media.rb +1 -4
  23. data/lib/sass/plugin/compiler.rb +32 -136
  24. data/lib/sass/script/css_lexer.rb +1 -1
  25. data/lib/sass/script/functions.rb +363 -39
  26. data/lib/sass/script/lexer.rb +68 -50
  27. data/lib/sass/script/parser.rb +29 -14
  28. data/lib/sass/script/tree.rb +1 -0
  29. data/lib/sass/script/tree/funcall.rb +1 -1
  30. data/lib/sass/script/tree/interpolation.rb +19 -1
  31. data/lib/sass/script/tree/selector.rb +26 -0
  32. data/lib/sass/script/value.rb +0 -1
  33. data/lib/sass/script/value/bool.rb +0 -5
  34. data/lib/sass/script/value/color.rb +32 -12
  35. data/lib/sass/script/value/helpers.rb +107 -0
  36. data/lib/sass/script/value/list.rb +0 -15
  37. data/lib/sass/script/value/null.rb +0 -5
  38. data/lib/sass/script/value/number.rb +60 -14
  39. data/lib/sass/script/value/string.rb +53 -9
  40. data/lib/sass/scss/css_parser.rb +8 -2
  41. data/lib/sass/scss/parser.rb +175 -319
  42. data/lib/sass/scss/rx.rb +14 -5
  43. data/lib/sass/scss/static_parser.rb +298 -1
  44. data/lib/sass/selector.rb +56 -193
  45. data/lib/sass/selector/abstract_sequence.rb +28 -13
  46. data/lib/sass/selector/comma_sequence.rb +91 -12
  47. data/lib/sass/selector/pseudo.rb +256 -0
  48. data/lib/sass/selector/sequence.rb +99 -31
  49. data/lib/sass/selector/simple.rb +14 -25
  50. data/lib/sass/selector/simple_sequence.rb +101 -37
  51. data/lib/sass/shared.rb +1 -1
  52. data/lib/sass/source/map.rb +23 -9
  53. data/lib/sass/stack.rb +0 -6
  54. data/lib/sass/supports.rb +1 -1
  55. data/lib/sass/tree/at_root_node.rb +1 -0
  56. data/lib/sass/tree/directive_node.rb +7 -1
  57. data/lib/sass/tree/error_node.rb +18 -0
  58. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  59. data/lib/sass/tree/prop_node.rb +1 -1
  60. data/lib/sass/tree/rule_node.rb +11 -6
  61. data/lib/sass/tree/visitors/check_nesting.rb +3 -4
  62. data/lib/sass/tree/visitors/convert.rb +8 -17
  63. data/lib/sass/tree/visitors/cssize.rb +12 -24
  64. data/lib/sass/tree/visitors/deep_copy.rb +5 -0
  65. data/lib/sass/tree/visitors/perform.rb +43 -28
  66. data/lib/sass/tree/visitors/set_options.rb +5 -0
  67. data/lib/sass/tree/visitors/to_css.rb +14 -13
  68. data/lib/sass/util.rb +94 -90
  69. data/test/sass/cache_test.rb +1 -1
  70. data/test/sass/callbacks_test.rb +1 -1
  71. data/test/sass/compiler_test.rb +5 -14
  72. data/test/sass/conversion_test.rb +47 -1
  73. data/test/sass/css2sass_test.rb +3 -3
  74. data/test/sass/encoding_test.rb +219 -0
  75. data/test/sass/engine_test.rb +128 -191
  76. data/test/sass/exec_test.rb +2 -2
  77. data/test/sass/extend_test.rb +234 -17
  78. data/test/sass/functions_test.rb +268 -213
  79. data/test/sass/importer_test.rb +31 -21
  80. data/test/sass/logger_test.rb +1 -1
  81. data/test/sass/more_results/more_import.css +1 -1
  82. data/test/sass/plugin_test.rb +12 -11
  83. data/test/sass/results/compact.css +1 -1
  84. data/test/sass/results/complex.css +4 -4
  85. data/test/sass/results/expanded.css +1 -1
  86. data/test/sass/results/import.css +1 -1
  87. data/test/sass/results/import_charset_ibm866.css +2 -2
  88. data/test/sass/results/mixins.css +17 -17
  89. data/test/sass/results/nested.css +1 -1
  90. data/test/sass/results/parent_ref.css +2 -2
  91. data/test/sass/results/script.css +3 -3
  92. data/test/sass/results/scss_import.css +1 -1
  93. data/test/sass/script_conversion_test.rb +7 -4
  94. data/test/sass/script_test.rb +202 -79
  95. data/test/sass/scss/css_test.rb +95 -25
  96. data/test/sass/scss/rx_test.rb +4 -4
  97. data/test/sass/scss/scss_test.rb +363 -19
  98. data/test/sass/source_map_test.rb +48 -41
  99. data/test/sass/superselector_test.rb +191 -0
  100. data/test/sass/templates/scss_import.scss +2 -1
  101. data/test/sass/test_helper.rb +1 -1
  102. data/test/sass/util/multibyte_string_scanner_test.rb +1 -1
  103. data/test/sass/util/normalized_map_test.rb +1 -1
  104. data/test/sass/util/subset_map_test.rb +2 -2
  105. data/test/sass/util_test.rb +1 -1
  106. data/test/sass/value_helpers_test.rb +3 -3
  107. data/test/test_helper.rb +2 -2
  108. metadata +30 -7
  109. data/lib/sass/importers/deprecated_path.rb +0 -51
  110. data/lib/sass/script/value/deprecated_false.rb +0 -55
@@ -82,6 +82,8 @@ module Sass
82
82
  # with identifier names.
83
83
  IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
84
84
 
85
+ PARSEABLE_NUMBER = /(?:(\d*\.\d+)|(\d+))(?:[eE]([+-]?\d+))?(#{UNIT})?/
86
+
85
87
  # A hash of regular expressions that are used for tokenizing.
86
88
  REGULAR_EXPRESSIONS = {
87
89
  :whitespace => /\s+/,
@@ -89,9 +91,11 @@ module Sass
89
91
  :single_line_comment => SINGLE_LINE_COMMENT,
90
92
  :variable => /(\$)(#{IDENT})/,
91
93
  :ident => /(#{IDENT})(\()?/,
92
- :number => /(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
93
- :unary_minus_number => /-(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
94
+ :number => PARSEABLE_NUMBER,
95
+ :unary_minus_number => /-#{PARSEABLE_NUMBER}/,
94
96
  :color => HEXCOLOR,
97
+ :id => /##{IDENT}/,
98
+ :selector => /&/,
95
99
  :ident_op => /(#{Regexp.union(*IDENT_OP_NAMES.map do |s|
96
100
  Regexp.new(Regexp.escape(s) + "(?!#{NMCHAR}|\Z)")
97
101
  end)})/,
@@ -102,7 +106,7 @@ module Sass
102
106
  private
103
107
 
104
108
  def string_re(open, close)
105
- /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/
109
+ /#{open}((?:\\.|\#(?!\{)|[^#{close}\\#])*)(#{close}|#\{)/m
106
110
  end
107
111
  end
108
112
 
@@ -246,19 +250,13 @@ module Sass
246
250
  end
247
251
 
248
252
  def token
249
- if after_interpolation? && (interp = @interpolation_stack.pop)
250
- interp_type, interp_value = interp
251
- if interp_type == :special_fun
252
- return special_fun_body(interp_value)
253
- else
254
- raise "[BUG]: Unknown interp_type #{interp_type}" unless interp_type == :string
255
- return string(interp_value, true)
256
- end
253
+ if after_interpolation? && (interp_type = @interpolation_stack.pop)
254
+ return string(interp_type, true)
257
255
  end
258
256
 
259
- variable || string(:double, false) || string(:single, false) || number || color ||
260
- string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val || ident_op ||
261
- ident || op
257
+ variable || string(:double, false) || string(:single, false) || number || id || color ||
258
+ selector || string(:uri, false) || raw(UNICODERANGE) || special_fun || special_val ||
259
+ ident_op || ident || op
262
260
  end
263
261
 
264
262
  def variable
@@ -277,18 +275,27 @@ module Sass
277
275
  end
278
276
 
279
277
  def string(re, open)
278
+ line, offset = @line, @offset
280
279
  return unless scan(STRING_REGULAR_EXPRESSIONS[re][open])
280
+ if @scanner[0] =~ /([^\\]|^)\n/
281
+ Sass::Util.sass_warn <<MESSAGE
282
+ DEPRECATION WARNING on line #{line}, column #{offset}#{" of #{@filename}" if @filename}:
283
+ Unescaped multiline strings are deprecated and will be removed in a future version of Sass.
284
+ To include a newline in a string, use "\\a" or "\\a " as in CSS.
285
+ MESSAGE
286
+ end
287
+
281
288
  if @scanner[2] == '#{' # '
282
289
  @scanner.pos -= 2 # Don't actually consume the #{
283
290
  @offset -= 2
284
- @interpolation_stack << [:string, re]
291
+ @interpolation_stack << re
285
292
  end
286
293
  str =
287
294
  if re == :uri
288
295
  url = "#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2] == '#{'}"
289
296
  Script::Value::String.new(url)
290
297
  else
291
- Script::Value::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
298
+ Script::Value::String.new(Sass::Script::Value::String.value(@scanner[1]), :string)
292
299
  end
293
300
  [:string, str]
294
301
  end
@@ -318,49 +325,60 @@ module Sass
318
325
  end
319
326
 
320
327
  value = (@scanner[1] ? @scanner[1].to_f : @scanner[2].to_i) * (minus ? -1 : 1)
321
- script_number = Script::Value::Number.new(value, Array(@scanner[3]))
328
+ value *= 10**@scanner[3].to_i if @scanner[3]
329
+ script_number = Script::Value::Number.new(value, Array(@scanner[4]))
322
330
  [:number, script_number]
323
331
  end
324
332
 
333
+ def id
334
+ # Colors and ids are tough to tell apart, because they overlap but
335
+ # neither is a superset of the other. "#xyz" is an id but not a color,
336
+ # "#000" is a color but not an id, "#abc" is both, and "#0" is neither.
337
+ # We need to handle all these cases correctly.
338
+ #
339
+ # To do so, we first try to parse something as an id. If this works and
340
+ # the id is also a valid color, we return the color. Otherwise, we
341
+ # return the id. If it didn't parse as an id, we then try to parse it as
342
+ # a color. If *this* works, we return the color, and if it doesn't we
343
+ # give up and throw an error.
344
+ #
345
+ # IDs in properties are used in the Basic User Interface Module
346
+ # (http://www.w3.org/TR/css3-ui/).
347
+ return unless scan(REGULAR_EXPRESSIONS[:id])
348
+ if @scanner[0] =~ /^\#[0-9a-fA-F]+$/ && (@scanner[0].length == 4 || @scanner[0].length == 7)
349
+ return [:color, Script::Value::Color.from_hex(@scanner[0])]
350
+ end
351
+ [:ident, @scanner[0]]
352
+ end
353
+
325
354
  def color
326
- s = scan(REGULAR_EXPRESSIONS[:color])
327
- return unless s
328
- raise Sass::SyntaxError.new(<<MESSAGE.rstrip) unless s.size == 4 || s.size == 7
329
- Colors must have either three or six digits: '#{s}'
330
- MESSAGE
331
- script_color = Script::Value::Color.from_hex(s)
355
+ return unless @scanner.match?(REGULAR_EXPRESSIONS[:color])
356
+ return unless @scanner[0].length == 4 || @scanner[0].length == 7
357
+ script_color = Script::Value::Color.from_hex(scan(REGULAR_EXPRESSIONS[:color]))
332
358
  [:color, script_color]
333
359
  end
334
360
 
335
- def special_fun
336
- prefix = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
337
- return unless prefix
338
- special_fun_body(1, prefix)
361
+ def selector
362
+ start_pos = source_position
363
+ return unless scan(REGULAR_EXPRESSIONS[:selector])
364
+ script_selector = Script::Tree::Selector.new
365
+ script_selector.source_range = range(start_pos)
366
+ [:selector, script_selector]
339
367
  end
340
368
 
341
- def special_fun_body(parens, prefix = nil)
342
- str = prefix || ''
343
- while (scanned = scan(/.*?([()]|\#{)/m))
344
- str << scanned
345
- if scanned[-1] == ?(
346
- parens += 1
347
- next
348
- elsif scanned[-1] == ?)
349
- parens -= 1
350
- next unless parens == 0
351
- else
352
- raise "[BUG] Unreachable" unless @scanner[1] == '#{' # '
353
- str.slice!(-2..-1)
354
- @scanner.pos -= 2 # Don't actually consume the #{
355
- @offset -= 2
356
- @interpolation_stack << [:special_fun, parens]
357
- end
358
-
359
- return [:special_fun, Sass::Script::Value::String.new(str)]
360
- end
361
-
362
- scan(/.*/)
363
- expected!('")"')
369
+ def special_fun
370
+ str1 = scan(/((-[\w-]+-)?(calc|element)|expression|progid:[a-z\.]*)\(/i)
371
+ return unless str1
372
+ str2, _ = Sass::Shared.balance(@scanner, ?(, ?), 1)
373
+ c = str2.count("\n")
374
+ old_line = @line
375
+ old_offset = @offset
376
+ @line += c
377
+ @offset = c == 0 ? @offset + str2.size : str2[/\n([^\n]*)/, 1].size + 1
378
+ [:special_fun,
379
+ Sass::Util.merge_adjacent_strings(
380
+ [str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
381
+ str1.size + str2.size]
364
382
  end
365
383
 
366
384
  def special_val
@@ -36,13 +36,19 @@ module Sass
36
36
  # which signals the end of an interpolated segment,
37
37
  # it returns rather than throwing an error.
38
38
  #
39
+ # @param warn_for_color [Boolean] Whether raw color values passed to
40
+ # interoplation should cause a warning.
39
41
  # @return [Script::Tree::Node] The root node of the parse tree
40
42
  # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
41
- def parse_interpolated
43
+ def parse_interpolated(warn_for_color = false)
44
+ # Start two characters back to compensate for #{
45
+ start_pos = Sass::Source::Position.new(line, offset - 2)
42
46
  expr = assert_expr :expr
43
47
  assert_tok :end_interpolation
48
+ expr = Sass::Script::Tree::Interpolation.new(
49
+ nil, expr, nil, !:wb, !:wa, !:originally_text, warn_for_color)
44
50
  expr.options = @options
45
- expr
51
+ node(expr, start_pos)
46
52
  rescue Sass::SyntaxError => e
47
53
  e.modify_backtrace :line => @lexer.line, :filename => @options[:filename]
48
54
  raise e
@@ -340,7 +346,8 @@ RUBY
340
346
  e = first
341
347
  while (interp = try_tok(:begin_interpolation))
342
348
  wb = @lexer.whitespace?(interp)
343
- mid = parse_interpolated
349
+ mid = assert_expr :expr
350
+ assert_tok :end_interpolation
344
351
  wa = @lexer.whitespace?
345
352
  e = node(
346
353
  Script::Tree::Interpolation.new(e, mid, space, wb, wa),
@@ -382,7 +389,7 @@ RUBY
382
389
 
383
390
  name = @lexer.next
384
391
  if (color = Sass::Script::Value::Color::COLOR_NAMES[name.value.downcase])
385
- literal_node(Sass::Script::Value::Color.new(color), name.source_range)
392
+ literal_node(Sass::Script::Value::Color.new(color, name.value), name.source_range)
386
393
  elsif name.value == "true"
387
394
  literal_node(Sass::Script::Value::Bool.new(true), name.source_range)
388
395
  elsif name.value == "false"
@@ -487,14 +494,22 @@ RUBY
487
494
  end
488
495
 
489
496
  def special_fun
490
- first = try_tok(:special_fun)
491
- return paren unless first
492
- str = literal_node(first.value, first.source_range)
493
- return str unless try_tok(:begin_interpolation)
494
- mid = parse_interpolated
495
- last = assert_expr(:special_fun)
496
- node(Tree::Interpolation.new(str, mid, last, false, false),
497
- first.source_range.start_pos)
497
+ start_pos = source_position
498
+ tok = try_tok(:special_fun)
499
+ return paren unless tok
500
+ first = literal_node(Script::Value::String.new(tok.value.first),
501
+ start_pos, start_pos.after(tok.value.first))
502
+ Sass::Util.enum_slice(tok.value[1..-1], 2).inject(first) do |l, (i, r)|
503
+ end_pos = i.source_range.end_pos
504
+ end_pos = end_pos.after(r) if r
505
+ node(
506
+ Script::Tree::Interpolation.new(
507
+ l, i,
508
+ r && literal_node(Script::Value::String.new(r),
509
+ i.source_range.end_pos, end_pos),
510
+ false, false),
511
+ start_pos, end_pos)
512
+ end
498
513
  end
499
514
 
500
515
  def paren
@@ -522,7 +537,8 @@ RUBY
522
537
  return number unless first
523
538
  str = literal_node(first.value, first.source_range)
524
539
  return str unless try_tok(:begin_interpolation)
525
- mid = parse_interpolated
540
+ mid = assert_expr :expr
541
+ assert_tok :end_interpolation
526
542
  last = assert_expr(:string)
527
543
  node(Tree::StringInterpolation.new(str, mid, last), first.source_range.start_pos)
528
544
  end
@@ -555,7 +571,6 @@ RUBY
555
571
  :mixin_arglist => "mixin argument",
556
572
  :fn_arglist => "function argument",
557
573
  :splat => "...",
558
- :special_fun => '")"',
559
574
  }
560
575
 
561
576
  def assert_expr(name, expected = nil)
@@ -13,3 +13,4 @@ require 'sass/script/tree/string_interpolation'
13
13
  require 'sass/script/tree/literal'
14
14
  require 'sass/script/tree/list_literal'
15
15
  require 'sass/script/tree/map_literal'
16
+ require 'sass/script/tree/selector'
@@ -214,7 +214,7 @@ module Sass::Script::Tree
214
214
  elsif deprecated_argname && keywords.has_key?(deprecated_argname)
215
215
  deprecated_argname = keywords.denormalize(deprecated_argname)
216
216
  Sass::Util.sass_warn("DEPRECATION WARNING: The `$#{deprecated_argname}' argument for " +
217
- "`#{name}()' has been renamed to `$#{argname}'.")
217
+ "`#{@name}()' has been renamed to `$#{argname}'.")
218
218
  keywords.delete(deprecated_argname)
219
219
  else
220
220
  raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")
@@ -23,6 +23,10 @@ module Sass::Script::Tree
23
23
  # SassScript.
24
24
  attr_reader :originally_text
25
25
 
26
+ # @return [Boolean] Whether a color value passed to the interpolation should
27
+ # generate a warning.
28
+ attr_reader :warn_for_color
29
+
26
30
  # Interpolation in a property is of the form `before #{mid} after`.
27
31
  #
28
32
  # @param before [Node] See {Interpolation#before}
@@ -31,9 +35,10 @@ module Sass::Script::Tree
31
35
  # @param wb [Boolean] See {Interpolation#whitespace_before}
32
36
  # @param wa [Boolean] See {Interpolation#whitespace_after}
33
37
  # @param originally_text [Boolean] See {Interpolation#originally_text}
38
+ # @param warn_for_color [Boolean] See {Interpolation#warn_for_color}
34
39
  # @comment
35
40
  # rubocop:disable ParameterLists
36
- def initialize(before, mid, after, wb, wa, originally_text = false)
41
+ def initialize(before, mid, after, wb, wa, originally_text = false, warn_for_color = false)
37
42
  # rubocop:enable ParameterLists
38
43
  @before = before
39
44
  @mid = mid
@@ -41,6 +46,7 @@ module Sass::Script::Tree
41
46
  @whitespace_before = wb
42
47
  @whitespace_after = wa
43
48
  @originally_text = originally_text
49
+ @warn_for_color = warn_for_color
44
50
  end
45
51
 
46
52
  # @return [String] A human-readable s-expression representation of the interpolation
@@ -90,7 +96,19 @@ module Sass::Script::Tree
90
96
  res = ""
91
97
  res << @before.perform(environment).to_s if @before
92
98
  res << " " if @before && @whitespace_before
99
+
93
100
  val = @mid.perform(environment)
101
+ if @warn_for_color && val.is_a?(Sass::Script::Value::Color) && val.name
102
+ alternative = Operation.new(Sass::Script::Value::String.new("", :string), @mid, :plus)
103
+ Sass::Util.sass_warn <<MESSAGE
104
+ WARNING on line #{line}, column #{source_range.start_pos.offset}#{" of #{filename}" if filename}:
105
+ You probably don't mean to use the color value `#{val}' in interpolation here.
106
+ It may end up represented as #{val.inspect}, which will likely produce invalid CSS.
107
+ Always quote color names when using them as strings (for example, "#{val}").
108
+ If you really want to use the color value here, use `#{alternative.to_sass}'.
109
+ MESSAGE
110
+ end
111
+
94
112
  res << (val.is_a?(Sass::Script::Value::String) ? val.value : val.to_s)
95
113
  res << " " if @after && @whitespace_after
96
114
  res << @after.perform(environment).to_s if @after
@@ -0,0 +1,26 @@
1
+ module Sass::Script::Tree
2
+ # A SassScript node that will resolve to the current selector.
3
+ class Selector < Node
4
+ def initialize; end
5
+
6
+ def children
7
+ []
8
+ end
9
+
10
+ def to_sass(opts = {})
11
+ '&'
12
+ end
13
+
14
+ def deep_copy
15
+ dup
16
+ end
17
+
18
+ protected
19
+
20
+ def _perform(environment)
21
+ selector = environment.selector
22
+ return opts(Sass::Script::Value::Null.new) unless selector
23
+ opts(selector.to_sass_script)
24
+ end
25
+ end
26
+ end
@@ -5,7 +5,6 @@ require 'sass/script/value/string'
5
5
  require 'sass/script/value/number'
6
6
  require 'sass/script/value/color'
7
7
  require 'sass/script/value/bool'
8
- require 'sass/script/value/deprecated_false'
9
8
  require 'sass/script/value/null'
10
9
  require 'sass/script/value/list'
11
10
  require 'sass/script/value/arg_list'
@@ -20,11 +20,6 @@ module Sass::Script::Value
20
20
  value ? TRUE : FALSE
21
21
  end
22
22
 
23
- def eq(other)
24
- return other.eq(self) if other.is_a?(DeprecatedFalse)
25
- super
26
- end
27
-
28
23
  # The Ruby value of the boolean.
29
24
  #
30
25
  # @return [Boolean]
@@ -151,6 +151,7 @@ module Sass::Script::Value
151
151
  'powderblue' => 0xB0E0E6FF,
152
152
  'purple' => 0x800080FF,
153
153
  'red' => 0xFF0000FF,
154
+ 'rebeccapurple' => 0x663399FF,
154
155
  'rosybrown' => 0xBC8F8FFF,
155
156
  'royalblue' => 0x4169E1FF,
156
157
  'saddlebrown' => 0x8B4513FF,
@@ -187,6 +188,11 @@ module Sass::Script::Value
187
188
  # different ruby implementations and versions vary on the ordering of the result of invert.
188
189
  COLOR_NAMES.update(ALTERNATE_COLOR_NAMES).freeze
189
190
 
191
+ # The user's original representation of the color.
192
+ #
193
+ # @return [String]
194
+ attr_reader :representation
195
+
190
196
  # Constructs an RGB or HSL color object,
191
197
  # optionally with an alpha channel.
192
198
  #
@@ -197,25 +203,28 @@ module Sass::Script::Value
197
203
  # @raise [Sass::SyntaxError] if any color value isn't in the specified range
198
204
  #
199
205
  # @overload initialize(attrs)
200
- # The attributes are specified as a hash.
201
- # This hash must contain either `:hue`, `:saturation`, and `:value` keys,
202
- # or `:red`, `:green`, and `:blue` keys.
203
- # It cannot contain both HSL and RGB keys.
204
- # It may also optionally contain an `:alpha` key.
206
+ # The attributes are specified as a hash. This hash must contain either
207
+ # `:hue`, `:saturation`, and `:value` keys, or `:red`, `:green`, and
208
+ # `:blue` keys. It cannot contain both HSL and RGB keys. It may also
209
+ # optionally contain an `:alpha` key, and a `:representation` key
210
+ # indicating the original representation of the color that the user wrote
211
+ # in their stylesheet.
205
212
  #
206
213
  # @param attrs [{Symbol => Numeric}] A hash of color attributes to values
207
214
  # @raise [ArgumentError] if not enough attributes are specified,
208
215
  # or both RGB and HSL attributes are specified
209
216
  #
210
- # @overload initialize(rgba)
217
+ # @overload initialize(rgba, [representation])
211
218
  # The attributes are specified as an array.
212
219
  # This overload only supports RGB or RGBA colors.
213
220
  #
214
221
  # @param rgba [Array<Numeric>] A three- or four-element array
215
222
  # of the red, green, blue, and optionally alpha values (respectively)
216
223
  # of the color
224
+ # @param representation [String] The original representation of the color
225
+ # that the user wrote in their stylesheet.
217
226
  # @raise [ArgumentError] if not enough attributes are specified
218
- def initialize(attrs, allow_both_rgb_and_hsl = false)
227
+ def initialize(attrs, representation = nil, allow_both_rgb_and_hsl = false)
219
228
  super(nil)
220
229
 
221
230
  if attrs.is_a?(Array)
@@ -226,6 +235,7 @@ module Sass::Script::Value
226
235
  red, green, blue = attrs[0...3].map {|c| c.to_i}
227
236
  @attrs = {:red => red, :green => green, :blue => blue}
228
237
  @attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
238
+ @representation = representation
229
239
  else
230
240
  attrs = attrs.reject {|k, v| v.nil?}
231
241
  hsl = [:hue, :saturation, :lightness] & attrs.keys
@@ -243,6 +253,7 @@ module Sass::Script::Value
243
253
  @attrs = attrs
244
254
  @attrs[:hue] %= 360 if @attrs[:hue]
245
255
  @attrs[:alpha] ||= 1
256
+ @representation = @attrs.delete(:representation)
246
257
  end
247
258
 
248
259
  [:red, :green, :blue].each do |k|
@@ -273,7 +284,9 @@ module Sass::Script::Value
273
284
  red = $1.ljust(2, $1).to_i(16)
274
285
  green = $2.ljust(2, $2).to_i(16)
275
286
  blue = $3.ljust(2, $3).to_i(16)
276
- attrs = {:red => red, :green => green, :blue => blue}
287
+
288
+ hex_string = '##{hex_string}' unless hex_string[0] == ?#
289
+ attrs = {:red => red, :green => green, :blue => blue, :representation => hex_string}
277
290
  attrs[:alpha] = alpha if alpha
278
291
  new(attrs)
279
292
  end
@@ -549,7 +562,8 @@ module Sass::Script::Value
549
562
  # @return [String] The string representation
550
563
  def to_s(opts = {})
551
564
  return smallest if options[:style] == :compressed
552
- return COLOR_NAMES_REVERSE[rgba] if COLOR_NAMES_REVERSE[rgba]
565
+ return representation if representation
566
+ return name if name
553
567
  alpha? ? rgba_str : hex_str
554
568
  end
555
569
  alias_method :to_sass, :to_s
@@ -561,13 +575,19 @@ module Sass::Script::Value
561
575
  alpha? ? rgba_str : hex_str
562
576
  end
563
577
 
578
+ # Returns the color's name, if it has one.
579
+ #
580
+ # @return [String, nil]
581
+ def name
582
+ COLOR_NAMES_REVERSE[rgba]
583
+ end
584
+
564
585
  private
565
586
 
566
587
  def smallest
567
588
  small_explicit_str = alpha? ? rgba_str : hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3')
568
- return small_explicit_str unless (color = COLOR_NAMES_REVERSE[rgba]) &&
569
- color.size <= small_explicit_str.size
570
- color
589
+ [representation, COLOR_NAMES_REVERSE[rgba], small_explicit_str].
590
+ compact.min_by {|str| str.size}
571
591
  end
572
592
 
573
593
  def rgba_str