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
@@ -0,0 +1,22 @@
1
+ module Sass
2
+ module Script
3
+ class CssLexer < Lexer
4
+ def token
5
+ important || super
6
+ end
7
+
8
+ def string(*args)
9
+ return unless scan(STRING)
10
+ str = (@scanner[1] || @scanner[2]).
11
+ gsub(/\\([^0-9a-f])/, '\1').
12
+ gsub(/\\([0-9a-f]{1,4})/, "\\\\\\1")
13
+ [:string, Script::String.new(str, :string)]
14
+ end
15
+
16
+ def important
17
+ return unless s = scan(IMPORTANT)
18
+ [:raw, s]
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ require 'sass/script'
2
+ require 'sass/script/css_lexer'
3
+
4
+ module Sass
5
+ module Script
6
+ class CssParser < Parser
7
+ private
8
+
9
+ # @private
10
+ def lexer_class; CssLexer; end
11
+
12
+ # We need a production that only does /,
13
+ # since * and % aren't allowed in plain CSS
14
+ production :div, :unary_plus, :div
15
+
16
+ def string
17
+ return number unless tok = try_tok(:string)
18
+ return tok.value unless @lexer.peek && @lexer.peek.type == :begin_interpolation
19
+ end
20
+
21
+ # Short-circuit all the SassScript-only productions
22
+ alias_method :interpolation, :concat
23
+ alias_method :or_expr, :div
24
+ alias_method :unary_div, :funcall
25
+ alias_method :paren, :string
26
+ end
27
+ end
28
+ end
@@ -22,6 +22,7 @@ module Sass
22
22
  def initialize(name, args)
23
23
  @name = name
24
24
  @args = args
25
+ super()
25
26
  end
26
27
 
27
28
  # @return [String] A string representation of the function call
@@ -29,18 +30,36 @@ module Sass
29
30
  "#{name}(#{args.map {|a| a.inspect}.join(', ')})"
30
31
  end
31
32
 
33
+ # @see Node#to_sass
34
+ def to_sass
35
+ "#{name}(#{args.map {|a| a.to_sass}.join(', ')})"
36
+ end
37
+
38
+ # Returns the arguments to the function.
39
+ #
40
+ # @return [Array<Node>]
41
+ # @see Node#children
42
+ def children
43
+ @args
44
+ end
45
+
46
+ protected
47
+
32
48
  # Evaluates the function call.
33
49
  #
34
50
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
35
51
  # @return [Literal] The SassScript object that is the value of the function call
36
52
  # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
37
- def perform(environment)
53
+ def _perform(environment)
38
54
  args = self.args.map {|a| a.perform(environment)}
39
- unless Haml::Util.has?(:public_instance_method, Functions, name) && name !~ /^__/
55
+ ruby_name = name.gsub('-', '_')
56
+ unless Haml::Util.has?(:public_instance_method, Functions, ruby_name) && ruby_name !~ /^__/
40
57
  return Script::String.new("#{name}(#{args.map {|a| a.perform(environment)}.join(', ')})")
41
58
  end
42
59
 
43
- return Functions::EvaluationContext.new(environment.options).send(name, *args)
60
+ result = Functions::EvaluationContext.new(environment.options).send(ruby_name, *args)
61
+ result.options = environment.options
62
+ return result
44
63
  rescue ArgumentError => e
