haml 2.2.24 → 3.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (168) hide show
  1. data/.yardopts +0 -1
  2. data/README.md +91 -151
  3. data/REMEMBER +11 -1
  4. data/Rakefile +73 -55
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/css2sass +7 -1
  8. data/bin/sass-convert +7 -0
  9. data/extra/haml-mode.el +2 -1
  10. data/lib/haml/buffer.rb +22 -4
  11. data/lib/haml/engine.rb +5 -1
  12. data/lib/haml/exec.rb +231 -46
  13. data/lib/haml/filters.rb +19 -8
  14. data/lib/haml/helpers.rb +47 -20
  15. data/lib/haml/helpers/action_view_extensions.rb +2 -4
  16. data/lib/haml/helpers/action_view_mods.rb +11 -8
  17. data/lib/haml/helpers/xss_mods.rb +13 -2
  18. data/lib/haml/html.rb +179 -48
  19. data/lib/haml/html/erb.rb +141 -0
  20. data/lib/haml/precompiler.rb +40 -15
  21. data/lib/haml/railtie.rb +1 -5
  22. data/lib/haml/root.rb +3 -0
  23. data/lib/haml/template.rb +4 -14
  24. data/lib/haml/util.rb +120 -30
  25. data/lib/haml/version.rb +25 -2
  26. data/lib/sass.rb +5 -1
  27. data/lib/sass/callbacks.rb +50 -0
  28. data/lib/sass/css.rb +40 -191
  29. data/lib/sass/engine.rb +170 -74
  30. data/lib/sass/environment.rb +8 -2
  31. data/lib/sass/error.rb +163 -25
  32. data/lib/sass/files.rb +31 -28
  33. data/lib/sass/plugin.rb +268 -87
  34. data/lib/sass/plugin/rails.rb +9 -4
  35. data/lib/sass/repl.rb +1 -1
  36. data/lib/sass/script.rb +31 -29
  37. data/lib/sass/script/bool.rb +1 -0
  38. data/lib/sass/script/color.rb +290 -23
  39. data/lib/sass/script/css_lexer.rb +22 -0
  40. data/lib/sass/script/css_parser.rb +28 -0
  41. data/lib/sass/script/funcall.rb +22 -3
  42. data/lib/sass/script/functions.rb +523 -33
  43. data/lib/sass/script/interpolation.rb +42 -0
  44. data/lib/sass/script/lexer.rb +169 -52
  45. data/lib/sass/script/literal.rb +58 -9
  46. data/lib/sass/script/node.rb +79 -1
  47. data/lib/sass/script/number.rb +20 -5
  48. data/lib/sass/script/operation.rb +49 -3
  49. data/lib/sass/script/parser.rb +162 -28
  50. data/lib/sass/script/string.rb +50 -2
  51. data/lib/sass/script/unary_operation.rb +25 -2
  52. data/lib/sass/script/variable.rb +21 -4
  53. data/lib/sass/scss.rb +14 -0
  54. data/lib/sass/scss/css_parser.rb +39 -0
  55. data/lib/sass/scss/parser.rb +683 -0
  56. data/lib/sass/scss/rx.rb +112 -0
  57. data/lib/sass/scss/script_lexer.rb +13 -0
  58. data/lib/sass/scss/script_parser.rb +25 -0
  59. data/lib/sass/tree/comment_node.rb +69 -27
  60. data/lib/sass/tree/debug_node.rb +7 -2
  61. data/lib/sass/tree/directive_node.rb +41 -35
  62. data/lib/sass/tree/for_node.rb +6 -0
  63. data/lib/sass/tree/if_node.rb +13 -1
  64. data/lib/sass/tree/import_node.rb +52 -27
  65. data/lib/sass/tree/mixin_def_node.rb +18 -0
  66. data/lib/sass/tree/mixin_node.rb +41 -6
  67. data/lib/sass/tree/node.rb +197 -70
  68. data/lib/sass/tree/prop_node.rb +152 -57
  69. data/lib/sass/tree/root_node.rb +118 -0
  70. data/lib/sass/tree/rule_node.rb +193 -96
  71. data/lib/sass/tree/variable_node.rb +9 -5
  72. data/lib/sass/tree/while_node.rb +4 -0
  73. data/test/benchmark.rb +5 -5
  74. data/test/haml/engine_test.rb +147 -10
  75. data/test/haml/{rhtml/_av_partial_1.rhtml → erb/_av_partial_1.erb} +1 -1
  76. data/test/haml/{rhtml/_av_partial_2.rhtml → erb/_av_partial_2.erb} +1 -1
  77. data/test/haml/{rhtml/action_view.rhtml → erb/action_view.erb} +1 -1
  78. data/test/haml/{rhtml/standard.rhtml → erb/standard.erb} +0 -0
  79. data/test/haml/helper_test.rb +91 -24
  80. data/test/haml/html2haml/erb_tests.rb +410 -0
  81. data/test/haml/html2haml_test.rb +210 -66
  82. data/test/haml/results/filters.xhtml +1 -1
  83. data/test/haml/results/just_stuff.xhtml +2 -0
  84. data/test/haml/spec_test.rb +44 -0
  85. data/test/haml/template_test.rb +22 -2
  86. data/test/haml/templates/helpers.haml +0 -13
  87. data/test/haml/templates/just_stuff.haml +2 -0
  88. data/test/haml/util_test.rb +48 -0
  89. data/test/sass/callbacks_test.rb +61 -0
  90. data/test/sass/conversion_test.rb +884 -0
  91. data/test/sass/css2sass_test.rb +99 -18
  92. data/test/sass/data/hsl-rgb.txt +319 -0
  93. data/test/sass/engine_test.rb +1049 -131
  94. data/test/sass/functions_test.rb +398 -47
  95. data/test/sass/more_results/more_import.css +1 -1
  96. data/test/sass/more_templates/more_import.sass +3 -3
  97. data/test/sass/plugin_test.rb +184 -10
  98. data/test/sass/results/compact.css +1 -1
  99. data/test/sass/results/complex.css +5 -5
  100. data/test/sass/results/compressed.css +1 -1
  101. data/test/sass/results/expanded.css +1 -1
  102. data/test/sass/results/import.css +3 -1
  103. data/test/sass/results/mixins.css +12 -12
  104. data/test/sass/results/nested.css +1 -1
  105. data/test/sass/results/options.css +1 -0
  106. data/test/sass/results/parent_ref.css +4 -4
  107. data/test/sass/results/script.css +3 -3
  108. data/test/sass/results/scss_import.css +15 -0
  109. data/test/sass/results/scss_importee.css +2 -0
  110. data/test/sass/script_conversion_test.rb +153 -0
  111. data/test/sass/script_test.rb +137 -70
  112. data/test/sass/scss/css_test.rb +811 -0
  113. data/test/sass/scss/rx_test.rb +156 -0
  114. data/test/sass/scss/scss_test.rb +871 -0
  115. data/test/sass/scss/test_helper.rb +37 -0
  116. data/test/sass/templates/alt.sass +2 -2
  117. data/test/sass/templates/bork1.sass +2 -0
  118. data/test/sass/templates/bork3.sass +2 -0
  119. data/test/sass/templates/bork4.sass +2 -0
  120. data/test/sass/templates/import.sass +4 -4
  121. data/test/sass/templates/importee.sass +3 -3
  122. data/test/sass/templates/line_numbers.sass +1 -1
  123. data/test/sass/templates/mixin_bork.sass +5 -0
  124. data/test/sass/templates/mixins.sass +2 -2
  125. data/test/sass/templates/nested_bork1.sass +2 -0
  126. data/test/sass/templates/nested_bork2.sass +2 -0
  127. data/test/sass/templates/nested_bork3.sass +2 -0
  128. data/test/sass/templates/nested_bork4.sass +2 -0
  129. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  130. data/test/sass/templates/options.sass +2 -0
  131. data/test/sass/templates/parent_ref.sass +2 -2
  132. data/test/sass/templates/script.sass +69 -69
  133. data/test/sass/templates/scss_import.scss +10 -0
  134. data/test/sass/templates/scss_importee.scss +1 -0
  135. data/test/sass/templates/units.sass +10 -10
  136. data/test/test_helper.rb +20 -8
  137. data/vendor/fssm/LICENSE +20 -0
  138. data/vendor/fssm/README.markdown +55 -0
  139. data/vendor/fssm/Rakefile +59 -0
  140. data/vendor/fssm/VERSION.yml +5 -0
  141. data/vendor/fssm/example.rb +9 -0
  142. data/vendor/fssm/fssm.gemspec +77 -0
  143. data/vendor/fssm/lib/fssm.rb +33 -0
  144. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  145. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  146. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  147. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  148. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  149. data/vendor/fssm/lib/fssm/path.rb +91 -0
  150. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  151. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  152. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  153. data/vendor/fssm/lib/fssm/support.rb +63 -0
  154. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  155. data/vendor/fssm/profile/prof-cache.rb +40 -0
  156. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  157. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  158. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  159. data/vendor/fssm/profile/prof.html +2379 -0
  160. data/vendor/fssm/spec/path_spec.rb +75 -0
  161. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  162. data/vendor/fssm/spec/root/file.css +0 -0
  163. data/vendor/fssm/spec/root/file.rb +0 -0
  164. data/vendor/fssm/spec/root/file.yml +0 -0
  165. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  166. data/vendor/fssm/spec/spec_helper.rb +14 -0
  167. metadata +94 -14
  168. data/test/sass/templates/bork.sass +0 -2
