sass4 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/AGENTS.md +534 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/CONTRIBUTING.md +148 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +242 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/sass-spec-ref.sh +40 -0
  14. data/extra/update_watch.rb +13 -0
  15. data/init.rb +18 -0
  16. data/lib/sass/cache_stores/base.rb +88 -0
  17. data/lib/sass/cache_stores/chain.rb +34 -0
  18. data/lib/sass/cache_stores/filesystem.rb +60 -0
  19. data/lib/sass/cache_stores/memory.rb +46 -0
  20. data/lib/sass/cache_stores/null.rb +25 -0
  21. data/lib/sass/cache_stores.rb +15 -0
  22. data/lib/sass/callbacks.rb +67 -0
  23. data/lib/sass/css.rb +407 -0
  24. data/lib/sass/deprecation.rb +55 -0
  25. data/lib/sass/engine.rb +1236 -0
  26. data/lib/sass/environment.rb +236 -0
  27. data/lib/sass/error.rb +198 -0
  28. data/lib/sass/exec/base.rb +188 -0
  29. data/lib/sass/exec/sass_convert.rb +283 -0
  30. data/lib/sass/exec/sass_scss.rb +436 -0
  31. data/lib/sass/exec.rb +9 -0
  32. data/lib/sass/features.rb +48 -0
  33. data/lib/sass/importers/base.rb +182 -0
  34. data/lib/sass/importers/deprecated_path.rb +51 -0
  35. data/lib/sass/importers/filesystem.rb +221 -0
  36. data/lib/sass/importers.rb +23 -0
  37. data/lib/sass/logger/base.rb +47 -0
  38. data/lib/sass/logger/delayed.rb +50 -0
  39. data/lib/sass/logger/log_level.rb +45 -0
  40. data/lib/sass/logger.rb +17 -0
  41. data/lib/sass/media.rb +210 -0
  42. data/lib/sass/plugin/compiler.rb +552 -0
  43. data/lib/sass/plugin/configuration.rb +134 -0
  44. data/lib/sass/plugin/generic.rb +15 -0
  45. data/lib/sass/plugin/merb.rb +48 -0
  46. data/lib/sass/plugin/rack.rb +60 -0
  47. data/lib/sass/plugin/rails.rb +47 -0
  48. data/lib/sass/plugin/staleness_checker.rb +199 -0
  49. data/lib/sass/plugin.rb +134 -0
  50. data/lib/sass/railtie.rb +10 -0
  51. data/lib/sass/repl.rb +57 -0
  52. data/lib/sass/root.rb +7 -0
  53. data/lib/sass/script/css_lexer.rb +33 -0
  54. data/lib/sass/script/css_parser.rb +36 -0
  55. data/lib/sass/script/functions.rb +3103 -0
  56. data/lib/sass/script/lexer.rb +518 -0
  57. data/lib/sass/script/parser.rb +1164 -0
  58. data/lib/sass/script/tree/funcall.rb +314 -0
  59. data/lib/sass/script/tree/interpolation.rb +220 -0
  60. data/lib/sass/script/tree/list_literal.rb +119 -0
  61. data/lib/sass/script/tree/literal.rb +49 -0
  62. data/lib/sass/script/tree/map_literal.rb +64 -0
  63. data/lib/sass/script/tree/node.rb +119 -0
  64. data/lib/sass/script/tree/operation.rb +149 -0
  65. data/lib/sass/script/tree/selector.rb +26 -0
  66. data/lib/sass/script/tree/string_interpolation.rb +125 -0
  67. data/lib/sass/script/tree/unary_operation.rb +69 -0
  68. data/lib/sass/script/tree/variable.rb +57 -0
  69. data/lib/sass/script/tree.rb +16 -0
  70. data/lib/sass/script/value/arg_list.rb +36 -0
  71. data/lib/sass/script/value/base.rb +258 -0
  72. data/lib/sass/script/value/bool.rb +35 -0
  73. data/lib/sass/script/value/callable.rb +25 -0
  74. data/lib/sass/script/value/color.rb +704 -0
  75. data/lib/sass/script/value/function.rb +19 -0
  76. data/lib/sass/script/value/helpers.rb +298 -0
  77. data/lib/sass/script/value/list.rb +135 -0
  78. data/lib/sass/script/value/map.rb +70 -0
  79. data/lib/sass/script/value/null.rb +44 -0
  80. data/lib/sass/script/value/number.rb +564 -0
  81. data/lib/sass/script/value/string.rb +138 -0
  82. data/lib/sass/script/value.rb +13 -0
  83. data/lib/sass/script.rb +66 -0
  84. data/lib/sass/scss/css_parser.rb +61 -0
  85. data/lib/sass/scss/parser.rb +1343 -0
  86. data/lib/sass/scss/rx.rb +134 -0
  87. data/lib/sass/scss/static_parser.rb +351 -0
  88. data/lib/sass/scss.rb +14 -0
  89. data/lib/sass/selector/abstract_sequence.rb +112 -0
  90. data/lib/sass/selector/comma_sequence.rb +195 -0
  91. data/lib/sass/selector/pseudo.rb +291 -0
  92. data/lib/sass/selector/sequence.rb +661 -0
  93. data/lib/sass/selector/simple.rb +124 -0
  94. data/lib/sass/selector/simple_sequence.rb +348 -0
  95. data/lib/sass/selector.rb +327 -0
  96. data/lib/sass/shared.rb +76 -0
  97. data/lib/sass/source/map.rb +209 -0
  98. data/lib/sass/source/position.rb +39 -0
  99. data/lib/sass/source/range.rb +41 -0
  100. data/lib/sass/stack.rb +140 -0
  101. data/lib/sass/supports.rb +225 -0
  102. data/lib/sass/tree/at_root_node.rb +83 -0
  103. data/lib/sass/tree/charset_node.rb +22 -0
  104. data/lib/sass/tree/comment_node.rb +82 -0
  105. data/lib/sass/tree/content_node.rb +9 -0
  106. data/lib/sass/tree/css_import_node.rb +68 -0
  107. data/lib/sass/tree/debug_node.rb +18 -0
  108. data/lib/sass/tree/directive_node.rb +59 -0
  109. data/lib/sass/tree/each_node.rb +24 -0
  110. data/lib/sass/tree/error_node.rb +18 -0
  111. data/lib/sass/tree/extend_node.rb +43 -0
  112. data/lib/sass/tree/for_node.rb +36 -0
  113. data/lib/sass/tree/function_node.rb +44 -0
  114. data/lib/sass/tree/if_node.rb +52 -0
  115. data/lib/sass/tree/import_node.rb +75 -0
  116. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  117. data/lib/sass/tree/media_node.rb +48 -0
  118. data/lib/sass/tree/mixin_def_node.rb +38 -0
  119. data/lib/sass/tree/mixin_node.rb +52 -0
  120. data/lib/sass/tree/node.rb +240 -0
  121. data/lib/sass/tree/prop_node.rb +162 -0
  122. data/lib/sass/tree/return_node.rb +19 -0
  123. data/lib/sass/tree/root_node.rb +44 -0
  124. data/lib/sass/tree/rule_node.rb +153 -0
  125. data/lib/sass/tree/supports_node.rb +38 -0
  126. data/lib/sass/tree/trace_node.rb +33 -0
  127. data/lib/sass/tree/variable_node.rb +36 -0
  128. data/lib/sass/tree/visitors/base.rb +72 -0
  129. data/lib/sass/tree/visitors/check_nesting.rb +173 -0
  130. data/lib/sass/tree/visitors/convert.rb +350 -0
  131. data/lib/sass/tree/visitors/cssize.rb +362 -0
  132. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  133. data/lib/sass/tree/visitors/extend.rb +64 -0
  134. data/lib/sass/tree/visitors/perform.rb +572 -0
  135. data/lib/sass/tree/visitors/set_options.rb +139 -0
  136. data/lib/sass/tree/visitors/to_css.rb +440 -0
  137. data/lib/sass/tree/warn_node.rb +18 -0
  138. data/lib/sass/tree/while_node.rb +18 -0
  139. data/lib/sass/util/multibyte_string_scanner.rb +151 -0
  140. data/lib/sass/util/normalized_map.rb +122 -0
  141. data/lib/sass/util/subset_map.rb +109 -0
  142. data/lib/sass/util/test.rb +9 -0
  143. data/lib/sass/util.rb +1137 -0
  144. data/lib/sass/version.rb +120 -0
  145. data/lib/sass.rb +102 -0
  146. data/rails/init.rb +1 -0
  147. metadata +283 -0