45
64
  raise e unless e.backtrace.any? {|t| t =~ /:in `(block in )?(#{name}|perform)'$/}
46
65
  raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
@@ -2,18 +2,94 @@ module Sass::Script
2
2
  # Methods in this module are accessible from the SassScript context.
3
3
  # For example, you can write
4
4
  #
5
- # !color = hsl(120, 100%, 50%)
5
+ # $color = hsl(120deg, 100%, 50%)
6
6
  #
7
7
  # and it will call {Sass::Script::Functions#hsl}.
8
8
  #
9
9
  # The following functions are provided:
10
10
  #
11
- # \{#hsl}
12
- # : Converts an `hsl(hue, saturation, lightness)` triplet into a color.
11
+ # ## RGB Functions
13
12
  #
14
13
  # \{#rgb}
15
14
  # : Converts an `rgb(red, green, blue)` triplet into a color.
16
15
  #
16
+ # \{#rgba}
17
+ # : Converts an `rgba(red, green, blue, alpha)` quadruplet into a color.
18
+ #
19
+ # \{#red}
20
+ # : Gets the red component of a color.
21
+ #
22
+ # \{#green}
23
+ # : Gets the green component of a color.
24
+ #
25
+ # \{#blue}
26
+ # : Gets the blue component of a color.
27
+ #
28
+ # \{#mix}
29
+ # : Mixes two colors together.
30
+ #
31
+ # ## HSL Functions
32
+ #
33
+ # \{#hsl}
34
+ # : Converts an `hsl(hue, saturation, lightness)` triplet into a color.
35
+ #
36
+ # \{#hsla}
37
+ # : Converts an `hsla(hue, saturation, lightness, alpha)` quadruplet into a color.
38
+ #
39
+ # \{#hue}
40
+ # : Gets the hue component of a color.
41
+ #
42
+ # \{#saturation}
43
+ # : Gets the saturation component of a color.
44
+ #
45
+ # \{#lightness}
46
+ # : Gets the lightness component of a color.
47
+ #
48
+ # \{#adjust_hue #adjust-hue}
49
+ # : Changes the hue of a color.
50
+ #
51
+ # \{#lighten}
52
+ # : Makes a color lighter.
53
+ #
54
+ # \{#darken}
55
+ # : Makes a color darker.
56
+ #
57
+ # \{#saturate}
58
+ # : Makes a color more saturated.
59
+ #
60
+ # \{#desaturate}
61
+ # : Makes a color less saturated.
62
+ #
63
+ # \{#grayscale}
64
+ # : Converts a color to grayscale.
65
+ #
66
+ # \{#complement}
67
+ # : Returns the complement of a color.
68
+ #
69
+ # ## Opacity Functions
70
+ #
71
+ # \{#alpha} / \{#opacity}
72
+ # : Gets the alpha component (opacity) of a color.
73
+ #
74
+ # \{#rgba}
75
+ # : Sets the alpha component of a color.
76
+ #
77
+ # \{#opacify} / \{#fade_in #fade-in}
78
+ # : Makes a color more opaque.
79
+ #
80
+ # \{#transparentize} / \{#fade_out #fade-out}
81
+ # : Makes a color more transparent.
82
+ #
83
+ # ## String Functions
84
+ #
85
+ # \{#unquote}
86
+ # : Removes the quotes from a string.
87
+ #
88
+ # \{#quote}
89
+ # : Adds quotes to a string.
90
+ #
91
+ # ## Number Functions
92
+ #
17
93
  # \{#percentage}
18
94
  # : Converts a unitless number to a percentage.
19
95
  #
@@ -50,7 +126,8 @@ module Sass::Script
50
126
  #
51
127
  # Most Literal objects support the {Sass::Script::Literal#value value} accessor
52
128
  # for getting their Ruby values.
53
- # Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb}.
129
+ # Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb},
130
+ # {Sass::Script::Color#red red}, {Sass::Script::Color#blue green}, or {Sass::Script::Color#blue blue}.
54
131
  #
55
132
  # Second, making Ruby functions accessible from Sass introduces the temptation
56
133
  # to do things like database access within stylesheets.
@@ -67,6 +144,13 @@ module Sass::Script
67
144
  #
68
145
  # Within one of the functions in this module,
69
146
  # methods of {EvaluationContext} can be used.
147
+ #
148
+ # ### Caveats
149
+ #
150
+ # When creating new {Literal} objects within functions,
151
+ # be aware that it's not safe to call {Literal#to_s #to_s}
152
+ # (or other methods that use the string representation)
153
+ # on those objects without first setting {Node#options= the #options attribute}.
70
154
  module Functions
71
155
  # The context in which methods in {Script::Functions} are evaluated.
72
156
  # That means that all instance methods of {EvaluationContext}
@@ -94,6 +178,8 @@ module Sass::Script
94
178
  # assert_type value, :Number
95
179
  #
96
180
  # Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
181
+ # Note that `:String` will match both double-quoted strings
182
+ # and unquoted identifiers.
97
183
  #
98
184
  # @param value [Sass::Script::Literal] A SassScript value
99
185
  # @param type [Symbol] The name of the type the value is expected to be
@@ -107,31 +193,78 @@ module Sass::Script
107
193
 
108
194
 
109
195
  # Creates a {Color} object from red, green, and blue values.
110
- # @param red
196
+ #
197
+ # @param red [Number]
111
198
  # A number between 0 and 255 inclusive,
112
199
  # or between 0% and 100% inclusive
113
- # @param green
200
+ # @param green [Number]
114
201
  # A number between 0 and 255 inclusive,
115
202
  # or between 0% and 100% inclusive
116
- # @param blue
203
+ # @param blue [Number]
117
204
  # A number between 0 and 255 inclusive,
118
205
  # or between 0% and 100% inclusive
206
+ # @return [Color]
119
207
  def rgb(red, green, blue)
120
208
  assert_type red, :Number
121
209
  assert_type green, :Number
122
210
  assert_type blue, :Number
123
211
 
124
- rgb = [red, green, blue].map do |c|
125
- v = c.value
126
- if c.numerator_units == ["%"] && c.denominator_units.empty?
127
- next v * 255 / 100.0 if (0..100).include?(v)
128
- raise ArgumentError.new("Color value #{c} must be between 0% and 100% inclusive")
129
- else
130
- next v if (0..255).include?(v)
131
- raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
212
+ Color.new([red, green, blue].map do |c|
213
+ v = c.value
214
+ if c.numerator_units == ["%"] && c.denominator_units.empty?
215
+ next v * 255 / 100.0 if (0..100).include?(v)
216
+ raise ArgumentError.new("Color value #{c} must be between 0% and 100% inclusive")
217
+ else
218
+ next v if (0..255).include?(v)
219
+ raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
220
+ end
221
+ end)
222
+ end
223
+
224
+ # @overload rgba(red, green, blue, alpha)
225
+ # Creates a {Color} object from red, green, and blue values,
226
+ # as well as an alpha channel indicating opacity.
227
+ #
228
+ # @param red [Number]
229
+ # A number between 0 and 255 inclusive
230
+ # @param green [Number]
231
+ # A number between 0 and 255 inclusive
232
+ # @param blue [Number]
233
+ # A number between 0 and 255 inclusive
234
+ # @param alpha [Number]
235
+ # A number between 0 and 1
236
+ # @return [Color]
237
+ #
238
+ # @overload rgba(color, alpha)
239
+ # Sets the opacity of a color.
240
+ #
241
+ # @example
242
+ # rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5)
243
+ # rgba(blue, 0.2) => rgba(0, 0, 255, 0.2)
244
+ #
245
+ # @param color [Color]
246
+ # @param alpha [Number]
247
+ # A number between 0 and 1
248
+ # @return [Color]
249
+ def rgba(*args)
250
+ case args.size
251
+ when 2
252
+ color, alpha = args
253
+
254
+ assert_type color, :Color
255
+ assert_type alpha, :Number
256
+
257
+ unless (0..1).include?(alpha.value)
258
+ raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1 inclusive")
132
259
  end
260
+
261
+ color.with(:alpha => alpha.value)
262
+ when 4
263
+ red, green, blue, alpha = args
264
+ rgba(rgb(red, green, blue), alpha)
265
+ else
266
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)")
133
267
  end
134
- Color.new(rgb)
135
268
  end
136
269
 
137
270
  # Creates a {Color} object from hue, saturation, and lightness.
@@ -146,26 +279,373 @@ module Sass::Script
146
279
  # @return [Color] The resulting color
147
280
  # @raise [ArgumentError] if `saturation` or `lightness` are out of bounds
148
281
  def hsl(hue, saturation, lightness)
282
+ hsla(hue, saturation, lightness, Number.new(1))
283
+ end
284
+
285
+ # Creates a {Color} object from hue, saturation, and lightness,
286
+ # as well as an alpha channel indicating opacity.
287
+ # Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
288
+ #
289
+ # @param hue [Number] The hue of the color.
290
+ # Should be between 0 and 360 degrees, inclusive
291
+ # @param saturation [Number] The saturation of the color.
292
+ # Must be between `0%` and `100%`, inclusive
293
+ # @param lightness [Number] The lightness of the color.
294
+ # Must be between `0%` and `100%`, inclusive
295
+ # @param alpha [Number] The opacity of the color.
296
+ # Must be between 0 and 1, inclusive
297
+ # @return [Color] The resulting color
298
+ # @raise [ArgumentError] if `saturation`, `lightness`, or `alpha` are out of bounds
299
+ def hsla(hue, saturation, lightness, alpha)
149
300
  assert_type hue, :Number
150
301
  assert_type saturation, :Number
151
302
  assert_type lightness, :Number
303
+ assert_type alpha, :Number
304
+
305
+ unless (0..1).include?(alpha.value)
306
+ raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1")
307
+ end
152
308
 
153
309
  original_s = saturation
154
310
  original_l = lightness
155
311
  # This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
156
312
  h, s, l = [hue, saturation, lightness].map { |a| a.value }
157
- raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") if s < 0 || s > 100
158
- raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") if l < 0 || l > 100
313
+ raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") unless (0..100).include?(s)
314
+ raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") unless (0..100).include?(l)
315
+
316
+ Color.new(:hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
317
+ end
159
318
 
160
- h = (h % 360) / 360.0
161
- s /= 100.0
162
- l /= 100.0
319
+ # Returns the red component of a color.
320
+ #
321
+ # @param color [Color]
322
+ # @return [Number]
323
+ # @raise [ArgumentError] If `color` isn't a color
324
+ def red(color)
325
+ assert_type color, :Color
326
+ Sass::Script::Number.new(color.red)
327
+ end
163
328
 
164
- m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
165
- m1 = l * 2 - m2
166
- Color.new([hue_to_rgb(m1, m2, h + 1.0/3),
167
- hue_to_rgb(m1, m2, h),
168
- hue_to_rgb(m1, m2, h - 1.0/3)].map { |c| (c * 0xff).round })
329
+ # Returns the green component of a color.
330
+ #
331
+ # @param color [Color]
332
+ # @return [Number]
333
+ # @raise [ArgumentError] If `color` isn't a color
334
+ def green(color)
335
+ assert_type color, :Color
336
+ Sass::Script::Number.new(color.green)
337
+ end
338
+
339
+ # Returns the blue component of a color.
340
+ #
341
+ # @param color [Color]
342
+ # @return [Number]
343
+ # @raise [ArgumentError] If `color` isn't a color
344
+ def blue(color)
345
+ assert_type color, :Color
346
+ Sass::Script::Number.new(color.blue)
347
+ end
348
+
349
+ # Returns the hue component of a color.
350
+ #
351
+ # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
352
+ #
353
+ # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
354
+ #
355
+ # @param color [Color]
356
+ # @return [Number] between 0deg and 360deg
357
+ # @raise [ArgumentError] if `color` isn't a color
358
+ def hue(color)
359
+ assert_type color, :Color
360
+ Sass::Script::Number.new(color.hue, ["deg"])
361
+ end
362
+
363
+ # Returns the saturation component of a color.
364
+ #
365
+ # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
366
+ #
367
+ # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
368
+ #
369
+ # @param color [Color]
370
+ # @return [Number] between 0% and 100%
371
+ # @raise [ArgumentError] if `color` isn't a color
372
+ def saturation(color)
373
+ assert_type color, :Color
374
+ Sass::Script::Number.new(color.saturation, ["%"])
375
+ end
376
+
377
+ # Returns the hue component of a color.
378
+ #
379
+ # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
380
+ #
381
+ # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
382
+ #
383
+ # @param color [Color]
384
+ # @return [Number] between 0% and 100%
385
+ # @raise [ArgumentError] if `color` isn't a color
386
+ def lightness(color)
387
+ assert_type color, :Color
388
+ Sass::Script::Number.new(color.lightness, ["%"])
389
+ end
390
+
391
+ # Returns the alpha component (opacity) of a color.
392
+ # This is 1 unless otherwise specified.
393
+ #
394
+ # @param color [Color]
395
+ # @return [Number]
396
+ # @raise [ArgumentError] If `color` isn't a color
397
+ def alpha(color)
398
+ assert_type color, :Color
399
+ Sass::Script::Number.new(color.alpha)
400
+ end
401
+ alias_method :opacity, :alpha
402
+
403
+ # Makes a color more opaque.
404
+ # Takes a color and an amount between 0 and 1,
405
+ # and returns a color with the opacity increased by that value.
406
+ #
407
+ # For example:
408
+ #
409
+ # opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6)
410
+ # opacify(rgba(0, 0, 17, 0.8), 0.2) => #001
411
+ #
412
+ # @param color [Color]
413
+ # @param amount [Number]
414
+ # @return [Color]
415
+ # @raise [ArgumentError] If `color` isn't a color,
416
+ # or `number` isn't a number between 0 and 1
417
+ def opacify(color, amount)
418
+ adjust(color, amount, :alpha, 0..1, :+)
419
+ end
420
+ alias_method :fade_in, :opacify
421
+
422
+ # Makes a color more transparent.
423
+ # Takes a color and an amount between 0 and 1,
424
+ # and returns a color with the opacity decreased by that value.
425
+ #
426
+ # For example:
427
+ #
428
+ # transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4)
429
+ # transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6)
430
+ #
431
+ # @param color [Color]
432
+ # @param amount [Number]
433
+ # @return [Color]
434
+ # @raise [ArgumentError] If `color` isn't a color,
435
+ # or `number` isn't a number between 0 and 1
436
+ def transparentize(color, amount)
437
+ adjust(color, amount, :alpha, 0..1, :-)
438
+ end
439
+ alias_method :fade_out, :transparentize
440
+
441
+ # Makes a color lighter.
442
+ # Takes a color and an amount between 0% and 100%,
443
+ # and returns a color with the lightness increased by that value.
444
+ #
445
+ # For example:
446
+ #
447
+ # lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
448
+ # lighten(#800, 20%) => #e00
449
+ #
450
+ # @param color [Color]
451
+ # @param amount [Number]
452
+ # @return [Color]
453
+ # @raise [ArgumentError] If `color` isn't a color,
454
+ # or `number` isn't a number between 0% and 100%
455
+ def lighten(color, amount)
456
+ adjust(color, amount, :lightness, 0..100, :+, "%")
457
+ end
458
+
459
+ # Makes a color darker.
460
+ # Takes a color and an amount between 0% and 100%,
461
+ # and returns a color with the lightness decreased by that value.
462
+ #
463
+ # For example:
464
+ #
465
+ # darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
466
+ # darken(#800, 20%) => #200
467
+ #
468
+ # @param color [Color]
469
+ # @param amount [Number]
470
+ # @return [Color]
471
+ # @raise [ArgumentError] If `color` isn't a color,
472
+ # or `number` isn't a number between 0% and 100%
473
+ def darken(color, amount)
474
+ adjust(color, amount, :lightness, 0..100, :-, "%")
475
+ end
476
+
477
+ # Makes a color more saturated.
478
+ # Takes a color and an amount between 0% and 100%,
479
+ # and returns a color with the saturation increased by that value.
480
+ #
481
+ # For example:
482
+ #
483
+ # saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%)
484
+ # saturate(#855, 20%) => #9e3f3f
485
+ #
486
+ # @param color [Color]
487
+ # @param amount [Number]
488
+ # @return [Color]
489
+ # @raise [ArgumentError] If `color` isn't a color,
490
+ # or `number` isn't a number between 0% and 100%
491
+ def saturate(color, amount)
492
+ adjust(color, amount, :saturation, 0..100, :+, "%")
493
+ end
494
+
495
+ # Makes a color less saturated.
496
+ # Takes a color and an amount between 0% and 100%,
497
+ # and returns a color with the saturation decreased by that value.
498
+ #
499
+ # For example:
500
+ #
501
+ # desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%)
502
+ # desaturate(#855, 20%) => #726b6b
503
+ #
504
+ # @param color [Color]
505
+ # @param amount [Number]
506
+ # @return [Color]
507
+ # @raise [ArgumentError] If `color` isn't a color,
508
+ # or `number` isn't a number between 0% and 100%
509
+ def desaturate(color, amount)
510
+ adjust(color, amount, :saturation, 0..100, :-, "%")
511
+ end
512
+
513
+ # Changes the hue of a color while retaining the lightness and saturation.
514
+ # Takes a color and a number of degrees (usually between -360deg and 360deg),
515
+ # and returns a color with the hue rotated by that value.
516
+ #
517
+ # For example:
518
+ #
519
+ # adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%)
520
+ # adjust-hue(hsl(120, 30%, 90%), 060deg) => hsl(60, 30%, 90%)
521
+ # adjust-hue(#811, 45deg) => #886a11
522
+ #
523
+ # @param color [Color]
524
+ # @param amount [Number]
525
+ # @return [Color]
526
+ # @raise [ArgumentError] If `color` isn't a color, or `number` isn't a number
527
+ def adjust_hue(color, degrees)
528
+ assert_type color, :Color
529
+ assert_type degrees, :Number
530
+ color.with(:hue => color.hue + degrees.value)
531
+ end
532
+
533
+ # Mixes together two colors.
534
+ # Specifically, takes the average of each of the RGB components,
535
+ # optionally weighted by the given percentage.
536
+ # The opacity of the colors is also considered when weighting the components.
537
+ #
538
+ # The weight specifies the amount of the first color that should be included
539
+ # in the returned color.
540
+ # The default, 50%, means that half the first color
541
+ # and half the second color should be used.
542
+ # 25% means that a quarter of the first color
543
+ # and three quarters of the second color should be used.
544
+ #
545
+ # For example:
546
+ #
547
+ # mix(#f00, #00f) => #7f007f
548
+ # mix(#f00, #00f, 25%) => #3f00bf
549
+ # mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
550
+ #
551
+ # @overload mix(color1, color2, weight = 50%)
552
+ # @param color1 [Color]
553
+ # @param color2 [Color]
554
+ # @param weight [Number] between 0% and 100%
555
+ # @return [Color]
556
+ # @raise [ArgumentError] if `color1` or `color2` aren't colors,
557
+ # or `weight` isn't a number between 0% and 100%
558
+ def mix(color1, color2, weight = Number.new(50))
559
+ assert_type color1, :Color
560
+ assert_type color2, :Color
561
+ assert_type weight, :Number
562
+
563
+ unless (0..100).include?(weight.value)
564
+ raise ArgumentError.new("Weight #{weight} must be between 0% and 100%")
565
+ end
566
+
567
+ # This algorithm factors in both the user-provided weight
568
+ # and the difference between the alpha values of the two colors
569
+ # to decide how to perform the weighted average of the two RGB values.
570
+ #
571
+ # It works by first normalizing both parameters to be within [-1, 1],
572
+ # where 1 indicates "only use color1", -1 indicates "only use color 0",
573
+ # and all values in between indicated a proportionately weighted average.
574
+ #
575
+ # Once we have the normalized variables w and a,
576
+ # we apply the formula (w + a)/(1 + w*a)
577
+ # to get the combined weight (in [-1, 1]) of color1.
578
+ # This formula has two especially nice properties:
579
+ #
580
+ # * When either w or a are -1 or 1, the combined weight is also that number
581
+ # (cases where w * a == -1 are undefined, and handled as a special case).
582
+ #
583
+ # * When a is 0, the combined weight is w, and vice versa
584
+ #
585
+ # Finally, the weight of color1 is renormalized to be within [0, 1]
586
+ # and the weight of color2 is given by 1 minus the weight of color1.
587
+ p = weight.value/100.0
588
+ w = p*2 - 1
589
+ a = color1.alpha - color2.alpha
590
+
591
+ w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0
592
+ w2 = 1 - w1
593
+
594
+ rgb = color1.rgb.zip(color2.rgb).map {|v1, v2| v1*w1 + v2*w2}
595
+ alpha = color1.alpha*p + color2.alpha*(1-p)
596
+ Color.new(rgb + [alpha])
597
+ end
598
+
599
+ # Converts a color to grayscale.
600
+ # This is identical to `desaturate(color, 100%)`.
601
+ #
602
+ # @param color [Color]
603
+ # @return [Color]
604
+ # @raise [ArgumentError] if `color` isn't a color
605
+ # @see #desaturate
606
+ def grayscale(color)
607
+ desaturate color, Number.new(100)
608
+ end
609
+
610
+ # Returns the complement of a color.
611
+ # This is identical to `adjust-hue(color, 180deg)`.
612
+ #
613
+ # @param color [Color]
614
+ # @return [Color]
615
+ # @raise [ArgumentError] if `color` isn't a color
616
+ # @see #adjust_hue #adjust-hue
617
+ def complement(color)
618
+ adjust_hue color, Number.new(180)
619
+ end
620
+
621
+ # Removes quotes from a string if the string is quoted,
622
+ # or returns the same string if it's not.
623
+ #
624
+ # @param str [String]
625
+ # @return [String]
626
+ # @raise [ArgumentError] if `str` isn't a string
627
+ # @see #quote
628
+ # @example
629
+ # unquote("foo") => foo
630
+ # unquote(foo) => foo
631
+ def unquote(str)
632
+ assert_type str, :String
633
+ Sass::Script::String.new(str.value, :identifier)
634
+ end
635
+
636
+ # Add quotes to a string if the string isn't quoted,
637
+ # or returns the same string if it is.
638
+ #
639
+ # @param str [String]
640
+ # @return [String]
641
+ # @raise [ArgumentError] if `str` isn't a string
642
+ # @see #unquote
643
+ # @example
644
+ # quote("foo") => "foo"
645
+ # quote(foo) => "foo"
646
+ def quote(str)
647
+ assert_type str, :String
648
+ Sass::Script::String.new(str.value, :string)
169
649
  end
170
650
 
171
651
  # Converts a decimal number to a percentage.
@@ -235,6 +715,11 @@ module Sass::Script
235
715
  numeric_transformation(value) {|n| n.abs}
236
716
  end
237
717
 
718
+ def unquote(value)
719
+ assert_type value, :String
720
+ Sass::Script::String.new(value.value)
721
+ end
722
+
238
723
  private
239
724
 
240
725
  # This method implements the pattern of transforming a numeric value into
@@ -245,13 +730,18 @@ module Sass::Script
245
730
  Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
246
731
  end
247
732
 
248
- def hue_to_rgb(m1, m2, h)
249
- h += 1 if h < 0
250
- h -= 1 if h > 1
251
- return m1 + (m2 - m1) * h * 6 if h * 6 < 1
252
- return m2 if h * 2 < 1
253
- return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
254
- return m1
733
+ def adjust(color, amount, attr, range, op, units = "")
734
+ assert_type color, :Color
735
+ assert_type amount, :Number
736
+ unless range.include?(amount.value)
737
+ raise ArgumentError.new("Amount #{amount} must be between #{range.first}#{units} and #{range.last}#{units}")
738
+ end
739
+
740
+ # TODO: is it worth restricting here,
741
+ # or should we do so in the Color constructor itself,
742
+ # and allow clipping in rgb() et al?
743
+ color.with(attr => Haml::Util.restrict(
744
+ color.send(attr).send(op, amount.value), range))
255
745
  end
256
746
  end
257
747
  end