@@ -7,10 +7,15 @@ unless defined?(Sass::RAILS_LOADED)
7
7
  :always_check => Haml::Util.rails_env != "production",
8
8
  :full_exception => Haml::Util.rails_env != "production")
9
9
 
10
- if defined?(ActionDispatch::Callbacks) &&
11
- defined?(ActionDispatch::Callbacks.to_prepare)
12
- # Rails >= 3.0.0
13
- ActionDispatch::Callbacks.to_prepare(:sass_process) {Sass::Plugin.check_for_updates}
10
+ if defined?(Rails.configuration) && defined?(Rails.configuration.middleware)
11
+ # Rails >= 3.0
12
+ require 'sass/plugin/rack'
13
+ Rails.configuration.middleware.use(Sass::Plugin::Rack)
14
+ elsif defined?(ActionController::Dispatcher) &&
15
+ defined?(ActionController::Dispatcher.middleware)
16
+ # Rails >= 2.3
17
+ require 'sass/plugin/rack'
18
+ ActionController::Dispatcher.middleware.use(Sass::Plugin::Rack)
14
19
  else
15
20
  module ActionController
16
21
  class Base
@@ -35,7 +35,7 @@ module Sass
35
35
  case text
36
36
  when Script::MATCH
37
37
  name = $1
