sass 3.1.0.alpha.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (205) hide show
  1. data/.yardopts +11 -0
  2. data/CONTRIBUTING +3 -0
  3. data/EDGE_GEM_VERSION +1 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +201 -0
  6. data/REVISION +1 -0
  7. data/Rakefile +353 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/css2sass +13 -0
  11. data/bin/sass +8 -0
  12. data/bin/sass-convert +7 -0
  13. data/extra/update_watch.rb +13 -0
  14. data/init.rb +18 -0
  15. data/lib/sass.rb +71 -0
  16. data/lib/sass/cache_store.rb +208 -0
  17. data/lib/sass/callbacks.rb +66 -0
  18. data/lib/sass/css.rb +294 -0
  19. data/lib/sass/engine.rb +792 -0
  20. data/lib/sass/environment.rb +143 -0
  21. data/lib/sass/error.rb +201 -0
  22. data/lib/sass/exec.rb +619 -0
  23. data/lib/sass/importers.rb +22 -0
  24. data/lib/sass/importers/base.rb +138 -0
  25. data/lib/sass/importers/filesystem.rb +121 -0
  26. data/lib/sass/less.rb +363 -0
  27. data/lib/sass/plugin.rb +126 -0
  28. data/lib/sass/plugin/compiler.rb +346 -0
  29. data/lib/sass/plugin/configuration.rb +123 -0
  30. data/lib/sass/plugin/generic.rb +15 -0
  31. data/lib/sass/plugin/merb.rb +48 -0
  32. data/lib/sass/plugin/rack.rb +47 -0
  33. data/lib/sass/plugin/rails.rb +41 -0
  34. data/lib/sass/plugin/staleness_checker.rb +145 -0
  35. data/lib/sass/railtie.rb +8 -0
  36. data/lib/sass/repl.rb +58 -0
  37. data/lib/sass/root.rb +7 -0
  38. data/lib/sass/script.rb +63 -0
  39. data/lib/sass/script/bool.rb +18 -0
  40. data/lib/sass/script/color.rb +490 -0
  41. data/lib/sass/script/css_lexer.rb +29 -0
  42. data/lib/sass/script/css_parser.rb +31 -0
  43. data/lib/sass/script/funcall.rb +78 -0
  44. data/lib/sass/script/functions.rb +852 -0
  45. data/lib/sass/script/interpolation.rb +70 -0
  46. data/lib/sass/script/lexer.rb +337 -0
  47. data/lib/sass/script/literal.rb +236 -0
  48. data/lib/sass/script/node.rb +101 -0
  49. data/lib/sass/script/number.rb +420 -0
  50. data/lib/sass/script/operation.rb +92 -0
  51. data/lib/sass/script/parser.rb +392 -0
  52. data/lib/sass/script/string.rb +67 -0
  53. data/lib/sass/script/string_interpolation.rb +93 -0
  54. data/lib/sass/script/unary_operation.rb +57 -0
  55. data/lib/sass/script/variable.rb +48 -0
  56. data/lib/sass/scss.rb +17 -0
  57. data/lib/sass/scss/css_parser.rb +51 -0
  58. data/lib/sass/scss/parser.rb +838 -0
  59. data/lib/sass/scss/rx.rb +126 -0
  60. data/lib/sass/scss/sass_parser.rb +11 -0
  61. data/lib/sass/scss/script_lexer.rb +15 -0
  62. data/lib/sass/scss/script_parser.rb +25 -0
  63. data/lib/sass/scss/static_parser.rb +40 -0
  64. data/lib/sass/selector.rb +361 -0
  65. data/lib/sass/selector/abstract_sequence.rb +62 -0
  66. data/lib/sass/selector/comma_sequence.rb +82 -0
  67. data/lib/sass/selector/sequence.rb +236 -0
  68. data/lib/sass/selector/simple.rb +113 -0
  69. data/lib/sass/selector/simple_sequence.rb +135 -0
  70. data/lib/sass/shared.rb +78 -0
  71. data/lib/sass/tree/comment_node.rb +128 -0
  72. data/lib/sass/tree/debug_node.rb +36 -0
  73. data/lib/sass/tree/directive_node.rb +75 -0
  74. data/lib/sass/tree/extend_node.rb +65 -0
  75. data/lib/sass/tree/for_node.rb +67 -0
  76. data/lib/sass/tree/if_node.rb +81 -0
  77. data/lib/sass/tree/import_node.rb +124 -0
  78. data/lib/sass/tree/mixin_def_node.rb +60 -0
  79. data/lib/sass/tree/mixin_node.rb +123 -0
  80. data/lib/sass/tree/node.rb +490 -0
  81. data/lib/sass/tree/prop_node.rb +220 -0
  82. data/lib/sass/tree/root_node.rb +125 -0
  83. data/lib/sass/tree/rule_node.rb +273 -0
  84. data/lib/sass/tree/variable_node.rb +39 -0
  85. data/lib/sass/tree/warn_node.rb +42 -0
  86. data/lib/sass/tree/while_node.rb +48 -0
  87. data/lib/sass/util.rb +687 -0
  88. data/lib/sass/util/subset_map.rb +101 -0
  89. data/lib/sass/version.rb +109 -0
  90. data/rails/init.rb +1 -0
  91. data/test/sass/cache_test.rb +74 -0
  92. data/test/sass/callbacks_test.rb +61 -0
  93. data/test/sass/conversion_test.rb +1210 -0
  94. data/test/sass/css2sass_test.rb +364 -0
  95. data/test/sass/data/hsl-rgb.txt +319 -0
  96. data/test/sass/engine_test.rb +2273 -0
  97. data/test/sass/extend_test.rb +1348 -0
  98. data/test/sass/functions_test.rb +565 -0
  99. data/test/sass/importer_test.rb +104 -0
  100. data/test/sass/less_conversion_test.rb +632 -0
  101. data/test/sass/mock_importer.rb +49 -0
  102. data/test/sass/more_results/more1.css +9 -0
  103. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  104. data/test/sass/more_results/more_import.css +29 -0
  105. data/test/sass/more_templates/_more_partial.sass +2 -0
  106. data/test/sass/more_templates/more1.sass +23 -0
  107. data/test/sass/more_templates/more_import.sass +11 -0
  108. data/test/sass/plugin_test.rb +430 -0
  109. data/test/sass/results/alt.css +4 -0
  110. data/test/sass/results/basic.css +9 -0
  111. data/test/sass/results/compact.css +5 -0
  112. data/test/sass/results/complex.css +86 -0
  113. data/test/sass/results/compressed.css +1 -0
  114. data/test/sass/results/expanded.css +19 -0
  115. data/test/sass/results/import.css +31 -0
  116. data/test/sass/results/line_numbers.css +49 -0
  117. data/test/sass/results/mixins.css +95 -0
  118. data/test/sass/results/multiline.css +24 -0
  119. data/test/sass/results/nested.css +22 -0
  120. data/test/sass/results/options.css +1 -0
  121. data/test/sass/results/parent_ref.css +13 -0
  122. data/test/sass/results/script.css +16 -0
  123. data/test/sass/results/scss_import.css +31 -0
  124. data/test/sass/results/scss_importee.css +2 -0
  125. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  126. data/test/sass/results/subdir/subdir.css +3 -0
  127. data/test/sass/results/units.css +11 -0
  128. data/test/sass/results/warn.css +0 -0
  129. data/test/sass/results/warn_imported.css +0 -0
  130. data/test/sass/script_conversion_test.rb +254 -0
  131. data/test/sass/script_test.rb +459 -0
  132. data/test/sass/scss/css_test.rb +897 -0
  133. data/test/sass/scss/rx_test.rb +156 -0
  134. data/test/sass/scss/scss_test.rb +1088 -0
  135. data/test/sass/scss/test_helper.rb +37 -0
  136. data/test/sass/templates/_partial.sass +2 -0
  137. data/test/sass/templates/alt.sass +16 -0
  138. data/test/sass/templates/basic.sass +23 -0
  139. data/test/sass/templates/bork1.sass +2 -0
  140. data/test/sass/templates/bork2.sass +2 -0
  141. data/test/sass/templates/bork3.sass +2 -0
  142. data/test/sass/templates/bork4.sass +2 -0
  143. data/test/sass/templates/compact.sass +17 -0
  144. data/test/sass/templates/complex.sass +305 -0
  145. data/test/sass/templates/compressed.sass +15 -0
  146. data/test/sass/templates/expanded.sass +17 -0
  147. data/test/sass/templates/import.sass +12 -0
  148. data/test/sass/templates/importee.less +2 -0
  149. data/test/sass/templates/importee.sass +19 -0
  150. data/test/sass/templates/line_numbers.sass +13 -0
  151. data/test/sass/templates/mixin_bork.sass +5 -0
  152. data/test/sass/templates/mixins.sass +76 -0
  153. data/test/sass/templates/multiline.sass +20 -0
  154. data/test/sass/templates/nested.sass +25 -0
  155. data/test/sass/templates/nested_bork1.sass +2 -0
  156. data/test/sass/templates/nested_bork2.sass +2 -0
  157. data/test/sass/templates/nested_bork3.sass +2 -0
  158. data/test/sass/templates/nested_bork4.sass +2 -0
  159. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  160. data/test/sass/templates/options.sass +2 -0
  161. data/test/sass/templates/parent_ref.sass +25 -0
  162. data/test/sass/templates/script.sass +101 -0
  163. data/test/sass/templates/scss_import.scss +11 -0
  164. data/test/sass/templates/scss_importee.scss +1 -0
  165. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  166. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  167. data/test/sass/templates/subdir/subdir.sass +6 -0
  168. data/test/sass/templates/units.sass +11 -0
  169. data/test/sass/templates/warn.sass +3 -0
  170. data/test/sass/templates/warn_imported.sass +4 -0
  171. data/test/sass/test_helper.rb +8 -0
  172. data/test/sass/util/subset_map_test.rb +91 -0
  173. data/test/sass/util_test.rb +275 -0
  174. data/test/test_helper.rb +64 -0
  175. data/vendor/fssm/LICENSE +20 -0
  176. data/vendor/fssm/README.markdown +55 -0
  177. data/vendor/fssm/Rakefile +59 -0
  178. data/vendor/fssm/VERSION.yml +5 -0
  179. data/vendor/fssm/example.rb +9 -0
  180. data/vendor/fssm/fssm.gemspec +77 -0
  181. data/vendor/fssm/lib/fssm.rb +33 -0
  182. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  183. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  184. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  185. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  186. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  187. data/vendor/fssm/lib/fssm/path.rb +91 -0
  188. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  189. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  190. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  191. data/vendor/fssm/lib/fssm/support.rb +63 -0
  192. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  193. data/vendor/fssm/profile/prof-cache.rb +40 -0
  194. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  195. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  196. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  197. data/vendor/fssm/profile/prof.html +2379 -0
  198. data/vendor/fssm/spec/path_spec.rb +75 -0
  199. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  200. data/vendor/fssm/spec/root/file.css +0 -0
  201. data/vendor/fssm/spec/root/file.rb +0 -0
  202. data/vendor/fssm/spec/root/file.yml +0 -0
  203. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  204. data/vendor/fssm/spec/spec_helper.rb +14 -0
  205. metadata +297 -0
