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