38
- guarded = $2 == '||='
38
+ guarded = $3 == '||=' || $4
39
39
  val = Script::Parser.parse($3, @line, text.size - $3.size)
40
40
 
41
41
  unless guarded && environment.var(name)
@@ -12,30 +12,13 @@ module Sass
12
12
  #
13
13
  # This module contains code that handles the parsing and evaluation of SassScript.
14
14
  module Script
15
- # The character that begins a variable.
16
- # @private
17
- VARIABLE_CHAR = ?!
18
-
19
15
  # The regular expression used to parse variables.
20
16
  # @private
21
- MATCH = /^!([a-zA-Z_]\w*)\s*((?:\|\|)?=)\s*(.+)/
17
+ MATCH = /^[!\$](#{Sass::SCSS::RX::IDENT})\s*((?:\|\|)?=|:)\s*(.+?)(!(?i:default))?$/
22
18
 
23
19
  # The regular expression used to validate variables without matching.
24
20
  # @private
25
- VALIDATE = /^![a-zA-Z_]\w*$/
26
-
27
- # Parses and evaluates a string of SassScript.
28
- #
29
- # @param value [String] The SassScript
30
- # @param line [Fixnum] The number of the line on which the SassScript appeared.
31
- # Used for error reporting
32
- # @param offset [Fixnum] The number of characters in on `line` that the SassScript started.
33
- # Used for error reporting
34
- # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
35
- # @return [String] The string result of evaluating the SassScript
36
- def self.resolve(value, line, offset, environment)
37
- parse(value, line, offset).perform(environment).to_s
38
- end
21
+ VALIDATE = /^[!\$]#{Sass::SCSS::RX::IDENT}$/
39
22
 
40
23
  # Parses a string of SassScript
41
24
  #
@@ -44,19 +27,38 @@ module Sass
44
27
  # Used for error reporting
45
28
  # @param offset [Fixnum] The number of characters in on `line` that the SassScript started.
46
29
  # Used for error reporting
47
- # @param filename [String] The path to the file in which the SassScript appeared.
48
- # Used for error reporting
30
+ # @param options [{Symbol => Object}] An options hash;
31
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
49
32
  # @return [Script::Node] The root node of the parse tree
50
- def self.parse(value, line, offset, filename = nil)
51
- Parser.parse(value, line, offset, filename)
33
+ def self.parse(value, line, offset, options = {})
34
+ Parser.parse(value, line, offset, options)
52
35
  rescue Sass::SyntaxError => e
53
- if e.message == "SassScript error"
54
- e.instance_eval do
55
- @message += ": #{value.dump}."
56
- end
57
- end
58
- e.sass_line = line
36
+ e.message << ": #{value.inspect}." if e.message == "SassScript error"
37
+ e.modify_backtrace(:line => line, :filename => options[:filename])
59
38
  raise e
60
39
  end
40
+
41
+ # @private
42
+ def self.var_warning(varname, line, offset, filename)
43
+ Haml::Util.haml_warn <<MESSAGE
44
+ DEPRECATION WARNING:
45
+ On line #{line}, character #{offset}#{" of '#{filename}'" if filename}
46
+ Variables with ! have been deprecated and will be removed in version 3.2.
47
+ Use \"$#{varname}\" instead.
48
+
49
+ You can use `sass-convert --in-place --from sass2 file.sass' to convert files automatically.
50
+ MESSAGE
51
+ end
52
+
53
+ def self.equals_warning(types, name, val, guarded, line, offset, filename)
54
+ Haml::Util.haml_warn <<MESSAGE
55
+ DEPRECATION WARNING:
56
+ On line #{line}#{", character #{offset}" if offset}#{" of '#{filename}'" if filename}
57
+ Setting #{types} with #{"||" if guarded}= has been deprecated and will be removed in version 3.2.
58
+ Use "#{name}: #{val}#{" !default" if guarded}" instead.
59
+
60
+ You can use `sass-convert --in-place --from sass2 file.sass' to convert files automatically.
61
+ MESSAGE
62
+ end
61
63
  end
62
64
  end
@@ -13,5 +13,6 @@ module Sass::Script
13
13
  def to_s
14
14
  @value.to_s
15
15
  end
16
+ alias_method :to_sass, :to_s
16
17
  end
17
18
  end
@@ -2,6 +2,19 @@ require 'sass/script/literal'
2
2
 
3
3
  module Sass::Script
4
4
  # A SassScript object representing a CSS color.
5
+ #
6
+ # A color may be represented internally as RGBA, HSLA, or both.
7
+ # It's originally represented as whatever its input is;
8
+ # if it's created with RGB values, it's represented as RGBA,
9
+ # and if it's created with HSL values, it's represented as HSLA.
10
+ # Once a property is accessed that requires the other representation --
11
+ # for example, \{#red} for an HSL color --
12
+ # that component is calculated and cached.
13
+ #
14
+ # The alpha channel of a color is independent of its RGB or HSL representation.
15
+ # It's always stored, as 1 if nothing else is specified.
16
+ # If only the alpha channel is modified using \{#with},
17
+ # the cached RGB and HSL values are retained.
5
18
  class Color < Literal
6
19
  class << self; include Haml::Util; end
7
20
 
@@ -29,24 +42,149 @@ module Sass::Script
29
42
  # @private
30
43
  HTML4_COLORS_REVERSE = map_hash(HTML4_COLORS) {|k, v| [v, k]}
31
44
 
32
- # Creates a new color from RGB components.
33
- # *Note*: when modifying the components of an existing color,
34
- # use \{#with} rather than creating a new color object.
35
- # This preserves forwards-compatiblity for alpha channels and such.
45
+ # Constructs an RGB or HSL color object,
46
+ # optionally with an alpha channel.
47
+ #
48
+ # The RGB values must be between 0 and 255.
49
+ # The saturation and lightness values must be between 0 and 100.
50
+ # The alpha value must be between 0 and 1.
36
51
  #
37
- # @param rgb [Array<Fixnum>] A three-element array of the red, green, and blue values (respectively)
38
- # of the color
39
- # @raise [Sass::SyntaxError] if any color value isn't between 0 and 255
40
- def initialize(rgb)
41
- rgb = rgb.map {|c| c.to_i}
42
- raise Sass::SyntaxError.new("Color values must be between 0 and 255") if rgb.any? {|c| c < 0 || c > 255}
43
- super(rgb.freeze)
52
+ # @raise [Sass::SyntaxError] if any color value isn't in the specified range
53
+ #
54
+ # @overload initialize(attrs)
55
+ # The attributes are specified as a hash.
56
+ # This hash must contain either `:hue`, `:saturation`, and `:value` keys,
57
+ # or `:red`, `:green`, and `:blue` keys.
58
+ # It cannot contain both HSL and RGB keys.
59
+ # It may also optionally contain an `:alpha` key.
60
+ #
61
+ # @param attrs [{Symbol => Numeric}] A hash of color attributes to values
62
+ # @raise [ArgumentError] if not enough attributes are specified,
63
+ # or both RGB and HSL attributes are specified
64
+ #
65
+ # @overload initialize(rgba)
66
+ # The attributes are specified as an array.
67
+ # This overload only supports RGB or RGBA colors.
68
+ #
69
+ # @param rgba [Array<Numeric>] A three- or four-element array
70
+ # of the red, green, blue, and optionally alpha values (respectively)
71
+ # of the color
72
+ # @raise [ArgumentError] if not enough attributes are specified
73
+ def initialize(attrs, allow_both_rgb_and_hsl = false)
74
+ super(nil)
75
+
76
+ if attrs.is_a?(Array)
77
+ unless (3..4).include?(attrs.size)
78
+ raise ArgumentError.new("Color.new(array) expects a three- or four-element array")
79
+ end
80
+
81
+ red, green, blue = attrs[0...3].map {|c| c.to_i}
82
+ @attrs = {:red => red, :green => green, :blue => blue}
83
+ @attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
84
+ else
85
+ attrs = attrs.reject {|k, v| v.nil?}
86
+ hsl = [:hue, :saturation, :lightness] & attrs.keys
87
+ rgb = [:red, :green, :blue] & attrs.keys
88
+ if !allow_both_rgb_and_hsl && !hsl.empty? && !rgb.empty?
89
+ raise ArgumentError.new("Color.new(hash) may not have both HSL and RGB keys specified")
90
+ elsif hsl.empty? && rgb.empty?
91
+ raise ArgumentError.new("Color.new(hash) must have either HSL or RGB keys specified")
92
+ elsif !hsl.empty? && hsl.size != 3
93
+ raise ArgumentError.new("Color.new(hash) must have all three HSL values specified")
94
+ elsif !rgb.empty? && rgb.size != 3
95
+ raise ArgumentError.new("Color.new(hash) must have all three RGB values specified")
96
+ end
97
+
98
+ @attrs = attrs
99
+ @attrs[:hue] %= 360 if @attrs[:hue]
100
+ @attrs[:alpha] ||= 1
101
+ end
102
+
103
+ [:red, :green, :blue].each do |k|
104
+ next if @attrs[k].nil?
105
+ @attrs[k] = @attrs[k].to_i
106
+ next if (0..255).include?(@attrs[k])
107
+ raise Sass::SyntaxError.new("#{k.to_s.capitalize} value must be between 0 and 255")
108
+ end
109
+
110
+ [:saturation, :lightness].each do |k|
111
+ next if @attrs[k].nil? || (0..100).include?(@attrs[k])
112
+ raise Sass::SyntaxError.new("#{k.to_s.capitalize} must be between 0 and 100")
113
+ end
114
+
115
+ unless (0..1).include?(@attrs[:alpha])
116
+ raise Sass::SyntaxError.new("Alpha channel must between 0 and 1")
117
+ end
118
+ end
119
+
120
+ # The red component of the color.
121
+ #
122
+ # @return [Fixnum]
123
+ def red
124
+ hsl_to_rgb!
125
+ @attrs[:red]
126
+ end
127
+
128
+ # The green component of the color.
129
+ #
130
+ # @return [Fixnum]
131
+ def green
132
+ hsl_to_rgb!
133
+ @attrs[:green]
134
+ end
135
+
136
+ # The blue component of the color.
137
+ #
138
+ # @return [Fixnum]
139
+ def blue
140
+ hsl_to_rgb!
141
+ @attrs[:blue]
142
+ end
143
+
144
+ # The hue component of the color.
145
+ #
146
+ # @return [Numeric]
147
+ def hue
148
+ rgb_to_hsl!
149
+ @attrs[:hue]
150
+ end
151
+
152
+ # The saturation component of the color.
153
+ #
154
+ # @return [Numeric]
155
+ def saturation
156
+ rgb_to_hsl!
157
+ @attrs[:saturation]
158
+ end
159
+
160
+ # The lightness component of the color.
161
+ #
162
+ # @return [Numeric]
163
+ def lightness
164
+ rgb_to_hsl!
165
+ @attrs[:lightness]
166
+ end
167
+
168
+ # The alpha channel (opacity) of the color.
169
+ # This is 1 unless otherwise defined.
170
+ #
171
+ # @return [Fixnum]
172
+ def alpha
173
+ @attrs[:alpha]
174
+ end
175
+
176
+ # Returns whether this color object is translucent;
177
+ # that is, whether the alpha channel is non-1.
178
+ #
179
+ # @return [Boolean]
180
+ def alpha?
181
+ alpha < 1
44
182
  end
45
183
 
46
184
  # @deprecated This will be removed in version 3.2.
47
185
  # @see #rgb
48
186
  def value
49
- warn <<END
187
+ Haml::Util.haml_warn <<END
50
188
  DEPRECATION WARNING:
51
189
  The Sass::Script::Color #value attribute is deprecated and will be
52
190
  removed in version 3.2. Use the #rgb attribute instead.
@@ -59,10 +197,31 @@ END
59
197
  # @return [Array<Fixnum>] A frozen three-element array of the red, green, and blue
60
198
  # values (respectively) of the color
61
199
  def rgb
62
- @value
200
+ [red, green, blue].freeze
201
+ end
202
+
203
+ # Returns the hue, saturation, and lightness components of the color.
204
+ #
205
+ # @return [Array<Fixnum>] A frozen three-element array of the
206
+ # hue, saturation, and lightness values (respectively) of the color
207
+ def hsl
208
+ [hue, saturation, lightness].freeze
209
+ end
210
+
211
+ # The SassScript `==` operation.
212
+ # **Note that this returns a {Sass::Script::Bool} object,
213
+ # not a Ruby boolean**.
214
+ #
215
+ # @param other [Literal] The right-hand side of the operator
216
+ # @return [Bool] True if this literal is the same as the other,
217
+ # false otherwise
218
+ def eq(other)
219
+ Sass::Script::Bool.new(
220
+ other.is_a?(Color) && rgb == other.rgb && alpha == other.alpha)
63
221
  end
64
222
 
65
223
  # Returns a copy of this color with one or more channels changed.
224
+ # RGB or HSL colors may be changed, but not both at once.
66
225
  #
67
226
  # For example:
68
227
  #
@@ -70,16 +229,36 @@ END
70
229
  # #=> rgb(10, 40, 30)
71
230
  # Color.new([126, 126, 126]).with(:red => 0, :green => 255)
72
231
  # #=> rgb(0, 255, 126)
232
+ # Color.new([255, 0, 127]).with(:saturation => 60)
233
+ # #=> rgb(204, 51, 127)
234
+ # Color.new([1, 2, 3]).with(:alpha => 0.4)
235
+ # #=> rgba(1, 2, 3, 0.4)
73
236
  #
74
- # @param attrs [{Symbol => Fixnum}]
75
- # A map of channel names (`:red`, `:green`, or `:blue`) to values
237
+ # @param attrs [{Symbol => Numeric}]
238
+ # A map of channel names (`:red`, `:green`, `:blue`,
239
+ # `:hue`, `:saturation`, `:lightness`, or `:alpha`) to values
76
240
  # @return [Color] The new Color object
241
+ # @raise [ArgumentError] if both RGB and HSL keys are specified
77
242
  def with(attrs)
78
- Color.new([
79
- attrs[:red] || rgb[0],
80
- attrs[:green] || rgb[1],
81
- attrs[:blue] || rgb[2],
82
- ])
243
+ attrs = attrs.reject {|k, v| v.nil?}
244
+ hsl = !([:hue, :saturation, :lightness] & attrs.keys).empty?
245
+ rgb = !([:red, :green, :blue] & attrs.keys).empty?
246
+ if hsl && rgb
247
+ raise ArgumentError.new("Color#with may not have both HSL and RGB keys specified")
248
+ end
249
+
250
+ if hsl
251
+ [:hue, :saturation, :lightness].each {|k| attrs[k] ||= send(k)}
252
+ elsif rgb
253
+ [:red, :green, :blue].each {|k| attrs[k] ||= send(k)}
254
+ else
255
+ # If we're just changing the alpha channel,
256
+ # keep all the HSL/RGB stuff we've calculated
257
+ attrs = @attrs.merge(attrs)
258
+ end
259
+ attrs[:alpha] ||= alpha
260
+
261
+ Color.new(attrs, :allow_both_rgb_and_hsl)
83
262
  end
84
263
 
85
264
  # The SassScript `+` operation.
@@ -197,14 +376,38 @@ END
197
376
  #
198
377
  # @return [String] The string representation
199
378
  def to_s
379
+ return rgba_str if alpha?
380
+ return smallest if options[:style] == :compressed
200
381
  return HTML4_COLORS_REVERSE[rgb] if HTML4_COLORS_REVERSE[rgb]
201
- red, green, blue = rgb.map { |num| num.to_s(16).rjust(2, '0') }
202
- "##{red}#{green}#{blue}"
382
+ hex_str
383
+ end
384
+ alias_method :to_sass, :to_s
385
+
386
+ # Returns a string representation of the color.
387
+ #
388
+ # @return [String] The hex value
389
+ def inspect
390
+ alpha? ? rgba_str : hex_str
203
391
  end
204
- alias_method :inspect, :to_s
205
392
 
206
393
  private
207
394
 
395
+ def smallest
396
+ small_hex_str = hex_str.gsub(/^#(.)\1(.)\2(.)\3$/, '#\1\2\3')
397
+ return small_hex_str unless (color = HTML4_COLORS_REVERSE[rgb]) &&
398
+ color.size <= small_hex_str.size
399
+ return color
400
+ end
401
+
402
+ def rgba_str
403
+ "rgba(#{rgb.join(', ')}, #{alpha % 1 == 0.0 ? alpha.to_i : alpha})"
404
+ end
405
+
406
+ def hex_str
407
+ red, green, blue = rgb.map { |num| num.to_s(16).rjust(2, '0') }
408
+ "##{red}#{green}#{blue}"
409
+ end
410
+
208
411
  def piecewise(other, operation)
209
412
  other_num = other.is_a? Number
210
413
  if other_num && !other.unitless?
@@ -216,7 +419,71 @@ END
216
419
  res = rgb[i].send(operation, other_num ? other.value : other.rgb[i])
217
420
  result[i] = [ [res, 255].min, 0 ].max
218
421
  end
422
+
423
+ if !other_num && other.alpha != alpha
424
+ raise Sass::SyntaxError.new("Alpha channels must be equal: #{self} #{operation} #{other}")
425
+ end
426
+
219
427
  with(:red => result[0], :green => result[1], :blue => result[2])
220
428
  end
429
+
430
+ def hsl_to_rgb!
431
+ return if @attrs[:red] && @attrs[:blue] && @attrs[:green]
432
+
433
+ h = @attrs[:hue] / 360.0
434
+ s = @attrs[:saturation] / 100.0
435
+ l = @attrs[:lightness] / 100.0
436
+
437
+ # Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color.
438
+ m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
439
+ m1 = l * 2 - m2
440
+ @attrs[:red], @attrs[:green], @attrs[:blue] = [
441
+ hue_to_rgb(m1, m2, h + 1.0/3),
442
+ hue_to_rgb(m1, m2, h),
443
+ hue_to_rgb(m1, m2, h - 1.0/3)
444
+ ].map {|c| (c * 0xff).round}
445
+ end
446
+
447
+ def hue_to_rgb(m1, m2, h)
448
+ h += 1 if h < 0
449
+ h -= 1 if h > 1
450
+ return m1 + (m2 - m1) * h * 6 if h * 6 < 1
451
+ return m2 if h * 2 < 1
452
+ return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
453
+ return m1
454
+ end
455
+
456
+ def rgb_to_hsl!
457
+ return if @attrs[:hue] && @attrs[:saturation] && @attrs[:lightness]
458
+ r, g, b = [:red, :green, :blue].map {|k| @attrs[k] / 255.0}
459
+
460
+ # Algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
461
+ max = [r, g, b].max
462
+ min = [r, g, b].min
463
+ d = max - min
464
+
465
+ h =
466
+ case max
467
+ when min; 0
468
+ when r; 60 * (g-b)/d
469
+ when g; 60 * (b-r)/d + 120
470
+ when b; 60 * (r-g)/d + 240
471
+ end
472
+
473
+ l = (max + min)/2.0
474
+
475
+ s =
476
+ if max == min
477
+ 0
478
+ elsif l < 0.5
479
+ d/(2*l)
480
+ else
481
+ d/(2 - 2*l)
482
+ end
483
+
484
+ @attrs[:hue] = h % 360
485
+ @attrs[:saturation] = s * 100
486
+ @attrs[:lightness] = l * 100
487
+ end
221
488
  end
222
489
  end