@@ -0,0 +1,29 @@
1
+ module Sass
2
+ module Script
3
+ # This is a subclass of {Lexer} for use in parsing plain CSS properties.
4
+ #
5
+ # @see Sass::SCSS::CssParser
6
+ class CssLexer < Lexer
7
+ private
8
+
9
+ def token
10
+ important || super
11
+ end
12
+
13
+ def string(re, *args)
14
+ if re == :uri
15
+ return unless uri = scan(URI)
16
+ return [:string, Script::String.new(uri)]
17
+ end
18
+
19
+ return unless scan(STRING)
20
+ [:string, Script::String.new((@scanner[1] || @scanner[2]).gsub(/\\(['"])/, '\1'), :string)]
21
+ end
22
+
23
+ def important
24
+ return unless s = scan(IMPORTANT)
25
+ [:raw, s]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ require 'sass/script'
2
+ require 'sass/script/css_lexer'
3
+
4
+ module Sass
5
+ module Script
6
+ # This is a subclass of {Parser} for use in parsing plain CSS properties.
7
+ #
8
+ # @see Sass::SCSS::CssParser
9
+ class CssParser < Parser
10
+ private
11
+
12
+ # @private
13
+ def lexer_class; CssLexer; end
14
+
15
+ # We need a production that only does /,
16
+ # since * and % aren't allowed in plain CSS
17
+ production :div, :unary_plus, :div
18
+
19
+ def string
20
+ return number unless tok = try_tok(:string)
21
+ return tok.value unless @lexer.peek && @lexer.peek.type == :begin_interpolation
22
+ end
23
+
24
+ # Short-circuit all the SassScript-only productions
25
+ alias_method :interpolation, :concat
26
+ alias_method :or_expr, :div
27
+ alias_method :unary_div, :ident
28
+ alias_method :paren, :string
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,78 @@
1
+ require File.join(File.dirname(__FILE__), 'functions')
2
+ module Sass
3
+ module Script
4
+ # A SassScript parse node representing a function call.
5
+ #
6
+ # A function call either calls one of the functions in {Script::Functions},
7
+ # or if no function with the given name exists
8
+ # it returns a string representation of the function call.
9
+ class Funcall < Node
10
+ # The name of the function.
11
+ #
12
+ # @return [String]
13
+ attr_reader :name
14
+
15
+ # The arguments to the function.
16
+ #
17
+ # @return [Array<Script::Node>]
18
+ attr_reader :args
19
+
20
+ # Don't set the context for child nodes if this is `url()`,
21
+ # since `url()` allows quoted strings.
22
+ #
23
+ # @param context [Symbol]
24
+ # @see Node#context=
25
+ def context=(context)
26
+ super unless @name == "url"
27
+ end
28
+
29
+ # @param name [String] See \{#name}
30
+ # @param name [Array<Script::Node>] See \{#args}
31
+ def initialize(name, args)
32
+ @name = name
33
+ @args = args
34
+ super()
35
+ end
36
+
37
+ # @return [String] A string representation of the function call
38
+ def inspect
39
+ "#{name}(#{args.map {|a| a.inspect}.join(', ')})"
40
+ end
41
+
42
+ # @see Node#to_sass
43
+ def to_sass(opts = {})
44
+ "#{dasherize(name, opts)}(#{args.map {|a| a.to_sass(opts)}.join(', ')})"
45
+ end
46
+
47
+ # Returns the arguments to the function.
48
+ #
49
+ # @return [Array<Node>]
50
+ # @see Node#children
51
+ def children
52
+ @args
53
+ end
54
+
55
+ protected
56
+
57
+ # Evaluates the function call.
58
+ #
59
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
60
+ # @return [Literal] The SassScript object that is the value of the function call
61
+ # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
62
+ def _perform(environment)
63
+ args = self.args.map {|a| a.perform(environment)}
64
+ ruby_name = name.gsub('-', '_')
65
+ unless Sass::Util.has?(:public_instance_method, Functions, ruby_name) && ruby_name !~ /^__/
66
+ return Script::String.new("#{name}(#{args.map {|a| a.perform(environment)}.join(', ')})")
67
+ end
68
+
69
+ result = Functions::EvaluationContext.new(environment.options).send(ruby_name, *args)
70
+ result.options = environment.options
71
+ return result
72
+ rescue ArgumentError => e
73
+ raise e unless e.backtrace.any? {|t| t =~ /:in `(block in )?(#{name}|perform)'$/}
74
+ raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,852 @@
1
+ module Sass::Script
2
+ # Methods in this module are accessible from the SassScript context.
3
+ # For example, you can write
4
+ #
5
+ # $color = hsl(120deg, 100%, 50%)
6
+ #
7
+ # and it will call {Sass::Script::Functions#hsl}.
8
+ #
9
+ # The following functions are provided:
10
+ #
11
+ # ## RGB Functions
12
+ #
13
+ # \{#rgb}
14
+ # : Converts an `rgb(red, green, blue)` triplet into a color.
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
+ # \{#invert}
70
+ # : Returns the inverse of a color.
71
+ #
72
+ # ## Opacity Functions
73
+ #
74
+ # \{#alpha} / \{#opacity}
75
+ # : Gets the alpha component (opacity) of a color.
76
+ #
77
+ # \{#rgba}
78
+ # : Sets the alpha component of a color.
79
+ #
80
+ # \{#opacify} / \{#fade_in #fade-in}
81
+ # : Makes a color more opaque.
82
+ #
83
+ # \{#transparentize} / \{#fade_out #fade-out}
84
+ # : Makes a color more transparent.
85
+ #
86
+ # ## String Functions
87
+ #
88
+ # \{#unquote}
89
+ # : Removes the quotes from a string.
90
+ #
91
+ # \{#quote}
92
+ # : Adds quotes to a string.
93
+ #
94
+ # ## Number Functions
95
+ #
96
+ # \{#percentage}
97
+ # : Converts a unitless number to a percentage.
98
+ #
99
+ # \{#round}
100
+ # : Rounds a number to the nearest whole number.
101
+ #
102
+ # \{#ceil}
103
+ # : Rounds a number up to the nearest whole number.
104
+ #
105
+ # \{#floor}
106
+ # : Rounds a number down to the nearest whole number.
107
+ #
108
+ # \{#abs}
109
+ # : Returns the absolute value of a number.
110
+ #
111
+ # ## Introspection Functions
112
+ #
113
+ # \{#type_of}
114
+ # : Returns the type of a value.
115
+ #
116
+ # \{#unit}
117
+ # : Returns the units associated with a number.
118
+ #
119
+ # \{#unitless}
120
+ # : Returns whether a number has units or not.
121
+ #
122
+ # \{#comparable}
123
+ # : Returns whether two numbers can be added or compared.
124
+ #
125
+ # These functions are described in more detail below.
126
+ #
127
+ # ## Adding Custom Functions
128
+ #
129
+ # New Sass functions can be added by adding Ruby methods to this module.
130
+ # For example:
131
+ #
132
+ # module Sass::Script::Functions
133
+ # def reverse(string)
134
+ # assert_type string, :String
135
+ # Sass::Script::String.new(string.value.reverse)
136
+ # end
137
+ # end
138
+ #
139
+ # There are a few things to keep in mind when modifying this module.
140
+ # First of all, the arguments passed are {Sass::Script::Literal} objects.
141
+ # Literal objects are also expected to be returned.
142
+ # This means that Ruby values must be unwrapped and wrapped.
143
+ #
144
+ # Most Literal objects support the {Sass::Script::Literal#value value} accessor
145
+ # for getting their Ruby values.
146
+ # Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb},
147
+ # {Sass::Script::Color#red red}, {Sass::Script::Color#blue green}, or {Sass::Script::Color#blue blue}.
148
+ #
149
+ # Second, making Ruby functions accessible from Sass introduces the temptation
150
+ # to do things like database access within stylesheets.
151
+ # This is generally a bad idea;
152
+ # since Sass files are by default only compiled once,
153
+ # dynamic code is not a great fit.
154
+ #
155
+ # If you really, really need to compile Sass on each request,
156
+ # first make sure you have adequate caching set up.
157
+ # Then you can use {Sass::Engine} to render the code,
158
+ # using the {file:SASS_REFERENCE.md#custom-option `options` parameter}
159
+ # to pass in data that {EvaluationContext#options can be accessed}
160
+ # from your Sass functions.
161
+ #
162
+ # Within one of the functions in this module,
163
+ # methods of {EvaluationContext} can be used.
164
+ #
165
+ # ### Caveats
166
+ #
167
+ # When creating new {Literal} objects within functions,
168
+ # be aware that it's not safe to call {Literal#to_s #to_s}
169
+ # (or other methods that use the string representation)
170
+ # on those objects without first setting {Node#options= the #options attribute}.
171
+ module Functions
172
+ # The context in which methods in {Script::Functions} are evaluated.
173
+ # That means that all instance methods of {EvaluationContext}
174
+ # are available to use in functions.
175
+ class EvaluationContext
176
+ # The options hash for the {Sass::Engine} that is processing the function call
177
+ #
178
+ # @return [{Symbol => Object}]
179
+ attr_reader :options
180
+
181
+ # @param options [{Symbol => Object}] See \{#options}
182
+ def initialize(options)
183
+ @options = options
184
+
185
+ # We need to include this individually in each instance
186
+ # because of an icky Ruby restriction
187
+ class << self; include Sass::Script::Functions; end
188
+ end
189
+
190
+ # Asserts that the type of a given SassScript value
191
+ # is the expected type (designated by a symbol).
192
+ #
193
+ # Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
194
+ # Note that `:String` will match both double-quoted strings
195
+ # and unquoted identifiers.
196
+ #
197
+ # @example
198
+ # assert_type value, :String
199
+ # assert_type value, :Number
200
+ # @param value [Sass::Script::Literal] A SassScript value
201
+ # @param type [Symbol] The name of the type the value is expected to be
202
+ def assert_type(value, type)
203
+ return if value.is_a?(Sass::Script.const_get(type))
204
+ raise ArgumentError.new("#{value.inspect} is not a #{type.to_s.downcase}")
205
+ end
206
+ end
207
+
208
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
209
+
210
+
211
+ # Creates a {Color} object from red, green, and blue values.
212
+ #
213
+ # @param red [Number]
214
+ # A number between 0 and 255 inclusive,
215
+ # or between 0% and 100% inclusive
216
+ # @param green [Number]
217
+ # A number between 0 and 255 inclusive,
218
+ # or between 0% and 100% inclusive
219
+ # @param blue [Number]
220
+ # A number between 0 and 255 inclusive,
221
+ # or between 0% and 100% inclusive
222
+ # @see #rgba
223
+ # @return [Color]
224
+ def rgb(red, green, blue)
225
+ assert_type red, :Number
226
+ assert_type green, :Number
227
+ assert_type blue, :Number
228
+
229
+ Color.new([red, green, blue].map do |c|
230
+ v = c.value
231
+ if c.numerator_units == ["%"] && c.denominator_units.empty?
232
+ next v * 255 / 100.0 if (0..100).include?(v)
233
+ raise ArgumentError.new("Color value #{c} must be between 0% and 100% inclusive")
234
+ else
235
+ next v if (0..255).include?(v)
236
+ raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
237
+ end
238
+ end)
239
+ end
240
+
241
+ # @see #rgb
242
+ # @overload rgba(red, green, blue, alpha)
243
+ # Creates a {Color} object from red, green, and blue values,
244
+ # as well as an alpha channel indicating opacity.
245
+ #
246
+ # @param red [Number]
247
+ # A number between 0 and 255 inclusive
248
+ # @param green [Number]
249
+ # A number between 0 and 255 inclusive
250
+ # @param blue [Number]
251
+ # A number between 0 and 255 inclusive
252
+ # @param alpha [Number]
253
+ # A number between 0 and 1
254
+ # @return [Color]
255
+ #
256
+ # @overload rgba(color, alpha)
257
+ # Sets the opacity of a color.
258
+ #
259
+ # @example
260
+ # rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5)
261
+ # rgba(blue, 0.2) => rgba(0, 0, 255, 0.2)
262
+ #
263
+ # @param color [Color]
264
+ # @param alpha [Number]
265
+ # A number between 0 and 1
266
+ # @return [Color]
267
+ def rgba(*args)
268
+ case args.size
269
+ when 2
270
+ color, alpha = args
271
+
272
+ assert_type color, :Color
273
+ assert_type alpha, :Number
274
+
275
+ unless (0..1).include?(alpha.value)
276
+ raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1 inclusive")
277
+ end
278
+
279
+ color.with(:alpha => alpha.value)
280
+ when 4
281
+ red, green, blue, alpha = args
282
+ rgba(rgb(red, green, blue), alpha)
283
+ else
284
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)")
285
+ end
286
+ end
287
+
288
+ # Creates a {Color} object from hue, saturation, and lightness.
289
+ # Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
290
+ #
291
+ # @param hue [Number] The hue of the color.
292
+ # Should be between 0 and 360 degrees, inclusive
293
+ # @param saturation [Number] The saturation of the color.
294
+ # Must be between `0%` and `100%`, inclusive
295
+ # @param lightness [Number] The lightness of the color.
296
+ # Must be between `0%` and `100%`, inclusive
297
+ # @return [Color] The resulting color
298
+ # @see #hsla
299
+ # @raise [ArgumentError] if `saturation` or `lightness` are out of bounds
300
+ def hsl(hue, saturation, lightness)
301
+ hsla(hue, saturation, lightness, Number.new(1))
302
+ end
303
+
304
+ # Creates a {Color} object from hue, saturation, and lightness,
305
+ # as well as an alpha channel indicating opacity.
306
+ # Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
307
+ #
308
+ # @param hue [Number] The hue of the color.
309
+ # Should be between 0 and 360 degrees, inclusive
310
+ # @param saturation [Number] The saturation of the color.
311
+ # Must be between `0%` and `100%`, inclusive
312
+ # @param lightness [Number] The lightness of the color.
313
+ # Must be between `0%` and `100%`, inclusive
314
+ # @param alpha [Number] The opacity of the color.
315
+ # Must be between 0 and 1, inclusive
316
+ # @return [Color] The resulting color
317
+ # @see #hsl
318
+ # @raise [ArgumentError] if `saturation`, `lightness`, or `alpha` are out of bounds
319
+ def hsla(hue, saturation, lightness, alpha)
320
+ assert_type hue, :Number
321
+ assert_type saturation, :Number
322
+ assert_type lightness, :Number
323
+ assert_type alpha, :Number
324
+
325
+ unless (0..1).include?(alpha.value)
326
+ raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1")
327
+ end
328
+
329
+ original_s = saturation
330
+ original_l = lightness
331
+ # This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
332
+ h, s, l = [hue, saturation, lightness].map { |a| a.value }
333
+ raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") unless (0..100).include?(s)
334
+ raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") unless (0..100).include?(l)
335
+
336
+ Color.new(:hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
337
+ end
338
+
339
+ # Returns the red component of a color.
340
+ #
341
+ # @param color [Color]
342
+ # @return [Number]
343
+ # @raise [ArgumentError] If `color` isn't a color
344
+ def red(color)
345
+ assert_type color, :Color
346
+ Sass::Script::Number.new(color.red)
347
+ end
348
+
349
+ # Returns the green component of a color.
350
+ #
351
+ # @param color [Color]
352
+ # @return [Number]
353
+ # @raise [ArgumentError] If `color` isn't a color
354
+ def green(color)
355
+ assert_type color, :Color
356
+ Sass::Script::Number.new(color.green)
357
+ end
358
+
359
+ # Returns the blue component of a color.
360
+ #
361
+ # @param color [Color]
362
+ # @return [Number]
363
+ # @raise [ArgumentError] If `color` isn't a color
364
+ def blue(color)
365
+ assert_type color, :Color
366
+ Sass::Script::Number.new(color.blue)
367
+ end
368
+
369
+ # Returns the hue component of a color.
370
+ #
371
+ # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
372
+ #
373
+ # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
374
+ #
375
+ # @param color [Color]
376
+ # @return [Number] between 0deg and 360deg
377
+ # @see #adjust_hue
378
+ # @raise [ArgumentError] if `color` isn't a color
379
+ def hue(color)
380
+ assert_type color, :Color
381
+ Sass::Script::Number.new(color.hue, ["deg"])
382
+ end
383
+
384
+ # Returns the saturation component of a color.
385
+ #
386
+ # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
387
+ #
388
+ # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
389
+ #
390
+ # @param color [Color]
391
+ # @return [Number] between 0% and 100%
392
+ # @see #saturate
393
+ # @see #desaturate
394
+ # @raise [ArgumentError] if `color` isn't a color
395
+ def saturation(color)
396
+ assert_type color, :Color
397
+ Sass::Script::Number.new(color.saturation, ["%"])
398
+ end
399
+
400
+ # Returns the hue component of a color.
401
+ #
402
+ # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
403
+ #
404
+ # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
405
+ #
406
+ # @param color [Color]
407
+ # @return [Number] between 0% and 100%
408
+ # @see #lighten
409
+ # @see #darken
410
+ # @raise [ArgumentError] if `color` isn't a color
411
+ def lightness(color)
412
+ assert_type color, :Color
413
+ Sass::Script::Number.new(color.lightness, ["%"])
414
+ end
415
+
416
+ # Returns the alpha component (opacity) of a color.
417
+ # This is 1 unless otherwise specified.
418
+ #
419
+ # This function also supports the proprietary Microsoft
420
+ # `alpha(opacity=20)` syntax.
421
+ #
422
+ # @overload def alpha(color)
423
+ # @param color [Color]
424
+ # @return [Number]
425
+ # @see #opacify
426
+ # @see #transparentize
427
+ # @raise [ArgumentError] If `color` isn't a color
428
+ def alpha(*args)
429
+ if args.all? do |a|
430
+ a.is_a?(Sass::Script::String) && a.type == :identifier &&
431
+ a.value =~ /^[a-zA-Z]+\s*=/
432
+ end
433
+ # Support the proprietary MS alpha() function
434
+ return Sass::Script::String.new("alpha(#{args.map {|a| a.to_s}.join(", ")})")
435
+ end
436
+
437
+ opacity(*args)
438
+ end
439
+
440
+ # Returns the alpha component (opacity) of a color.
441
+ # This is 1 unless otherwise specified.
442
+ #
443
+ # @param color [Color]
444
+ # @return [Number]
445
+ # @see #opacify
446
+ # @see #transparentize
447
+ # @raise [ArgumentError] If `color` isn't a color
448
+ def opacity(color)
449
+ assert_type color, :Color
450
+ Sass::Script::Number.new(color.alpha)
451
+ end
452
+
453
+ # Makes a color more opaque.
454
+ # Takes a color and an amount between 0 and 1,
455
+ # and returns a color with the opacity increased by that value.
456
+ #
457
+ # @example
458
+ # opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6)
459
+ # opacify(rgba(0, 0, 17, 0.8), 0.2) => #001
460
+ # @param color [Color]
461
+ # @param amount [Number]
462
+ # @return [Color]
463
+ # @see #transparentize
464
+ # @raise [ArgumentError] If `color` isn't a color,
465
+ # or `number` isn't a number between 0 and 1
466
+ def opacify(color, amount)
467
+ adjust(color, amount, :alpha, 0..1, :+)
468
+ end
469
+ alias_method :fade_in, :opacify
470
+
471
+ # Makes a color more transparent.
472
+ # Takes a color and an amount between 0 and 1,
473
+ # and returns a color with the opacity decreased by that value.
474
+ #
475
+ # @example
476
+ # transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4)
477
+ # transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6)
478
+ # @param color [Color]
479
+ # @param amount [Number]
480
+ # @return [Color]
481
+ # @see #opacify
482
+ # @raise [ArgumentError] If `color` isn't a color,
483
+ # or `number` isn't a number between 0 and 1
484
+ def transparentize(color, amount)
485
+ adjust(color, amount, :alpha, 0..1, :-)
486
+ end
487
+ alias_method :fade_out, :transparentize
488
+
489
+ # Makes a color lighter.
490
+ # Takes a color and an amount between 0% and 100%,
491
+ # and returns a color with the lightness increased by that value.
492
+ #
493
+ # @example
494
+ # lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
495
+ # lighten(#800, 20%) => #e00
496
+ # @param color [Color]
497
+ # @param amount [Number]
498
+ # @return [Color]
499
+ # @see #darken
500
+ # @raise [ArgumentError] If `color` isn't a color,
501
+ # or `number` isn't a number between 0% and 100%
502
+ def lighten(color, amount)
503
+ adjust(color, amount, :lightness, 0..100, :+, "%")
504
+ end
505
+
506
+ # Makes a color darker.
507
+ # Takes a color and an amount between 0% and 100%,
508
+ # and returns a color with the lightness decreased by that value.
509
+ #
510
+ # @example
511
+ # darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
512
+ # darken(#800, 20%) => #200
513
+ # @param color [Color]
514
+ # @param amount [Number]
515
+ # @return [Color]
516
+ # @see #lighten
517
+ # @raise [ArgumentError] If `color` isn't a color,
518
+ # or `number` isn't a number between 0% and 100%
519
+ def darken(color, amount)
520
+ adjust(color, amount, :lightness, 0..100, :-, "%")
521
+ end
522
+
523
+ # Makes a color more saturated.
524
+ # Takes a color and an amount between 0% and 100%,
525
+ # and returns a color with the saturation increased by that value.
526
+ #
527
+ # @example
528
+ # saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%)
529
+ # saturate(#855, 20%) => #9e3f3f
530
+ # @param color [Color]
531
+ # @param amount [Number]
532
+ # @return [Color]
533
+ # @see #desaturate
534
+ # @raise [ArgumentError] If `color` isn't a color,
535
+ # or `number` isn't a number between 0% and 100%
536
+ def saturate(color, amount)
537
+ adjust(color, amount, :saturation, 0..100, :+, "%")
538
+ end
539
+
540
+ # Makes a color less saturated.
541
+ # Takes a color and an amount between 0% and 100%,
542
+ # and returns a color with the saturation decreased by that value.
543
+ #
544
+ # @example
545
+ # desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%)
546
+ # desaturate(#855, 20%) => #726b6b
547
+ # @param color [Color]
548
+ # @param amount [Number]
549
+ # @return [Color]
550
+ # @see #saturate
551
+ # @raise [ArgumentError] If `color` isn't a color,
552
+ # or `number` isn't a number between 0% and 100%
553
+ def desaturate(color, amount)
554
+ adjust(color, amount, :saturation, 0..100, :-, "%")
555
+ end
556
+
557
+ # Changes the hue of a color while retaining the lightness and saturation.
558
+ # Takes a color and a number of degrees (usually between -360deg and 360deg),
559
+ # and returns a color with the hue rotated by that value.
560
+ #
561
+ # @example
562
+ # adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%)
563
+ # adjust-hue(hsl(120, 30%, 90%), 060deg) => hsl(60, 30%, 90%)
564
+ # adjust-hue(#811, 45deg) => #886a11
565
+ # @param color [Color]
566
+ # @param amount [Number]
567
+ # @return [Color]
568
+ # @raise [ArgumentError] If `color` isn't a color, or `number` isn't a number
569
+ def adjust_hue(color, degrees)
570
+ assert_type color, :Color
571
+ assert_type degrees, :Number
572
+ color.with(:hue => color.hue + degrees.value)
573
+ end
574
+
575
+ # Mixes together two colors.
576
+ # Specifically, takes the average of each of the RGB components,
577
+ # optionally weighted by the given percentage.
578
+ # The opacity of the colors is also considered when weighting the components.
579
+ #
580
+ # The weight specifies the amount of the first color that should be included
581
+ # in the returned color.
582
+ # The default, 50%, means that half the first color
583
+ # and half the second color should be used.
584
+ # 25% means that a quarter of the first color
585
+ # and three quarters of the second color should be used.
586
+ #
587
+ # @example
588
+ # mix(#f00, #00f) => #7f007f
589
+ # mix(#f00, #00f, 25%) => #3f00bf
590
+ # mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
591
+ # @overload mix(color1, color2, weight = 50%)
592
+ # @param color1 [Color]
593
+ # @param color2 [Color]
594
+ # @param weight [Number] between 0% and 100%
595
+ # @return [Color]
596
+ # @raise [ArgumentError] if `color1` or `color2` aren't colors,
597
+ # or `weight` isn't a number between 0% and 100%
598
+ def mix(color1, color2, weight = Number.new(50))
599
+ assert_type color1, :Color
600
+ assert_type color2, :Color
601
+ assert_type weight, :Number
602
+
603
+ unless (0..100).include?(weight.value)
604
+ raise ArgumentError.new("Weight #{weight} must be between 0% and 100%")
605
+ end
606
+
607
+ # This algorithm factors in both the user-provided weight
608
+ # and the difference between the alpha values of the two colors
609
+ # to decide how to perform the weighted average of the two RGB values.
610
+ #
611
+ # It works by first normalizing both parameters to be within [-1, 1],
612
+ # where 1 indicates "only use color1", -1 indicates "only use color 0",
613
+ # and all values in between indicated a proportionately weighted average.
614
+ #
615
+ # Once we have the normalized variables w and a,
616
+ # we apply the formula (w + a)/(1 + w*a)
617
+ # to get the combined weight (in [-1, 1]) of color1.
618
+ # This formula has two especially nice properties:
619
+ #
620
+ # * When either w or a are -1 or 1, the combined weight is also that number
621
+ # (cases where w * a == -1 are undefined, and handled as a special case).
622
+ #
623
+ # * When a is 0, the combined weight is w, and vice versa
624
+ #
625
+ # Finally, the weight of color1 is renormalized to be within [0, 1]
626
+ # and the weight of color2 is given by 1 minus the weight of color1.
627
+ p = weight.value/100.0
628
+ w = p*2 - 1
629
+ a = color1.alpha - color2.alpha
630
+
631
+ w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0
632
+ w2 = 1 - w1
633
+
634
+ rgb = color1.rgb.zip(color2.rgb).map {|v1, v2| v1*w1 + v2*w2}
635
+ alpha = color1.alpha*p + color2.alpha*(1-p)
636
+ Color.new(rgb + [alpha])
637
+ end
638
+
639
+ # Converts a color to grayscale.
640
+ # This is identical to `desaturate(color, 100%)`.
641
+ #
642
+ # @param color [Color]
643
+ # @return [Color]
644
+ # @raise [ArgumentError] if `color` isn't a color
645
+ # @see #desaturate
646
+ def grayscale(color)
647
+ desaturate color, Number.new(100)
648
+ end
649
+
650
+ # Returns the complement of a color.
651
+ # This is identical to `adjust-hue(color, 180deg)`.
652
+ #
653
+ # @param color [Color]
654
+ # @return [Color]
655
+ # @raise [ArgumentError] if `color` isn't a color
656
+ # @see #adjust_hue #adjust-hue
657
+ def complement(color)
658
+ adjust_hue color, Number.new(180)
659
+ end
660
+
661
+ # Returns the inverse (negative) of a color.
662
+ # The red, green, and blue values are inverted, while the opacity is left alone.
663
+ #
664
+ # @param color [Color]
665
+ # @return [Color]
666
+ # @raise [ArgumentError] if `color` isn't a color
667
+ def invert(color)
668
+ assert_type color, :Color
669
+ color.with(
670
+ :red => (255 - color.red),
671
+ :green => (255 - color.green),
672
+ :blue => (255 - color.blue))
673
+ end
674
+
675
+ # Removes quotes from a string if the string is quoted,
676
+ # or returns the same string if it's not.
677
+ #
678
+ # @param str [String]
679
+ # @return [String]
680
+ # @raise [ArgumentError] if `str` isn't a string
681
+ # @see #quote
682
+ # @example
683
+ # unquote("foo") => foo
684
+ # unquote(foo) => foo
685
+ def unquote(str)
686
+ assert_type str, :String
687
+ Sass::Script::String.new(str.value, :identifier)
688
+ end
689
+
690
+ # Add quotes to a string if the string isn't quoted,
691
+ # or returns the same string if it is.
692
+ #
693
+ # @param str [String]
694
+ # @return [String]
695
+ # @raise [ArgumentError] if `str` isn't a string
696
+ # @see #unquote
697
+ # @example
698
+ # quote("foo") => "foo"
699
+ # quote(foo) => "foo"
700
+ def quote(str)
701
+ assert_type str, :String
702
+ Sass::Script::String.new(str.value, :string)
703
+ end
704
+
705
+ # Inspects the type of the argument, returning it as an unquoted string.
706
+ #
707
+ # @example
708
+ # type-of(100px) => number
709
+ # type-of(asdf) => string
710
+ # type-of("asdf") => string
711
+ # type-of(true) => bool
712
+ # type-of(#fff) => color
713
+ # type-of(blue) => color
714
+ # @param obj [Literal] The object to inspect
715
+ # @return [String] The unquoted string name of the literal's type
716
+ def type_of(obj)
717
+ Sass::Script::String.new(obj.class.name.gsub(/Sass::Script::/,'').downcase)
718
+ end
719
+
720
+ # Inspects the unit of the number, returning it as a quoted string.
721
+ # Complex units are sorted in alphabetical order by numerator and denominator.
722
+ #
723
+ # @example
724
+ # unit(100) => ""
725
+ # unit(100px) => "px"
726
+ # unit(3em) => "em"
727
+ # unit(10px * 5em) => "em*px"
728
+ # unit(10px * 5em / 30cm / 1rem) => "em*px/cm*rem"
729
+ # @param number [Literal] The number to inspect
730
+ # @return [String] The unit(s) of the number
731
+ # @raise [ArgumentError] if `number` isn't a number
732
+ def unit(number)
733
+ assert_type number, :Number
734
+ Sass::Script::String.new(number.unit_str, :string)
735
+ end
736
+
737
+ # Inspects the unit of the number, returning a boolean indicating if it is unitless.
738
+ #
739
+ # @example
740
+ # unitless(100) => true
741
+ # unitless(100px) => false
742
+ # @param number [Literal] The number to inspect
743
+ # @return [Bool] Whether or not the number is unitless
744
+ # @raise [ArgumentError] if `number` isn't a number
745
+ def unitless(number)
746
+ assert_type number, :Number
747
+ Sass::Script::Bool.new(number.unitless?)
748
+ end
749
+
750
+ # Returns true if two numbers are similar enough to be added, subtracted, or compared.
751
+ #
752
+ # @example
753
+ # comparable(2px, 1px) => true
754
+ # comparable(100px, 3em) => false
755
+ # comparable(10cm, 3mm) => true
756
+ # @param number1 [Number]
757
+ # @param number2 [Number]
758
+ # @return [Bool] indicating if the numbers can be compared.
759
+ # @raise [ArgumentError] if `number1` or `number2` aren't numbers
760
+ def comparable(number1, number2)
761
+ assert_type number1, :Number
762
+ assert_type number2, :Number
763
+ Sass::Script::Bool.new(number1.comparable_to?(number2))
764
+ end
765
+
766
+ # Converts a decimal number to a percentage.
767
+ #
768
+ # @example
769
+ # percentage(100px / 50px) => 200%
770
+ # @param value [Number] The decimal number to convert to a percentage
771
+ # @return [Number] The percentage
772
+ # @raise [ArgumentError] If `value` isn't a unitless number
773
+ def percentage(value)
774
+ unless value.is_a?(Sass::Script::Number) && value.unitless?
775
+ raise ArgumentError.new("#{value.inspect} is not a unitless number")
776
+ end
777
+ Sass::Script::Number.new(value.value * 100, ['%'])
778
+ end
779
+
780
+ # Rounds a number to the nearest whole number.
781
+ #
782
+ # @example
783
+ # round(10.4px) => 10px
784
+ # round(10.6px) => 11px
785
+ # @param value [Number] The number
786
+ # @return [Number] The rounded number
787
+ # @raise [Sass::SyntaxError] if `value` isn't a number
788
+ def round(value)
789
+ numeric_transformation(value) {|n| n.round}
790
+ end
791
+
792
+ # Rounds a number up to the nearest whole number.
793
+ #
794
+ # @example
795
+ # ciel(10.4px) => 11px
796
+ # ciel(10.6px) => 11px
797
+ # @param value [Number] The number
798
+ # @return [Number] The rounded number
799
+ # @raise [Sass::SyntaxError] if `value` isn't a number
800
+ def ceil(value)
801
+ numeric_transformation(value) {|n| n.ceil}
802
+ end
803
+
804
+ # Rounds down to the nearest whole number.
805
+ #
806
+ # @example
807
+ # floor(10.4px) => 10px
808
+ # floor(10.6px) => 10px
809
+ # @param value [Number] The number
810
+ # @return [Number] The rounded number
811
+ # @raise [Sass::SyntaxError] if `value` isn't a number
812
+ def floor(value)
813
+ numeric_transformation(value) {|n| n.floor}
814
+ end
815
+
816
+ # Finds the absolute value of a number.
817
+ #
818
+ # @example
819
+ # abs(10px) => 10px
820
+ # abs(-10px) => 10px
821
+ # @param value [Number] The number
822
+ # @return [Number] The absolute value
823
+ # @raise [Sass::SyntaxError] if `value` isn't a number
824
+ def abs(value)
825
+ numeric_transformation(value) {|n| n.abs}
826
+ end
827
+
828
+ private
829
+
830
+ # This method implements the pattern of transforming a numeric value into
831
+ # another numeric value with the same units.
832
+ # It yields a number to a block to perform the operation and return a number
833
+ def numeric_transformation(value)
834
+ assert_type value, :Number
835
+ Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
836
+ end
837
+
838
+ def adjust(color, amount, attr, range, op, units = "")
839
+ assert_type color, :Color
840
+ assert_type amount, :Number
841
+ unless range.include?(amount.value)
842
+ raise ArgumentError.new("Amount #{amount} must be between #{range.first}#{units} and #{range.last}#{units}")
843
+ end
844
+
845
+ # TODO: is it worth restricting here,
846
+ # or should we do so in the Color constructor itself,
847
+ # and allow clipping in rgb() et al?
848
+ color.with(attr => Sass::Util.restrict(
849
+ color.send(attr).send(op, amount.value), range))
850
+ end
851
+ end
852
+ end