@@ -0,0 +1,3103 @@
1
+ require 'sass/script/value/helpers'
2
+
3
+ module Sass::Script
4
+ # YARD can't handle some multiline tags, and we need really long tags for function declarations.
5
+ # Methods in this module are accessible from the SassScript context.
6
+ # For example, you can write
7
+ #
8
+ # $color: hsl(120deg, 100%, 50%)
9
+ #
10
+ # and it will call {Functions#hsl}.
11
+ #
12
+ # The following functions are provided:
13
+ #
14
+ # *Note: These functions are described in more detail below.*
15
+ #
16
+ # ## RGB Functions
17
+ #
18
+ # \{#rgb rgb($red, $green, $blue)}
19
+ # : Creates a {Sass::Script::Value::Color Color} from red, green, and blue
20
+ # values.
21
+ #
22
+ # \{#rgba rgba($red, $green, $blue, $alpha)}
23
+ # : Creates a {Sass::Script::Value::Color Color} from red, green, blue, and
24
+ # alpha values.
25
+ #
26
+ # \{#red red($color)}
27
+ # : Gets the red component of a color.
28
+ #
29
+ # \{#green green($color)}
30
+ # : Gets the green component of a color.
31
+ #
32
+ # \{#blue blue($color)}
33
+ # : Gets the blue component of a color.
34
+ #
35
+ # \{#mix mix($color1, $color2, \[$weight\])}
36
+ # : Mixes two colors together.
37
+ #
38
+ # ## HSL Functions
39
+ #
40
+ # \{#hsl hsl($hue, $saturation, $lightness)}
41
+ # : Creates a {Sass::Script::Value::Color Color} from hue, saturation, and
42
+ # lightness values.
43
+ #
44
+ # \{#hsla hsla($hue, $saturation, $lightness, $alpha)}
45
+ # : Creates a {Sass::Script::Value::Color Color} from hue, saturation,
46
+ # lightness, and alpha values.
47
+ #
48
+ # \{#hue hue($color)}
49
+ # : Gets the hue component of a color.
50
+ #
51
+ # \{#saturation saturation($color)}
52
+ # : Gets the saturation component of a color.
53
+ #
54
+ # \{#lightness lightness($color)}
55
+ # : Gets the lightness component of a color.
56
+ #
57
+ # \{#adjust_hue adjust-hue($color, $degrees)}
58
+ # : Changes the hue of a color.
59
+ #
60
+ # \{#lighten lighten($color, $amount)}
61
+ # : Makes a color lighter.
62
+ #
63
+ # \{#darken darken($color, $amount)}
64
+ # : Makes a color darker.
65
+ #
66
+ # \{#saturate saturate($color, $amount)}
67
+ # : Makes a color more saturated.
68
+ #
69
+ # \{#desaturate desaturate($color, $amount)}
70
+ # : Makes a color less saturated.
71
+ #
72
+ # \{#grayscale grayscale($color)}
73
+ # : Converts a color to grayscale.
74
+ #
75
+ # \{#complement complement($color)}
76
+ # : Returns the complement of a color.
77
+ #
78
+ # \{#invert invert($color, \[$weight\])}
79
+ # : Returns the inverse of a color.
80
+ #
81
+ # ## Opacity Functions
82
+ #
83
+ # \{#alpha alpha($color)} / \{#opacity opacity($color)}
84
+ # : Gets the alpha component (opacity) of a color.
85
+ #
86
+ # \{#rgba rgba($color, $alpha)}
87
+ # : Changes the alpha component for a color.
88
+ #
89
+ # \{#opacify opacify($color, $amount)} / \{#fade_in fade-in($color, $amount)}
90
+ # : Makes a color more opaque.
91
+ #
92
+ # \{#transparentize transparentize($color, $amount)} / \{#fade_out fade-out($color, $amount)}
93
+ # : Makes a color more transparent.
94
+ #
95
+ # ## Other Color Functions
96
+ #
97
+ # \{#adjust_color adjust-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\])}
98
+ # : Increases or decreases one or more components of a color.
99
+ #
100
+ # \{#scale_color scale-color($color, \[$red\], \[$green\], \[$blue\], \[$saturation\], \[$lightness\], \[$alpha\])}
101
+ # : Fluidly scales one or more properties of a color.
102
+ #
103
+ # \{#change_color change-color($color, \[$red\], \[$green\], \[$blue\], \[$hue\], \[$saturation\], \[$lightness\], \[$alpha\])}
104
+ # : Changes one or more properties of a color.
105
+ #
106
+ # \{#ie_hex_str ie-hex-str($color)}
107
+ # : Converts a color into the format understood by IE filters.
108
+ #
109
+ # ## String Functions
110
+ #
111
+ # \{#unquote unquote($string)}
112
+ # : Removes quotes from a string.
113
+ #
114
+ # \{#quote quote($string)}
115
+ # : Adds quotes to a string.
116
+ #
117
+ # \{#str_length str-length($string)}
118
+ # : Returns the number of characters in a string.
119
+ #
120
+ # \{#str_insert str-insert($string, $insert, $index)}
121
+ # : Inserts `$insert` into `$string` at `$index`.
122
+ #
123
+ # \{#str_index str-index($string, $substring)}
124
+ # : Returns the index of the first occurrence of `$substring` in `$string`.
125
+ #
126
+ # \{#str_slice str-slice($string, $start-at, [$end-at])}
127
+ # : Extracts a substring from `$string`.
128
+ #
129
+ # \{#to_upper_case to-upper-case($string)}
130
+ # : Converts a string to upper case.
131
+ #
132
+ # \{#to_lower_case to-lower-case($string)}
133
+ # : Converts a string to lower case.
134
+ #
135
+ # ## Number Functions
136
+ #
137
+ # \{#percentage percentage($number)}
138
+ # : Converts a unitless number to a percentage.
139
+ #
140
+ # \{#round round($number)}
141
+ # : Rounds a number to the nearest whole number.
142
+ #
143
+ # \{#ceil ceil($number)}
144
+ # : Rounds a number up to the next whole number.
145
+ #
146
+ # \{#floor floor($number)}
147
+ # : Rounds a number down to the previous whole number.
148
+ #
149
+ # \{#abs abs($number)}
150
+ # : Returns the absolute value of a number.
151
+ #
152
+ # \{#min min($numbers...)\}
153
+ # : Finds the minimum of several numbers.
154
+ #
155
+ # \{#max max($numbers...)\}
156
+ # : Finds the maximum of several numbers.
157
+ #
158
+ # \{#random random([$limit])\}
159
+ # : Returns a random number.
160
+ #
161
+ # ## List Functions {#list-functions}
162
+ #
163
+ # Lists in Sass are immutable; all list functions return a new list rather
164
+ # than updating the existing list in-place.
165
+ #
166
+ # All list functions work for maps as well, treating them as lists of pairs.
167
+ #
168
+ # \{#length length($list)}
169
+ # : Returns the length of a list.
170
+ #
171
+ # \{#nth nth($list, $n)}
172
+ # : Returns a specific item in a list.
173
+ #
174
+ # \{#set-nth set-nth($list, $n, $value)}
175
+ # : Replaces the nth item in a list.
176
+ #
177
+ # \{#join join($list1, $list2, \[$separator, $bracketed\])}
178
+ # : Joins together two lists into one.
179
+ #
180
+ # \{#append append($list1, $val, \[$separator\])}
181
+ # : Appends a single value onto the end of a list.
182
+ #
183
+ # \{#zip zip($lists...)}
184
+ # : Combines several lists into a single multidimensional list.
185
+ #
186
+ # \{#index index($list, $value)}
187
+ # : Returns the position of a value within a list.
188
+ #
189
+ # \{#list_separator list-separator($list)}
190
+ # : Returns the separator of a list.
191
+ #
192
+ # \{#is_bracketed is-bracketed($list)}
193
+ # : Returns whether a list has square brackets.
194
+ #
195
+ # ## Map Functions {#map-functions}
196
+ #
197
+ # Maps in Sass are immutable; all map functions return a new map rather than
198
+ # updating the existing map in-place.
199
+ #
200
+ # \{#map_get map-get($map, $key)}
201
+ # : Returns the value in a map associated with a given key.
202
+ #
203
+ # \{#map_merge map-merge($map1, $map2)}
204
+ # : Merges two maps together into a new map.
205
+ #
206
+ # \{#map_remove map-remove($map, $keys...)}
207
+ # : Returns a new map with keys removed.
208
+ #
209
+ # \{#map_keys map-keys($map)}
210
+ # : Returns a list of all keys in a map.
211
+ #
212
+ # \{#map_values map-values($map)}
213
+ # : Returns a list of all values in a map.
214
+ #
215
+ # \{#map_has_key map-has-key($map, $key)}
216
+ # : Returns whether a map has a value associated with a given key.
217
+ #
218
+ # \{#keywords keywords($args)}
219
+ # : Returns the keywords passed to a function that takes variable arguments.
220
+ #
221
+ # ## Selector Functions
222
+ #
223
+ # Selector functions are very liberal in the formats they support
224
+ # for selector arguments. They can take a plain string, a list of
225
+ # lists as returned by `&` or anything in between:
226
+ #
227
+ # * A plain string, such as `".foo .bar, .baz .bang"`.
228
+ # * A space-separated list of strings such as `(".foo" ".bar")`.
229
+ # * A comma-separated list of strings such as `(".foo .bar", ".baz .bang")`.
230
+ # * A comma-separated list of space-separated lists of strings such
231
+ # as `((".foo" ".bar"), (".baz" ".bang"))`.
232
+ #
233
+ # In general, selector functions allow placeholder selectors
234
+ # (`%foo`) but disallow parent-reference selectors (`&`).
235
+ #
236
+ # \{#selector_nest selector-nest($selectors...)}
237
+ # : Nests selector beneath one another like they would be nested in the
238
+ # stylesheet.
239
+ #
240
+ # \{#selector_append selector-append($selectors...)}
241
+ # : Appends selectors to one another without spaces in between.
242
+ #
243
+ # \{#selector_extend selector-extend($selector, $extendee, $extender)}
244
+ # : Extends `$extendee` with `$extender` within `$selector`.
245
+ #
246
+ # \{#selector_replace selector-replace($selector, $original, $replacement)}
247
+ # : Replaces `$original` with `$replacement` within `$selector`.
248
+ #
249
+ # \{#selector_unify selector-unify($selector1, $selector2)}
250
+ # : Unifies two selectors to produce a selector that matches
251
+ # elements matched by both.
252
+ #
253
+ # \{#is_superselector is-superselector($super, $sub)}
254
+ # : Returns whether `$super` matches all the elements `$sub` does, and
255
+ # possibly more.
256
+ #
257
+ # \{#simple_selectors simple-selectors($selector)}
258
+ # : Returns the simple selectors that comprise a compound selector.
259
+ #
260
+ # \{#selector_parse selector-parse($selector)}
261
+ # : Parses a selector into the format returned by `&`.
262
+ #
263
+ # ## Introspection Functions
264
+ #
265
+ # \{#feature_exists feature-exists($feature)}
266
+ # : Returns whether a feature exists in the current Sass runtime.
267
+ #
268
+ # \{#variable_exists variable-exists($name)}
269
+ # : Returns whether a variable with the given name exists in the current scope.
270
+ #
271
+ # \{#global_variable_exists global-variable-exists($name)}
272
+ # : Returns whether a variable with the given name exists in the global scope.
273
+ #
274
+ # \{#function_exists function-exists($name)}
275
+ # : Returns whether a function with the given name exists.
276
+ #
277
+ # \{#mixin_exists mixin-exists($name)}
278
+ # : Returns whether a mixin with the given name exists.
279
+ #
280
+ # \{#content_exists content-exists()}
281
+ # : Returns whether the current mixin was passed a content block.
282
+ #
283
+ # \{#inspect inspect($value)}
284
+ # : Returns the string representation of a value as it would be represented in Sass.
285
+ #
286
+ # \{#type_of type-of($value)}
287
+ # : Returns the type of a value.
288
+ #
289
+ # \{#unit unit($number)}
290
+ # : Returns the unit(s) associated with a number.
291
+ #
292
+ # \{#unitless unitless($number)}
293
+ # : Returns whether a number has units.
294
+ #
295
+ # \{#comparable comparable($number1, $number2)}
296
+ # : Returns whether two numbers can be added, subtracted, or compared.
297
+ #
298
+ # \{#call call($function, $args...)}
299
+ # : Dynamically calls a Sass function reference returned by `get-function`.
300
+ #
301
+ # \{#get_function get-function($name, $css: false)}
302
+ # : Looks up a function with the given name in the current lexical scope
303
+ # and returns a reference to it.
304
+ #
305
+ # ## Miscellaneous Functions
306
+ #
307
+ # \{#if if($condition, $if-true, $if-false)}
308
+ # : Returns one of two values, depending on whether or not `$condition` is
309
+ # true.
310
+ #
311
+ # \{#unique_id unique-id()}
312
+ # : Returns a unique CSS identifier.
313
+ #
314
+ # ## Adding Custom Functions
315
+ #
316
+ # New Sass functions can be added by adding Ruby methods to this module.
317
+ # For example:
318
+ #
319
+ # module Sass::Script::Functions
320
+ # def reverse(string)
321
+ # assert_type string, :String
322
+ # Sass::Script::Value::String.new(string.value.reverse)
323
+ # end
324
+ # declare :reverse, [:string]
325
+ # end
326
+ #
327
+ # Calling {declare} tells Sass the argument names for your function.
328
+ # If omitted, the function will still work, but will not be able to accept keyword arguments.
329
+ # {declare} can also allow your function to take arbitrary keyword arguments.
330
+ #
331
+ # There are a few things to keep in mind when modifying this module.
332
+ # First of all, the arguments passed are {Value} objects.
333
+ # Value objects are also expected to be returned.
334
+ # This means that Ruby values must be unwrapped and wrapped.
335
+ #
336
+ # Most Value objects support the {Value::Base#value value} accessor for getting
337
+ # their Ruby values. Color objects, though, must be accessed using
338
+ # {Sass::Script::Value::Color#rgb rgb}, {Sass::Script::Value::Color#red red},
339
+ # {Sass::Script::Value::Color#blue green}, or {Sass::Script::Value::Color#blue
340
+ # blue}.
341
+ #
342
+ # Second, making Ruby functions accessible from Sass introduces the temptation
343
+ # to do things like database access within stylesheets.
344
+ # This is generally a bad idea;
345
+ # since Sass files are by default only compiled once,
346
+ # dynamic code is not a great fit.
347
+ #
348
+ # If you really, really need to compile Sass on each request,
349
+ # first make sure you have adequate caching set up.
350
+ # Then you can use {Sass::Engine} to render the code,
351
+ # using the {file:SASS_REFERENCE.md#custom-option `options` parameter}
352
+ # to pass in data that {EvaluationContext#options can be accessed}
353
+ # from your Sass functions.
354
+ #
355
+ # Within one of the functions in this module,
356
+ # methods of {EvaluationContext} can be used.
357
+ #
358
+ # ### Caveats
359
+ #
360
+ # When creating new {Value} objects within functions, be aware that it's not
361
+ # safe to call {Value::Base#to_s #to_s} (or other methods that use the string
362
+ # representation) on those objects without first setting {Tree::Node#options=
363
+ # the #options attribute}.
364
+ #
365
+ module Functions
366
+ @signatures = {}
367
+
368
+ # A class representing a Sass function signature.
369
+ #
370
+ # @attr args [Array<String>] The names of the arguments to the function.
371
+ # @attr delayed_args [Array<String>] The names of the arguments whose evaluation should be
372
+ # delayed.
373
+ # @attr var_args [Boolean] Whether the function takes a variable number of arguments.
374
+ # @attr var_kwargs [Boolean] Whether the function takes an arbitrary set of keyword arguments.
375
+ Signature = Struct.new(:args, :delayed_args, :var_args, :var_kwargs, :deprecated)
376
+
377
+ # Declare a Sass signature for a Ruby-defined function.
378
+ # This includes the names of the arguments,
379
+ # whether the function takes a variable number of arguments,
380
+ # and whether the function takes an arbitrary set of keyword arguments.
381
+ #
382
+ # It's not necessary to declare a signature for a function.
383
+ # However, without a signature it won't support keyword arguments.
384
+ #
385
+ # A single function can have multiple signatures declared
386
+ # as long as each one takes a different number of arguments.
387
+ # It's also possible to declare multiple signatures
388
+ # that all take the same number of arguments,
389
+ # but none of them but the first will be used
390
+ # unless the user uses keyword arguments.
391
+ #
392
+ # @example
393
+ # declare :rgba, [:hex, :alpha]
394
+ # declare :rgba, [:red, :green, :blue, :alpha]
395
+ # declare :accepts_anything, [], :var_args => true, :var_kwargs => true
396
+ # declare :some_func, [:foo, :bar, :baz], :var_kwargs => true
397
+ #
398
+ # @param method_name [Symbol] The name of the method
399
+ # whose signature is being declared.
400
+ # @param args [Array<Symbol>] The names of the arguments for the function signature.
401
+ # @option options :var_args [Boolean] (false)
402
+ # Whether the function accepts a variable number of (unnamed) arguments
403
+ # in addition to the named arguments.
404
+ # @option options :var_kwargs [Boolean] (false)
405
+ # Whether the function accepts other keyword arguments
406
+ # in addition to those in `:args`.
407
+ # If this is true, the Ruby function will be passed a hash from strings
408
+ # to {Value}s as the last argument.
409
+ # In addition, if this is true and `:var_args` is not,
410
+ # Sass will ensure that the last argument passed is a hash.
411
+ def self.declare(method_name, args, options = {})
412
+ delayed_args = []
413
+ args = args.map do |a|
414
+ a = a.to_s
415
+ if a[0] == ?&
416
+ a = a[1..-1]
417
+ delayed_args << a
418
+ end
419
+ a
420
+ end
421
+ # We don't expose this functionality except to certain builtin methods.
422
+ if delayed_args.any? && method_name != :if
423
+ raise ArgumentError.new("Delayed arguments are not allowed for method #{method_name}")
424
+ end
425
+ @signatures[method_name] ||= []
426
+ @signatures[method_name] << Signature.new(
427
+ args,
428
+ delayed_args,
429
+ options[:var_args],
430
+ options[:var_kwargs],
431
+ options[:deprecated] && options[:deprecated].map {|a| a.to_s})
432
+ end
433
+
434
+ # Determine the correct signature for the number of arguments
435
+ # passed in for a given function.
436
+ # If no signatures match, the first signature is returned for error messaging.
437
+ #
438
+ # @param method_name [Symbol] The name of the Ruby function to be called.
439
+ # @param arg_arity [Integer] The number of unnamed arguments the function was passed.
440
+ # @param kwarg_arity [Integer] The number of keyword arguments the function was passed.
441
+ #
442
+ # @return [{Symbol => Object}, nil]
443
+ # The signature options for the matching signature,
444
+ # or nil if no signatures are declared for this function. See {declare}.
445
+ def self.signature(method_name, arg_arity, kwarg_arity)
446
+ return unless @signatures[method_name]
447
+ @signatures[method_name].each do |signature|
448
+ sig_arity = signature.args.size
449
+ return signature if sig_arity == arg_arity + kwarg_arity
450
+ next unless sig_arity < arg_arity + kwarg_arity
451
+
452
+ # We have enough args.
453
+ # Now we need to figure out which args are varargs
454
+ # and if the signature allows them.
455
+ t_arg_arity, t_kwarg_arity = arg_arity, kwarg_arity
456
+ if sig_arity > t_arg_arity
457
+ # we transfer some kwargs arity to args arity
458
+ # if it does not have enough args -- assuming the names will work out.
459
+ t_kwarg_arity -= (sig_arity - t_arg_arity)
460
+ t_arg_arity = sig_arity
461
+ end
462
+
463
+ if (t_arg_arity == sig_arity || t_arg_arity > sig_arity && signature.var_args) &&
464
+ (t_kwarg_arity == 0 || t_kwarg_arity > 0 && signature.var_kwargs)
465
+ return signature
466
+ end
467
+ end
468
+ @signatures[method_name].first
469
+ end
470
+
471
+ # Sets the random seed used by Sass's internal random number generator.
472
+ #
473
+ # This can be used to ensure consistent random number sequences which
474
+ # allows for consistent results when testing, etc.
475
+ #
476
+ # @param seed [Integer]
477
+ # @return [Integer] The same seed.
478
+ def self.random_seed=(seed)
479
+ @random_number_generator = Random.new(seed)
480
+ end
481
+
482
+ # Get Sass's internal random number generator.
483
+ #
484
+ # @return [Random]
485
+ def self.random_number_generator
486
+ @random_number_generator ||= Random.new
487
+ end
488
+
489
+ # The context in which methods in {Script::Functions} are evaluated.
490
+ # That means that all instance methods of {EvaluationContext}
491
+ # are available to use in functions.
492
+ class EvaluationContext
493
+ include Functions
494
+ include Value::Helpers
495
+
496
+ # The human-readable names for [Sass::Script::Value::Base]. The default is
497
+ # just the downcased name of the type.
498
+ TYPE_NAMES = {:ArgList => 'variable argument list'}
499
+
500
+ # The environment for this function. This environment's
501
+ # {Environment#parent} is the global environment, and its
502
+ # {Environment#caller} is a read-only view of the local environment of the
503
+ # caller of this function.
504
+ #
505
+ # @return [Environment]
506
+ attr_reader :environment
507
+
508
+ # The options hash for the {Sass::Engine} that is processing the function call
509
+ #
510
+ # @return [{Symbol => Object}]
511
+ attr_reader :options
512
+
513
+ # @param environment [Environment] See \{#environment}
514
+ def initialize(environment)
515
+ @environment = environment
516
+ @options = environment.options
517
+ end
518
+
519
+ # Asserts that the type of a given SassScript value
520
+ # is the expected type (designated by a symbol).
521
+ #
522
+ # Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
523
+ # Note that `:String` will match both double-quoted strings
524
+ # and unquoted identifiers.
525
+ #
526
+ # @example
527
+ # assert_type value, :String
528
+ # assert_type value, :Number
529
+ # @param value [Sass::Script::Value::Base] A SassScript value
530
+ # @param type [Symbol, Array<Symbol>] The name(s) of the type the value is expected to be
531
+ # @param name [String, Symbol, nil] The name of the argument.
532
+ # @raise [ArgumentError] if value is not of the correct type.
533
+ def assert_type(value, type, name = nil)
534
+ valid_types = Array(type)
535
+ found_type = valid_types.find do |t|
536
+ value.is_a?(Sass::Script::Value.const_get(t)) ||
537
+ t == :Map && value.is_a?(Sass::Script::Value::List) && value.value.empty?
538
+ end
539
+
540
+ if found_type
541
+ value.check_deprecated_interp if found_type == :String
542
+ return
543
+ end
544
+
545
+ err = if valid_types.size == 1
546
+ "#{value.inspect} is not a #{TYPE_NAMES[type] || type.to_s.downcase}"
547
+ else
548
+ type_names = valid_types.map {|t| TYPE_NAMES[t] || t.to_s.downcase}
549
+ "#{value.inspect} is not any of #{type_names.join(', ')}"
550
+ end
551
+ err = "$#{name.to_s.tr('_', '-')}: " + err if name
552
+ raise ArgumentError.new(err)
553
+ end
554
+
555
+ # Asserts that the unit of the number is as expected.
556
+ #
557
+ # @example
558
+ # assert_unit number, "px"
559
+ # assert_unit number, nil
560
+ # @param number [Sass::Script::Value::Number] The number to be validated.
561
+ # @param unit [::String]
562
+ # The unit that the number must have.
563
+ # If nil, the number must be unitless.
564
+ # @param name [::String] The name of the parameter being validated.
565
+ # @raise [ArgumentError] if number is not of the correct unit or is not a number.
566
+ def assert_unit(number, unit, name = nil)
567
+ assert_type number, :Number, name
568
+ return if number.is_unit?(unit)
569
+ expectation = unit ? "have a unit of #{unit}" : "be unitless"
570
+ if name
571
+ raise ArgumentError.new("Expected $#{name} to #{expectation} but got #{number}")
572
+ else
573
+ raise ArgumentError.new("Expected #{number} to #{expectation}")
574
+ end
575
+ end
576
+
577
+ # Asserts that the value is an integer.
578
+ #
579
+ # @example
580
+ # assert_integer 2px
581
+ # assert_integer 2.5px
582
+ # => SyntaxError: "Expected 2.5px to be an integer"
583
+ # assert_integer 2.5px, "width"
584
+ # => SyntaxError: "Expected width to be an integer but got 2.5px"
585
+ # @param number [Sass::Script::Value::Base] The value to be validated.
586
+ # @param name [::String] The name of the parameter being validated.
587
+ # @raise [ArgumentError] if number is not an integer or is not a number.
588
+ def assert_integer(number, name = nil)
589
+ assert_type number, :Number, name
590
+ return if number.int?
591
+ if name
592
+ raise ArgumentError.new("Expected $#{name} to be an integer but got #{number}")
593
+ else
594
+ raise ArgumentError.new("Expected #{number} to be an integer")
595
+ end
596
+ end
597
+
598
+ # Performs a node that has been delayed for execution.
599
+ #
600
+ # @private
601
+ # @param node [Sass::Script::Tree::Node,
602
+ # Sass::Script::Value::Base] When this is a tree node, it's
603
+ # performed in the caller's environment. When it's a value
604
+ # (which can happen when the value had to be performed already
605
+ # -- like for a splat), it's returned as-is.
606
+ # @param env [Sass::Environment] The environment within which to perform the node.
607
+ # Defaults to the (read-only) environment of the caller.
608
+ def perform(node, env = environment.caller)
609
+ if node.is_a?(Sass::Script::Value::Base)
610
+ node
611
+ else
612
+ node.perform(env)
613
+ end
614
+ end
615
+ end
616
+
617
+ class << self
618
+ # Returns whether user function with a given name exists.
619
+ #
620
+ # @param function_name [String]
621
+ # @return [Boolean]
622
+ alias_method :callable?, :public_method_defined?
623
+
624
+ private
625
+
626
+ def include(*args)
627
+ r = super
628
+ # We have to re-include ourselves into EvaluationContext to work around
629
+ # an icky Ruby restriction.
630
+ EvaluationContext.send :include, self
631
+ r
632
+ end
633
+ end
634
+
635
+ # Creates a {Sass::Script::Value::Color Color} object from red, green, and
636
+ # blue values.
637
+ #
638
+ # @see #rgba
639
+ # @overload rgb($red, $green, $blue)
640
+ # @param $red [Sass::Script::Value::Number] The amount of red in the color.
641
+ # Must be between 0 and 255 inclusive, or between `0%` and `100%`
642
+ # inclusive
643
+ # @param $green [Sass::Script::Value::Number] The amount of green in the
644
+ # color. Must be between 0 and 255 inclusive, or between `0%` and `100%`
645
+ # inclusive
646
+ # @param $blue [Sass::Script::Value::Number] The amount of blue in the
647
+ # color. Must be between 0 and 255 inclusive, or between `0%` and `100%`
648
+ # inclusive
649
+ # @return [Sass::Script::Value::Color]
650
+ # @raise [ArgumentError] if any parameter is the wrong type or out of bounds
651
+ def rgb(*args)
652
+ # Support CSS Color Level 4 syntax: rgb(0% 100% 0% / 0.5)
653
+ if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
654
+ list = args[0]
655
+
656
+ if list.separator == :space
657
+ # New space-separated syntax
658
+ if list.value.length == 3
659
+ # rgb(0% 100% 0%) - simple space-separated without alpha
660
+ red, green, blue = list.value
661
+ # Check for special numbers (calc, var, etc.) and return as-is
662
+ if [red, green, blue].any? { |v| special_number?(v) || v.is_a?(Sass::Script::Value::String) }
663
+ return unquoted_string("rgb(#{list})")
664
+ end
665
+ return color_with_hex(red, green, blue)
666
+ elsif list.value.length == 2
667
+ # rgb(0% 100% 0% / 0.5) - space list with slash list for alpha
668
+ rgb_list, alpha_list = list.value
669
+ if rgb_list.is_a?(Sass::Script::Value::List) && rgb_list.separator == :space &&
670
+ alpha_list.is_a?(Sass::Script::Value::List) && alpha_list.separator == :slash &&
671
+ rgb_list.value.length == 3 && alpha_list.value.length == 1
672
+ red, green, blue = rgb_list.value
673
+ alpha = alpha_list.value[0]
674
+ # Check for special numbers and return as-is
675
+ if [red, green, blue, alpha].any? { |v| special_number?(v) || v.is_a?(Sass::Script::Value::String) }
676
+ return unquoted_string("rgb(#{list})")
677
+ end
678
+ return color_with_hex(red, green, blue, alpha)
679
+ end
680
+ end
681
+ end
682
+ return unquoted_string("rgb(#{list})")
683
+ end
684
+
685
+ # Legacy comma-separated syntax
686
+ red, green, blue = args
687
+ if green.nil?
688
+ return unquoted_string("rgb(#{red})") if var?(red)
689
+ raise ArgumentError.new("wrong number of arguments (1 for 3)")
690
+ elsif blue.nil?
691
+ return unquoted_string("rgb(#{red}, #{green})") if var?(red) || var?(green)
692
+ raise ArgumentError.new("wrong number of arguments (2 for 3)")
693
+ end
694
+
695
+ if special_number?(red) || special_number?(green) || special_number?(blue)
696
+ return unquoted_string("rgb(#{red}, #{green}, #{blue})")
697
+ end
698
+ assert_type red, :Number, :red
699
+ assert_type green, :Number, :green
700
+ assert_type blue, :Number, :blue
701
+
702
+ color_attrs = [
703
+ percentage_or_unitless(red, 255, "red"),
704
+ percentage_or_unitless(green, 255, "green"),
705
+ percentage_or_unitless(blue, 255, "blue")
706
+ ]
707
+
708
+ # Don't store the string representation for function-created colors, both
709
+ # because it's not very useful and because some functions aren't supported
710
+ # on older browsers.
711
+ Sass::Script::Value::Color.new(color_attrs)
712
+ end
713
+ declare :rgb, [:red, :green, :blue]
714
+ declare :rgb, [:red, :green]
715
+ declare :rgb, [:red]
716
+ declare :rgb, [:channels]
717
+
718
+ # Creates a {Sass::Script::Value::Color Color} from red, green, blue, and
719
+ # alpha values.
720
+ # @see #rgb
721
+ #
722
+ # @overload rgba($red, $green, $blue, $alpha)
723
+ # @param $red [Sass::Script::Value::Number] The amount of red in the
724
+ # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive
725
+ # @param $green [Sass::Script::Value::Number] The amount of green in the
726
+ # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive
727
+ # @param $blue [Sass::Script::Value::Number] The amount of blue in the
728
+ # color. Must be between 0 and 255 inclusive or 0% and 100% inclusive
729
+ # @param $alpha [Sass::Script::Value::Number] The opacity of the color.
730
+ # Must be between 0 and 1 inclusive
731
+ # @return [Sass::Script::Value::Color]
732
+ # @raise [ArgumentError] if any parameter is the wrong type or out of
733
+ # bounds
734
+ #
735
+ # @overload rgba($color, $alpha)
736
+ # Sets the opacity of an existing color.
737
+ #
738
+ # @example
739
+ # rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5)
740
+ # rgba(blue, 0.2) => rgba(0, 0, 255, 0.2)
741
+ #
742
+ # @param $color [Sass::Script::Value::Color] The color whose opacity will
743
+ # be changed.
744
+ # @param $alpha [Sass::Script::Value::Number] The new opacity of the
745
+ # color. Must be between 0 and 1 inclusive
746
+ # @return [Sass::Script::Value::Color]
747
+ # @raise [ArgumentError] if `$alpha` is out of bounds or either parameter
748
+ # is the wrong type
749
+ def rgba(*args)
750
+ # Support CSS Color Level 4 syntax: rgba(0% 100% 0% / 0.5)
751
+ if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
752
+ list = args[0]
753
+
754
+ if list.separator == :space
755
+ # New space-separated syntax
756
+ if list.value.length == 3
757
+ # rgba(0% 100% 0%) - same as rgb without alpha
758
+ red, green, blue = list.value
759
+ # Check for special numbers and return as-is
760
+ if special_number?(red) || special_number?(green) || special_number?(blue)
761
+ return unquoted_string("rgba(#{list})")
762
+ end
763
+ return color_with_hex(red, green, blue)
764
+ elsif list.value.length == 2
765
+ # rgba(0% 100% 0% / 0.5) - space list with slash list for alpha
766
+ rgb_list, alpha_list = list.value
767
+ if rgb_list.is_a?(Sass::Script::Value::List) && rgb_list.separator == :space &&
768
+ alpha_list.is_a?(Sass::Script::Value::List) && alpha_list.separator == :slash &&
769
+ rgb_list.value.length == 3 && alpha_list.value.length == 1
770
+ red, green, blue = rgb_list.value
771
+ alpha = alpha_list.value[0]
772
+ # Check for special numbers and return as-is
773
+ if special_number?(red) || special_number?(green) || special_number?(blue) || special_number?(alpha)
774
+ return unquoted_string("rgba(#{list})")
775
+ end
776
+ return color_with_hex(red, green, blue, alpha)
777
+ end
778
+ end
779
+ end
780
+ return unquoted_string("rgba(#{list})")
781
+ end
782
+
783
+ # Legacy comma-separated syntax
784
+ case args.size
785
+ when 1
786
+ return unquoted_string("rgba(#{args.first})") if var?(args.first)
787
+ raise ArgumentError.new("wrong number of arguments (1 for 4)")
788
+ when 2
789
+ color, alpha = args
790
+
791
+ if var?(color)
792
+ return unquoted_string("rgba(#{color}, #{alpha})")
793
+ elsif var?(alpha)
794
+ if color.is_a?(Sass::Script::Value::Color)
795
+ return unquoted_string("rgba(#{color.red}, #{color.green}, #{color.blue}, #{alpha})")
796
+ else
797
+ return unquoted_string("rgba(#{color}, #{alpha})")
798
+ end
799
+ end
800
+
801
+ assert_type color, :Color, :color
802
+ if special_number?(alpha)
803
+ unquoted_string("rgba(#{color.red}, #{color.green}, #{color.blue}, #{alpha})")
804
+ else
805
+ assert_type alpha, :Number, :alpha
806
+ color.with(:alpha => percentage_or_unitless(alpha, 1, "alpha"))
807
+ end
808
+ when 3
809
+ if var?(args[0]) || var?(args[1]) || var?(args[2])
810
+ unquoted_string("rgba(#{args.join(', ')})")
811
+ else
812
+ raise ArgumentError.new("wrong number of arguments (3 for 4)")
813
+ end
814
+ when 4
815
+ red, green, blue, alpha = args
816
+ if special_number?(red) || special_number?(green) ||
817
+ special_number?(blue) || special_number?(alpha)
818
+ unquoted_string("rgba(#{red}, #{green}, #{blue}, #{alpha})")
819
+ else
820
+ rgba(rgb(red, green, blue), alpha)
821
+ end
822
+ else
823
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)")
824
+ end
825
+ end
826
+ declare :rgba, [:red, :green, :blue, :alpha]
827
+ declare :rgba, [:red, :green, :blue]
828
+ declare :rgba, [:color, :alpha]
829
+ declare :rgba, [:red]
830
+ declare :rgba, [:channels]
831
+
832
+ # Creates a {Sass::Script::Value::Color Color} from hue, saturation, and
833
+ # lightness values. Uses the algorithm from the [CSS3 spec][].
834
+ #
835
+ # [CSS3 spec]: http://www.w3.org/TR/css3-color/#hsl-color
836
+ #
837
+ # @see #hsla
838
+ # @overload hsl($hue, $saturation, $lightness)
839
+ # @param $hue [Sass::Script::Value::Number] The hue of the color. Should be
840
+ # between 0 and 360 degrees, inclusive
841
+ # @param $saturation [Sass::Script::Value::Number] The saturation of the
842
+ # color. Must be between `0%` and `100%`, inclusive
843
+ # @param $lightness [Sass::Script::Value::Number] The lightness of the
844
+ # color. Must be between `0%` and `100%`, inclusive
845
+ # @return [Sass::Script::Value::Color]
846
+ # @raise [ArgumentError] if `$saturation` or `$lightness` are out of bounds
847
+ # or any parameter is the wrong type
848
+ def hsl(*args)
849
+ # Support CSS Color Level 4 syntax: hsl(180 60% 50% / 0.5)
850
+ if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
851
+ list = args[0]
852
+
853
+ if list.separator == :space
854
+ # New space-separated syntax
855
+ if list.value.length == 3
856
+ # hsl(180 60% 50%) - simple space-separated without alpha
857
+ hue, saturation, lightness = list.value
858
+ return hsl(hue, saturation, lightness)
859
+ elsif list.value.length == 2
860
+ # hsl(180 60% 50% / 0.5) - space list with slash list for alpha
861
+ hsl_list, alpha_list = list.value
862
+ if hsl_list.is_a?(Sass::Script::Value::List) && hsl_list.separator == :space &&
863
+ alpha_list.is_a?(Sass::Script::Value::List) && alpha_list.separator == :slash &&
864
+ hsl_list.value.length == 3 && alpha_list.value.length == 1
865
+ hue, saturation, lightness = hsl_list.value
866
+ alpha = alpha_list.value[0]
867
+ return hsla(hue, saturation, lightness, alpha)
868
+ end
869
+ end
870
+ end
871
+ return unquoted_string("hsl(#{list})")
872
+ end
873
+
874
+ # Legacy comma-separated syntax
875
+ hue, saturation, lightness = args
876
+ if saturation.nil?
877
+ return unquoted_string("hsl(#{hue})") if var?(hue)
878
+ raise ArgumentError.new("wrong number of arguments (1 for 3)")
879
+ elsif lightness.nil?
880
+ return unquoted_string("hsl(#{hue}, #{saturation})") if var?(hue) || var?(saturation)
881
+ raise ArgumentError.new("wrong number of arguments (2 for 3)")
882
+ end
883
+
884
+ if special_number?(hue) || special_number?(saturation) || special_number?(lightness)
885
+ unquoted_string("hsl(#{hue}, #{saturation}, #{lightness})")
886
+ else
887
+ hsla(hue, saturation, lightness, number(1))
888
+ end
889
+ end
890
+ declare :hsl, [:hue, :saturation, :lightness]
891
+ declare :hsl, [:hue, :saturation]
892
+ declare :hsl, [:hue]
893
+ declare :hsl, [:channels]
894
+
895
+ # Creates a {Sass::Script::Value::Color Color} from hue,
896
+ # saturation, lightness, and alpha values. Uses the algorithm from
897
+ # the [CSS3 spec][].
898
+ #
899
+ # [CSS3 spec]: http://www.w3.org/TR/css3-color/#hsl-color
900
+ #
901
+ # @see #hsl
902
+ # @overload hsla($hue, $saturation, $lightness, $alpha)
903
+ # @param $hue [Sass::Script::Value::Number] The hue of the color. Should be
904
+ # between 0 and 360 degrees, inclusive
905
+ # @param $saturation [Sass::Script::Value::Number] The saturation of the
906
+ # color. Must be between `0%` and `100%`, inclusive
907
+ # @param $lightness [Sass::Script::Value::Number] The lightness of the
908
+ # color. Must be between `0%` and `100%`, inclusive
909
+ # @param $alpha [Sass::Script::Value::Number] The opacity of the color. Must
910
+ # be between 0 and 1, inclusive
911
+ # @return [Sass::Script::Value::Color]
912
+ # @raise [ArgumentError] if `$saturation`, `$lightness`, or `$alpha` are out
913
+ # of bounds or any parameter is the wrong type
914
+ def hsla(*args)
915
+ # Support CSS Color Level 4 syntax: hsla(180 60% 50% / 0.5)
916
+ if args.length == 1 && args[0].is_a?(Sass::Script::Value::List)
917
+ list = args[0]
918
+
919
+ if list.separator == :space
920
+ # New space-separated syntax
921
+ if list.value.length == 3
922
+ # hsla(180 60% 50%) - same as hsl without alpha
923
+ hue, saturation, lightness = list.value
924
+ return hsla(hue, saturation, lightness, number(1))
925
+ elsif list.value.length == 2
926
+ # hsla(180 60% 50% / 0.5) - space list with slash list for alpha
927
+ hsl_list, alpha_list = list.value
928
+ if hsl_list.is_a?(Sass::Script::Value::List) && hsl_list.separator == :space &&
929
+ alpha_list.is_a?(Sass::Script::Value::List) && alpha_list.separator == :slash &&
930
+ hsl_list.value.length == 3 && alpha_list.value.length == 1
931
+ hue, saturation, lightness = hsl_list.value
932
+ alpha = alpha_list.value[0]
933
+ return hsla(hue, saturation, lightness, alpha)
934
+ end
935
+ end
936
+ end
937
+ return unquoted_string("hsla(#{list})")
938
+ end
939
+
940
+ # Legacy comma-separated syntax
941
+ hue, saturation, lightness, alpha = args
942
+ if saturation.nil?
943
+ return unquoted_string("hsla(#{hue})") if var?(hue)
944
+ raise ArgumentError.new("wrong number of arguments (1 for 4)")
945
+ elsif lightness.nil?
946
+ return unquoted_string("hsla(#{hue}, #{saturation})") if var?(hue) || var?(saturation)
947
+ raise ArgumentError.new("wrong number of arguments (2 for 4)")
948
+ elsif alpha.nil?
949
+ if var?(hue) || var?(saturation) || var?(lightness)
950
+ return unquoted_string("hsla(#{hue}, #{saturation}, #{lightness})")
951
+ else
952
+ raise ArgumentError.new("wrong number of arguments (2 for 4)")
953
+ end
954
+ end
955
+
956
+ if special_number?(hue) || special_number?(saturation) ||
957
+ special_number?(lightness) || special_number?(alpha)
958
+ return unquoted_string("hsla(#{hue}, #{saturation}, #{lightness}, #{alpha})")
959
+ end
960
+ assert_type hue, :Number, :hue
961
+ assert_type saturation, :Number, :saturation
962
+ assert_type lightness, :Number, :lightness
963
+ assert_type alpha, :Number, :alpha
964
+
965
+ h = normalize_hue(hue)
966
+ s = saturation.value
967
+ l = lightness.value
968
+
969
+ # Don't store the string representation for function-created colors, both
970
+ # because it's not very useful and because some functions aren't supported
971
+ # on older browsers.
972
+ Sass::Script::Value::Color.new(
973
+ :hue => h, :saturation => s, :lightness => l,
974
+ :alpha => percentage_or_unitless(alpha, 1, "alpha"))
975
+ end
976
+ declare :hsla, [:hue, :saturation, :lightness, :alpha]
977
+ declare :hsla, [:hue, :saturation, :lightness]
978
+ declare :hsla, [:hue, :saturation]
979
+ declare :hsla, [:hue]
980
+ declare :hsla, [:channels]
981
+
982
+ # Gets the red component of a color. Calculated from HSL where necessary via
983
+ # [this algorithm][hsl-to-rgb].
984
+ #
985
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
986
+ #
987
+ # @overload red($color)
988
+ # @param $color [Sass::Script::Value::Color]
989
+ # @return [Sass::Script::Value::Number] The red component, between 0 and 255
990
+ # inclusive
991
+ # @raise [ArgumentError] if `$color` isn't a color
992
+ def red(color)
993
+ assert_type color, :Color, :color
994
+ number(color.red)
995
+ end
996
+ declare :red, [:color]
997
+
998
+ # Gets the green component of a color. Calculated from HSL where necessary
999
+ # via [this algorithm][hsl-to-rgb].
1000
+ #
1001
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
1002
+ #
1003
+ # @overload green($color)
1004
+ # @param $color [Sass::Script::Value::Color]
1005
+ # @return [Sass::Script::Value::Number] The green component, between 0 and
1006
+ # 255 inclusive
1007
+ # @raise [ArgumentError] if `$color` isn't a color
1008
+ def green(color)
1009
+ assert_type color, :Color, :color
1010
+ number(color.green)
1011
+ end
1012
+ declare :green, [:color]
1013
+
1014
+ # Gets the blue component of a color. Calculated from HSL where necessary
1015
+ # via [this algorithm][hsl-to-rgb].
1016
+ #
1017
+ # [hsl-to-rgb]: http://www.w3.org/TR/css3-color/#hsl-color
1018
+ #
1019
+ # @overload blue($color)
1020
+ # @param $color [Sass::Script::Value::Color]
1021
+ # @return [Sass::Script::Value::Number] The blue component, between 0 and
1022
+ # 255 inclusive
1023
+ # @raise [ArgumentError] if `$color` isn't a color
1024
+ def blue(color)
1025
+ assert_type color, :Color, :color
1026
+ number(color.blue)
1027
+ end
1028
+ declare :blue, [:color]
1029
+
1030
+ # Returns the hue component of a color. See [the CSS3 HSL
1031
+ # specification][hsl]. Calculated from RGB where necessary via [this
1032
+ # algorithm][rgb-to-hsl].
1033
+ #
1034
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
1035
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
1036
+ #
1037
+ # @overload hue($color)
1038
+ # @param $color [Sass::Script::Value::Color]
1039
+ # @return [Sass::Script::Value::Number] The hue component, between 0deg and
1040
+ # 360deg
1041
+ # @raise [ArgumentError] if `$color` isn't a color
1042
+ def hue(color)
1043
+ assert_type color, :Color, :color
1044
+ number(color.hue, "deg")
1045
+ end
1046
+ declare :hue, [:color]
1047
+
1048
+ # Returns the saturation component of a color. See [the CSS3 HSL
1049
+ # specification][hsl]. Calculated from RGB where necessary via [this
1050
+ # algorithm][rgb-to-hsl].
1051
+ #
1052
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
1053
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
1054
+ #
1055
+ # @overload saturation($color)
1056
+ # @param $color [Sass::Script::Value::Color]
1057
+ # @return [Sass::Script::Value::Number] The saturation component, between 0%
1058
+ # and 100%
1059
+ # @raise [ArgumentError] if `$color` isn't a color
1060
+ def saturation(color)
1061
+ assert_type color, :Color, :color
1062
+ number(color.saturation, "%")
1063
+ end
1064
+ declare :saturation, [:color]
1065
+
1066
+ # Returns the lightness component of a color. See [the CSS3 HSL
1067
+ # specification][hsl]. Calculated from RGB where necessary via [this
1068
+ # algorithm][rgb-to-hsl].
1069
+ #
1070
+ # [hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
1071
+ # [rgb-to-hsl]: http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
1072
+ #
1073
+ # @overload lightness($color)
1074
+ # @param $color [Sass::Script::Value::Color]
1075
+ # @return [Sass::Script::Value::Number] The lightness component, between 0%
1076
+ # and 100%
1077
+ # @raise [ArgumentError] if `$color` isn't a color
1078
+ def lightness(color)
1079
+ assert_type color, :Color, :color
1080
+ number(color.lightness, "%")
1081
+ end
1082
+ declare :lightness, [:color]
1083
+
1084
+ # Returns the alpha component (opacity) of a color. This is 1 unless
1085
+ # otherwise specified.
1086
+ #
1087
+ # This function also supports the proprietary Microsoft `alpha(opacity=20)`
1088
+ # syntax as a special case.
1089
+ #
1090
+ # @overload alpha($color)
1091
+ # @param $color [Sass::Script::Value::Color]
1092
+ # @return [Sass::Script::Value::Number] The alpha component, between 0 and 1
1093
+ # @raise [ArgumentError] if `$color` isn't a color
1094
+ def alpha(*args)
1095
+ if args.all? do |a|
1096
+ a.is_a?(Sass::Script::Value::String) && a.type == :identifier &&
1097
+ a.value =~ /^[a-zA-Z]+\s*=/
1098
+ end
1099
+ # Support the proprietary MS alpha() function
1100
+ return identifier("alpha(#{args.map {|a| a.to_s}.join(', ')})")
1101
+ end
1102
+
1103
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 1)") if args.size != 1
1104
+
1105
+ assert_type args.first, :Color, :color
1106
+ number(args.first.alpha)
1107
+ end
1108
+ declare :alpha, [:color]
1109
+
1110
+ # Returns the alpha component (opacity) of a color. This is 1 unless
1111
+ # otherwise specified.
1112
+ #
1113
+ # @overload opacity($color)
1114
+ # @param $color [Sass::Script::Value::Color]
1115
+ # @return [Sass::Script::Value::Number] The alpha component, between 0 and 1
1116
+ # @raise [ArgumentError] if `$color` isn't a color
1117
+ def opacity(color)
1118
+ if color.is_a?(Sass::Script::Value::Number)
1119
+ return identifier("opacity(#{color})")
1120
+ end
1121
+ assert_type color, :Color, :color
1122
+ number(color.alpha)
1123
+ end
1124
+ declare :opacity, [:color]
1125
+
1126
+ # Makes a color more opaque. Takes a color and a number between 0 and 1, and
1127
+ # returns a color with the opacity increased by that amount.
1128
+ #
1129
+ # @see #transparentize
1130
+ # @example
1131
+ # opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6)
1132
+ # opacify(rgba(0, 0, 17, 0.8), 0.2) => #001
1133
+ # @overload opacify($color, $amount)
1134
+ # @param $color [Sass::Script::Value::Color]
1135
+ # @param $amount [Sass::Script::Value::Number] The amount to increase the
1136
+ # opacity by, between 0 and 1
1137
+ # @return [Sass::Script::Value::Color]
1138
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
1139
+ # is the wrong type
1140
+ def opacify(color, amount)
1141
+ _adjust(color, amount, :alpha, 0..1, :+)
1142
+ end
1143
+ declare :opacify, [:color, :amount]
1144
+
1145
+ alias_method :fade_in, :opacify
1146
+ declare :fade_in, [:color, :amount]
1147
+
1148
+ # Makes a color more transparent. Takes a color and a number between 0 and
1149
+ # 1, and returns a color with the opacity decreased by that amount.
1150
+ #
1151
+ # @see #opacify
1152
+ # @example
1153
+ # transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4)
1154
+ # transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6)
1155
+ # @overload transparentize($color, $amount)
1156
+ # @param $color [Sass::Script::Value::Color]
1157
+ # @param $amount [Sass::Script::Value::Number] The amount to decrease the
1158
+ # opacity by, between 0 and 1
1159
+ # @return [Sass::Script::Value::Color]
1160
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
1161
+ # is the wrong type
1162
+ def transparentize(color, amount)
1163
+ _adjust(color, amount, :alpha, 0..1, :-)
1164
+ end
1165
+ declare :transparentize, [:color, :amount]
1166
+
1167
+ alias_method :fade_out, :transparentize
1168
+ declare :fade_out, [:color, :amount]
1169
+
1170
+ # Makes a color lighter. Takes a color and a number between `0%` and `100%`,
1171
+ # and returns a color with the lightness increased by that amount.
1172
+ #
1173
+ # @see #darken
1174
+ # @example
1175
+ # lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
1176
+ # lighten(#800, 20%) => #e00
1177
+ # @overload lighten($color, $amount)
1178
+ # @param $color [Sass::Script::Value::Color]
1179
+ # @param $amount [Sass::Script::Value::Number] The amount to increase the
1180
+ # lightness by, between `0%` and `100%`
1181
+ # @return [Sass::Script::Value::Color]
1182
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
1183
+ # is the wrong type
1184
+ def lighten(color, amount)
1185
+ _adjust(color, amount, :lightness, 0..100, :+, "%")
1186
+ end
1187
+ declare :lighten, [:color, :amount]
1188
+
1189
+ # Makes a color darker. Takes a color and a number between 0% and 100%, and
1190
+ # returns a color with the lightness decreased by that amount.
1191
+ #
1192
+ # @see #lighten
1193
+ # @example
1194
+ # darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
1195
+ # darken(#800, 20%) => #200
1196
+ # @overload darken($color, $amount)
1197
+ # @param $color [Sass::Script::Value::Color]
1198
+ # @param $amount [Sass::Script::Value::Number] The amount to decrease the
1199
+ # lightness by, between `0%` and `100%`
1200
+ # @return [Sass::Script::Value::Color]
1201
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
1202
+ # is the wrong type
1203
+ def darken(color, amount)
1204
+ _adjust(color, amount, :lightness, 0..100, :-, "%")
1205
+ end
1206
+ declare :darken, [:color, :amount]
1207
+
1208
+ # Makes a color more saturated. Takes a color and a number between 0% and
1209
+ # 100%, and returns a color with the saturation increased by that amount.
1210
+ #
1211
+ # @see #desaturate
1212
+ # @example
1213
+ # saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%)
1214
+ # saturate(#855, 20%) => #9e3f3f
1215
+ # @overload saturate($color, $amount)
1216
+ # @param $color [Sass::Script::Value::Color]
1217
+ # @param $amount [Sass::Script::Value::Number] The amount to increase the
1218
+ # saturation by, between `0%` and `100%`
1219
+ # @return [Sass::Script::Value::Color]
1220
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
1221
+ # is the wrong type
1222
+ def saturate(color, amount = nil)
1223
+ # Support the filter effects definition of saturate.
1224
+ # https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
1225
+ return identifier("saturate(#{color})") if amount.nil?
1226
+ _adjust(color, amount, :saturation, 0..100, :+, "%")
1227
+ end
1228
+ declare :saturate, [:color, :amount]
1229
+ declare :saturate, [:amount]
1230
+
1231
+ # Makes a color less saturated. Takes a color and a number between 0% and
1232
+ # 100%, and returns a color with the saturation decreased by that value.
1233
+ #
1234
+ # @see #saturate
1235
+ # @example
1236
+ # desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%)
1237
+ # desaturate(#855, 20%) => #726b6b
1238
+ # @overload desaturate($color, $amount)
1239
+ # @param $color [Sass::Script::Value::Color]
1240
+ # @param $amount [Sass::Script::Value::Number] The amount to decrease the
1241
+ # saturation by, between `0%` and `100%`
1242
+ # @return [Sass::Script::Value::Color]
1243
+ # @raise [ArgumentError] if `$amount` is out of bounds, or either parameter
1244
+ # is the wrong type
1245
+ def desaturate(color, amount)
1246
+ _adjust(color, amount, :saturation, 0..100, :-, "%")
1247
+ end
1248
+ declare :desaturate, [:color, :amount]
1249
+
1250
+ # Changes the hue of a color. Takes a color and a number of degrees (usually
1251
+ # between `-360deg` and `360deg`), and returns a color with the hue rotated
1252
+ # along the color wheel by that amount.
1253
+ #
1254
+ # @example
1255
+ # adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%)
1256
+ # adjust-hue(hsl(120, 30%, 90%), -60deg) => hsl(60, 30%, 90%)
1257
+ # adjust-hue(#811, 45deg) => #886a11
1258
+ # @overload adjust_hue($color, $degrees)
1259
+ # @param $color [Sass::Script::Value::Color]
1260
+ # @param $degrees [Sass::Script::Value::Number] The number of degrees to
1261
+ # rotate the hue
1262
+ # @return [Sass::Script::Value::Color]
1263
+ # @raise [ArgumentError] if either parameter is the wrong type
1264
+ def adjust_hue(color, degrees)
1265
+ assert_type color, :Color, :color
1266
+ assert_type degrees, :Number, :degrees
1267
+ color.with(:hue => color.hue + degrees.value)
1268
+ end
1269
+ declare :adjust_hue, [:color, :degrees]
1270
+
1271
+ # Converts a color into the format understood by IE filters.
1272
+ #
1273
+ # @example
1274
+ # ie-hex-str(#abc) => #FFAABBCC
1275
+ # ie-hex-str(#3322BB) => #FF3322BB
1276
+ # ie-hex-str(rgba(0, 255, 0, 0.5)) => #8000FF00
1277
+ # @overload ie_hex_str($color)
1278
+ # @param $color [Sass::Script::Value::Color]
1279
+ # @return [Sass::Script::Value::String] The IE-formatted string
1280
+ # representation of the color
1281
+ # @raise [ArgumentError] if `$color` isn't a color
1282
+ def ie_hex_str(color)
1283
+ assert_type color, :Color, :color
1284
+ alpha = Sass::Util.round(color.alpha * 255).to_s(16).rjust(2, '0')
1285
+ identifier("##{alpha}#{color.send(:hex_str)[1..-1]}".upcase)
1286
+ end
1287
+ declare :ie_hex_str, [:color]
1288
+
1289
+ # Increases or decreases one or more properties of a color. This can change
1290
+ # the red, green, blue, hue, saturation, value, and alpha properties. The
1291
+ # properties are specified as keyword arguments, and are added to or
1292
+ # subtracted from the color's current value for that property.
1293
+ #
1294
+ # All properties are optional. You can't specify both RGB properties
1295
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$hue`, `$saturation`,
1296
+ # `$value`) at the same time.
1297
+ #
1298
+ # @example
1299
+ # adjust-color(#102030, $blue: 5) => #102035
1300
+ # adjust-color(#102030, $red: -5, $blue: 5) => #0b2035
1301
+ # adjust-color(hsl(25, 100%, 80%), $lightness: -30%, $alpha: -0.4) => hsla(25, 100%, 50%, 0.6)
1302
+ # @overload adjust_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])
1303
+ # @param $color [Sass::Script::Value::Color]
1304
+ # @param $red [Sass::Script::Value::Number] The adjustment to make on the
1305
+ # red component, between -255 and 255 inclusive
1306
+ # @param $green [Sass::Script::Value::Number] The adjustment to make on the
1307
+ # green component, between -255 and 255 inclusive
1308
+ # @param $blue [Sass::Script::Value::Number] The adjustment to make on the
1309
+ # blue component, between -255 and 255 inclusive
1310
+ # @param $hue [Sass::Script::Value::Number] The adjustment to make on the
1311
+ # hue component, in degrees
1312
+ # @param $saturation [Sass::Script::Value::Number] The adjustment to make on
1313
+ # the saturation component, between `-100%` and `100%` inclusive
1314
+ # @param $lightness [Sass::Script::Value::Number] The adjustment to make on
1315
+ # the lightness component, between `-100%` and `100%` inclusive
1316
+ # @param $alpha [Sass::Script::Value::Number] The adjustment to make on the
1317
+ # alpha component, between -1 and 1 inclusive
1318
+ # @return [Sass::Script::Value::Color]
1319
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
1320
+ # bounds, or if RGB properties and HSL properties are adjusted at the
1321
+ # same time
1322
+ def adjust_color(color, kwargs)
1323
+ assert_type color, :Color, :color
1324
+ with = Sass::Util.map_hash(
1325
+ "red" => [-255..255, ""],
1326
+ "green" => [-255..255, ""],
1327
+ "blue" => [-255..255, ""],
1328
+ "hue" => nil,
1329
+ "saturation" => [-100..100, "%"],
1330
+ "lightness" => [-100..100, "%"],
1331
+ "alpha" => [-1..1, ""]
1332
+ ) do |name, (range, units)|
1333
+ val = kwargs.delete(name)
1334
+ next unless val
1335
+ assert_type val, :Number, name
1336
+ Sass::Util.check_range("$#{name}: Amount", range, val, units) if range
1337
+ adjusted = color.send(name) + val.value
1338
+ adjusted = [0, Sass::Util.restrict(adjusted, range)].max if range
1339
+ [name.to_sym, adjusted]
1340
+ end
1341
+
1342
+ unless kwargs.empty?
1343
+ name, val = kwargs.to_a.first
1344
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
1345
+ end
1346
+
1347
+ color.with(with)
1348
+ end
1349
+ declare :adjust_color, [:color], :var_kwargs => true
1350
+
1351
+ # Fluidly scales one or more properties of a color. Unlike
1352
+ # \{#adjust_color adjust-color}, which changes a color's properties by fixed
1353
+ # amounts, \{#scale_color scale-color} fluidly changes them based on how
1354
+ # high or low they already are. That means that lightening an already-light
1355
+ # color with \{#scale_color scale-color} won't change the lightness much,
1356
+ # but lightening a dark color by the same amount will change it more
1357
+ # dramatically. This has the benefit of making `scale-color($color, ...)`
1358
+ # have a similar effect regardless of what `$color` is.
1359
+ #
1360
+ # For example, the lightness of a color can be anywhere between `0%` and
1361
+ # `100%`. If `scale-color($color, $lightness: 40%)` is called, the resulting
1362
+ # color's lightness will be 40% of the way between its original lightness
1363
+ # and 100. If `scale-color($color, $lightness: -40%)` is called instead, the
1364
+ # lightness will be 40% of the way between the original and 0.
1365
+ #
1366
+ # This can change the red, green, blue, saturation, value, and alpha
1367
+ # properties. The properties are specified as keyword arguments. All
1368
+ # arguments should be percentages between `0%` and `100%`.
1369
+ #
1370
+ # All properties are optional. You can't specify both RGB properties
1371
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$saturation`, `$value`)
1372
+ # at the same time.
1373
+ #
1374
+ # @example
1375
+ # scale-color(hsl(120, 70%, 80%), $lightness: 50%) => hsl(120, 70%, 90%)
1376
+ # scale-color(rgb(200, 150%, 170%), $green: -40%, $blue: 70%) => rgb(200, 90, 229)
1377
+ # scale-color(hsl(200, 70%, 80%), $saturation: -90%, $alpha: -30%) => hsla(200, 7%, 80%, 0.7)
1378
+ # @overload scale_color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha])
1379
+ # @param $color [Sass::Script::Value::Color]
1380
+ # @param $red [Sass::Script::Value::Number]
1381
+ # @param $green [Sass::Script::Value::Number]
1382
+ # @param $blue [Sass::Script::Value::Number]
1383
+ # @param $saturation [Sass::Script::Value::Number]
1384
+ # @param $lightness [Sass::Script::Value::Number]
1385
+ # @param $alpha [Sass::Script::Value::Number]
1386
+ # @return [Sass::Script::Value::Color]
1387
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
1388
+ # bounds, or if RGB properties and HSL properties are adjusted at the
1389
+ # same time
1390
+ def scale_color(color, kwargs)
1391
+ assert_type color, :Color, :color
1392
+ with = Sass::Util.map_hash(
1393
+ "red" => 255,
1394
+ "green" => 255,
1395
+ "blue" => 255,
1396
+ "saturation" => 100,
1397
+ "lightness" => 100,
1398
+ "alpha" => 1
1399
+ ) do |name, max|
1400
+ val = kwargs.delete(name)
1401
+ next unless val
1402
+ assert_type val, :Number, name
1403
+ assert_unit val, '%', name
1404
+ Sass::Util.check_range("$#{name}: Amount", -100..100, val, '%')
1405
+
1406
+ current = color.send(name)
1407
+ scale = val.value / 100.0
1408
+ diff = scale > 0 ? max - current : current
1409
+ [name.to_sym, current + diff * scale]
1410
+ end
1411
+
1412
+ unless kwargs.empty?
1413
+ name, val = kwargs.to_a.first
1414
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
1415
+ end
1416
+
1417
+ color.with(with)
1418
+ end
1419
+ declare :scale_color, [:color], :var_kwargs => true
1420
+
1421
+ # Changes one or more properties of a color. This can change the red, green,
1422
+ # blue, hue, saturation, value, and alpha properties. The properties are
1423
+ # specified as keyword arguments, and replace the color's current value for
1424
+ # that property.
1425
+ #
1426
+ # All properties are optional. You can't specify both RGB properties
1427
+ # (`$red`, `$green`, `$blue`) and HSL properties (`$hue`, `$saturation`,
1428
+ # `$value`) at the same time.
1429
+ #
1430
+ # @example
1431
+ # change-color(#102030, $blue: 5) => #102005
1432
+ # change-color(#102030, $red: 120, $blue: 5) => #782005
1433
+ # change-color(hsl(25, 100%, 80%), $lightness: 40%, $alpha: 0.8) => hsla(25, 100%, 40%, 0.8)
1434
+ # @overload change_color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])
1435
+ # @param $color [Sass::Script::Value::Color]
1436
+ # @param $red [Sass::Script::Value::Number] The new red component for the
1437
+ # color, within 0 and 255 inclusive
1438
+ # @param $green [Sass::Script::Value::Number] The new green component for
1439
+ # the color, within 0 and 255 inclusive
1440
+ # @param $blue [Sass::Script::Value::Number] The new blue component for the
1441
+ # color, within 0 and 255 inclusive
1442
+ # @param $hue [Sass::Script::Value::Number] The new hue component for the
1443
+ # color, in degrees
1444
+ # @param $saturation [Sass::Script::Value::Number] The new saturation
1445
+ # component for the color, between `0%` and `100%` inclusive
1446
+ # @param $lightness [Sass::Script::Value::Number] The new lightness
1447
+ # component for the color, within `0%` and `100%` inclusive
1448
+ # @param $alpha [Sass::Script::Value::Number] The new alpha component for
1449
+ # the color, within 0 and 1 inclusive
1450
+ # @return [Sass::Script::Value::Color]
1451
+ # @raise [ArgumentError] if any parameter is the wrong type or out-of
1452
+ # bounds, or if RGB properties and HSL properties are adjusted at the
1453
+ # same time
1454
+ def change_color(color, kwargs)
1455
+ assert_type color, :Color, :color
1456
+ with = Sass::Util.map_hash(
1457
+ 'red' => ['Red value', 0..255],
1458
+ 'green' => ['Green value', 0..255],
1459
+ 'blue' => ['Blue value', 0..255],
1460
+ 'hue' => [],
1461
+ 'saturation' => ['Saturation', 0..100, '%'],
1462
+ 'lightness' => ['Lightness', 0..100, '%'],
1463
+ 'alpha' => ['Alpha channel', 0..1]
1464
+ ) do |name, (desc, range, unit)|
1465
+ val = kwargs.delete(name)
1466
+ next unless val
1467
+ assert_type val, :Number, name
1468
+
1469
+ if range
1470
+ val = Sass::Util.check_range(desc, range, val, unit)
1471
+ else
1472
+ val = val.value
1473
+ end
1474
+
1475
+ [name.to_sym, val]
1476
+ end
1477
+
1478
+ unless kwargs.empty?
1479
+ name, val = kwargs.to_a.first
1480
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
1481
+ end
1482
+
1483
+ color.with(with)
1484
+ end
1485
+ declare :change_color, [:color], :var_kwargs => true
1486
+
1487
+ # Mixes two colors together. Specifically, takes the average of each of the
1488
+ # RGB components, optionally weighted by the given percentage. The opacity
1489
+ # of the colors is also considered when weighting the components.
1490
+ #
1491
+ # The weight specifies the amount of the first color that should be included
1492
+ # in the returned color. The default, `50%`, means that half the first color
1493
+ # and half the second color should be used. `25%` means that a quarter of
1494
+ # the first color and three quarters of the second color should be used.
1495
+ #
1496
+ # @example
1497
+ # mix(#f00, #00f) => #7f007f
1498
+ # mix(#f00, #00f, 25%) => #3f00bf
1499
+ # mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
1500
+ # @overload mix($color1, $color2, $weight: 50%)
1501
+ # @param $color1 [Sass::Script::Value::Color]
1502
+ # @param $color2 [Sass::Script::Value::Color]
1503
+ # @param $weight [Sass::Script::Value::Number] The relative weight of each
1504
+ # color. Closer to `100%` gives more weight to `$color1`, closer to `0%`
1505
+ # gives more weight to `$color2`
1506
+ # @return [Sass::Script::Value::Color]
1507
+ # @raise [ArgumentError] if `$weight` is out of bounds or any parameter is
1508
+ # the wrong type
1509
+ def mix(color1, color2, weight = number(50))
1510
+ assert_type color1, :Color, :color1
1511
+ assert_type color2, :Color, :color2
1512
+ assert_type weight, :Number, :weight
1513
+
1514
+ Sass::Util.check_range("Weight", 0..100, weight, '%')
1515
+
1516
+ # This algorithm factors in both the user-provided weight (w) and the
1517
+ # difference between the alpha values of the two colors (a) to decide how
1518
+ # to perform the weighted average of the two RGB values.
1519
+ #
1520
+ # It works by first normalizing both parameters to be within [-1, 1],
1521
+ # where 1 indicates "only use color1", -1 indicates "only use color2", and
1522
+ # all values in between indicated a proportionately weighted average.
1523
+ #
1524
+ # Once we have the normalized variables w and a, we apply the formula
1525
+ # (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color1.
1526
+ # This formula has two especially nice properties:
1527
+ #
1528
+ # * When either w or a are -1 or 1, the combined weight is also that number
1529
+ # (cases where w * a == -1 are undefined, and handled as a special case).
1530
+ #
1531
+ # * When a is 0, the combined weight is w, and vice versa.
1532
+ #
1533
+ # Finally, the weight of color1 is renormalized to be within [0, 1]
1534
+ # and the weight of color2 is given by 1 minus the weight of color1.
1535
+ p = (weight.value / 100.0).to_f
1536
+ w = p * 2 - 1
1537
+ a = color1.alpha - color2.alpha
1538
+
1539
+ w1 = ((w * a == -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0
1540
+ w2 = 1 - w1
1541
+
1542
+ rgba = color1.rgb.zip(color2.rgb).map {|v1, v2| v1 * w1 + v2 * w2}
1543
+ rgba << color1.alpha * p + color2.alpha * (1 - p)
1544
+ rgb_color(*rgba)
1545
+ end
1546
+ declare :mix, [:color1, :color2]
1547
+ declare :mix, [:color1, :color2, :weight]
1548
+
1549
+ # Converts a color to grayscale. This is identical to `desaturate(color,
1550
+ # 100%)`.
1551
+ #
1552
+ # @see #desaturate
1553
+ # @overload grayscale($color)
1554
+ # @param $color [Sass::Script::Value::Color]
1555
+ # @return [Sass::Script::Value::Color]
1556
+ # @raise [ArgumentError] if `$color` isn't a color
1557
+ def grayscale(color)
1558
+ if color.is_a?(Sass::Script::Value::Number)
1559
+ return identifier("grayscale(#{color})")
1560
+ end
1561
+ desaturate color, number(100)
1562
+ end
1563
+ declare :grayscale, [:color]
1564
+
1565
+ # Returns the complement of a color. This is identical to `adjust-hue(color,
1566
+ # 180deg)`.
1567
+ #
1568
+ # @see #adjust_hue #adjust-hue
1569
+ # @overload complement($color)
1570
+ # @param $color [Sass::Script::Value::Color]
1571
+ # @return [Sass::Script::Value::Color]
1572
+ # @raise [ArgumentError] if `$color` isn't a color
1573
+ def complement(color)
1574
+ adjust_hue color, number(180)
1575
+ end
1576
+ declare :complement, [:color]
1577
+
1578
+ # Returns the inverse (negative) of a color. The red, green, and blue values
1579
+ # are inverted, while the opacity is left alone.
1580
+ #
1581
+ # @overload invert($color)
1582
+ # @param $color [Sass::Script::Value::Color]
1583
+ # @overload invert($color, $weight: 100%)
1584
+ # @param $color [Sass::Script::Value::Color]
1585
+ # @param $weight [Sass::Script::Value::Number] The relative weight of the
1586
+ # color color's inverse
1587
+ # @return [Sass::Script::Value::Color]
1588
+ # @raise [ArgumentError] if `$color` isn't a color or `$weight`
1589
+ # isn't a percentage between 0% and 100%
1590
+ def invert(color, weight = number(100))
1591
+ if color.is_a?(Sass::Script::Value::Number)
1592
+ return identifier("invert(#{color})")
1593
+ end
1594
+
1595
+ assert_type color, :Color, :color
1596
+ inv = color.with(
1597
+ :red => (255 - color.red),
1598
+ :green => (255 - color.green),
1599
+ :blue => (255 - color.blue))
1600
+
1601
+ mix(inv, color, weight)
1602
+ end
1603
+ declare :invert, [:color]
1604
+ declare :invert, [:color, :weight]
1605
+
1606
+ # Removes quotes from a string. If the string is already unquoted, this will
1607
+ # return it unmodified.
1608
+ #
1609
+ # @see #quote
1610
+ # @example
1611
+ # unquote("foo") => foo
1612
+ # unquote(foo) => foo
1613
+ # @overload unquote($string)
1614
+ # @param $string [Sass::Script::Value::String]
1615
+ # @return [Sass::Script::Value::String]
1616
+ # @raise [ArgumentError] if `$string` isn't a string
1617
+ def unquote(string)
1618
+ unless string.is_a?(Sass::Script::Value::String)
1619
+ # Don't warn multiple times for the same source line.
1620
+ $_sass_warned_for_unquote ||= Set.new
1621
+ frame = environment.stack.frames.last
1622
+ key = [frame.filename, frame.line] if frame
1623
+ return string if frame && $_sass_warned_for_unquote.include?(key)
1624
+ $_sass_warned_for_unquote << key if frame
1625
+
1626
+ Sass::Util.sass_warn(<<MESSAGE.strip)
1627
+ DEPRECATION WARNING: Passing #{string.to_sass}, a non-string value, to unquote()
1628
+ will be an error in future versions of Sass.
1629
+ #{environment.stack.to_s.gsub(/^/, ' ' * 8)}
1630
+ MESSAGE
1631
+ return string
1632
+ end
1633
+
1634
+ string.check_deprecated_interp
1635
+ return string if string.type == :identifier
1636
+ identifier(string.value)
1637
+ end
1638
+ declare :unquote, [:string]
1639
+
1640
+ # Add quotes to a string if the string isn't quoted,
1641
+ # or returns the same string if it is.
1642
+ #
1643
+ # @see #unquote
1644
+ # @example
1645
+ # quote("foo") => "foo"
1646
+ # quote(foo) => "foo"
1647
+ # @overload quote($string)
1648
+ # @param $string [Sass::Script::Value::String]
1649
+ # @return [Sass::Script::Value::String]
1650
+ # @raise [ArgumentError] if `$string` isn't a string
1651
+ def quote(string)
1652
+ assert_type string, :String, :string
1653
+ if string.type != :string
1654
+ quoted_string(string.value)
1655
+ else
1656
+ string
1657
+ end
1658
+ end
1659
+ declare :quote, [:string]
1660
+
1661
+ # Returns the number of characters in a string.
1662
+ #
1663
+ # @example
1664
+ # str-length("foo") => 3
1665
+ # @overload str_length($string)
1666
+ # @param $string [Sass::Script::Value::String]
1667
+ # @return [Sass::Script::Value::Number]
1668
+ # @raise [ArgumentError] if `$string` isn't a string
1669
+ def str_length(string)
1670
+ assert_type string, :String, :string
1671
+ number(string.value.size)
1672
+ end
1673
+ declare :str_length, [:string]
1674
+
1675
+ # Inserts `$insert` into `$string` at `$index`.
1676
+ #
1677
+ # Note that unlike some languages, the first character in a Sass string is
1678
+ # number 1, the second number 2, and so forth.
1679
+ #
1680
+ # @example
1681
+ # str-insert("abcd", "X", 1) => "Xabcd"
1682
+ # str-insert("abcd", "X", 4) => "abcXd"
1683
+ # str-insert("abcd", "X", 5) => "abcdX"
1684
+ #
1685
+ # @overload str_insert($string, $insert, $index)
1686
+ # @param $string [Sass::Script::Value::String]
1687
+ # @param $insert [Sass::Script::Value::String]
1688
+ # @param $index [Sass::Script::Value::Number] The position at which
1689
+ # `$insert` will be inserted. Negative indices count from the end of
1690
+ # `$string`. An index that's outside the bounds of the string will insert
1691
+ # `$insert` at the front or back of the string
1692
+ # @return [Sass::Script::Value::String] The result string. This will be
1693
+ # quoted if and only if `$string` was quoted
1694
+ # @raise [ArgumentError] if any parameter is the wrong type
1695
+ def str_insert(original, insert, index)
1696
+ assert_type original, :String, :string
1697
+ assert_type insert, :String, :insert
1698
+ assert_integer index, :index
1699
+ assert_unit index, nil, :index
1700
+ insertion_point = if index.to_i > 0
1701
+ [index.to_i - 1, original.value.size].min
1702
+ else
1703
+ [index.to_i, -original.value.size - 1].max
1704
+ end
1705
+ result = original.value.dup.insert(insertion_point, insert.value)
1706
+ Sass::Script::Value::String.new(result, original.type)
1707
+ end
1708
+ declare :str_insert, [:string, :insert, :index]
1709
+
1710
+ # Returns the index of the first occurrence of `$substring` in `$string`. If
1711
+ # there is no such occurrence, returns `null`.
1712
+ #
1713
+ # Note that unlike some languages, the first character in a Sass string is
1714
+ # number 1, the second number 2, and so forth.
1715
+ #
1716
+ # @example
1717
+ # str-index(abcd, a) => 1
1718
+ # str-index(abcd, ab) => 1
1719
+ # str-index(abcd, X) => null
1720
+ # str-index(abcd, c) => 3
1721
+ #
1722
+ # @overload str_index($string, $substring)
1723
+ # @param $string [Sass::Script::Value::String]
1724
+ # @param $substring [Sass::Script::Value::String]
1725
+ # @return [Sass::Script::Value::Number, Sass::Script::Value::Null]
1726
+ # @raise [ArgumentError] if any parameter is the wrong type
1727
+ def str_index(string, substring)
1728
+ assert_type string, :String, :string
1729
+ assert_type substring, :String, :substring
1730
+ index = string.value.index(substring.value)
1731
+ index ? number(index + 1) : null
1732
+ end
1733
+ declare :str_index, [:string, :substring]
1734
+
1735
+ # Extracts a substring from `$string`. The substring will begin at index
1736
+ # `$start-at` and ends at index `$end-at`.
1737
+ #
1738
+ # Note that unlike some languages, the first character in a Sass string is
1739
+ # number 1, the second number 2, and so forth.
1740
+ #
1741
+ # @example
1742
+ # str-slice("abcd", 2, 3) => "bc"
1743
+ # str-slice("abcd", 2) => "bcd"
1744
+ # str-slice("abcd", -3, -2) => "bc"
1745
+ # str-slice("abcd", 2, -2) => "bc"
1746
+ #
1747
+ # @overload str_slice($string, $start-at, $end-at: -1)
1748
+ # @param $start-at [Sass::Script::Value::Number] The index of the first
1749
+ # character of the substring. If this is negative, it counts from the end
1750
+ # of `$string`
1751
+ # @param $end-at [Sass::Script::Value::Number] The index of the last
1752
+ # character of the substring. If this is negative, it counts from the end
1753
+ # of `$string`. Defaults to -1
1754
+ # @return [Sass::Script::Value::String] The substring. This will be quoted
1755
+ # if and only if `$string` was quoted
1756
+ # @raise [ArgumentError] if any parameter is the wrong type
1757
+ def str_slice(string, start_at, end_at = nil)
1758
+ assert_type string, :String, :string
1759
+ assert_unit start_at, nil, "start-at"
1760
+
1761
+ end_at = number(-1) if end_at.nil?
1762
+ assert_unit end_at, nil, "end-at"
1763
+
1764
+ return Sass::Script::Value::String.new("", string.type) if end_at.value == 0
1765
+ s = start_at.value > 0 ? start_at.value - 1 : start_at.value
1766
+ e = end_at.value > 0 ? end_at.value - 1 : end_at.value
1767
+ s = string.value.length + s if s < 0
1768
+ s = 0 if s < 0
1769
+ e = string.value.length + e if e < 0
1770
+ return Sass::Script::Value::String.new("", string.type) if e < 0
1771
+ extracted = string.value.slice(s..e)
1772
+ Sass::Script::Value::String.new(extracted || "", string.type)
1773
+ end
1774
+ declare :str_slice, [:string, :start_at]
1775
+ declare :str_slice, [:string, :start_at, :end_at]
1776
+
1777
+ # Converts a string to upper case.
1778
+ #
1779
+ # @example
1780
+ # to-upper-case(abcd) => ABCD
1781
+ #
1782
+ # @overload to_upper_case($string)
1783
+ # @param $string [Sass::Script::Value::String]
1784
+ # @return [Sass::Script::Value::String]
1785
+ # @raise [ArgumentError] if `$string` isn't a string
1786
+ def to_upper_case(string)
1787
+ assert_type string, :String, :string
1788
+ Sass::Script::Value::String.new(Sass::Util.upcase(string.value), string.type)
1789
+ end
1790
+ declare :to_upper_case, [:string]
1791
+
1792
+ # Convert a string to lower case,
1793
+ #
1794
+ # @example
1795
+ # to-lower-case(ABCD) => abcd
1796
+ #
1797
+ # @overload to_lower_case($string)
1798
+ # @param $string [Sass::Script::Value::String]
1799
+ # @return [Sass::Script::Value::String]
1800
+ # @raise [ArgumentError] if `$string` isn't a string
1801
+ def to_lower_case(string)
1802
+ assert_type string, :String, :string
1803
+ Sass::Script::Value::String.new(Sass::Util.downcase(string.value), string.type)
1804
+ end
1805
+ declare :to_lower_case, [:string]
1806
+
1807
+ # Returns the type of a value.
1808
+ #
1809
+ # @example
1810
+ # type-of(100px) => number
1811
+ # type-of(asdf) => string
1812
+ # type-of("asdf") => string
1813
+ # type-of(true) => bool
1814
+ # type-of(#fff) => color
1815
+ # type-of(blue) => color
1816
+ # type-of(null) => null
1817
+ # type-of(a b c) => list
1818
+ # type-of((a: 1, b: 2)) => map
1819
+ # type-of(get-function("foo")) => function
1820
+ #
1821
+ # @overload type_of($value)
1822
+ # @param $value [Sass::Script::Value::Base] The value to inspect
1823
+ # @return [Sass::Script::Value::String] The unquoted string name of the
1824
+ # value's type
1825
+ def type_of(value)
1826
+ value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String)
1827
+ identifier(value.class.name.gsub(/Sass::Script::Value::/, '').downcase)
1828
+ end
1829
+ declare :type_of, [:value]
1830
+
1831
+ # Returns whether a feature exists in the current Sass runtime.
1832
+ #
1833
+ # The following features are supported:
1834
+ #
1835
+ # * `global-variable-shadowing` indicates that a local variable will shadow
1836
+ # a global variable unless `!global` is used.
1837
+ #
1838
+ # * `extend-selector-pseudoclass` indicates that `@extend` will reach into
1839
+ # selector pseudoclasses like `:not`.
1840
+ #
1841
+ # * `units-level-3` indicates full support for unit arithmetic using units
1842
+ # defined in the [Values and Units Level 3][] spec.
1843
+ #
1844
+ # [Values and Units Level 3]: http://www.w3.org/TR/css3-values/
1845
+ #
1846
+ # * `at-error` indicates that the Sass `@error` directive is supported.
1847
+ #
1848
+ # * `custom-property` indicates that the [Custom Properties Level 1][] spec
1849
+ # is supported. This means that custom properties are parsed statically,
1850
+ # with only interpolation treated as SassScript.
1851
+ #
1852
+ # [Custom Properties Level 1]: https://www.w3.org/TR/css-variables-1/
1853
+ #
1854
+ # @example
1855
+ # feature-exists(some-feature-that-exists) => true
1856
+ # feature-exists(what-is-this-i-dont-know) => false
1857
+ #
1858
+ # @overload feature_exists($feature)
1859
+ # @param $feature [Sass::Script::Value::String] The name of the feature
1860
+ # @return [Sass::Script::Value::Bool] Whether the feature is supported in this version of Sass
1861
+ # @raise [ArgumentError] if `$feature` isn't a string
1862
+ def feature_exists(feature)
1863
+ assert_type feature, :String, :feature
1864
+ bool(Sass.has_feature?(feature.value))
1865
+ end
1866
+ declare :feature_exists, [:feature]
1867
+
1868
+ # Returns a reference to a function for later invocation with the `call()` function.
1869
+ #
1870
+ # If `$css` is `false`, the function reference may refer to a function
1871
+ # defined in your stylesheet or built-in to the host environment. If it's
1872
+ # `true` it will refer to a plain-CSS function.
1873
+ #
1874
+ # @example
1875
+ # get-function("rgb")
1876
+ #
1877
+ # @function myfunc { @return "something"; }
1878
+ # get-function("myfunc")
1879
+ #
1880
+ # @overload get_function($name, $css: false)
1881
+ # @param name [Sass::Script::Value::String] The name of the function being referenced.
1882
+ # @param css [Sass::Script::Value::Bool] Whether to get a plain CSS function.
1883
+ #
1884
+ # @return [Sass::Script::Value::Function] A function reference.
1885
+ def get_function(name, kwargs = {})
1886
+ assert_type name, :String, :name
1887
+
1888
+ css = if kwargs.has_key?("css")
1889
+ v = kwargs.delete("css")
1890
+ assert_type v, :Bool, :css
1891
+ v.value
1892
+ else
1893
+ false
1894
+ end
1895
+
1896
+ if kwargs.any?
1897
+ raise ArgumentError.new("Illegal keyword argument '#{kwargs.keys.first}'")
1898
+ end
1899
+
1900
+ if css
1901
+ return Sass::Script::Value::Function.new(
1902
+ Sass::Callable.new(name.value, nil, nil, nil, nil, nil, "function", :css))
1903
+ end
1904
+
1905
+ callable = environment.caller.function(name.value) ||
1906
+ (Sass::Script::Functions.callable?(name.value.tr("-", "_")) &&
1907
+ Sass::Callable.new(name.value, nil, nil, nil, nil, nil, "function", :builtin))
1908
+
1909
+ if callable
1910
+ Sass::Script::Value::Function.new(callable)
1911
+ else
1912
+ raise Sass::SyntaxError.new("Function not found: #{name}")
1913
+ end
1914
+ end
1915
+ declare :get_function, [:name], :var_kwargs => true
1916
+
1917
+ # Returns the unit(s) associated with a number. Complex units are sorted in
1918
+ # alphabetical order by numerator and denominator.
1919
+ #
1920
+ # @example
1921
+ # unit(100) => ""
1922
+ # unit(100px) => "px"
1923
+ # unit(3em) => "em"
1924
+ # unit(10px * 5em) => "em*px"
1925
+ # unit(10px * 5em / 30cm / 1rem) => "em*px/cm*rem"
1926
+ # @overload unit($number)
1927
+ # @param $number [Sass::Script::Value::Number]
1928
+ # @return [Sass::Script::Value::String] The unit(s) of the number, as a
1929
+ # quoted string
1930
+ # @raise [ArgumentError] if `$number` isn't a number
1931
+ def unit(number)
1932
+ assert_type number, :Number, :number
1933
+ quoted_string(number.unit_str)
1934
+ end
1935
+ declare :unit, [:number]
1936
+
1937
+ # Returns whether a number has units.
1938
+ #
1939
+ # @example
1940
+ # unitless(100) => true
1941
+ # unitless(100px) => false
1942
+ # @overload unitless($number)
1943
+ # @param $number [Sass::Script::Value::Number]
1944
+ # @return [Sass::Script::Value::Bool]
1945
+ # @raise [ArgumentError] if `$number` isn't a number
1946
+ def unitless(number)
1947
+ assert_type number, :Number, :number
1948
+ bool(number.unitless?)
1949
+ end
1950
+ declare :unitless, [:number]
1951
+
1952
+ # Returns whether two numbers can added, subtracted, or compared.
1953
+ #
1954
+ # @example
1955
+ # comparable(2px, 1px) => true
1956
+ # comparable(100px, 3em) => false
1957
+ # comparable(10cm, 3mm) => true
1958
+ # @overload comparable($number1, $number2)
1959
+ # @param $number1 [Sass::Script::Value::Number]
1960
+ # @param $number2 [Sass::Script::Value::Number]
1961
+ # @return [Sass::Script::Value::Bool]
1962
+ # @raise [ArgumentError] if either parameter is the wrong type
1963
+ def comparable(number1, number2)
1964
+ assert_type number1, :Number, :number1
1965
+ assert_type number2, :Number, :number2
1966
+ bool(number1.comparable_to?(number2))
1967
+ end
1968
+ declare :comparable, [:number1, :number2]
1969
+
1970
+ # Converts a unitless number to a percentage.
1971
+ #
1972
+ # @example
1973
+ # percentage(0.2) => 20%
1974
+ # percentage(100px / 50px) => 200%
1975
+ # @overload percentage($number)
1976
+ # @param $number [Sass::Script::Value::Number]
1977
+ # @return [Sass::Script::Value::Number]
1978
+ # @raise [ArgumentError] if `$number` isn't a unitless number
1979
+ def percentage(number)
1980
+ unless number.is_a?(Sass::Script::Value::Number) && number.unitless?
1981
+ raise ArgumentError.new("$number: #{number.inspect} is not a unitless number")
1982
+ end
1983
+ number(number.value * 100, '%')
1984
+ end
1985
+ declare :percentage, [:number]
1986
+
1987
+ # Rounds a number to the nearest whole number.
1988
+ #
1989
+ # @example
1990
+ # round(10.4px) => 10px
1991
+ # round(10.6px) => 11px
1992
+ # @overload round($number)
1993
+ # @param $number [Sass::Script::Value::Number]
1994
+ # @return [Sass::Script::Value::Number]
1995
+ # @raise [ArgumentError] if `$number` isn't a number
1996
+ def round(number)
1997
+ numeric_transformation(number) {|n| Sass::Util.round(n)}
1998
+ end
1999
+ declare :round, [:number]
2000
+
2001
+ # Rounds a number up to the next whole number.
2002
+ #
2003
+ # @example
2004
+ # ceil(10.4px) => 11px
2005
+ # ceil(10.6px) => 11px
2006
+ # @overload ceil($number)
2007
+ # @param $number [Sass::Script::Value::Number]
2008
+ # @return [Sass::Script::Value::Number]
2009
+ # @raise [ArgumentError] if `$number` isn't a number
2010
+ def ceil(number)
2011
+ numeric_transformation(number) {|n| n.ceil}
2012
+ end
2013
+ declare :ceil, [:number]
2014
+
2015
+ # Rounds a number down to the previous whole number.
2016
+ #
2017
+ # @example
2018
+ # floor(10.4px) => 10px
2019
+ # floor(10.6px) => 10px
2020
+ # @overload floor($number)
2021
+ # @param $number [Sass::Script::Value::Number]
2022
+ # @return [Sass::Script::Value::Number]
2023
+ # @raise [ArgumentError] if `$number` isn't a number
2024
+ def floor(number)
2025
+ numeric_transformation(number) {|n| n.floor}
2026
+ end
2027
+ declare :floor, [:number]
2028
+
2029
+ # Returns the absolute value of a number.
2030
+ #
2031
+ # @example
2032
+ # abs(10px) => 10px
2033
+ # abs(-10px) => 10px
2034
+ # @overload abs($number)
2035
+ # @param $number [Sass::Script::Value::Number]
2036
+ # @return [Sass::Script::Value::Number]
2037
+ # @raise [ArgumentError] if `$number` isn't a number
2038
+ def abs(number)
2039
+ numeric_transformation(number) {|n| n.abs}
2040
+ end
2041
+ declare :abs, [:number]
2042
+
2043
+ # Finds the minimum of several numbers. This function takes any number of
2044
+ # arguments.
2045
+ #
2046
+ # @example
2047
+ # min(1px, 4px) => 1px
2048
+ # min(5em, 3em, 4em) => 3em
2049
+ # @overload min($numbers...)
2050
+ # @param $numbers [[Sass::Script::Value::Number]]
2051
+ # @return [Sass::Script::Value::Number]
2052
+ # @raise [ArgumentError] if any argument isn't a number, or if not all of
2053
+ # the arguments have comparable units
2054
+ def min(*numbers)
2055
+ numbers.each {|n| assert_type n, :Number}
2056
+ numbers.inject {|min, num| min.lt(num).to_bool ? min : num}
2057
+ end
2058
+ declare :min, [], :var_args => :true
2059
+
2060
+ # Finds the maximum of several numbers. This function takes any number of
2061
+ # arguments.
2062
+ #
2063
+ # @example
2064
+ # max(1px, 4px) => 4px
2065
+ # max(5em, 3em, 4em) => 5em
2066
+ # @overload max($numbers...)
2067
+ # @param $numbers [[Sass::Script::Value::Number]]
2068
+ # @return [Sass::Script::Value::Number]
2069
+ # @raise [ArgumentError] if any argument isn't a number, or if not all of
2070
+ # the arguments have comparable units
2071
+ def max(*values)
2072
+ values.each {|v| assert_type v, :Number}
2073
+ values.inject {|max, val| max.gt(val).to_bool ? max : val}
2074
+ end
2075
+ declare :max, [], :var_args => :true
2076
+
2077
+ # Return the length of a list.
2078
+ #
2079
+ # This can return the number of pairs in a map as well.
2080
+ #
2081
+ # @example
2082
+ # length(10px) => 1
2083
+ # length(10px 20px 30px) => 3
2084
+ # length((width: 10px, height: 20px)) => 2
2085
+ # @overload length($list)
2086
+ # @param $list [Sass::Script::Value::Base]
2087
+ # @return [Sass::Script::Value::Number]
2088
+ def length(list)
2089
+ number(list.to_a.size)
2090
+ end
2091
+ declare :length, [:list]
2092
+
2093
+ # Return a new list, based on the list provided, but with the nth
2094
+ # element changed to the value given.
2095
+ #
2096
+ # Note that unlike some languages, the first item in a Sass list is number
2097
+ # 1, the second number 2, and so forth.
2098
+ #
2099
+ # Negative index values address elements in reverse order, starting with the last element
2100
+ # in the list.
2101
+ #
2102
+ # @example
2103
+ # set-nth($list: 10px 20px 30px, $n: 2, $value: -20px) => 10px -20px 30px
2104
+ # @overload set-nth($list, $n, $value)
2105
+ # @param $list [Sass::Script::Value::Base] The list that will be copied, having the element
2106
+ # at index `$n` changed.
2107
+ # @param $n [Sass::Script::Value::Number] The index of the item to set.
2108
+ # Negative indices count from the end of the list.
2109
+ # @param $value [Sass::Script::Value::Base] The new value at index `$n`.
2110
+ # @return [Sass::Script::Value::List]
2111
+ # @raise [ArgumentError] if `$n` isn't an integer between 1 and the length
2112
+ # of `$list`
2113
+ def set_nth(list, n, value)
2114
+ assert_type n, :Number, :n
2115
+ Sass::Script::Value::List.assert_valid_index(list, n)
2116
+ index = n.to_i > 0 ? n.to_i - 1 : n.to_i
2117
+ new_list = list.to_a.dup
2118
+ new_list[index] = value
2119
+ list.with_contents(new_list)
2120
+ end
2121
+ declare :set_nth, [:list, :n, :value]
2122
+
2123
+ # Gets the nth item in a list.
2124
+ #
2125
+ # Note that unlike some languages, the first item in a Sass list is number
2126
+ # 1, the second number 2, and so forth.
2127
+ #
2128
+ # This can return the nth pair in a map as well.
2129
+ #
2130
+ # Negative index values address elements in reverse order, starting with the last element in
2131
+ # the list.
2132
+ #
2133
+ # @example
2134
+ # nth(10px 20px 30px, 1) => 10px
2135
+ # nth((Helvetica, Arial, sans-serif), 3) => sans-serif
2136
+ # nth((width: 10px, length: 20px), 2) => length, 20px
2137
+ # @overload nth($list, $n)
2138
+ # @param $list [Sass::Script::Value::Base]
2139
+ # @param $n [Sass::Script::Value::Number] The index of the item to get.
2140
+ # Negative indices count from the end of the list.
2141
+ # @return [Sass::Script::Value::Base]
2142
+ # @raise [ArgumentError] if `$n` isn't an integer between 1 and the length
2143
+ # of `$list`
2144
+ def nth(list, n)
2145
+ assert_type n, :Number, :n
2146
+ Sass::Script::Value::List.assert_valid_index(list, n)
2147
+
2148
+ index = n.to_i > 0 ? n.to_i - 1 : n.to_i
2149
+ list.to_a[index]
2150
+ end
2151
+ declare :nth, [:list, :n]
2152
+
2153
+ # Joins together two lists into one.
2154
+ #
2155
+ # Unless `$separator` is passed, if one list is comma-separated and one is
2156
+ # space-separated, the first parameter's separator is used for the resulting
2157
+ # list. If both lists have fewer than two items, spaces are used for the
2158
+ # resulting list.
2159
+ #
2160
+ # Unless `$bracketed` is passed, the resulting list is bracketed if the
2161
+ # first parameter is.
2162
+ #
2163
+ # Like all list functions, `join()` returns a new list rather than modifying
2164
+ # its arguments in place.
2165
+ #
2166
+ # @example
2167
+ # join(10px 20px, 30px 40px) => 10px 20px 30px 40px
2168
+ # join((blue, red), (#abc, #def)) => blue, red, #abc, #def
2169
+ # join(10px, 20px) => 10px 20px
2170
+ # join(10px, 20px, comma) => 10px, 20px
2171
+ # join((blue, red), (#abc, #def), space) => blue red #abc #def
2172
+ # join([10px], 20px) => [10px 20px]
2173
+ # @overload join($list1, $list2, $separator: auto, $bracketed: auto)
2174
+ # @param $list1 [Sass::Script::Value::Base]
2175
+ # @param $list2 [Sass::Script::Value::Base]
2176
+ # @param $separator [Sass::Script::Value::String] The list separator to use.
2177
+ # If this is `comma` or `space`, that separator will be used. If this is
2178
+ # `auto` (the default), the separator is determined as explained above.
2179
+ # @param $bracketed [Sass::Script::Value::Base] Whether the resulting list
2180
+ # will be bracketed. If this is `auto` (the default), the separator is
2181
+ # determined as explained above.
2182
+ # @return [Sass::Script::Value::List]
2183
+ def join(list1, list2,
2184
+ separator = identifier("auto"), bracketed = identifier("auto"),
2185
+ kwargs = nil, *rest)
2186
+ if separator.is_a?(Hash)
2187
+ kwargs = separator
2188
+ separator = identifier("auto")
2189
+ elsif bracketed.is_a?(Hash)
2190
+ kwargs = bracketed
2191
+ bracketed = identifier("auto")
2192
+ elsif rest.last.is_a?(Hash)
2193
+ rest.unshift kwargs
2194
+ kwargs = rest.pop
2195
+ end
2196
+
2197
+ unless rest.empty?
2198
+ # Add 4 to rest.length because we don't want to count the kwargs hash,
2199
+ # which is always passed.
2200
+ raise ArgumentError.new("wrong number of arguments (#{rest.length + 4} for 2..4)")
2201
+ end
2202
+
2203
+ if kwargs
2204
+ separator = kwargs.delete("separator") || separator
2205
+ bracketed = kwargs.delete("bracketed") || bracketed
2206
+
2207
+ unless kwargs.empty?
2208
+ name, val = kwargs.to_a.first
2209
+ raise ArgumentError.new("Unknown argument $#{name} (#{val})")
2210
+ end
2211
+ end
2212
+
2213
+ assert_type separator, :String, :separator
2214
+ unless %w(auto space comma).include?(separator.value)
2215
+ raise ArgumentError.new("Separator name must be space, comma, or auto")
2216
+ end
2217
+
2218
+ list(list1.to_a + list2.to_a,
2219
+ separator:
2220
+ if separator.value == 'auto'
2221
+ list1.separator || list2.separator || :space
2222
+ else
2223
+ separator.value.to_sym
2224
+ end,
2225
+ bracketed:
2226
+ if bracketed.is_a?(Sass::Script::Value::String) && bracketed.value == 'auto'
2227
+ list1.bracketed
2228
+ else
2229
+ bracketed.to_bool
2230
+ end)
2231
+ end
2232
+ # We don't actually take variable arguments or keyword arguments, but this
2233
+ # is the best way to take either `$separator` or `$bracketed` as keywords
2234
+ # without complaining about the other missing.
2235
+ declare :join, [:list1, :list2], :var_args => true, :var_kwargs => true
2236
+
2237
+ # Appends a single value onto the end of a list.
2238
+ #
2239
+ # Unless the `$separator` argument is passed, if the list had only one item,
2240
+ # the resulting list will be space-separated.
2241
+ #
2242
+ # Like all list functions, `append()` returns a new list rather than
2243
+ # modifying its argument in place.
2244
+ #
2245
+ # @example
2246
+ # append(10px 20px, 30px) => 10px 20px 30px
2247
+ # append((blue, red), green) => blue, red, green
2248
+ # append(10px 20px, 30px 40px) => 10px 20px (30px 40px)
2249
+ # append(10px, 20px, comma) => 10px, 20px
2250
+ # append((blue, red), green, space) => blue red green
2251
+ # @overload append($list, $val, $separator: auto)
2252
+ # @param $list [Sass::Script::Value::Base]
2253
+ # @param $val [Sass::Script::Value::Base]
2254
+ # @param $separator [Sass::Script::Value::String] The list separator to use.
2255
+ # If this is `comma` or `space`, that separator will be used. If this is
2256
+ # `auto` (the default), the separator is determined as explained above.
2257
+ # @return [Sass::Script::Value::List]
2258
+ def append(list, val, separator = identifier("auto"))
2259
+ assert_type separator, :String, :separator
2260
+ unless %w(auto space comma).include?(separator.value)
2261
+ raise ArgumentError.new("Separator name must be space, comma, or auto")
2262
+ end
2263
+ list.with_contents(list.to_a + [val],
2264
+ separator:
2265
+ if separator.value == 'auto'
2266
+ list.separator || :space
2267
+ else
2268
+ separator.value.to_sym
2269
+ end)
2270
+ end
2271
+ declare :append, [:list, :val]
2272
+ declare :append, [:list, :val, :separator]
2273
+
2274
+ # Combines several lists into a single multidimensional list. The nth value
2275
+ # of the resulting list is a space separated list of the source lists' nth
2276
+ # values.
2277
+ #
2278
+ # The length of the resulting list is the length of the
2279
+ # shortest list.
2280
+ #
2281
+ # @example
2282
+ # zip(1px 1px 3px, solid dashed solid, red green blue)
2283
+ # => 1px solid red, 1px dashed green, 3px solid blue
2284
+ # @overload zip($lists...)
2285
+ # @param $lists [[Sass::Script::Value::Base]]
2286
+ # @return [Sass::Script::Value::List]
2287
+ def zip(*lists)
2288
+ length = nil
2289
+ values = []
2290
+ lists.each do |list|
2291
+ array = list.to_a
2292
+ values << array.dup
2293
+ length = length.nil? ? array.length : [length, array.length].min
2294
+ end
2295
+ values.each do |value|
2296
+ value.slice!(length)
2297
+ end
2298
+ new_list_value = values.first.zip(*values[1..-1])
2299
+ list(new_list_value.map {|list| list(list, :space)}, :comma)
2300
+ end
2301
+ declare :zip, [], :var_args => true
2302
+
2303
+ # Returns the position of a value within a list. If the value isn't found,
2304
+ # returns `null` instead.
2305
+ #
2306
+ # Note that unlike some languages, the first item in a Sass list is number
2307
+ # 1, the second number 2, and so forth.
2308
+ #
2309
+ # This can return the position of a pair in a map as well.
2310
+ #
2311
+ # @example
2312
+ # index(1px solid red, solid) => 2
2313
+ # index(1px solid red, dashed) => null
2314
+ # index((width: 10px, height: 20px), (height 20px)) => 2
2315
+ # @overload index($list, $value)
2316
+ # @param $list [Sass::Script::Value::Base]
2317
+ # @param $value [Sass::Script::Value::Base]
2318
+ # @return [Sass::Script::Value::Number, Sass::Script::Value::Null] The
2319
+ # 1-based index of `$value` in `$list`, or `null`
2320
+ def index(list, value)
2321
+ index = list.to_a.index {|e| e.eq(value).to_bool}
2322
+ index ? number(index + 1) : null
2323
+ end
2324
+ declare :index, [:list, :value]
2325
+
2326
+ # Returns the separator of a list. If the list doesn't have a separator due
2327
+ # to having fewer than two elements, returns `space`.
2328
+ #
2329
+ # @example
2330
+ # list-separator(1px 2px 3px) => space
2331
+ # list-separator(1px, 2px, 3px) => comma
2332
+ # list-separator('foo') => space
2333
+ # @overload list_separator($list)
2334
+ # @param $list [Sass::Script::Value::Base]
2335
+ # @return [Sass::Script::Value::String] `comma` or `space`
2336
+ def list_separator(list)
2337
+ identifier((list.separator || :space).to_s)
2338
+ end
2339
+ declare :list_separator, [:list]
2340
+
2341
+ # Returns whether a list uses square brackets.
2342
+ #
2343
+ # @example
2344
+ # is-bracketed(1px 2px 3px) => false
2345
+ # is-bracketed([1px, 2px, 3px]) => true
2346
+ # @overload is_bracketed($list)
2347
+ # @param $list [Sass::Script::Value::Base]
2348
+ # @return [Sass::Script::Value::Bool]
2349
+ def is_bracketed(list)
2350
+ bool(list.bracketed)
2351
+ end
2352
+ declare :is_bracketed, [:list]
2353
+
2354
+ # Returns the value in a map associated with the given key. If the map
2355
+ # doesn't have such a key, returns `null`.
2356
+ #
2357
+ # @example
2358
+ # map-get(("foo": 1, "bar": 2), "foo") => 1
2359
+ # map-get(("foo": 1, "bar": 2), "bar") => 2
2360
+ # map-get(("foo": 1, "bar": 2), "baz") => null
2361
+ # @overload map_get($map, $key)
2362
+ # @param $map [Sass::Script::Value::Map]
2363
+ # @param $key [Sass::Script::Value::Base]
2364
+ # @return [Sass::Script::Value::Base] The value indexed by `$key`, or `null`
2365
+ # if the map doesn't contain the given key
2366
+ # @raise [ArgumentError] if `$map` is not a map
2367
+ def map_get(map, key)
2368
+ assert_type map, :Map, :map
2369
+ map.to_h[key] || null
2370
+ end
2371
+ declare :map_get, [:map, :key]
2372
+
2373
+ # Merges two maps together into a new map. Keys in `$map2` will take
2374
+ # precedence over keys in `$map1`.
2375
+ #
2376
+ # This is the best way to add new values to a map.
2377
+ #
2378
+ # All keys in the returned map that also appear in `$map1` will have the
2379
+ # same order as in `$map1`. New keys from `$map2` will be placed at the end
2380
+ # of the map.
2381
+ #
2382
+ # Like all map functions, `map-merge()` returns a new map rather than
2383
+ # modifying its arguments in place.
2384
+ #
2385
+ # @example
2386
+ # map-merge(("foo": 1), ("bar": 2)) => ("foo": 1, "bar": 2)
2387
+ # map-merge(("foo": 1, "bar": 2), ("bar": 3)) => ("foo": 1, "bar": 3)
2388
+ # @overload map_merge($map1, $map2)
2389
+ # @param $map1 [Sass::Script::Value::Map]
2390
+ # @param $map2 [Sass::Script::Value::Map]
2391
+ # @return [Sass::Script::Value::Map]
2392
+ # @raise [ArgumentError] if either parameter is not a map
2393
+ def map_merge(map1, map2)
2394
+ assert_type map1, :Map, :map1
2395
+ assert_type map2, :Map, :map2
2396
+ map(map1.to_h.merge(map2.to_h))
2397
+ end
2398
+ declare :map_merge, [:map1, :map2]
2399
+
2400
+ # Returns a new map with keys removed.
2401
+ #
2402
+ # Like all map functions, `map-merge()` returns a new map rather than
2403
+ # modifying its arguments in place.
2404
+ #
2405
+ # @example
2406
+ # map-remove(("foo": 1, "bar": 2), "bar") => ("foo": 1)
2407
+ # map-remove(("foo": 1, "bar": 2, "baz": 3), "bar", "baz") => ("foo": 1)
2408
+ # map-remove(("foo": 1, "bar": 2), "baz") => ("foo": 1, "bar": 2)
2409
+ # @overload map_remove($map, $keys...)
2410
+ # @param $map [Sass::Script::Value::Map]
2411
+ # @param $keys [[Sass::Script::Value::Base]]
2412
+ # @return [Sass::Script::Value::Map]
2413
+ # @raise [ArgumentError] if `$map` is not a map
2414
+ def map_remove(map, *keys)
2415
+ assert_type map, :Map, :map
2416
+ hash = map.to_h.dup
2417
+ hash.delete_if {|key, _| keys.include?(key)}
2418
+ map(hash)
2419
+ end
2420
+ declare :map_remove, [:map, :key], :var_args => true
2421
+
2422
+ # Returns a list of all keys in a map.
2423
+ #
2424
+ # @example
2425
+ # map-keys(("foo": 1, "bar": 2)) => "foo", "bar"
2426
+ # @overload map_keys($map)
2427
+ # @param $map [Map]
2428
+ # @return [List] the list of keys, comma-separated
2429
+ # @raise [ArgumentError] if `$map` is not a map
2430
+ def map_keys(map)
2431
+ assert_type map, :Map, :map
2432
+ list(map.to_h.keys, :comma)
2433
+ end
2434
+ declare :map_keys, [:map]
2435
+
2436
+ # Returns a list of all values in a map. This list may include duplicate
2437
+ # values, if multiple keys have the same value.
2438
+ #
2439
+ # @example
2440
+ # map-values(("foo": 1, "bar": 2)) => 1, 2
2441
+ # map-values(("foo": 1, "bar": 2, "baz": 1)) => 1, 2, 1
2442
+ # @overload map_values($map)
2443
+ # @param $map [Map]
2444
+ # @return [List] the list of values, comma-separated
2445
+ # @raise [ArgumentError] if `$map` is not a map
2446
+ def map_values(map)
2447
+ assert_type map, :Map, :map
2448
+ list(map.to_h.values, :comma)
2449
+ end
2450
+ declare :map_values, [:map]
2451
+
2452
+ # Returns whether a map has a value associated with a given key.
2453
+ #
2454
+ # @example
2455
+ # map-has-key(("foo": 1, "bar": 2), "foo") => true
2456
+ # map-has-key(("foo": 1, "bar": 2), "baz") => false
2457
+ # @overload map_has_key($map, $key)
2458
+ # @param $map [Sass::Script::Value::Map]
2459
+ # @param $key [Sass::Script::Value::Base]
2460
+ # @return [Sass::Script::Value::Bool]
2461
+ # @raise [ArgumentError] if `$map` is not a map
2462
+ def map_has_key(map, key)
2463
+ assert_type map, :Map, :map
2464
+ bool(map.to_h.has_key?(key))
2465
+ end
2466
+ declare :map_has_key, [:map, :key]
2467
+
2468
+ # Returns the map of named arguments passed to a function or mixin that
2469
+ # takes a variable argument list. The argument names are strings, and they
2470
+ # do not contain the leading `$`.
2471
+ #
2472
+ # @example
2473
+ # @mixin foo($args...) {
2474
+ # @debug keywords($args); //=> (arg1: val, arg2: val)
2475
+ # }
2476
+ #
2477
+ # @include foo($arg1: val, $arg2: val);
2478
+ # @overload keywords($args)
2479
+ # @param $args [Sass::Script::Value::ArgList]
2480
+ # @return [Sass::Script::Value::Map]
2481
+ # @raise [ArgumentError] if `$args` isn't a variable argument list
2482
+ def keywords(args)
2483
+ assert_type args, :ArgList, :args
2484
+ map(Sass::Util.map_keys(args.keywords.as_stored) {|k| Sass::Script::Value::String.new(k)})
2485
+ end
2486
+ declare :keywords, [:args]
2487
+
2488
+ # Returns one of two values, depending on whether or not `$condition` is
2489
+ # true. Just like in `@if`, all values other than `false` and `null` are
2490
+ # considered to be true.
2491
+ #
2492
+ # @example
2493
+ # if(true, 1px, 2px) => 1px
2494
+ # if(false, 1px, 2px) => 2px
2495
+ # @overload if($condition, $if-true, $if-false)
2496
+ # @param $condition [Sass::Script::Value::Base] Whether the `$if-true` or
2497
+ # `$if-false` will be returned
2498
+ # @param $if-true [Sass::Script::Tree::Node]
2499
+ # @param $if-false [Sass::Script::Tree::Node]
2500
+ # @return [Sass::Script::Value::Base] `$if-true` or `$if-false`
2501
+ def if(condition, if_true, if_false)
2502
+ if condition.to_bool
2503
+ perform(if_true)
2504
+ else
2505
+ perform(if_false)
2506
+ end
2507
+ end
2508
+ declare :if, [:condition, :"&if_true", :"&if_false"]
2509
+
2510
+ # Returns a unique CSS identifier. The identifier is returned as an unquoted
2511
+ # string. The identifier returned is only guaranteed to be unique within the
2512
+ # scope of a single Sass run.
2513
+ #
2514
+ # @overload unique_id()
2515
+ # @return [Sass::Script::Value::String]
2516
+ def unique_id
2517
+ generator = Sass::Script::Functions.random_number_generator
2518
+ Thread.current[:sass_last_unique_id] ||= generator.rand(36**8)
2519
+ # avoid the temptation of trying to guess the next unique value.
2520
+ value = (Thread.current[:sass_last_unique_id] += (generator.rand(10) + 1))
2521
+ # the u makes this a legal identifier if it would otherwise start with a number.
2522
+ identifier("u" + value.to_s(36).rjust(8, '0'))
2523
+ end
2524
+ declare :unique_id, []
2525
+
2526
+ # Dynamically calls a function. This can call user-defined
2527
+ # functions, built-in functions, or plain CSS functions. It will
2528
+ # pass along all arguments, including keyword arguments, to the
2529
+ # called function.
2530
+ #
2531
+ # @example
2532
+ # call(rgb, 10, 100, 255) => #0a64ff
2533
+ # call(scale-color, #0a64ff, $lightness: -10%) => #0058ef
2534
+ #
2535
+ # $fn: nth;
2536
+ # call($fn, (a b c), 2) => b
2537
+ #
2538
+ # @overload call($function, $args...)
2539
+ # @param $function [Sass::Script::Value::Function] The function to call.
2540
+ def call(name, *args)
2541
+ unless name.is_a?(Sass::Script::Value::String) ||
2542
+ name.is_a?(Sass::Script::Value::Function)
2543
+ assert_type name, :Function, :function
2544
+ end
2545
+ if name.is_a?(Sass::Script::Value::String)
2546
+ name = if function_exists(name).to_bool
2547
+ get_function(name)
2548
+ else
2549
+ get_function(name, "css" => bool(true))
2550
+ end
2551
+ Sass::Util.sass_warn(<<WARNING)
2552
+ DEPRECATION WARNING: Passing a string to call() is deprecated and will be illegal
2553
+ in Sass 4.0. Use call(#{name.to_sass}) instead.
2554
+ WARNING
2555
+ end
2556
+ kwargs = args.last.is_a?(Hash) ? args.pop : {}
2557
+ funcall = Sass::Script::Tree::Funcall.new(
2558
+ name.value,
2559
+ args.map {|a| Sass::Script::Tree::Literal.new(a)},
2560
+ Sass::Util.map_vals(kwargs) {|v| Sass::Script::Tree::Literal.new(v)},
2561
+ nil,
2562
+ nil)
2563
+ funcall.line = environment.stack.frames.last.line
2564
+ funcall.filename = environment.stack.frames.last.filename
2565
+ funcall.options = options
2566
+ perform(funcall)
2567
+ end
2568
+ declare :call, [:name], :var_args => true, :var_kwargs => true
2569
+
2570
+ # This function only exists as a workaround for IE7's [`content:
2571
+ # counter` bug](http://jes.st/2013/ie7s-css-breaking-content-counter-bug/).
2572
+ # It works identically to any other plain-CSS function, except it
2573
+ # avoids adding spaces between the argument commas.
2574
+ #
2575
+ # @example
2576
+ # counter(item, ".") => counter(item,".")
2577
+ # @overload counter($args...)
2578
+ # @return [Sass::Script::Value::String]
2579
+ def counter(*args)
2580
+ identifier("counter(#{args.map {|a| a.to_s(options)}.join(',')})")
2581
+ end
2582
+ declare :counter, [], :var_args => true
2583
+
2584
+ # This function only exists as a workaround for IE7's [`content:
2585
+ # counter` bug](http://jes.st/2013/ie7s-css-breaking-content-counter-bug/).
2586
+ # It works identically to any other plain-CSS function, except it
2587
+ # avoids adding spaces between the argument commas.
2588
+ #
2589
+ # @example
2590
+ # counters(item, ".") => counters(item,".")
2591
+ # @overload counters($args...)
2592
+ # @return [Sass::Script::Value::String]
2593
+ def counters(*args)
2594
+ identifier("counters(#{args.map {|a| a.to_s(options)}.join(',')})")
2595
+ end
2596
+ declare :counters, [], :var_args => true
2597
+
2598
+ # Check whether a variable with the given name exists in the current
2599
+ # scope or in the global scope.
2600
+ #
2601
+ # @example
2602
+ # $a-false-value: false;
2603
+ # variable-exists(a-false-value) => true
2604
+ # variable-exists(a-null-value) => true
2605
+ #
2606
+ # variable-exists(nonexistent) => false
2607
+ #
2608
+ # @overload variable_exists($name)
2609
+ # @param $name [Sass::Script::Value::String] The name of the variable to
2610
+ # check. The name should not include the `$`.
2611
+ # @return [Sass::Script::Value::Bool] Whether the variable is defined in
2612
+ # the current scope.
2613
+ def variable_exists(name)
2614
+ assert_type name, :String, :name
2615
+ bool(environment.caller.var(name.value))
2616
+ end
2617
+ declare :variable_exists, [:name]
2618
+
2619
+ # Check whether a variable with the given name exists in the global
2620
+ # scope (at the top level of the file).
2621
+ #
2622
+ # @example
2623
+ # $a-false-value: false;
2624
+ # global-variable-exists(a-false-value) => true
2625
+ # global-variable-exists(a-null-value) => true
2626
+ #
2627
+ # .foo {
2628
+ # $some-var: false;
2629
+ # @if global-variable-exists(some-var) { /* false, doesn't run */ }
2630
+ # }
2631
+ #
2632
+ # @overload global_variable_exists($name)
2633
+ # @param $name [Sass::Script::Value::String] The name of the variable to
2634
+ # check. The name should not include the `$`.
2635
+ # @return [Sass::Script::Value::Bool] Whether the variable is defined in
2636
+ # the global scope.
2637
+ def global_variable_exists(name)
2638
+ assert_type name, :String, :name
2639
+ bool(environment.global_env.var(name.value))
2640
+ end
2641
+ declare :global_variable_exists, [:name]
2642
+
2643
+ # Check whether a function with the given name exists.
2644
+ #
2645
+ # @example
2646
+ # function-exists(lighten) => true
2647
+ #
2648
+ # @function myfunc { @return "something"; }
2649
+ # function-exists(myfunc) => true
2650
+ #
2651
+ # @overload function_exists($name)
2652
+ # @param name [Sass::Script::Value::String] The name of the function to
2653
+ # check or a function reference.
2654
+ # @return [Sass::Script::Value::Bool] Whether the function is defined.
2655
+ def function_exists(name)
2656
+ assert_type name, :String, :name
2657
+ exists = Sass::Script::Functions.callable?(name.value.tr("-", "_"))
2658
+ exists ||= environment.caller.function(name.value)
2659
+ bool(exists)
2660
+ end
2661
+ declare :function_exists, [:name]
2662
+
2663
+ # Check whether a mixin with the given name exists.
2664
+ #
2665
+ # @example
2666
+ # mixin-exists(nonexistent) => false
2667
+ #
2668
+ # @mixin red-text { color: red; }
2669
+ # mixin-exists(red-text) => true
2670
+ #
2671
+ # @overload mixin_exists($name)
2672
+ # @param name [Sass::Script::Value::String] The name of the mixin to
2673
+ # check.
2674
+ # @return [Sass::Script::Value::Bool] Whether the mixin is defined.
2675
+ def mixin_exists(name)
2676
+ assert_type name, :String, :name
2677
+ bool(environment.mixin(name.value))
2678
+ end
2679
+ declare :mixin_exists, [:name]
2680
+
2681
+ # Check whether a mixin was passed a content block.
2682
+ #
2683
+ # Unless `content-exists()` is called directly from a mixin, an error will be raised.
2684
+ #
2685
+ # @example
2686
+ # @mixin needs-content {
2687
+ # @if not content-exists() {
2688
+ # @error "You must pass a content block!"
2689
+ # }
2690
+ # @content;
2691
+ # }
2692
+ #
2693
+ # @overload content_exists()
2694
+ # @return [Sass::Script::Value::Bool] Whether a content block was passed to the mixin.
2695
+ def content_exists
2696
+ # frames.last is the stack frame for this function,
2697
+ # so we use frames[-2] to get the frame before that.
2698
+ mixin_frame = environment.stack.frames[-2]
2699
+ unless mixin_frame && mixin_frame.type == :mixin
2700
+ raise Sass::SyntaxError.new("Cannot call content-exists() except within a mixin.")
2701
+ end
2702
+ bool(!environment.caller.content.nil?)
2703
+ end
2704
+ declare :content_exists, []
2705
+
2706
+ # Return a string containing the value as its Sass representation.
2707
+ #
2708
+ # @overload inspect($value)
2709
+ # @param $value [Sass::Script::Value::Base] The value to inspect.
2710
+ # @return [Sass::Script::Value::String] A representation of the value as
2711
+ # it would be written in Sass.
2712
+ def inspect(value)
2713
+ value.check_deprecated_interp if value.is_a?(Sass::Script::Value::String)
2714
+ unquoted_string(value.to_sass)
2715
+ end
2716
+ declare :inspect, [:value]
2717
+
2718
+ # @overload random()
2719
+ # Return a decimal between 0 and 1, inclusive of 0 but not 1.
2720
+ # @return [Sass::Script::Value::Number] A decimal value.
2721
+ # @overload random($limit)
2722
+ # Return an integer between 1 and `$limit`, inclusive of both 1 and `$limit`.
2723
+ # @param $limit [Sass::Script::Value::Number] The maximum of the random integer to be
2724
+ # returned, a positive integer.
2725
+ # @return [Sass::Script::Value::Number] An integer.
2726
+ # @raise [ArgumentError] if the `$limit` is not 1 or greater
2727
+ def random(limit = nil)
2728
+ generator = Sass::Script::Functions.random_number_generator
2729
+ if limit
2730
+ assert_integer limit, "limit"
2731
+ if limit.to_i < 1
2732
+ raise ArgumentError.new("$limit #{limit} must be greater than or equal to 1")
2733
+ end
2734
+ number(1 + generator.rand(limit.to_i))
2735
+ else
2736
+ number(generator.rand)
2737
+ end
2738
+ end
2739
+ declare :random, []
2740
+ declare :random, [:limit]
2741
+
2742
+ # Parses a user-provided selector into a list of lists of strings
2743
+ # as returned by `&`.
2744
+ #
2745
+ # @example
2746
+ # selector-parse(".foo .bar, .baz .bang") => ('.foo' '.bar', '.baz' '.bang')
2747
+ #
2748
+ # @overload selector_parse($selector)
2749
+ # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List]
2750
+ # The selector to parse. This can be either a string, a list of
2751
+ # strings, or a list of lists of strings as returned by `&`.
2752
+ # @return [Sass::Script::Value::List]
2753
+ # A list of lists of strings representing `$selector`. This is
2754
+ # in the same format as a selector returned by `&`.
2755
+ def selector_parse(selector)
2756
+ parse_selector(selector, :selector).to_sass_script
2757
+ end
2758
+ declare :selector_parse, [:selector]
2759
+
2760
+ # Return a new selector with all selectors in `$selectors` nested beneath
2761
+ # one another as though they had been nested in the stylesheet as
2762
+ # `$selector1 { $selector2 { ... } }`.
2763
+ #
2764
+ # Unlike most selector functions, `selector-nest` allows the
2765
+ # parent selector `&` to be used in any selector but the first.
2766
+ #
2767
+ # @example
2768
+ # selector-nest(".foo", ".bar", ".baz") => .foo .bar .baz
2769
+ # selector-nest(".a .foo", ".b .bar") => .a .foo .b .bar
2770
+ # selector-nest(".foo", "&.bar") => .foo.bar
2771
+ #
2772
+ # @overload selector_nest($selectors...)
2773
+ # @param $selectors [[Sass::Script::Value::String, Sass::Script::Value::List]]
2774
+ # The selectors to nest. At least one selector must be passed. Each of
2775
+ # these can be either a string, a list of strings, or a list of lists of
2776
+ # strings as returned by `&`.
2777
+ # @return [Sass::Script::Value::List]
2778
+ # A list of lists of strings representing the result of nesting
2779
+ # `$selectors`. This is in the same format as a selector returned by
2780
+ # `&`.
2781
+ def selector_nest(*selectors)
2782
+ if selectors.empty?
2783
+ raise ArgumentError.new("$selectors: At least one selector must be passed")
2784
+ end
2785
+
2786
+ parsed = [parse_selector(selectors.first, :selectors)]
2787
+ parsed += selectors[1..-1].map {|sel| parse_selector(sel, :selectors, true)}
2788
+ parsed.inject {|result, child| child.resolve_parent_refs(result)}.to_sass_script
2789
+ end
2790
+ declare :selector_nest, [], :var_args => true
2791
+
2792
+ # Return a new selector with all selectors in `$selectors` appended one
2793
+ # another as though they had been nested in the stylesheet as `$selector1 {
2794
+ # &$selector2 { ... } }`.
2795
+ #
2796
+ # @example
2797
+ # selector-append(".foo", ".bar", ".baz") => .foo.bar.baz
2798
+ # selector-append(".a .foo", ".b .bar") => "a .foo.b .bar"
2799
+ # selector-append(".foo", "-suffix") => ".foo-suffix"
2800
+ #
2801
+ # @overload selector_append($selectors...)
2802
+ # @param $selectors [[Sass::Script::Value::String, Sass::Script::Value::List]]
2803
+ # The selectors to append. At least one selector must be passed. Each of
2804
+ # these can be either a string, a list of strings, or a list of lists of
2805
+ # strings as returned by `&`.
2806
+ # @return [Sass::Script::Value::List]
2807
+ # A list of lists of strings representing the result of appending
2808
+ # `$selectors`. This is in the same format as a selector returned by
2809
+ # `&`.
2810
+ # @raise [ArgumentError] if a selector could not be appended.
2811
+ def selector_append(*selectors)
2812
+ if selectors.empty?
2813
+ raise ArgumentError.new("$selectors: At least one selector must be passed")
2814
+ end
2815
+
2816
+ selectors.map {|sel| parse_selector(sel, :selectors)}.inject do |parent, child|
2817
+ child.members.each do |seq|
2818
+ sseq = seq.members.first
2819
+ unless sseq.is_a?(Sass::Selector::SimpleSequence)
2820
+ raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"")
2821
+ end
2822
+
2823
+ base = sseq.base
2824
+ case base
2825
+ when Sass::Selector::Universal
2826
+ raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"")
2827
+ when Sass::Selector::Element
2828
+ unless base.namespace.nil?
2829
+ raise ArgumentError.new("Can't append \"#{seq}\" to \"#{parent}\"")
2830
+ end
2831
+ sseq.members[0] = Sass::Selector::Parent.new(base.name)
2832
+ else
2833
+ sseq.members.unshift Sass::Selector::Parent.new
2834
+ end
2835
+ end
2836
+ child.resolve_parent_refs(parent)
2837
+ end.to_sass_script
2838
+ end
2839
+ declare :selector_append, [], :var_args => true
2840
+
2841
+ # Returns a new version of `$selector` with `$extendee` extended
2842
+ # with `$extender`. This works just like the result of
2843
+ #
2844
+ # $selector { ... }
2845
+ # $extender { @extend $extendee }
2846
+ #
2847
+ # @example
2848
+ # selector-extend(".a .b", ".b", ".foo .bar") => .a .b, .a .foo .bar, .foo .a .bar
2849
+ #
2850
+ # @overload selector_extend($selector, $extendee, $extender)
2851
+ # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List]
2852
+ # The selector within which `$extendee` is extended with
2853
+ # `$extender`. This can be either a string, a list of strings,
2854
+ # or a list of lists of strings as returned by `&`.
2855
+ # @param $extendee [Sass::Script::Value::String, Sass::Script::Value::List]
2856
+ # The selector being extended. This can be either a string, a
2857
+ # list of strings, or a list of lists of strings as returned
2858
+ # by `&`.
2859
+ # @param $extender [Sass::Script::Value::String, Sass::Script::Value::List]
2860
+ # The selector being injected into `$selector`. This can be
2861
+ # either a string, a list of strings, or a list of lists of
2862
+ # strings as returned by `&`.
2863
+ # @return [Sass::Script::Value::List]
2864
+ # A list of lists of strings representing the result of the
2865
+ # extension. This is in the same format as a selector returned
2866
+ # by `&`.
2867
+ # @raise [ArgumentError] if the extension fails
2868
+ def selector_extend(selector, extendee, extender)
2869
+ selector = parse_selector(selector, :selector)
2870
+ extendee = parse_selector(extendee, :extendee)
2871
+ extender = parse_selector(extender, :extender)
2872
+
2873
+ extends = Sass::Util::SubsetMap.new
2874
+ begin
2875
+ extender.populate_extends(extends, extendee, nil, [], true)
2876
+ selector.do_extend(extends).to_sass_script
2877
+ rescue Sass::SyntaxError => e
2878
+ raise ArgumentError.new(e.to_s)
2879
+ end
2880
+ end
2881
+ declare :selector_extend, [:selector, :extendee, :extender]
2882
+
2883
+ # Replaces all instances of `$original` with `$replacement` in `$selector`
2884
+ #
2885
+ # This works by using `@extend` and throwing away the original
2886
+ # selector. This means that it can be used to do very advanced
2887
+ # replacements; see the examples below.
2888
+ #
2889
+ # @example
2890
+ # selector-replace(".foo .bar", ".bar", ".baz") => ".foo .baz"
2891
+ # selector-replace(".foo.bar.baz", ".foo.baz", ".qux") => ".bar.qux"
2892
+ #
2893
+ # @overload selector_replace($selector, $original, $replacement)
2894
+ # @param $selector [Sass::Script::Value::String, Sass::Script::Value::List]
2895
+ # The selector within which `$original` is replaced with
2896
+ # `$replacement`. This can be either a string, a list of
2897
+ # strings, or a list of lists of strings as returned by `&`.
2898
+ # @param $original [Sass::Script::Value::String, Sass::Script::Value::List]
2899
+ # The selector being replaced. This can be either a string, a
2900
+ # list of strings, or a list of lists of strings as returned
2901
+ # by `&`.
2902
+ # @param $replacement [Sass::Script::Value::String, Sass::Script::Value::List]
2903
+ # The selector that `$original` is being replaced with. This
2904
+ # can be either a string, a list of strings, or a list of
2905
+ # lists of strings as returned by `&`.
2906
+ # @return [Sass::Script::Value::List]
2907
+ # A list of lists of strings representing the result of the
2908
+ # extension. This is in the same format as a selector returned
2909
+ # by `&`.
2910
+ # @raise [ArgumentError] if the replacement fails
2911
+ def selector_replace(selector, original, replacement)
2912
+ selector = parse_selector(selector, :selector)
2913
+ original = parse_selector(original, :original)
2914
+ replacement = parse_selector(replacement, :replacement)
2915
+
2916
+ extends = Sass::Util::SubsetMap.new
2917
+ begin
2918
+ replacement.populate_extends(extends, original, nil, [], true)
2919
+ selector.do_extend(extends, [], true).to_sass_script
2920
+ rescue Sass::SyntaxError => e
2921
+ raise ArgumentError.new(e.to_s)
2922
+ end
2923
+ end
2924
+ declare :selector_replace, [:selector, :original, :replacement]
2925
+
2926
+ # Unifies two selectors into a single selector that matches only
2927
+ # elements matched by both input selectors. Returns `null` if
2928
+ # there is no such selector.
2929
+ #
2930
+ # Like the selector unification done for `@extend`, this doesn't
2931
+ # guarantee that the output selector will match *all* elements
2932
+ # matched by both input selectors. For example, if `.a .b` is
2933
+ # unified with `.x .y`, `.a .x .b.y, .x .a .b.y` will be returned,
2934
+ # but `.a.x .b.y` will not. This avoids exponential output size
2935
+ # while matching all elements that are likely to exist in
2936
+ # practice.
2937
+ #
2938
+ # @example
2939
+ # selector-unify(".a", ".b") => .a.b
2940
+ # selector-unify(".a .b", ".x .y") => .a .x .b.y, .x .a .b.y
2941
+ # selector-unify(".a.b", ".b.c") => .a.b.c
2942
+ # selector-unify("#a", "#b") => null
2943
+ #
2944
+ # @overload selector_unify($selector1, $selector2)
2945
+ # @param $selector1 [Sass::Script::Value::String, Sass::Script::Value::List]
2946
+ # The first selector to be unified. This can be either a
2947
+ # string, a list of strings, or a list of lists of strings as
2948
+ # returned by `&`.
2949
+ # @param $selector2 [Sass::Script::Value::String, Sass::Script::Value::List]
2950
+ # The second selector to be unified. This can be either a
2951
+ # string, a list of strings, or a list of lists of strings as
2952
+ # returned by `&`.
2953
+ # @return [Sass::Script::Value::List, Sass::Script::Value::Null]
2954
+ # A list of lists of strings representing the result of the
2955
+ # unification, or null if no unification exists. This is in
2956
+ # the same format as a selector returned by `&`.
2957
+ def selector_unify(selector1, selector2)
2958
+ selector1 = parse_selector(selector1, :selector1)
2959
+ selector2 = parse_selector(selector2, :selector2)
2960
+ return null unless (unified = selector1.unify(selector2))
2961
+ unified.to_sass_script
2962
+ end
2963
+ declare :selector_unify, [:selector1, :selector2]
2964
+
2965
+ # Returns the [simple
2966
+ # selectors](http://dev.w3.org/csswg/selectors4/#simple) that
2967
+ # comprise the compound selector `$selector`.
2968
+ #
2969
+ # Note that `$selector` **must be** a [compound
2970
+ # selector](http://dev.w3.org/csswg/selectors4/#compound). That
2971
+ # means it cannot contain commas or spaces. It also means that
2972
+ # unlike other selector functions, this takes only strings, not
2973
+ # lists.
2974
+ #
2975
+ # @example
2976
+ # simple-selectors(".foo.bar") => ".foo", ".bar"
2977
+ # simple-selectors(".foo.bar.baz") => ".foo", ".bar", ".baz"
2978
+ #
2979
+ # @overload simple_selectors($selector)
2980
+ # @param $selector [Sass::Script::Value::String]
2981
+ # The compound selector whose simple selectors will be extracted.
2982
+ # @return [Sass::Script::Value::List]
2983
+ # A list of simple selectors in the compound selector.
2984
+ def simple_selectors(selector)
2985
+ selector = parse_compound_selector(selector, :selector)
2986
+ list(selector.members.map {|simple| unquoted_string(simple.to_s)}, :comma)
2987
+ end
2988
+ declare :simple_selectors, [:selector]
2989
+
2990
+ # Returns whether `$super` is a superselector of `$sub`. This means that
2991
+ # `$super` matches all the elements that `$sub` matches, as well as possibly
2992
+ # additional elements. In general, simpler selectors tend to be
2993
+ # superselectors of more complex oned.
2994
+ #
2995
+ # @example
2996
+ # is-superselector(".foo", ".foo.bar") => true
2997
+ # is-superselector(".foo.bar", ".foo") => false
2998
+ # is-superselector(".bar", ".foo .bar") => true
2999
+ # is-superselector(".foo .bar", ".bar") => false
3000
+ #
3001
+ # @overload is_superselector($super, $sub)
3002
+ # @param $super [Sass::Script::Value::String, Sass::Script::Value::List]
3003
+ # The potential superselector. This can be either a string, a list of
3004
+ # strings, or a list of lists of strings as returned by `&`.
3005
+ # @param $sub [Sass::Script::Value::String, Sass::Script::Value::List]
3006
+ # The potential subselector. This can be either a string, a list of
3007
+ # strings, or a list of lists of strings as returned by `&`.
3008
+ # @return [Sass::Script::Value::Bool]
3009
+ # Whether `$selector1` is a superselector of `$selector2`.
3010
+ def is_superselector(sup, sub)
3011
+ sup = parse_selector(sup, :super)
3012
+ sub = parse_selector(sub, :sub)
3013
+ bool(sup.superselector?(sub))
3014
+ end
3015
+ declare :is_superselector, [:super, :sub]
3016
+
3017
+ private
3018
+
3019
+ # This method implements the pattern of transforming a numeric value into
3020
+ # another numeric value with the same units.
3021
+ # It yields a number to a block to perform the operation and return a number
3022
+ def numeric_transformation(value)
3023
+ assert_type value, :Number, :value
3024
+ Sass::Script::Value::Number.new(
3025
+ yield(value.value), value.numerator_units, value.denominator_units)
3026
+ end
3027
+
3028
+ def _adjust(color, amount, attr, range, op, units = "")
3029
+ assert_type color, :Color, :color
3030
+ assert_type amount, :Number, :amount
3031
+ Sass::Util.check_range('Amount', range, amount, units)
3032
+
3033
+ color.with(attr => color.send(attr).send(op, amount.value))
3034
+ end
3035
+
3036
+ # Helper method to create color with forced hex representation
3037
+ def color_with_hex(red, green, blue, alpha = nil)
3038
+ assert_type red, :Number, :red
3039
+ assert_type green, :Number, :green
3040
+ assert_type blue, :Number, :blue
3041
+
3042
+ r = percentage_or_unitless(red, 255, "red").to_i
3043
+ g = percentage_or_unitless(green, 255, "green").to_i
3044
+ b = percentage_or_unitless(blue, 255, "blue").to_i
3045
+
3046
+ if alpha.nil?
3047
+ color = Sass::Script::Value::Color.new([r, g, b])
3048
+ hex_str = "#%02x%02x%02x" % [r, g, b]
3049
+ color.instance_variable_set(:@representation, hex_str)
3050
+ color
3051
+ else
3052
+ assert_type alpha, :Number, :alpha
3053
+ a = percentage_or_unitless(alpha, 1, "alpha")
3054
+ color = Sass::Script::Value::Color.new([r, g, b, a])
3055
+ if a == 1
3056
+ hex_str = "#%02x%02x%02x" % [r, g, b]
3057
+ else
3058
+ hex_str = "rgba(%d, %d, %d, %s)" % [r, g, b, a]
3059
+ end
3060
+ color.instance_variable_set(:@representation, hex_str)
3061
+ color
3062
+ end
3063
+ end
3064
+
3065
+ # Normalize angle to degrees
3066
+ # (0-360 range)
3067
+ # Supports: deg, grad, rad, turn
3068
+ def normalize_hue(hue)
3069
+ value = hue.value
3070
+
3071
+ if hue.unitless? || hue.is_unit?("deg")
3072
+ # Already in degrees or unitless (treat as degrees)
3073
+ value = value.to_f
3074
+ elsif hue.is_unit?("grad")
3075
+ # Convert gradians to degrees (400grad = 360deg)
3076
+ value = value * 360.0 / 400.0
3077
+ elsif hue.is_unit?("rad")
3078
+ # Convert radians to degrees (2π rad = 360deg)
3079
+ value = value * 180.0 / Math::PI
3080
+ elsif hue.is_unit?("turn")
3081
+ # Convert turns to degrees (1turn = 360deg)
3082
+ value = value * 360.0
3083
+ else
3084
+ # For any other unit, just use the value as-is
3085
+ value = value.to_f
3086
+ end
3087
+
3088
+ value
3089
+ end
3090
+
3091
+ def percentage_or_unitless(number, max, name)
3092
+
3093
+ if number.unitless?
3094
+ number.value
3095
+ elsif number.is_unit?("%")
3096
+ max * number.value / 100.0;
3097
+ else
3098
+ raise ArgumentError.new(
3099
+ "$#{name}: Expected #{number} to have no units or \"%\"");
3100
+ end
3101
+ end
3102
+ end
3103
+ end