aliddle-sass 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (238) hide show
  1. data/.yardopts +11 -0
  2. data/CONTRIBUTING +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +201 -0
  5. data/Rakefile +347 -0
  6. data/VERSION +1 -0
  7. data/VERSION_NAME +1 -0
  8. data/bin/sass +9 -0
  9. data/bin/sass-convert +8 -0
  10. data/bin/scss +9 -0
  11. data/extra/update_watch.rb +13 -0
  12. data/init.rb +18 -0
  13. data/lib/sass.rb +95 -0
  14. data/lib/sass/cache_stores.rb +15 -0
  15. data/lib/sass/cache_stores/base.rb +88 -0
  16. data/lib/sass/cache_stores/chain.rb +33 -0
  17. data/lib/sass/cache_stores/filesystem.rb +60 -0
  18. data/lib/sass/cache_stores/memory.rb +47 -0
  19. data/lib/sass/cache_stores/null.rb +25 -0
  20. data/lib/sass/callbacks.rb +66 -0
  21. data/lib/sass/css.rb +409 -0
  22. data/lib/sass/engine.rb +928 -0
  23. data/lib/sass/environment.rb +101 -0
  24. data/lib/sass/error.rb +201 -0
  25. data/lib/sass/exec.rb +707 -0
  26. data/lib/sass/importers.rb +22 -0
  27. data/lib/sass/importers/base.rb +139 -0
  28. data/lib/sass/importers/filesystem.rb +190 -0
  29. data/lib/sass/logger.rb +15 -0
  30. data/lib/sass/logger/base.rb +32 -0
  31. data/lib/sass/logger/log_level.rb +49 -0
  32. data/lib/sass/media.rb +213 -0
  33. data/lib/sass/plugin.rb +132 -0
  34. data/lib/sass/plugin/compiler.rb +406 -0
  35. data/lib/sass/plugin/configuration.rb +123 -0
  36. data/lib/sass/plugin/generic.rb +15 -0
  37. data/lib/sass/plugin/merb.rb +48 -0
  38. data/lib/sass/plugin/rack.rb +60 -0
  39. data/lib/sass/plugin/rails.rb +47 -0
  40. data/lib/sass/plugin/staleness_checker.rb +183 -0
  41. data/lib/sass/railtie.rb +9 -0
  42. data/lib/sass/repl.rb +57 -0
  43. data/lib/sass/root.rb +7 -0
  44. data/lib/sass/script.rb +39 -0
  45. data/lib/sass/script/arg_list.rb +52 -0
  46. data/lib/sass/script/bool.rb +18 -0
  47. data/lib/sass/script/color.rb +606 -0
  48. data/lib/sass/script/css_lexer.rb +29 -0
  49. data/lib/sass/script/css_parser.rb +31 -0
  50. data/lib/sass/script/funcall.rb +237 -0
  51. data/lib/sass/script/functions.rb +1543 -0
  52. data/lib/sass/script/interpolation.rb +79 -0
  53. data/lib/sass/script/lexer.rb +348 -0
  54. data/lib/sass/script/list.rb +85 -0
  55. data/lib/sass/script/literal.rb +221 -0
  56. data/lib/sass/script/node.rb +99 -0
  57. data/lib/sass/script/null.rb +37 -0
  58. data/lib/sass/script/number.rb +453 -0
  59. data/lib/sass/script/operation.rb +110 -0
  60. data/lib/sass/script/parser.rb +495 -0
  61. data/lib/sass/script/string.rb +51 -0
  62. data/lib/sass/script/string_interpolation.rb +103 -0
  63. data/lib/sass/script/unary_operation.rb +69 -0
  64. data/lib/sass/script/variable.rb +58 -0
  65. data/lib/sass/scss.rb +16 -0
  66. data/lib/sass/scss/css_parser.rb +36 -0
  67. data/lib/sass/scss/parser.rb +1179 -0
  68. data/lib/sass/scss/rx.rb +133 -0
  69. data/lib/sass/scss/script_lexer.rb +15 -0
  70. data/lib/sass/scss/script_parser.rb +25 -0
  71. data/lib/sass/scss/static_parser.rb +54 -0
  72. data/lib/sass/selector.rb +452 -0
  73. data/lib/sass/selector/abstract_sequence.rb +94 -0
  74. data/lib/sass/selector/comma_sequence.rb +92 -0
  75. data/lib/sass/selector/sequence.rb +507 -0
  76. data/lib/sass/selector/simple.rb +119 -0
  77. data/lib/sass/selector/simple_sequence.rb +212 -0
  78. data/lib/sass/shared.rb +76 -0
  79. data/lib/sass/supports.rb +229 -0
  80. data/lib/sass/tree/charset_node.rb +22 -0
  81. data/lib/sass/tree/comment_node.rb +82 -0
  82. data/lib/sass/tree/content_node.rb +9 -0
  83. data/lib/sass/tree/css_import_node.rb +60 -0
  84. data/lib/sass/tree/debug_node.rb +18 -0
  85. data/lib/sass/tree/directive_node.rb +42 -0
  86. data/lib/sass/tree/each_node.rb +24 -0
  87. data/lib/sass/tree/extend_node.rb +36 -0
  88. data/lib/sass/tree/for_node.rb +36 -0
  89. data/lib/sass/tree/function_node.rb +34 -0
  90. data/lib/sass/tree/if_node.rb +52 -0
  91. data/lib/sass/tree/import_node.rb +75 -0
  92. data/lib/sass/tree/media_node.rb +58 -0
  93. data/lib/sass/tree/mixin_def_node.rb +38 -0
  94. data/lib/sass/tree/mixin_node.rb +39 -0
  95. data/lib/sass/tree/node.rb +196 -0
  96. data/lib/sass/tree/prop_node.rb +152 -0
  97. data/lib/sass/tree/return_node.rb +18 -0
  98. data/lib/sass/tree/root_node.rb +28 -0
  99. data/lib/sass/tree/rule_node.rb +132 -0
  100. data/lib/sass/tree/supports_node.rb +51 -0
  101. data/lib/sass/tree/trace_node.rb +32 -0
  102. data/lib/sass/tree/variable_node.rb +30 -0
  103. data/lib/sass/tree/visitors/base.rb +75 -0
  104. data/lib/sass/tree/visitors/check_nesting.rb +147 -0
  105. data/lib/sass/tree/visitors/convert.rb +316 -0
  106. data/lib/sass/tree/visitors/cssize.rb +229 -0
  107. data/lib/sass/tree/visitors/deep_copy.rb +102 -0
  108. data/lib/sass/tree/visitors/extend.rb +68 -0
  109. data/lib/sass/tree/visitors/perform.rb +446 -0
  110. data/lib/sass/tree/visitors/set_options.rb +125 -0
  111. data/lib/sass/tree/visitors/to_css.rb +230 -0
  112. data/lib/sass/tree/warn_node.rb +18 -0
  113. data/lib/sass/tree/while_node.rb +18 -0
  114. data/lib/sass/util.rb +906 -0
  115. data/lib/sass/util/multibyte_string_scanner.rb +155 -0
  116. data/lib/sass/util/subset_map.rb +109 -0
  117. data/lib/sass/util/test.rb +10 -0
  118. data/lib/sass/version.rb +126 -0
  119. data/rails/init.rb +1 -0
  120. data/test/Gemfile +3 -0
  121. data/test/Gemfile.lock +10 -0
  122. data/test/sass/cache_test.rb +89 -0
  123. data/test/sass/callbacks_test.rb +61 -0
  124. data/test/sass/conversion_test.rb +1760 -0
  125. data/test/sass/css2sass_test.rb +439 -0
  126. data/test/sass/data/hsl-rgb.txt +319 -0
  127. data/test/sass/engine_test.rb +3243 -0
  128. data/test/sass/exec_test.rb +86 -0
  129. data/test/sass/extend_test.rb +1461 -0
  130. data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
  131. data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
  132. data/test/sass/functions_test.rb +1139 -0
  133. data/test/sass/importer_test.rb +192 -0
  134. data/test/sass/logger_test.rb +58 -0
  135. data/test/sass/mock_importer.rb +49 -0
  136. data/test/sass/more_results/more1.css +9 -0
  137. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  138. data/test/sass/more_results/more_import.css +29 -0
  139. data/test/sass/more_templates/_more_partial.sass +2 -0
  140. data/test/sass/more_templates/more1.sass +23 -0
  141. data/test/sass/more_templates/more_import.sass +11 -0
  142. data/test/sass/plugin_test.rb +550 -0
  143. data/test/sass/results/alt.css +4 -0
  144. data/test/sass/results/basic.css +9 -0
  145. data/test/sass/results/cached_import_option.css +3 -0
  146. data/test/sass/results/compact.css +5 -0
  147. data/test/sass/results/complex.css +86 -0
  148. data/test/sass/results/compressed.css +1 -0
  149. data/test/sass/results/expanded.css +19 -0
  150. data/test/sass/results/filename_fn.css +3 -0
  151. data/test/sass/results/if.css +3 -0
  152. data/test/sass/results/import.css +31 -0
  153. data/test/sass/results/import_charset.css +5 -0
  154. data/test/sass/results/import_charset_1_8.css +5 -0
  155. data/test/sass/results/import_charset_ibm866.css +5 -0
  156. data/test/sass/results/import_content.css +1 -0
  157. data/test/sass/results/line_numbers.css +49 -0
  158. data/test/sass/results/mixins.css +95 -0
  159. data/test/sass/results/multiline.css +24 -0
  160. data/test/sass/results/nested.css +22 -0
  161. data/test/sass/results/options.css +1 -0
  162. data/test/sass/results/parent_ref.css +13 -0
  163. data/test/sass/results/script.css +16 -0
  164. data/test/sass/results/scss_import.css +31 -0
  165. data/test/sass/results/scss_importee.css +2 -0
  166. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  167. data/test/sass/results/subdir/subdir.css +3 -0
  168. data/test/sass/results/units.css +11 -0
  169. data/test/sass/results/warn.css +0 -0
  170. data/test/sass/results/warn_imported.css +0 -0
  171. data/test/sass/script_conversion_test.rb +299 -0
  172. data/test/sass/script_test.rb +591 -0
  173. data/test/sass/scss/css_test.rb +1093 -0
  174. data/test/sass/scss/rx_test.rb +156 -0
  175. data/test/sass/scss/scss_test.rb +2043 -0
  176. data/test/sass/scss/test_helper.rb +37 -0
  177. data/test/sass/templates/_cached_import_option_partial.scss +1 -0
  178. data/test/sass/templates/_double_import_loop2.sass +1 -0
  179. data/test/sass/templates/_filename_fn_import.scss +11 -0
  180. data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
  181. data/test/sass/templates/_imported_charset_utf8.sass +4 -0
  182. data/test/sass/templates/_imported_content.sass +3 -0
  183. data/test/sass/templates/_partial.sass +2 -0
  184. data/test/sass/templates/_same_name_different_partiality.scss +1 -0
  185. data/test/sass/templates/alt.sass +16 -0
  186. data/test/sass/templates/basic.sass +23 -0
  187. data/test/sass/templates/bork1.sass +2 -0
  188. data/test/sass/templates/bork2.sass +2 -0
  189. data/test/sass/templates/bork3.sass +2 -0
  190. data/test/sass/templates/bork4.sass +2 -0
  191. data/test/sass/templates/bork5.sass +3 -0
  192. data/test/sass/templates/cached_import_option.scss +3 -0
  193. data/test/sass/templates/compact.sass +17 -0
  194. data/test/sass/templates/complex.sass +305 -0
  195. data/test/sass/templates/compressed.sass +15 -0
  196. data/test/sass/templates/double_import_loop1.sass +1 -0
  197. data/test/sass/templates/expanded.sass +17 -0
  198. data/test/sass/templates/filename_fn.scss +18 -0
  199. data/test/sass/templates/if.sass +11 -0
  200. data/test/sass/templates/import.sass +12 -0
  201. data/test/sass/templates/import_charset.sass +9 -0
  202. data/test/sass/templates/import_charset_1_8.sass +6 -0
  203. data/test/sass/templates/import_charset_ibm866.sass +11 -0
  204. data/test/sass/templates/import_content.sass +4 -0
  205. data/test/sass/templates/importee.less +2 -0
  206. data/test/sass/templates/importee.sass +19 -0
  207. data/test/sass/templates/line_numbers.sass +13 -0
  208. data/test/sass/templates/mixin_bork.sass +5 -0
  209. data/test/sass/templates/mixins.sass +76 -0
  210. data/test/sass/templates/multiline.sass +20 -0
  211. data/test/sass/templates/nested.sass +25 -0
  212. data/test/sass/templates/nested_bork1.sass +2 -0
  213. data/test/sass/templates/nested_bork2.sass +2 -0
  214. data/test/sass/templates/nested_bork3.sass +2 -0
  215. data/test/sass/templates/nested_bork4.sass +2 -0
  216. data/test/sass/templates/nested_import.sass +2 -0
  217. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  218. data/test/sass/templates/options.sass +2 -0
  219. data/test/sass/templates/parent_ref.sass +25 -0
  220. data/test/sass/templates/same_name_different_ext.sass +2 -0
  221. data/test/sass/templates/same_name_different_ext.scss +1 -0
  222. data/test/sass/templates/same_name_different_partiality.scss +1 -0
  223. data/test/sass/templates/script.sass +101 -0
  224. data/test/sass/templates/scss_import.scss +11 -0
  225. data/test/sass/templates/scss_importee.scss +1 -0
  226. data/test/sass/templates/single_import_loop.sass +1 -0
  227. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  228. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  229. data/test/sass/templates/subdir/subdir.sass +6 -0
  230. data/test/sass/templates/units.sass +11 -0
  231. data/test/sass/templates/warn.sass +3 -0
  232. data/test/sass/templates/warn_imported.sass +4 -0
  233. data/test/sass/test_helper.rb +8 -0
  234. data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
  235. data/test/sass/util/subset_map_test.rb +91 -0
  236. data/test/sass/util_test.rb +313 -0
  237. data/test/test_helper.rb +80 -0
  238. metadata +348 -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, :space
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,237 @@
1
+ require 'sass/script/functions'
2
+
3
+ module Sass
4
+ module Script
5
+ # A SassScript parse node representing a function call.
6
+ #
7
+ # A function call either calls one of the functions in {Script::Functions},
8
+ # or if no function with the given name exists
9
+ # it returns a string representation of the function call.
10
+ class Funcall < Node
11
+ # The name of the function.
12
+ #
13
+ # @return [String]
14
+ attr_reader :name
15
+
16
+ # The arguments to the function.
17
+ #
18
+ # @return [Array<Script::Node>]
19
+ attr_reader :args
20
+
21
+ # The keyword arguments to the function.
22
+ #
23
+ # @return [{String => Script::Node}]
24
+ attr_reader :keywords
25
+
26
+ # The splat argument for this function, if one exists.
27
+ #
28
+ # @return [Script::Node?]
29
+ attr_accessor :splat
30
+
31
+ # @param name [String] See \{#name}
32
+ # @param args [Array<Script::Node>] See \{#args}
33
+ # @param splat [Script::Node] See \{#splat}
34
+ # @param keywords [{String => Script::Node}] See \{#keywords}
35
+ def initialize(name, args, keywords, splat)
36
+ @name = name
37
+ @args = args
38
+ @keywords = keywords
39
+ @splat = splat
40
+ super()
41
+ end
42
+
43
+ # @return [String] A string representation of the function call
44
+ def inspect
45
+ args = @args.map {|a| a.inspect}.join(', ')
46
+ keywords = Sass::Util.hash_to_a(@keywords).
47
+ map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
48
+ if self.splat
49
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
50
+ splat = "#{splat}#{self.splat.inspect}..."
51
+ end
52
+ "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
53
+ end
54
+
55
+ # @see Node#to_sass
56
+ def to_sass(opts = {})
57
+ arg_to_sass = lambda do |arg|
58
+ sass = arg.to_sass(opts)
59
+ sass = "(#{sass})" if arg.is_a?(Sass::Script::List) && arg.separator == :comma
60
+ sass
61
+ end
62
+
63
+ args = @args.map(&arg_to_sass).join(', ')
64
+ keywords = Sass::Util.hash_to_a(@keywords).
65
+ map {|k, v| "$#{dasherize(k, opts)}: #{arg_to_sass[v]}"}.join(', ')
66
+ if self.splat
67
+ splat = (args.empty? && keywords.empty?) ? "" : ", "
68
+ splat = "#{splat}#{arg_to_sass[self.splat]}..."
69
+ end
70
+ "#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords}#{splat})"
71
+ end
72
+
73
+ # Returns the arguments to the function.
74
+ #
75
+ # @return [Array<Node>]
76
+ # @see Node#children
77
+ def children
78
+ res = @args + @keywords.values
79
+ res << @splat if @splat
80
+ res
81
+ end
82
+
83
+ # @see Node#deep_copy
84
+ def deep_copy
85
+ node = dup
86
+ node.instance_variable_set('@args', args.map {|a| a.deep_copy})
87
+ node.instance_variable_set('@keywords', Hash[keywords.map {|k, v| [k, v.deep_copy]}])
88
+ node
89
+ end
90
+
91
+ protected
92
+
93
+ # Evaluates the function call.
94
+ #
95
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
96
+ # @return [Literal] The SassScript object that is the value of the function call
97
+ # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
98
+ def _perform(environment)
99
+ args = @args.map {|a| a.perform(environment)}
100
+ splat = @splat.perform(environment) if @splat
101
+ if fn = environment.function(@name)
102
+ keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
103
+ return perform_sass_fn(fn, args, keywords, splat)
104
+ end
105
+
106
+ ruby_name = @name.tr('-', '_')
107
+ args = construct_ruby_args(ruby_name, args, splat, environment)
108
+
109
+ unless Functions.callable?(ruby_name)
110
+ opts(to_literal(args))
111
+ else
112
+ opts(Functions::EvaluationContext.new(environment.options).send(ruby_name, *args))
113
+ end
114
+ rescue ArgumentError => e
115
+ message = e.message
116
+
117
+ # If this is a legitimate Ruby-raised argument error, re-raise it.
118
+ # Otherwise, it's an error in the user's stylesheet, so wrap it.
119
+ if Sass::Util.rbx?
120
+ # Rubinius has a different error report string than vanilla Ruby. It
121
+ # also doesn't put the actual method for which the argument error was
122
+ # thrown in the backtrace, nor does it include `send`, so we look for
123
+ # `_perform`.
124
+ if e.message =~ /^method '([^']+)': given (\d+), expected (\d+)/
125
+ error_name, given, expected = $1, $2, $3
126
+ raise e if error_name != ruby_name || e.backtrace[0] !~ /:in `_perform'$/
127
+ message = "wrong number of arguments (#{given} for #{expected})"
128
+ end
129
+ elsif Sass::Util.jruby?
130
+ if Sass::Util.jruby1_6?
131
+ should_maybe_raise = e.message =~ /^wrong number of arguments \((\d+) for (\d+)\)/ &&
132
+ # The one case where JRuby does include the Ruby name of the function
133
+ # is manually-thrown ArgumentErrors, which are indistinguishable from
134
+ # legitimate ArgumentErrors. We treat both of these as
135
+ # Sass::SyntaxErrors even though it can hide Ruby errors.
136
+ e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
137
+ else
138
+ should_maybe_raise = e.message =~ /^wrong number of arguments calling `[^`]+` \((\d+) for (\d+)\)/
139
+ given, expected = $1, $2
140
+ end
141
+
142
+ if should_maybe_raise
143
+ # JRuby 1.7 includes __send__ before send and _perform.
144
+ trace = e.backtrace.dup
145
+ raise e if !Sass::Util.jruby1_6? && trace.shift !~ /:in `__send__'$/
146
+
147
+ # JRuby (as of 1.7.2) doesn't put the actual method
148
+ # for which the argument error was thrown in the backtrace, so we
149
+ # detect whether our send threw an argument error.
150
+ if !(trace[0] =~ /:in `send'$/ && trace[1] =~ /:in `_perform'$/)
151
+ raise e
152
+ elsif !Sass::Util.jruby1_6?
153
+ # JRuby 1.7 doesn't use standard formatting for its ArgumentErrors.
154
+ message = "wrong number of arguments (#{given} for #{expected})"
155
+ end
156
+ end
157
+ elsif e.message =~ /^wrong number of arguments \(\d+ for \d+\)/ &&
158
+ e.backtrace[0] !~ /:in `(block in )?#{ruby_name}'$/
159
+ raise e
160
+ end
161
+ raise Sass::SyntaxError.new("#{message} for `#{name}'")
162
+ end
163
+
164
+ # This method is factored out from `_perform` so that compass can override
165
+ # it with a cross-browser implementation for functions that require vendor prefixes
166
+ # in the generated css.
167
+ def to_literal(args)
168
+ Script::String.new("#{name}(#{args.join(', ')})")
169
+ end
170
+
171
+ private
172
+
173
+ def construct_ruby_args(name, args, splat, environment)
174
+ args += splat.to_a if splat
175
+
176
+ # If variable arguments were passed, there won't be any explicit keywords.
177
+ if splat.is_a?(Sass::Script::ArgList)
178
+ kwargs_size = splat.keywords.size
179
+ splat.keywords_accessed = false
180
+ else
181
+ kwargs_size = @keywords.size
182
+ end
183
+
184
+ unless signature = Functions.signature(name.to_sym, args.size, kwargs_size)
185
+ return args if @keywords.empty?
186
+ raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
187
+ end
188
+ keywords = splat.is_a?(Sass::Script::ArgList) ? splat.keywords :
189
+ Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
190
+
191
+ # If the user passes more non-keyword args than the function expects,
192
+ # but it does expect keyword args, Ruby's arg handling won't raise an error.
193
+ # Since we don't want to make functions think about this,
194
+ # we'll handle it for them here.
195
+ if signature.var_kwargs && !signature.var_args && args.size > signature.args.size
196
+ raise Sass::SyntaxError.new(
197
+ "#{args[signature.args.size].inspect} is not a keyword argument for `#{name}'")
198
+ elsif keywords.empty?
199
+ return args
200
+ end
201
+
202
+ args = args + signature.args[args.size..-1].map do |argname|
203
+ if keywords.has_key?(argname)
204
+ keywords.delete(argname)
205
+ else
206
+ raise Sass::SyntaxError.new("Function #{name} requires an argument named $#{argname}")
207
+ end
208
+ end
209
+
210
+ if keywords.size > 0
211
+ if signature.var_kwargs
212
+ args << keywords
213
+ else
214
+ argname = keywords.keys.sort.first
215
+ if signature.args.include?(argname)
216
+ raise Sass::SyntaxError.new("Function #{name} was passed argument $#{argname} both by position and by name")
217
+ else
218
+ raise Sass::SyntaxError.new("Function #{name} doesn't have an argument named $#{argname}")
219
+ end
220
+ end
221
+ end
222
+
223
+ args
224
+ end
225
+
226
+ def perform_sass_fn(function, args, keywords, splat)
227
+ Sass::Tree::Visitors::Perform.perform_arguments(function, args, keywords, splat) do |env|
228
+ val = catch :_sass_return do
229
+ function.tree.each {|c| Sass::Tree::Visitors::Perform.visit(c, env)}
230
+ raise Sass::SyntaxError.new("Function #{@name} finished without @return")
231
+ end
232
+ val
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,1543 @@
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
+ # *Note: These functions are described in more detail below.*
12
+ #
13
+ # ## RGB Functions
14
+ #
15
+ # \{#rgb rgb($red, $green, $blue)}
16
+ # : Creates a {Color} from red, green, and blue values.
17
+ #
18
+ # \{#rgba rgba($red, $green, $blue, $alpha)}
19
+ # : Creates a {Color} from red, green, blue, and alpha values.
20
+ #
21
+ # \{#red red($color)}
22
+ # : Gets the red component of a color.
23
+ #
24
+ # \{#green green($color)}
25
+ # : Gets the green component of a color.
26
+ #
27
+ # \{#blue blue($color)}
28
+ # : Gets the blue component of a color.
29
+ #
30
+ # \{#mix mix($color-1, $color-2, \[$weight\])}
31
+ # : Mixes two colors together.
32
+ #
33
+ # ## HSL Functions
34
+ #
35
+ # \{#hsl hsl($hue, $saturation, $lightness)}
36
+ # : Creates a {Color} from hue, saturation, and lightness values.
37
+ #
38
+ # \{#hsla hsla($hue, $saturation, $lightness, $alpha)}
39
+ # : Creates a {Color} from hue, saturation, lightness, and alpha
40
+ # values.
41
+ #
42
+ # \{#hue hue($color)}
43
+ # : Gets the hue component of a color.
44
+ #
45
+ # \{#saturation saturation($color)}
46
+ # : Gets the saturation component of a color.
47
+ #
48
+ # \{#lightness lightness($color)}
49
+ # : Gets the lightness component of a color.
50
+ #
51
+ # \{#adjust_hue adjust-hue($color, $degrees)}
52
+ # : Changes the hue of a color.
53
+ #
54
+ # \{#lighten lighten($color, $amount)}
55
+ # : Makes a color lighter.
56
+ #
57
+ # \{#darken darken($color, $amount)}
58
+ # : Makes a color darker.
59
+ #
60
+ # \{#saturate saturate($color, $amount)}
61
+ # : Makes a color more saturated.
62
+ #
63
+ # \{#desaturate desaturate($color, $amount)}
64
+ # : Makes a color less saturated.
65
+ #
66
+ # \{#grayscale grayscale($color)}
67
+ # : Converts a color to grayscale.
68
+ #
69
+ # \{#complement complement($color)}
70
+ # : Returns the complement of a color.
71
+ #
72
+ # \{#invert invert($color)}
73
+ # : Returns the inverse of a color.
74
+ #
75
+ # ## Opacity Functions
76
+ #
77
+ # \{#alpha alpha($color)} / \{#opacity opacity($color)}
78
+ # : Gets the alpha component (opacity) of a color.
79
+ #
80
+ # \{#rgba rgba($color, $alpha)}
81
+ # : Changes the alpha component for a color.
82
+ #
83
+ # \{#opacify opacify($color, $amount)} / \{#fade_in fade-in($color, $amount)}
84
+ # : Makes a color more opaque.
85
+ #
86
+ # \{#transparentize transparentize($color, $amount)} / \{#fade_out fade-out($color, $amount)}
87
+ # : Makes a color more transparent.
88
+ #
89
+ # ## Other Color Functions
90
+ #
91
+ # \{#adjust_color adjust-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\])}
92
+ # : Increases or decreases one or more components of a color.
93
+ #
94
+ # \{#scale_color scale-color($color, \[$red\], \[$green\], \[$blue\], \[$saturation\], \[$lightness\], \[$alpha\])}
95
+ # : Fluidly scales one or more properties of a color.
96
+ #
97
+ # \{#change_color change-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\])}
98
+ # : Changes one or more properties of a color.
99
+ #
100
+ # \{#ie_hex_str ie-hex-str($color)}
101
+ # : Converts a color into the format understood by IE filters.
102
+ #
103
+ # ## String Functions
104
+ #
105
+ # \{#unquote unquote($string)}
106
+ # : Removes quotes from a string.
107
+ #
108
+ # \{#quote quote($string)}
109
+ # : Adds quotes to a string.
110
+ #
111
+ # ## Number Functions
112
+ #
113
+ # \{#percentage percentage($value)}
114
+ # : Converts a unitless number to a percentage.
115
+ #
116
+ # \{#round round($value)}
117
+ # : Rounds a number to the nearest whole number.
118
+ #
119
+ # \{#ceil ceil($value)}
120
+ # : Rounds a number up to the next whole number.
121
+ #
122
+ # \{#floor floor($value)}
123
+ # : Rounds a number down to the previous whole number.
124
+ #
125
+ # \{#abs abs($value)}
126
+ # : Returns the absolute value of a number.
127
+ #
128
+ # \{#min min($numbers...)\}
129
+ # : Finds the minimum of several numbers.
130
+ #
131
+ # \{#max max($numbers...)\}
132
+ # : Finds the maximum of several numbers.
133
+ #
134
+ # ## List Functions {#list-functions}
135
+ #
136
+ # \{#length length($list)}
137
+ # : Returns the length of a list.
138
+ #
139
+ # \{#nth nth($list, $n)}
140
+ # : Returns a specific item in a list.
141
+ #
142
+ # \{#join join($list1, $list2, \[$separator\])}
143
+ # : Joins together two lists into one.
144
+ #
145
+ # \{#append append($list1, $val, \[$separator\])}
146
+ # : Appends a single value onto the end of a list.
147
+ #
148
+ # \{#zip zip($lists...)}
149
+ # : Combines several lists into a single multidimensional list.
150
+ #
151
+ # \{#index index($list, $value)}
152
+ # : Returns the position of a value within a list.
153
+ #
154
+ # ## Introspection Functions
155
+ #
156
+ # \{#type_of type-of($value)}
157
+ # : Returns the type of a value.
158
+ #
159
+ # \{#unit unit($number)}
160
+ # : Returns the unit(s) associated with a number.
161
+ #
162
+ # \{#unitless unitless($number)}
163
+ # : Returns whether a number has units.
164
+ #
165
+ # \{#comparable comparable($number-1, $number-2)}
166
+ # : Returns whether two numbers can be added, subtracted, or compared.
167
+ #
168
+ # ## Miscellaneous Functions
169
+ #
170
+ # \{#if if($condition, $if-true, $if-false)}
171
+ # : Returns one of two values, depending on whether or not `$condition` is
172
+ # true.
173
+ #
174
+ # ## Adding Custom Functions
175
+ #
176
+ # New Sass functions can be added by adding Ruby methods to this module.
177
+ # For example:
178
+ #
179
+ # module Sass::Script::Functions
180
+ # def reverse(string)
181
+ # assert_type string, :String
182
+ # Sass::Script::String.new(string.value.reverse)
183
+ # end
184
+ # declare :reverse, :args => [:string]
185
+ # end
186
+ #
187
+ # Calling {declare} tells Sass the argument names for your function.
188
+ # If omitted, the function will still work, but will not be able to accept keyword arguments.
189
+ # {declare} can also allow your function to take arbitrary keyword arguments.
190
+ #
191
+ # There are a few things to keep in mind when modifying this module.
192
+ # First of all, the arguments passed are {Sass::Script::Literal} objects.
193
+ # Literal objects are also expected to be returned.
194
+ # This means that Ruby values must be unwrapped and wrapped.
195
+ #
196
+ # Most Literal objects support the {Sass::Script::Literal#value value} accessor
197
+ # for getting their Ruby values.
198
+ # Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb},
199
+ # {Sass::Script::Color#red red}, {Sass::Script::Color#blue green}, or {Sass::Script::Color#blue blue}.
200
+ #
201
+ # Second, making Ruby functions accessible from Sass introduces the temptation
202
+ # to do things like database access within stylesheets.
203
+ # This is generally a bad idea;
204
+ # since Sass files are by default only compiled once,
205
+ # dynamic code is not a great fit.
206
+ #
207
+ # If you really, really need to compile Sass on each request,
208
+ # first make sure you have adequate caching set up.
209
+ # Then you can use {Sass::Engine} to render the code,
210
+ # using the {file:SASS_REFERENCE.md#custom-option `options` parameter}
211
+ # to pass in data that {EvaluationContext#options can be accessed}
212
+ # from your Sass functions.
213
+ #
214
+ # Within one of the functions in this module,
215
+ # methods of {EvaluationContext} can be used.
216
+ #
217
+ # ### Caveats
218
+ #
219
+ # When creating new {Literal} objects within functions,
220
+ # be aware that it's not safe to call {Literal#to_s #to_s}
221
+ # (or other methods that use the string representation)
222
+ # on those objects without first setting {Node#options= the #options attribute}.
223
+ module Functions
224
+ @signatures = {}
225
+
226
+ # A class representing a Sass function signature.
227
+ #
228
+ # @attr args [Array<Symbol>] The names of the arguments to the function.
229
+ # @attr var_args [Boolean] Whether the function takes a variable number of arguments.
230
+ # @attr var_kwargs [Boolean] Whether the function takes an arbitrary set of keyword arguments.
231
+ Signature = Struct.new(:args, :var_args, :var_kwargs)
232
+
233
+ # Declare a Sass signature for a Ruby-defined function.
234
+ # This includes the names of the arguments,
235
+ # whether the function takes a variable number of arguments,
236
+ # and whether the function takes an arbitrary set of keyword arguments.
237
+ #
238
+ # It's not necessary to declare a signature for a function.
239
+ # However, without a signature it won't support keyword arguments.
240
+ #
241
+ # A single function can have multiple signatures declared
242
+ # as long as each one takes a different number of arguments.
243
+ # It's also possible to declare multiple signatures
244
+ # that all take the same number of arguments,
245
+ # but none of them but the first will be used
246
+ # unless the user uses keyword arguments.
247
+ #
248
+ # @example
249
+ # declare :rgba, [:hex, :alpha]
250
+ # declare :rgba, [:red, :green, :blue, :alpha]
251
+ # declare :accepts_anything, [], :var_args => true, :var_kwargs => true
252
+ # declare :some_func, [:foo, :bar, :baz], :var_kwargs => true
253
+ #
254
+ # @param method_name [Symbol] The name of the method
255
+ # whose signature is being declared.
256
+ # @param args [Array<Symbol>] The names of the arguments for the function signature.
257
+ # @option options :var_args [Boolean] (false)
258
+ # Whether the function accepts a variable number of (unnamed) arguments
259
+ # in addition to the named arguments.
260
+ # @option options :var_kwargs [Boolean] (false)
261
+ # Whether the function accepts other keyword arguments
262
+ # in addition to those in `:args`.
263
+ # If this is true, the Ruby function will be passed a hash from strings
264
+ # to {Sass::Script::Literal}s as the last argument.
265
+ # In addition, if this is true and `:var_args` is not,
266
+ # Sass will ensure that the last argument passed is a hash.
267
+ def self.declare(method_name, args, options = {})
268
+ @signatures[method_name] ||= []
269
+ @signatures[method_name] << Signature.new(
270
+ args.map {|s| s.to_s},
271
+ options[:var_args],
272
+ options[:var_kwargs])
273
+ end
274
+
275
+ # Determine the correct signature for the number of arguments
276
+ # passed in for a given function.
277
+ # If no signatures match, the first signature is returned for error messaging.
278
+ #
279
+ # @param method_name [Symbol] The name of the Ruby function to be called.
280
+ # @param arg_arity [Number] The number of unnamed arguments the function was passed.
281
+ # @param kwarg_arity [Number] The number of keyword arguments the function was passed.
282
+ #
283
+ # @return [{Symbol => Object}, nil]
284
+ # The signature options for the matching signature,
285
+ # or nil if no signatures are declared for this function. See {declare}.
286
+ def self.signature(method_name, arg_arity, kwarg_arity)
287
+ return unless @signatures[method_name]
288
+ @signatures[method_name].each do |signature|
289
+ return signature if signature.args.size == arg_arity + kwarg_arity
290
+ next unless signature.args.size < arg_arity + kwarg_arity
291
+
292
+ # We have enough args.
293
+ # Now we need to figure out which args are varargs
294
+ # and if the signature allows them.
295
+ t_arg_arity, t_kwarg_arity = arg_arity, kwarg_arity
296
+ if signature.args.size > t_arg_arity
297
+ # we transfer some kwargs arity to args arity
298
+ # if it does not have enough args -- assuming the names will work out.
299
+ t_kwarg_arity -= (signature.args.size - t_arg_arity)
300
+ t_arg_arity = signature.args.size
301
+ end
302
+
303
+ if ( t_arg_arity == signature.args.size || t_arg_arity > signature.args.size && signature.var_args ) &&
304
+ (t_kwarg_arity == 0 || t_kwarg_arity > 0 && signature.var_kwargs)
305
+ return signature
306
+ end
307
+ end
308
+ @signatures[method_name].first
309
+ end
310
+
311
+ # The context in which methods in {Script::Functions} are evaluated.
312
+ # That means that all instance methods of {EvaluationContext}
313
+ # are available to use in functions.
314
+ class EvaluationContext
315
+ include Functions
316
+
317
+ # The options hash for the {Sass::Engine} that is processing the function call
318
+ #
319
+ # @return [{Symbol => Object}]
320
+ attr_reader :options
321
+
322
+ # @param options [{Symbol => Object}] See \{#options}
323
+ def initialize(options)
324
+ @options = options
325
+ end
326
+
327
+ # Asserts that the type of a given SassScript value
328
+ # is the expected type (designated by a symbol).
329
+ #
330
+ # Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
331
+ # Note that `:String` will match both double-quoted strings
332
+ # and unquoted identifiers.
333
+ #
334
+ # @example
335
+ # assert_type value, :String
336
+ # assert_type value, :Number
337
+ # @param value [Sass::Script::Literal] A SassScript value
338
+ # @param type [Symbol] The name of the type the value is expected to be
339
+ # @param name [String, Symbol, nil] The name of the argument.
340
+ def assert_type(value, type, name = nil)
341
+ return if value.is_a?(Sass::Script.const_get(type))
342
+ err = "#{value.inspect} is not a #{type.to_s.downcase}"
343
+ err = "$#{name.to_s.gsub('_', '-')}: " + err if name
344
+ raise ArgumentError.new(err)
345
+ end
346
+ end
347
+
348
+ class << self
349
+ # Returns whether user function with a given name exists.
350
+ #
351
+ # @param function_name [String]
352
+ # @return [Boolean]
353
+ alias_method :callable?, :public_method_defined?
354
+
355
+ private
356
+ def include(*args)
357
+ r = super
358
+ # We have to re-include ourselves into EvaluationContext to work around
359
+ # an icky Ruby restriction.
360
+ EvaluationContext.send :include, self
361
+ r
362
+ end
363
+ end
364
+
365
+ # Creates a {Color} object from red, green, and blue values.
366
+ #
367
+ # @see #rgba
368
+ # @overload rgb($red, $green, $blue)
369
+ # @param $red [Number] The amount of red in the color. Must be between 0 and
370
+ # 255 inclusive, or between `0%` and `100%` inclusive
371
+ # @param $green [Number] The amount of green in the color. Must be between 0
372
+ # and 255 inclusive, or between `0%` and `100%` inclusive
373
+ # @param $blue [Number] The amount of blue in the color. Must be between 0
374
+ # and 255 inclusive, or between `0%` and `100%` inclusive
375
+ # @return [Color]
376
+ # @raise [ArgumentError] if any parameter is the wrong type or out of bounds
377
+ def rgb(red, green, blue)
378
+ assert_type red, :Number, :red
379
+ assert_type green, :Number, :green
380
+ assert_type blue, :Number, :blue
381
+
382
+ Color.new([[red, :red], [green, :green], [blue, :blue]].map do |(c, name)|
383
+ v = c.value
384
+ if c.numerator_units == ["%"] && c.denominator_units.empty?
385
+ v = Sass::Util.check_range("$#{name}: Color value", 0..100, c, '%')
386
+ v * 255 / 100.0
387
+ else
388
+ Sass::Util.check_range("$#{name}: Color value", 0..255, c)
389
+ end
390
+ end)
391
+ end
392
+ declare :rgb, [:red, :green, :blue]
393
+
394
+ # Creates a {Color} from red, green, blue, and alpha values.
395
+ # @see #rgb
396
+ #
397
+ # @overload rgba($red, $green, $blue, $alpha)
398
+ # @param $red [Number] The amount of red in the color. Must be between 0
399
+ # and 255 inclusive
400
+ # @param $green [Number] The amount of green in the color. Must be between
401
+ # 0 and 255 inclusive
402
+ # @param $blue [Number] The amount of blue in the color. Must be between 0
403
+ # and 255 inclusive
404
+ # @param $alpha [Number] The opacity of the color. Must be between 0 and 1
405
+ # inclusive
406
+ # @return [Color]
407
+ # @raise [ArgumentError] if any parameter is the wrong type or out of
408
+ # bounds
409
+ #
410
+ # @overload rgba($color, $alpha)
411
+ # Sets the opacity of an existing color.
412
+ #
413
+ # @example
414
+ # rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5)
415
+ # rgba(blue, 0.2) => rgba(0, 0, 255, 0.2)
416
+ #
417
+ # @param $color [Color] The color whose opacity will be changed.
418
+ # @param $alpha [Number] The new opacity of the color. Must be between 0
419
+ # and 1 inclusive
420
+ # @return [Color]
421
+ # @raise [ArgumentError] if `$alpha` is out of bounds or either parameter
422
+ # is the wrong type
423
+ def rgba(*args)
424
+ case args.size
425
+ when 2
426
+ color, alpha = args
427
+
428
+ assert_type color, :Color, :color
429
+ assert_type alpha, :Number, :alpha
430
+
431
+ Sass::Util.check_range('Alpha channel', 0..1, alpha)
432
+ color.with(:alpha => alpha.value)
433
+ when 4
434
+ red, green, blue, alpha = args
435
+ rgba(rgb(red, green, blue), alpha)
436
+ else
437
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)")
438
+ end
439
+ end
440
+ declare :rgba, [:red, :green, :blue, :alpha]
441
+ declare :rgba, [:color, :alpha]
442
+
443
+ # Creates a {Color} from hue, saturation, and lightness values. Uses the
444
+ # algorithm from the [CSS3 spec][].
445
+ #
446
+ # [CSS3 spec]: http://www.w3.org/TR/css3-color/#hsl-color
447
+ #
448
+ # @see #hsla
449
+ # @overload hsl($hue, $saturation, $lightness)
450
+ # @param $hue [Number] The hue of the color. Should be between 0 and 360
451
+ # degrees, inclusive
452
+ # @param $saturation [Number] The saturation of the color. Must be between
453
+ # `0%` and `100%`, inclusive
454
+ # @param $lightness [Number] The lightness of the color. Must be between
455
+ # `0%` and `100%`, inclusive
456
+ # @return [Color]
457
+ # @raise [ArgumentError] if `$saturation` or `$lightness` are out of bounds
458
+ # or any parameter is the wrong type
459
+ def hsl(hue, saturation, lightness)
460
+ hsla(hue, saturation, lightness, Number.new(1))
461
+ end
462
+ declare :hsl, [:hue, :saturation, :lightness]
463
+
464
+ # Creates a {Color} from hue, saturation, lightness, and alpha
465
+ # values. Uses the algorithm from the [CSS3 spec][].
466
+ #
467
+ # [CSS3 spec]: http://www.w3.org/TR/css3-color/#hsl-color
468
+ #
469
+ # @see #hsl
470
+ # @overload hsla($hue, $saturation, $lightness, $alpha)
471
+ # @param $hue [Number] The hue of the color. Should be between 0 and 360
472
+ # degrees, inclusive
473
+ # @param $saturation [Number] The saturation of the color. Must be between
474
+ # `0%` and `100%`, inclusive
475
+ # @param $lightness [Number] The lightness of the color. Must be between
476
+ # `0%` and `100%`, inclusive
477
+ # @param $alpha [Number] The opacity of the color. Must be between 0 and 1,
478
+ # inclusive
479
+ # @return [Color]
480
+ # @raise [ArgumentError] if `$saturation`, `$lightness`, or `$alpha` are out
481
+ # of bounds or any parameter is the wrong type
482
+ def hsla(hue, saturation, lightness, alpha)
483
+ assert_type hue, :Number, :hue
484
+ assert_type saturation, :Number, :saturation
485
+ assert_type lightness, :Number, :lightness
486
+ assert_type alpha, :Number, :alpha
487
+
488
+ Sass::Util.check_range('Alpha channel', 0..1, alpha)
489
+
490
+ h = hue.value
491
+ s = Sass::Util.check_range('Saturation', 0..100, saturation, '%')
492
+ l = Sass::Util.check_range('Lightness', 0..100, lightness, '%')
493
+
494
+ Color.new(:hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
495
+ end
496
+ declare :hsla, [:hue, :saturation, :lightness, :alpha]
497
+
498
+ # Gets the red component of a color. Calculated from HSL where necessary via
499
+ # [this algorithm][hsl-to-rgb].
500
+ #
501
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
502
+ #
503
+ # @overload red($color)
504
+ # @param $color [Color]
505
+ # @return [Number] The red component, between 0 and 255 inclusive
506
+ # @raise [ArgumentError] if `$color` isn't a color
507
+ def red(color)
508
+ assert_type color, :Color, :color
509
+ Sass::Script::Number.new(color.red)
510
+ end
511
+ declare :red, [:color]
512
+
513
+ # Gets the green component of a color. Calculated from HSL where necessary
514
+ # via [this algorithm][hsl-to-rgb].
515
+ #
516
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
517
+ #
518
+ # @overload green($color)
519
+ # @param $color [Color]
520
+ # @return [Number] The green component, between 0 and 255 inclusive
521
+ # @raise [ArgumentError] if `$color` isn't a color
522
+ def green(color)
523
+ assert_type color, :Color, :color
524
+ Sass::Script::Number.new(color.green)
525
+ end
526
+ declare :green, [:color]
527
+
528
+ # Gets the blue component of a color. Calculated from HSL where necessary
529
+ # via [this algorithm][hsl-to-rgb].
530
+ #
531
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
532
+ #
533
+ # @overload blue($color)
534
+ # @param $color [Color]
535
+ # @return [Number] The blue component, between 0 and 255 inclusive
536
+ # @raise [ArgumentError] if `$color` isn't a color
537
+ def blue(color)
538
+ assert_type color, :Color, :color
539
+ Sass::Script::Number.new(color.blue)
540
+ end
541
+ declare :blue, [:color]
542
+
543
+ # Returns the hue component of a color. See [the CSS3 HSL
544
+ # specification][hsl]. Calculated from RGB where necessary via [this
545
+ # algorithm][rgb-to-hsl].
546
+ #
547
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
548
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
549
+ #
550
+ # @overload hue($color)
551
+ # @param $color [Color]
552
+ # @return [Number] The hue component, between 0deg and 360deg
553
+ # @raise [ArgumentError] if `$color` isn't a color
554
+ def hue(color)
555
+ assert_type color, :Color, :color
556
+ Sass::Script::Number.new(color.hue, ["deg"])
557
+ end
558
+ declare :hue, [:color]
559
+
560
+ # Returns the saturation component of a color. See [the CSS3 HSL
561
+ # specification][hsl]. Calculated from RGB where necessary via [this
562
+ # algorithm][rgb-to-hsl].
563
+ #
564
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
565
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
566
+ #
567
+ # @overload saturation($color)
568
+ # @param $color [Color]
569
+ # @return [Number] The saturation component, between 0% and 100%
570
+ # @raise [ArgumentError] if `$color` isn't a color
571
+ def saturation(color)
572
+ assert_type color, :Color, :color
573
+ Sass::Script::Number.new(color.saturation, ["%"])
574
+ end
575
+ declare :saturation, [:color]
576
+
577
+ # Returns the lightness component of a color. See [the CSS3 HSL
578
+ # specification][hsl]. Calculated from RGB where necessary via [this
579
+ # algorithm][rgb-to-hsl].
580
+ #
581
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
582
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
583
+ #
584
+ # @overload lightness($color)
585
+ # @param $color [Color]
586
+ # @return [Number] The lightness component, between 0% and 100%
587
+ # @raise [ArgumentError] if `$color` isn't a color
588
+ def lightness(color)
589
+ assert_type color, :Color, :color
590
+ Sass::Script::Number.new(color.lightness, ["%"])
591
+ end
592
+ declare :lightness, [:color]
593
+
594
+ # Returns the alpha component (opacity) of a color. This is 1 unless
595
+ # otherwise specified.
596
+ #
597
+ # This function also supports the proprietary Microsoft `alpha(opacity=20)`
598
+ # syntax as a special case.
599
+ #
600
+ # @overload alpha($color)
601
+ # @param $color [Color]
602
+ # @return [Number] The alpha component, between 0 and 1
603
+ # @raise [ArgumentError] if `$color` isn't a color
604
+ def alpha(*args)
605
+ if args.all? do |a|
606
+ a.is_a?(Sass::Script::String) && a.type == :identifier &&
607
+ a.value =~ /^[a-zA-Z]+\s*=/
608
+ end
609
+ # Support the proprietary MS alpha() function
610
+ return Sass::Script::String.new("alpha(#{args.map {|a| a.to_s}.join(", ")})")
611
+ end
612
+
613
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 1)") if args.size != 1
614
+
615
+ assert_type args.first, :Color, :color
616
+ Sass::Script::Number.new(args.first.alpha)
617
+ end
618
+ declare :alpha, [:color]
619
+
620
+ # Returns the alpha component (opacity) of a color. This is 1 unless
621
+ # otherwise specified.
622
+ #
623
+ # @overload opacity($color)
624
+ # @param $color [Color]
625
+ # @return [Number] The alpha component, between 0 and 1
626
+ # @raise [ArgumentError] if `$color` isn't a color
627
+ def opacity(color)
628
+ return Sass::Script::String.new("opacity(#{color})") if color.is_a?(Sass::Script::Number)
629
+ assert_type color, :Color, :color
630
+ Sass::Script::Number.new(color.alpha)
631
+ end
632
+ declare :opacity, [:color]
633
+
634
+ # Makes a color more opaque. Takes a color and a number between 0 and 1, and
635
+ # returns a color with the opacity increased by that amount.
636
+ #
637
+ # @see #transparentize
638
+ # @example
639
+ # opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6)
640
+ # opacify(rgba(0, 0, 17, 0.8), 0.2) => #001
641
+ # @overload opacify($color, $amount)
642
+ # @param $color [Color]
643
+ # @param $amount [Number] The amount to increase the opacity by, between 0
644
+ # and 1
645
+ # @return [Color]
646
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
647
+ # is the wrong type
648
+ def opacify(color, amount)
649
+ _adjust(color, amount, :alpha, 0..1, :+)
650
+ end
651
+ declare :opacify, [:color, :amount]
652
+
653
+ alias_method :fade_in, :opacify
654
+ declare :fade_in, [:color, :amount]
655
+
656
+ # Makes a color more transparent. Takes a color and a number between 0 and
657
+ # 1, and returns a color with the opacity decreased by that amount.
658
+ #
659
+ # @see #opacify
660
+ # @example
661
+ # transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4)
662
+ # transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6)
663
+ # @overload transparentize($color, $amount)
664
+ # @param $color [Color]
665
+ # @param $amount [Number] The amount to decrease the opacity by, between 0
666
+ # and 1
667
+ # @return [Color]
668
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
669
+ # is the wrong type
670
+ def transparentize(color, amount)
671
+ _adjust(color, amount, :alpha, 0..1, :-)
672
+ end
673
+ declare :transparentize, [:color, :amount]
674
+
675
+ alias_method :fade_out, :transparentize
676
+ declare :fade_out, [:color, :amount]
677
+
678
+ # Makes a color lighter. Takes a color and a number between `0%` and `100%`,
679
+ # and returns a color with the lightness increased by that amount.
680
+ #
681
+ # @see #darken
682
+ # @example
683
+ # lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
684
+ # lighten(#800, 20%) => #e00
685
+ # @overload lighten($color, $amount)
686
+ # @param $color [Color]
687
+ # @param $amount [Number] The amount to increase the lightness by, between
688
+ # `0%` and `100%`
689
+ # @return [Color]
690
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
691
+ # is the wrong type
692
+ def lighten(color, amount)
693
+ _adjust(color, amount, :lightness, 0..100, :+, "%")
694
+ end
695
+ declare :lighten, [:color, :amount]
696
+
697
+ # Makes a color darker. Takes a color and a number between 0% and 100%, and
698
+ # returns a color with the lightness decreased by that amount.
699
+ #
700
+ # @see #lighten
701
+ # @example
702
+ # darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
703
+ # darken(#800, 20%) => #200
704
+ # @overload darken($color, $amount)
705
+ # @param $color [Color]
706
+ # @param $amount [Number] The amount to dencrease the lightness by, between
707
+ # `0%` and `100%`
708
+ # @return [Color]
709
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
710
+ # is the wrong type
711
+ def darken(color, amount)
712
+ _adjust(color, amount, :lightness, 0..100, :-, "%")
713
+ end
714
+ declare :darken, [:color, :amount]
715
+
716
+ # Makes a color more saturated. Takes a color and a number between 0% and
717
+ # 100%, and returns a color with the saturation increased by that amount.
718
+ #
719
+ # @see #desaturate
720
+ # @example
721
+ # saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%)
722
+ # saturate(#855, 20%) => #9e3f3f
723
+ # @overload saturate($color, $amount)
724
+ # @param $color [Color]
725
+ # @param $amount [Number] The amount to increase the saturation by, between
726
+ # `0%` and `100%`
727
+ # @return [Color]
728
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
729
+ # is the wrong type
730
+ def saturate(color, amount = nil)
731
+ # Support the filter effects definition of saturate.
732
+ # https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
733
+ return Sass::Script::String.new("saturate(#{color})") if amount.nil?
734
+ _adjust(color, amount, :saturation, 0..100, :+, "%")
735
+ end
736
+ declare :saturate, [:color, :amount]
737
+ declare :saturate, [:amount]
738
+
739
+ # Makes a color less saturated. Takes a color and a number between 0% and
740
+ # 100%, and returns a color with the saturation decreased by that value.
741
+ #
742
+ # @see #saturate
743
+ # @example
744
+ # desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%)
745
+ # desaturate(#855, 20%) => #726b6b
746
+ # @overload desaturate($color, $amount)
747
+ # @param $color [Color]
748
+ # @param $amount [Number] The amount to decrease the saturation by, between
749
+ # `0%` and `100%`
750
+ # @return [Color]
751
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
752
+ # is the wrong type
753
+ def desaturate(color, amount)
754
+ _adjust(color, amount, :saturation, 0..100, :-, "%")
755
+ end
756
+ declare :desaturate, [:color, :amount]
757
+
758
+ # Changes the hue of a color. Takes a color and a number of degrees (usually
759
+ # between `-360deg` and `360deg`), and returns a color with the hue rotated
760
+ # along the color wheel by that amount.
761
+ #
762
+ # @example
763
+ # adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%)
764
+ # adjust-hue(hsl(120, 30%, 90%), 060deg) => hsl(60, 30%, 90%)
765
+ # adjust-hue(#811, 45deg) => #886a11
766
+ # @overload adjust_hue($color, $degrees)
767
+ # @param $color [Color]
768
+ # @param $degrees [Number] The number of degrees to rotate the hue
769
+ # @return [Color]
770
+ # @raise [ArgumentError] if either parameter is the wrong type
771
+ def adjust_hue(color, degrees)
772
+ assert_type color, :Color, :color
773
+ assert_type degrees, :Number, :degrees
774
+ color.with(:hue => color.hue + degrees.value)
775
+ end
776
+ declare :adjust_hue, [:color, :degrees]
777
+
778
+ # Converts a color into the format understood by IE filters.
779
+ #
780
+ # @example
781
+ # ie-hex-str(#abc) => #FFAABBCC
782
+ # ie-hex-str(#3322BB) => #FF3322BB
783
+ # ie-hex-str(rgba(0, 255, 0, 0.5)) => #8000FF00
784
+ # @overload ie_hex_str($color)
785
+ # @param $color [Color]
786
+ # @return [String] The IE-formatted string representation of the color
787
+ # @raise [ArgumentError] if `$color` isn't a color
788
+ def ie_hex_str(color)
789
+ assert_type color, :Color, :color
790
+ alpha = (color.alpha * 255).round.to_s(16).rjust(2, '0')
791
+ Sass::Script::String.new("##{alpha}#{color.send(:hex_str)[1..-1]}".upcase)
792
+ end
793
+ declare :ie_hex_str, [:color]
794
+
795
+ # Increases or decreases one or more properties of a color. This can change
796
+ # the red, green, blue, hue, saturation, value, and alpha properties. The
797
+ # properties are specified as keyword arguments, and are added to or
798
+ # subtracted from the color's current value for that property.
799
+ #
800
+ # All properties are optional. You can't specify both RGB properties
801
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$hue`, `$saturation`,
802
+ # `$value`) at the same time.
803
+ #
804
+ # @example
805
+ # adjust-color(#102030, $blue: 5) => #102035
806
+ # adjust-color(#102030, $red: -5, $blue: 5) => #0b2035
807
+ # adjust-color(hsl(25, 100%, 80%), $lightness: -30%, $alpha: -0.4) => hsla(25, 100%, 50%, 0.6)
808
+ # @overload adjust_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])
809
+ # @param $color [Color]
810
+ # @param $red [Number] The adjustment to make on the red component, between
811
+ # -255 and 255 inclusive
812
+ # @param $green [Number] The adjustment to make on the green component,
813
+ # between -255 and 255 inclusive
814
+ # @param $blue [Number] The adjustment to make on the blue component, between
815
+ # -255 and 255 inclusive
816
+ # @param $hue [Number] The adjustment to make on the hue component, in
817
+ # degrees
818
+ # @param $saturation [Number] The adjustment to make on the saturation
819
+ # component, between `-100%` and `100%` inclusive
820
+ # @param $lightness [Number] The adjustment to make on the lightness
821
+ # component, between `-100%` and `100%` inclusive
822
+ # @param $alpha [Number] The adjustment to make on the alpha component,
823
+ # between -1 and 1 inclusive
824
+ # @return [Color]
825
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
826
+ # bounds, or if RGB properties and HSL properties are adjusted at the
827
+ # same time
828
+ def adjust_color(color, kwargs)
829
+ assert_type color, :Color, :color
830
+ with = Sass::Util.map_hash({
831
+ "red" => [-255..255, ""],
832
+ "green" => [-255..255, ""],
833
+ "blue" => [-255..255, ""],
834
+ "hue" => nil,
835
+ "saturation" => [-100..100, "%"],
836
+ "lightness" => [-100..100, "%"],
837
+ "alpha" => [-1..1, ""]
838
+ }) do |name, (range, units)|
839
+
840
+ next unless val = kwargs.delete(name)
841
+ assert_type val, :Number, name
842
+ Sass::Util.check_range("$#{name}: Amount", range, val, units) if range
843
+ adjusted = color.send(name) + val.value
844
+ adjusted = [0, Sass::Util.restrict(adjusted, range)].max if range
845
+ [name.to_sym, adjusted]
846
+ end
847
+
848
+ unless kwargs.empty?
849
+ name, val = kwargs.to_a.first
850
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
851
+ end
852
+
853
+ color.with(with)
854
+ end
855
+ declare :adjust_color, [:color], :var_kwargs => true
856
+
857
+ # Fluidly scales one or more properties of a color. Unlike
858
+ # \{#adjust_color adjust-color}, which changes a color's properties by fixed
859
+ # amounts, \{#scale_color scale-color} fluidly changes them based on how
860
+ # high or low they already are. That means that lightening an already-light
861
+ # color with \{#scale_color scale-color} won't change the lightness much,
862
+ # but lightening a dark color by the same amount will change it more
863
+ # dramatically. This has the benefit of making `scale-color($color, ...)`
864
+ # have a similar effect regardless of what `$color` is.
865
+ #
866
+ # For example, the lightness of a color can be anywhere between `0%` and
867
+ # `100%`. If `scale-color($color, $lightness: 40%)` is called, the resulting
868
+ # color's lightness will be 40% of the way between its original lightness
869
+ # and 100. If `scale-color($color, $lightness: -40%)` is called instead, the
870
+ # lightness will be 40% of the way between the original and 0.
871
+ #
872
+ # This can change the red, green, blue, saturation, value, and alpha
873
+ # properties. The properties are specified as keyword arguments. All
874
+ # arguments should be percentages between `0%` and `100%`.
875
+ #
876
+ # All properties are optional. You can't specify both RGB properties
877
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$saturation`, `$value`)
878
+ # at the same time.
879
+ #
880
+ # @example
881
+ # scale-color(hsl(120, 70%, 80%), $lightness: 50%) => hsl(120, 70%, 90%)
882
+ # scale-color(rgb(200, 150%, 170%), $green: -40%, $blue: 70%) => rgb(200, 90, 229)
883
+ # scale-color(hsl(200, 70%, 80%), $saturation: -90%, $alpha: -30%) => hsla(200, 7%, 80%, 0.7)
884
+ # @overload scale_color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha])
885
+ # @param $color [Color]
886
+ # @param $red [Number]
887
+ # @param $green [Number]
888
+ # @param $blue [Number]
889
+ # @param $saturation [Number]
890
+ # @param $lightness [Number]
891
+ # @param $alpha [Number]
892
+ # @return [Color]
893
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
894
+ # bounds, or if RGB properties and HSL properties are adjusted at the
895
+ # same time
896
+ def scale_color(color, kwargs)
897
+ assert_type color, :Color, :color
898
+ with = Sass::Util.map_hash({
899
+ "red" => 255,
900
+ "green" => 255,
901
+ "blue" => 255,
902
+ "saturation" => 100,
903
+ "lightness" => 100,
904
+ "alpha" => 1
905
+ }) do |name, max|
906
+
907
+ next unless val = kwargs.delete(name)
908
+ assert_type val, :Number, name
909
+ if !(val.numerator_units == ['%'] && val.denominator_units.empty?)
910
+ raise ArgumentError.new("$#{name}: Amount #{val} must be a % (e.g. #{val.value}%)")
911
+ else
912
+ Sass::Util.check_range("$#{name}: Amount", -100..100, val, '%')
913
+ end
914
+
915
+ current = color.send(name)
916
+ scale = val.value/100.0
917
+ diff = scale > 0 ? max - current : current
918
+ [name.to_sym, current + diff*scale]
919
+ end
920
+
921
+ unless kwargs.empty?
922
+ name, val = kwargs.to_a.first
923
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
924
+ end
925
+
926
+ color.with(with)
927
+ end
928
+ declare :scale_color, [:color], :var_kwargs => true
929
+
930
+ # Changes one or more properties of a color. This can change the red, green,
931
+ # blue, hue, saturation, value, and alpha properties. The properties are
932
+ # specified as keyword arguments, and replace the color's current value for
933
+ # that property.
934
+ #
935
+ # All properties are optional. You can't specify both RGB properties
936
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$hue`, `$saturation`,
937
+ # `$value`) at the same time.
938
+ #
939
+ # @example
940
+ # change-color(#102030, $blue: 5) => #102005
941
+ # change-color(#102030, $red: 120, $blue: 5) => #782005
942
+ # change-color(hsl(25, 100%, 80%), $lightness: 40%, $alpha: 0.8) => hsla(25, 100%, 40%, 0.8)
943
+ # @overload change_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])
944
+ # @param $color [Color]
945
+ # @param $red [Number] The new red component for the color, within 0 and 255
946
+ # inclusive
947
+ # @param $green [Number] The new green component for the color, within 0 and
948
+ # 255 inclusive
949
+ # @param $blue [Number] The new blue component for the color, within 0 and
950
+ # 255 inclusive
951
+ # @param $hue [Number] The new hue component for the color, in degrees
952
+ # @param $saturation [Number] The new saturation component for the color,
953
+ # between `0%` and `100%` inclusive
954
+ # @param $lightness [Number] The new lightness component for the color,
955
+ # within `0%` and `100%` inclusive
956
+ # @param $alpha [Number] The new alpha component for the color, within 0 and
957
+ # 1 inclusive
958
+ # @return [Color]
959
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
960
+ # bounds, or if RGB properties and HSL properties are adjusted at the
961
+ # same time
962
+ def change_color(color, kwargs)
963
+ assert_type color, :Color, :color
964
+ with = Sass::Util.map_hash(%w[red green blue hue saturation lightness alpha]) do |name, max|
965
+ next unless val = kwargs.delete(name)
966
+ assert_type val, :Number, name
967
+ [name.to_sym, val.value]
968
+ end
969
+
970
+ unless kwargs.empty?
971
+ name, val = kwargs.to_a.first
972
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
973
+ end
974
+
975
+ color.with(with)
976
+ end
977
+ declare :change_color, [:color], :var_kwargs => true
978
+
979
+ # Mixes two colors together. Specifically, takes the average of each of the
980
+ # RGB components, optionally weighted by the given percentage. The opacity
981
+ # of the colors is also considered when weighting the components.
982
+ #
983
+ # The weight specifies the amount of the first color that should be included
984
+ # in the returned color. The default, `50%`, means that half the first color
985
+ # and half the second color should be used. `25%` means that a quarter of
986
+ # the first color and three quarters of the second color should be used.
987
+ #
988
+ # @example
989
+ # mix(#f00, #00f) => #7f007f
990
+ # mix(#f00, #00f, 25%) => #3f00bf
991
+ # mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
992
+ # @overload mix($color-1, $color-2, $weight: 50%)
993
+ # @param $color-1 [Color]
994
+ # @param $color-2 [Color]
995
+ # @param $weight [Number] The relative weight of each color. Closer to `0%`
996
+ # gives more weight to `$color`, closer to `100%` gives more weight to
997
+ # `$color2`
998
+ # @return [Color]
999
+ # @raise [ArgumentError] if `$weight` is out of bounds or any parameter is
1000
+ # the wrong type
1001
+ def mix(color_1, color_2, weight = Number.new(50))
1002
+ assert_type color_1, :Color, :color_1
1003
+ assert_type color_2, :Color, :color_2
1004
+ assert_type weight, :Number, :weight
1005
+
1006
+ Sass::Util.check_range("Weight", 0..100, weight, '%')
1007
+
1008
+ # This algorithm factors in both the user-provided weight (w) and the
1009
+ # difference between the alpha values of the two colors (a) to decide how
1010
+ # to perform the weighted average of the two RGB values.
1011
+ #
1012
+ # It works by first normalizing both parameters to be within [-1, 1],
1013
+ # where 1 indicates "only use color_1", -1 indicates "only use color_2", and
1014
+ # all values in between indicated a proportionately weighted average.
1015
+ #
1016
+ # Once we have the normalized variables w and a, we apply the formula
1017
+ # (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color_1.
1018
+ # This formula has two especially nice properties:
1019
+ #
1020
+ # * When either w or a are -1 or 1, the combined weight is also that number
1021
+ # (cases where w * a == -1 are undefined, and handled as a special case).
1022
+ #
1023
+ # * When a is 0, the combined weight is w, and vice versa.
1024
+ #
1025
+ # Finally, the weight of color_1 is renormalized to be within [0, 1]
1026
+ # and the weight of color_2 is given by 1 minus the weight of color_1.
1027
+ p = (weight.value/100.0).to_f
1028
+ w = p*2 - 1
1029
+ a = color_1.alpha - color_2.alpha
1030
+
1031
+ w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0
1032
+ w2 = 1 - w1
1033
+
1034
+ rgb = color_1.rgb.zip(color_2.rgb).map {|v1, v2| v1*w1 + v2*w2}
1035
+ alpha = color_1.alpha*p + color_2.alpha*(1-p)
1036
+ Color.new(rgb + [alpha])
1037
+ end
1038
+ declare :mix, [:color_1, :color_2]
1039
+ declare :mix, [:color_1, :color_2, :weight]
1040
+
1041
+ # Converts a color to grayscale. This is identical to `desaturate(color,
1042
+ # 100%)`.
1043
+ #
1044
+ # @see #desaturate
1045
+ # @overload grayscale($color)
1046
+ # @param $color [Color]
1047
+ # @return [Color]
1048
+ # @raise [ArgumentError] if `$color` isn't a color
1049
+ def grayscale(color)
1050
+ return Sass::Script::String.new("grayscale(#{color})") if color.is_a?(Sass::Script::Number)
1051
+ desaturate color, Number.new(100)
1052
+ end
1053
+ declare :grayscale, [:color]
1054
+
1055
+ # Returns the complement of a color. This is identical to `adjust-hue(color,
1056
+ # 180deg)`.
1057
+ #
1058
+ # @see #adjust_hue #adjust-hue
1059
+ # @overload complement($color)
1060
+ # @param $color [Color]
1061
+ # @return [Color]
1062
+ # @raise [ArgumentError] if `$color` isn't a color
1063
+ def complement(color)
1064
+ adjust_hue color, Number.new(180)
1065
+ end
1066
+ declare :complement, [:color]
1067
+
1068
+ # Returns the inverse (negative) of a color. The red, green, and blue values
1069
+ # are inverted, while the opacity is left alone.
1070
+ #
1071
+ # @overload invert($color)
1072
+ # @param $color [Color]
1073
+ # @return [Color]
1074
+ # @raise [ArgumentError] if `$color` isn't a color
1075
+ def invert(color)
1076
+ return Sass::Script::String.new("invert(#{color})") if color.is_a?(Sass::Script::Number)
1077
+
1078
+ assert_type color, :Color, :color
1079
+ color.with(
1080
+ :red => (255 - color.red),
1081
+ :green => (255 - color.green),
1082
+ :blue => (255 - color.blue))
1083
+ end
1084
+ declare :invert, [:color]
1085
+
1086
+ # Removes quotes from a string. If the string is already unquoted, this will
1087
+ # return it unmodified.
1088
+ #
1089
+ # @see #quote
1090
+ # @example
1091
+ # unquote("foo") => foo
1092
+ # unquote(foo) => foo
1093
+ # @overload unquote($string)
1094
+ # @param $string [String]
1095
+ # @return [String]
1096
+ # @raise [ArgumentError] if `$string` isn't a string
1097
+ def unquote(string)
1098
+ if string.is_a?(Sass::Script::String)
1099
+ Sass::Script::String.new(string.value, :identifier)
1100
+ else
1101
+ string
1102
+ end
1103
+ end
1104
+ declare :unquote, [:string]
1105
+
1106
+ # Add quotes to a string if the string isn't quoted,
1107
+ # or returns the same string if it is.
1108
+ #
1109
+ # @see #unquote
1110
+ # @example
1111
+ # quote("foo") => "foo"
1112
+ # quote(foo) => "foo"
1113
+ # @overload quote($string)
1114
+ # @param $string [String]
1115
+ # @return [String]
1116
+ # @raise [ArgumentError] if `$string` isn't a string
1117
+ def quote(string)
1118
+ assert_type string, :String, :string
1119
+ Sass::Script::String.new(string.value, :string)
1120
+ end
1121
+ declare :quote, [:string]
1122
+
1123
+ # Returns the type of a value.
1124
+ #
1125
+ # @example
1126
+ # type-of(100px) => number
1127
+ # type-of(asdf) => string
1128
+ # type-of("asdf") => string
1129
+ # type-of(true) => bool
1130
+ # type-of(#fff) => color
1131
+ # type-of(blue) => color
1132
+ # @overload type_of($value)
1133
+ # @param $value [Literal] The value to inspect
1134
+ # @return [String] The unquoted string name of the value's type
1135
+ def type_of(value)
1136
+ Sass::Script::String.new(value.class.name.gsub(/Sass::Script::/,'').downcase)
1137
+ end
1138
+ declare :type_of, [:value]
1139
+
1140
+ # Returns the unit(s) associated with a number. Complex units are sorted in
1141
+ # alphabetical order by numerator and denominator.
1142
+ #
1143
+ # @example
1144
+ # unit(100) => ""
1145
+ # unit(100px) => "px"
1146
+ # unit(3em) => "em"
1147
+ # unit(10px * 5em) => "em*px"
1148
+ # unit(10px * 5em / 30cm / 1rem) => "em*px/cm*rem"
1149
+ # @overload unit($number)
1150
+ # @param $number [Number]
1151
+ # @return [String] The unit(s) of the number, as a quoted string
1152
+ # @raise [ArgumentError] if `$number` isn't a number
1153
+ def unit(number)
1154
+ assert_type number, :Number, :number
1155
+ Sass::Script::String.new(number.unit_str, :string)
1156
+ end
1157
+ declare :unit, [:number]
1158
+
1159
+ # Returns whether a number has units.
1160
+ #
1161
+ # @example
1162
+ # unitless(100) => true
1163
+ # unitless(100px) => false
1164
+ # @overload unitless($number)
1165
+ # @param $number [Number]
1166
+ # @return [Bool]
1167
+ # @raise [ArgumentError] if `$number` isn't a number
1168
+ def unitless(number)
1169
+ assert_type number, :Number, :number
1170
+ Sass::Script::Bool.new(number.unitless?)
1171
+ end
1172
+ declare :unitless, [:number]
1173
+
1174
+ # Returns whether two numbers can added, subtracted, or compared.
1175
+ #
1176
+ # @example
1177
+ # comparable(2px, 1px) => true
1178
+ # comparable(100px, 3em) => false
1179
+ # comparable(10cm, 3mm) => true
1180
+ # @overload comparable($number-1, $number-2)
1181
+ # @param $number-1 [Number]
1182
+ # @param $number-2 [Number]
1183
+ # @return [Bool]
1184
+ # @raise [ArgumentError] if either parameter is the wrong type
1185
+ def comparable(number_1, number_2)
1186
+ assert_type number_1, :Number, :number_1
1187
+ assert_type number_2, :Number, :number_2
1188
+ Sass::Script::Bool.new(number_1.comparable_to?(number_2))
1189
+ end
1190
+ declare :comparable, [:number_1, :number_2]
1191
+
1192
+ # Converts a unitless number to a percentage.
1193
+ #
1194
+ # @example
1195
+ # percentage(0.2) => 20%
1196
+ # percentage(100px / 50px) => 200%
1197
+ # @overload percentage($value)
1198
+ # @param $value [Number]
1199
+ # @return [Number]
1200
+ # @raise [ArgumentError] if `$value` isn't a unitless number
1201
+ def percentage(value)
1202
+ unless value.is_a?(Sass::Script::Number) && value.unitless?
1203
+ raise ArgumentError.new("$value: #{value.inspect} is not a unitless number")
1204
+ end
1205
+ Sass::Script::Number.new(value.value * 100, ['%'])
1206
+ end
1207
+ declare :percentage, [:value]
1208
+
1209
+ # Rounds a number to the nearest whole number.
1210
+ #
1211
+ # @example
1212
+ # round(10.4px) => 10px
1213
+ # round(10.6px) => 11px
1214
+ # @overload round($value)
1215
+ # @param $value [Number]
1216
+ # @return [Number]
1217
+ # @raise [ArgumentError] if `$value` isn't a number
1218
+ def round(value)
1219
+ numeric_transformation(value) {|n| n.round}
1220
+ end
1221
+ declare :round, [:value]
1222
+
1223
+ # Rounds a number up to the next whole number.
1224
+ #
1225
+ # @example
1226
+ # ceil(10.4px) => 11px
1227
+ # ceil(10.6px) => 11px
1228
+ # @overload ceil($value)
1229
+ # @param $value [Number]
1230
+ # @return [Number]
1231
+ # @raise [ArgumentError] if `$value` isn't a number
1232
+ def ceil(value)
1233
+ numeric_transformation(value) {|n| n.ceil}
1234
+ end
1235
+ declare :ceil, [:value]
1236
+
1237
+ # Rounds a number down to the previous whole number.
1238
+ #
1239
+ # @example
1240
+ # floor(10.4px) => 10px
1241
+ # floor(10.6px) => 10px
1242
+ # @overload floor($value)
1243
+ # @param $value [Number]
1244
+ # @return [Number]
1245
+ # @raise [ArgumentError] if `$value` isn't a number
1246
+ def floor(value)
1247
+ numeric_transformation(value) {|n| n.floor}
1248
+ end
1249
+ declare :floor, [:value]
1250
+
1251
+ # Returns the absolute value of a number.
1252
+ #
1253
+ # @example
1254
+ # abs(10px) => 10px
1255
+ # abs(-10px) => 10px
1256
+ # @overload abs($value)
1257
+ # @param $value [Number]
1258
+ # @return [Number]
1259
+ # @raise [ArgumentError] if `$value` isn't a number
1260
+ def abs(value)
1261
+ numeric_transformation(value) {|n| n.abs}
1262
+ end
1263
+ declare :abs, [:value]
1264
+
1265
+ # Finds the minimum of several numbers. This function takes any number of
1266
+ # arguments.
1267
+ #
1268
+ # @example
1269
+ # min(1px, 4px) => 1px
1270
+ # min(5em, 3em, 4em) => 3em
1271
+ # @overload min($numbers...)
1272
+ # @param $numbers [[Number]]
1273
+ # @return [Number]
1274
+ # @raise [ArgumentError] if any argument isn't a number, or if not all of
1275
+ # the arguments have comparable units
1276
+ def min(*numbers)
1277
+ numbers.each {|n| assert_type n, :Number}
1278
+ numbers.inject {|min, num| min.lt(num).to_bool ? min : num}
1279
+ end
1280
+ declare :min, [], :var_args => :true
1281
+
1282
+ # Finds the maximum of several numbers. This function takes any number of
1283
+ # arguments.
1284
+ #
1285
+ # @example
1286
+ # max(1px, 4px) => 4px
1287
+ # max(5em, 3em, 4em) => 5em
1288
+ # @overload max($numbers...)
1289
+ # @param $numbers [[Number]]
1290
+ # @return [Number]
1291
+ # @raise [ArgumentError] if any argument isn't a number, or if not all of
1292
+ # the arguments have comparable units
1293
+ def max(*values)
1294
+ values.each {|v| assert_type v, :Number}
1295
+ values.inject {|max, val| max.gt(val).to_bool ? max : val}
1296
+ end
1297
+ declare :max, [], :var_args => :true
1298
+
1299
+ # Return the length of a list.
1300
+ #
1301
+ # @example
1302
+ # length(10px) => 1
1303
+ # length(10px 20px 30px) => 3
1304
+ # @overload length($list)
1305
+ # @param $list [Literal]
1306
+ # @return [Number]
1307
+ def length(list)
1308
+ Sass::Script::Number.new(list.to_a.size)
1309
+ end
1310
+ declare :length, [:list]
1311
+
1312
+ # Gets the nth item in a list.
1313
+ #
1314
+ # Note that unlike some languages, the first item in a Sass list is number
1315
+ # 1, the second number 2, and so forth.
1316
+ #
1317
+ # @example
1318
+ # nth(10px 20px 30px, 1) => 10px
1319
+ # nth((Helvetica, Arial, sans-serif), 3) => sans-serif
1320
+ # @overload nth($list, $n)
1321
+ # @param $list [Literal]
1322
+ # @param $n [Number] The index of the item to get
1323
+ # @return [Literal]
1324
+ # @raise [ArgumentError] if `$n` isn't an integer between 1 and the length
1325
+ # of `$list`
1326
+ def nth(list, n)
1327
+ assert_type n, :Number, :n
1328
+ if !n.int?
1329
+ raise ArgumentError.new("List index #{n} must be an integer")
1330
+ elsif n.to_i < 1
1331
+ raise ArgumentError.new("List index #{n} must be greater than or equal to 1")
1332
+ elsif list.to_a.size == 0
1333
+ raise ArgumentError.new("List index is #{n} but list has no items")
1334
+ elsif n.to_i > (size = list.to_a.size)
1335
+ raise ArgumentError.new("List index is #{n} but list is only #{size} item#{'s' if size != 1} long")
1336
+ end
1337
+
1338
+ list.to_a[n.to_i - 1]
1339
+ end
1340
+ declare :nth, [:list, :n]
1341
+
1342
+ # Joins together two lists into one.
1343
+ #
1344
+ # Unless `$separator` is passed, if one list is comma-separated and one is
1345
+ # space-separated, the first parameter's separator is used for the resulting
1346
+ # list. If both lists have fewer than two items, spaces are used for the
1347
+ # resulting list.
1348
+ #
1349
+ # @example
1350
+ # join(10px 20px, 30px 40px) => 10px 20px 30px 40px
1351
+ # join((blue, red), (#abc, #def)) => blue, red, #abc, #def
1352
+ # join(10px, 20px) => 10px 20px
1353
+ # join(10px, 20px, comma) => 10px, 20px
1354
+ # join((blue, red), (#abc, #def), space) => blue red #abc #def
1355
+ # @overload join($list1, $list2, $separator: auto)
1356
+ # @param $list1 [Literal]
1357
+ # @param $list2 [Literal]
1358
+ # @param $separator [String] The list separator to use. If this is `comma`
1359
+ # or `space`, that separator will be used. If this is `auto` (the
1360
+ # default), the separator is determined as explained above.
1361
+ # @return [List]
1362
+ def join(list1, list2, separator = Sass::Script::String.new("auto"))
1363
+ assert_type separator, :String, :separator
1364
+ unless %w[auto space comma].include?(separator.value)
1365
+ raise ArgumentError.new("Separator name must be space, comma, or auto")
1366
+ end
1367
+ sep1 = list1.separator if list1.is_a?(Sass::Script::List) && !list1.value.empty?
1368
+ sep2 = list2.separator if list2.is_a?(Sass::Script::List) && !list2.value.empty?
1369
+ Sass::Script::List.new(
1370
+ list1.to_a + list2.to_a,
1371
+ if separator.value == 'auto'
1372
+ sep1 || sep2 || :space
1373
+ else
1374
+ separator.value.to_sym
1375
+ end)
1376
+ end
1377
+ declare :join, [:list1, :list2]
1378
+ declare :join, [:list1, :list2, :separator]
1379
+
1380
+ # Appends a single value onto the end of a list.
1381
+ #
1382
+ # Unless the `$separator` argument is passed, if the list had only one item,
1383
+ # the resulting list will be space-separated.
1384
+ #
1385
+ # @example
1386
+ # append(10px 20px, 30px) => 10px 20px 30px
1387
+ # append((blue, red), green) => blue, red, green
1388
+ # append(10px 20px, 30px 40px) => 10px 20px (30px 40px)
1389
+ # append(10px, 20px, comma) => 10px, 20px
1390
+ # append((blue, red), green, space) => blue red green
1391
+ # @overload append($list, $val, $separator: auto)
1392
+ # @param $list [Literal]
1393
+ # @param $val [Literal]
1394
+ # @param $separator [String] The list separator to use. If this is `comma`
1395
+ # or `space`, that separator will be used. If this is `auto` (the
1396
+ # default), the separator is determined as explained above.
1397
+ # @return [List]
1398
+ def append(list, val, separator = Sass::Script::String.new("auto"))
1399
+ assert_type separator, :String, :separator
1400
+ unless %w[auto space comma].include?(separator.value)
1401
+ raise ArgumentError.new("Separator name must be space, comma, or auto")
1402
+ end
1403
+ sep = list.separator if list.is_a?(Sass::Script::List)
1404
+ Sass::Script::List.new(
1405
+ list.to_a + [val],
1406
+ if separator.value == 'auto'
1407
+ sep || :space
1408
+ else
1409
+ separator.value.to_sym
1410
+ end)
1411
+ end
1412
+ declare :append, [:list, :val]
1413
+ declare :append, [:list, :val, :separator]
1414
+
1415
+ # Combines several lists into a single multidimensional list. The nth value
1416
+ # of the resulting list is a space separated list of the source lists' nth
1417
+ # values.
1418
+ #
1419
+ # The length of the resulting list is the length of the
1420
+ # shortest list.
1421
+ #
1422
+ # @example
1423
+ # zip(1px 1px 3px, solid dashed solid, red green blue)
1424
+ # => 1px solid red, 1px dashed green, 3px solid blue
1425
+ # @overload zip($lists...)
1426
+ # @param $lists [[Literal]]
1427
+ # @return [List]
1428
+ def zip(*lists)
1429
+ length = nil
1430
+ values = []
1431
+ lists.each do |list|
1432
+ array = list.to_a
1433
+ values << array.dup
1434
+ length = length.nil? ? array.length : [length, array.length].min
1435
+ end
1436
+ values.each do |value|
1437
+ value.slice!(length)
1438
+ end
1439
+ new_list_value = values.first.zip(*values[1..-1])
1440
+ List.new(new_list_value.map{|list| List.new(list, :space)}, :comma)
1441
+ end
1442
+ declare :zip, [], :var_args => true
1443
+
1444
+
1445
+ # Returns the position of a value within a list. If the value isn't found,
1446
+ # returns false instead.
1447
+ #
1448
+ # Note that unlike some languages, the first item in a Sass list is number
1449
+ # 1, the second number 2, and so forth.
1450
+ #
1451
+ # @example
1452
+ # index(1px solid red, solid) => 2
1453
+ # index(1px solid red, dashed) => false
1454
+ # @overload index($list, $value)
1455
+ # @param $list [Literal]
1456
+ # @param $value [Literal]
1457
+ # @return [Number, Bool] The 1-based index of `$value` in `$list`, or
1458
+ # `false`
1459
+ def index(list, value)
1460
+ index = list.to_a.index {|e| e.eq(value).to_bool }
1461
+ if index
1462
+ Number.new(index + 1)
1463
+ else
1464
+ Bool.new(false)
1465
+ end
1466
+ end
1467
+ declare :index, [:list, :value]
1468
+
1469
+ # Returns one of two values, depending on whether or not `$condition` is
1470
+ # true. Just like in `@if`, all values other than `false` and `null` are
1471
+ # considered to be true.
1472
+ #
1473
+ # @example
1474
+ # if(true, 1px, 2px) => 1px
1475
+ # if(false, 1px, 2px) => 2px
1476
+ # @overload if($condition, $if-true, $if-false)
1477
+ # @param $condition [Literal] Whether the `$if-true` or `$if-false` will be
1478
+ # returned
1479
+ # @param $if-true [Literal]
1480
+ # @param $if-false [Literal]
1481
+ # @return [Literal] `$if-true` or `$if-false`
1482
+ def if(condition, if_true, if_false)
1483
+ if condition.to_bool
1484
+ if_true
1485
+ else
1486
+ if_false
1487
+ end
1488
+ end
1489
+ declare :if, [:condition, :if_true, :if_false]
1490
+
1491
+ # This function only exists as a workaround for IE7's [`content: counter`
1492
+ # bug][bug]. It works identically to any other plain-CSS function, except it
1493
+ # avoids adding spaces between the argument commas.
1494
+ #
1495
+ # [bug]: http://jes.st/2013/ie7s-css-breaking-content-counter-bug/
1496
+ #
1497
+ # @example
1498
+ # counter(item, ".") => counter(item,".")
1499
+ # @overload counter($args...)
1500
+ # @return [String]
1501
+ def counter(*args)
1502
+ Sass::Script::String.new("counter(#{args.map {|a| a.to_s(options)}.join(',')})")
1503
+ end
1504
+ declare :counter, [], :var_args => true
1505
+
1506
+ # This function only exists as a workaround for IE7's [`content: counters`
1507
+ # bug][bug]. It works identically to any other plain-CSS function, except it
1508
+ # avoids adding spaces between the argument commas.
1509
+ #
1510
+ # [bug]: http://jes.st/2013/ie7s-css-breaking-content-counter-bug/
1511
+ #
1512
+ # @example
1513
+ # counters(item, ".") => counters(item,".")
1514
+ # @overload counters($args...)
1515
+ # @return [String]
1516
+ def counters(*args)
1517
+ Sass::Script::String.new("counters(#{args.map {|a| a.to_s(options)}.join(',')})")
1518
+ end
1519
+ declare :counters, [], :var_args => true
1520
+
1521
+ private
1522
+
1523
+ # This method implements the pattern of transforming a numeric value into
1524
+ # another numeric value with the same units.
1525
+ # It yields a number to a block to perform the operation and return a number
1526
+ def numeric_transformation(value)
1527
+ assert_type value, :Number, :value
1528
+ Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
1529
+ end
1530
+
1531
+ def _adjust(color, amount, attr, range, op, units = "")
1532
+ assert_type color, :Color, :color
1533
+ assert_type amount, :Number, :amount
1534
+ Sass::Util.check_range('Amount', range, amount, units)
1535
+
1536
+ # TODO: is it worth restricting here,
1537
+ # or should we do so in the Color constructor itself,
1538
+ # and allow clipping in rgb() et al?
1539
+ color.with(attr => Sass::Util.restrict(
1540
+ color.send(attr).send(op, amount.value), range))
1541
+ end
1542
+ end
1543
+ end