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,564 @@
1
+ module Sass::Script::Value
2
+ # A SassScript object representing a number.
3
+ # SassScript numbers can have decimal values,
4
+ # and can also have units.
5
+ # For example, `12`, `1px`, and `10.45em`
6
+ # are all valid values.
7
+ #
8
+ # Numbers can also have more complex units, such as `1px*em/in`.
9
+ # These cannot be inputted directly in Sass code at the moment.
10
+ class Number < Base
11
+ # The Ruby value of the number.
12
+ #
13
+ # @return [Numeric]
14
+ attr_reader :value
15
+
16
+ # A list of units in the numerator of the number.
17
+ # For example, `1px*em/in*cm` would return `["px", "em"]`
18
+ # @return [Array<String>]
19
+ attr_reader :numerator_units
20
+
21
+ # A list of units in the denominator of the number.
22
+ # For example, `1px*em/in*cm` would return `["in", "cm"]`
23
+ # @return [Array<String>]
24
+ attr_reader :denominator_units
25
+
26
+ # The original representation of this number.
27
+ # For example, although the result of `1px/2px` is `0.5`,
28
+ # the value of `#original` is `"1px/2px"`.
29
+ #
30
+ # This is only non-nil when the original value should be used as the CSS value,
31
+ # as in `font: 1px/2px`.
32
+ #
33
+ # @return [Boolean, nil]
34
+ attr_accessor :original
35
+
36
+ def self.precision
37
+ Thread.current[:sass_numeric_precision] || Thread.main[:sass_numeric_precision] || 10
38
+ end
39
+
40
+ # Sets the number of digits of precision
41
+ # For example, if this is `3`,
42
+ # `3.1415926` will be printed as `3.142`.
43
+ # The numeric precision is stored as a thread local for thread safety reasons.
44
+ # To set for all threads, be sure to set the precision on the main thread.
45
+ def self.precision=(digits)
46
+ Thread.current[:sass_numeric_precision] = digits.round
47
+ Thread.current[:sass_numeric_precision_factor] = nil
48
+ Thread.current[:sass_numeric_epsilon] = nil
49
+ end
50
+
51
+ # the precision factor used in numeric output
52
+ # it is derived from the `precision` method.
53
+ def self.precision_factor
54
+ Thread.current[:sass_numeric_precision_factor] ||= 10.0**precision
55
+ end
56
+
57
+ # Used in checking equality of floating point numbers. Any
58
+ # numbers within an `epsilon` of each other are considered functionally equal.
59
+ # The value for epsilon is one tenth of the current numeric precision.
60
+ def self.epsilon
61
+ Thread.current[:sass_numeric_epsilon] ||= 1 / (precision_factor * 10)
62
+ end
63
+
64
+ # Used so we don't allocate two new arrays for each new number.
65
+ NO_UNITS = []
66
+
67
+ # @param value [Numeric] The value of the number
68
+ # @param numerator_units [::String, Array<::String>] See \{#numerator\_units}
69
+ # @param denominator_units [::String, Array<::String>] See \{#denominator\_units}
70
+ def initialize(value, numerator_units = NO_UNITS, denominator_units = NO_UNITS)
71
+ numerator_units = [numerator_units] if numerator_units.is_a?(::String)
72
+ denominator_units = [denominator_units] if denominator_units.is_a?(::String)
73
+ super(value)
74
+ @numerator_units = numerator_units
75
+ @denominator_units = denominator_units
76
+ @options = nil
77
+ normalize!
78
+ end
79
+
80
+ # The SassScript `+` operation.
81
+ # Its functionality depends on the type of its argument:
82
+ #
83
+ # {Number}
84
+ # : Adds the two numbers together, converting units if possible.
85
+ #
86
+ # {Color}
87
+ # : Adds this number to each of the RGB color channels.
88
+ #
89
+ # {Value}
90
+ # : See {Value::Base#plus}.
91
+ #
92
+ # @param other [Value] The right-hand side of the operator
93
+ # @return [Value] The result of the operation
94
+ # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
95
+ def plus(other)
96
+ if other.is_a? Number
97
+ operate(other, :+)
98
+ elsif other.is_a?(Color)
99
+ other.plus(self)
100
+ else
101
+ super
102
+ end
103
+ end
104
+
105
+ # The SassScript binary `-` operation (e.g. `$a - $b`).
106
+ # Its functionality depends on the type of its argument:
107
+ #
108
+ # {Number}
109
+ # : Subtracts this number from the other, converting units if possible.
110
+ #
111
+ # {Value}
112
+ # : See {Value::Base#minus}.
113
+ #
114
+ # @param other [Value] The right-hand side of the operator
115
+ # @return [Value] The result of the operation
116
+ # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
117
+ def minus(other)
118
+ if other.is_a? Number
119
+ operate(other, :-)
120
+ else
121
+ super
122
+ end
123
+ end
124
+
125
+ # The SassScript unary `+` operation (e.g. `+$a`).
126
+ #
127
+ # @return [Number] The value of this number
128
+ def unary_plus
129
+ self
130
+ end
131
+
132
+ # The SassScript unary `-` operation (e.g. `-$a`).
133
+ #
134
+ # @return [Number] The negative value of this number
135
+ def unary_minus
136
+ Number.new(-value, @numerator_units, @denominator_units)
137
+ end
138
+
139
+ # The SassScript `*` operation.
140
+ # Its functionality depends on the type of its argument:
141
+ #
142
+ # {Number}
143
+ # : Multiplies the two numbers together, converting units appropriately.
144
+ #
145
+ # {Color}
146
+ # : Multiplies each of the RGB color channels by this number.
147
+ #
148
+ # @param other [Number, Color] The right-hand side of the operator
149
+ # @return [Number, Color] The result of the operation
150
+ # @raise [NoMethodError] if `other` is an invalid type
151
+ def times(other)
152
+ if other.is_a? Number
153
+ operate(other, :*)
154
+ elsif other.is_a? Color
155
+ other.times(self)
156
+ else
157
+ raise NoMethodError.new(nil, :times)
158
+ end
159
+ end
160
+
161
+ # The SassScript `/` operation.
162
+ # Its functionality depends on the type of its argument:
163
+ #
164
+ # {Number}
165
+ # : Divides this number by the other, converting units appropriately.
166
+ #
167
+ # {Value}
168
+ # : See {Value::Base#div}.
169
+ #
170
+ # @param other [Value] The right-hand side of the operator
171
+ # @return [Value] The result of the operation
172
+ def div(other)
173
+ if other.is_a? Number
174
+ res = operate(other, :/)
175
+ if original && other.original
176
+ res.original = "#{original}/#{other.original}"
177
+ end
178
+ res
179
+ else
180
+ super
181
+ end
182
+ end
183
+
184
+ # The SassScript `%` operation.
185
+ #
186
+ # @param other [Number] The right-hand side of the operator
187
+ # @return [Number] This number modulo the other
188
+ # @raise [NoMethodError] if `other` is an invalid type
189
+ # @raise [Sass::UnitConversionError] if `other` has incompatible units
190
+ def mod(other)
191
+ if other.is_a?(Number)
192
+ return Number.new(Float::NAN) if other.value == 0
193
+ operate(other, :%)
194
+ else
195
+ raise NoMethodError.new(nil, :mod)
196
+ end
197
+ end
198
+
199
+ # The SassScript `==` operation.
200
+ #
201
+ # @param other [Value] The right-hand side of the operator
202
+ # @return [Boolean] Whether this number is equal to the other object
203
+ def eq(other)
204
+ return Bool::FALSE unless other.is_a?(Sass::Script::Value::Number)
205
+ this = self
206
+ begin
207
+ if unitless?
208
+ this = this.coerce(other.numerator_units, other.denominator_units)
209
+ else
210
+ other = other.coerce(@numerator_units, @denominator_units)
211
+ end
212
+ rescue Sass::UnitConversionError
213
+ return Bool::FALSE
214
+ end
215
+ Bool.new(basically_equal?(this.value, other.value))
216
+ end
217
+
218
+ def hash
219
+ [value, numerator_units, denominator_units].hash
220
+ end
221
+
222
+ # Hash-equality works differently than `==` equality for numbers.
223
+ # Hash-equality must be transitive, so it just compares the exact value,
224
+ # numerator units, and denominator units.
225
+ def eql?(other)
226
+ basically_equal?(value, other.value) && numerator_units == other.numerator_units &&
227
+ denominator_units == other.denominator_units
228
+ end
229
+
230
+ # The SassScript `>` operation.
231
+ #
232
+ # @param other [Number] The right-hand side of the operator
233
+ # @return [Boolean] Whether this number is greater than the other
234
+ # @raise [NoMethodError] if `other` is an invalid type
235
+ def gt(other)
236
+ raise NoMethodError.new(nil, :gt) unless other.is_a?(Number)
237
+ operate(other, :>)
238
+ end
239
+
240
+ # The SassScript `>=` operation.
241
+ #
242
+ # @param other [Number] The right-hand side of the operator
243
+ # @return [Boolean] Whether this number is greater than or equal to the other
244
+ # @raise [NoMethodError] if `other` is an invalid type
245
+ def gte(other)
246
+ raise NoMethodError.new(nil, :gte) unless other.is_a?(Number)
247
+ operate(other, :>=)
248
+ end
249
+
250
+ # The SassScript `<` operation.
251
+ #
252
+ # @param other [Number] The right-hand side of the operator
253
+ # @return [Boolean] Whether this number is less than the other
254
+ # @raise [NoMethodError] if `other` is an invalid type
255
+ def lt(other)
256
+ raise NoMethodError.new(nil, :lt) unless other.is_a?(Number)
257
+ operate(other, :<)
258
+ end
259
+
260
+ # The SassScript `<=` operation.
261
+ #
262
+ # @param other [Number] The right-hand side of the operator
263
+ # @return [Boolean] Whether this number is less than or equal to the other
264
+ # @raise [NoMethodError] if `other` is an invalid type
265
+ def lte(other)
266
+ raise NoMethodError.new(nil, :lte) unless other.is_a?(Number)
267
+ operate(other, :<=)
268
+ end
269
+
270
+ # @return [String] The CSS representation of this number
271
+ # @raise [Sass::SyntaxError] if this number has units that can't be used in CSS
272
+ # (e.g. `px*in`)
273
+ def to_s(opts = {})
274
+ return original if original
275
+ raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") unless legal_units?
276
+ inspect
277
+ end
278
+
279
+ # Returns a readable representation of this number.
280
+ #
281
+ # This representation is valid CSS (and valid SassScript)
282
+ # as long as there is only one unit.
283
+ #
284
+ # @return [String] The representation
285
+ def inspect(opts = {})
286
+ return original if original
287
+
288
+ value = self.class.round(self.value)
289
+ str = value.to_s
290
+
291
+ # Ruby will occasionally print in scientific notation if the number is
292
+ # small enough. That's technically valid CSS, but it's not well-supported
293
+ # and confusing.
294
+ str = ("%0.#{self.class.precision}f" % value).gsub(/0*$/, '') if str.include?('e')
295
+
296
+ # Sometimes numeric formatting will result in a decimal number with a trailing zero (x.0)
297
+ if str =~ /(.*)\.0$/
298
+ str = $1
299
+ end
300
+
301
+ # We omit a leading zero before the decimal point in compressed mode.
302
+ if @options && options[:style] == :compressed
303
+ str.sub!(/^(-)?0\./, '\1.')
304
+ end
305
+
306
+ unitless? ? str : "#{str}#{unit_str}"
307
+ end
308
+ alias_method :to_sass, :inspect
309
+
310
+ # @return [Integer] The integer value of the number
311
+ # @raise [Sass::SyntaxError] if the number isn't an integer
312
+ def to_i
313
+ super unless int?
314
+ value.to_i
315
+ end
316
+
317
+ # @return [Boolean] Whether or not this number is an integer.
318
+ def int?
319
+ basically_equal?(value % 1, 0.0)
320
+ end
321
+
322
+ # @return [Boolean] Whether or not this number has no units.
323
+ def unitless?
324
+ @numerator_units.empty? && @denominator_units.empty?
325
+ end
326
+
327
+ # Checks whether the number has the numerator unit specified.
328
+ #
329
+ # @example
330
+ # number = Sass::Script::Value::Number.new(10, "px")
331
+ # number.is_unit?("px") => true
332
+ # number.is_unit?(nil) => false
333
+ #
334
+ # @param unit [::String, nil] The unit the number should have or nil if the number
335
+ # should be unitless.
336
+ # @see Number#unitless? The unitless? method may be more readable.
337
+ def is_unit?(unit)
338
+ if unit
339
+ denominator_units.size == 0 && numerator_units.size == 1 && numerator_units.first == unit
340
+ else
341
+ unitless?
342
+ end
343
+ end
344
+
345
+ # @return [Boolean] Whether or not this number has units that can be represented in CSS
346
+ # (that is, zero or one \{#numerator\_units}).
347
+ def legal_units?
348
+ (@numerator_units.empty? || @numerator_units.size == 1) && @denominator_units.empty?
349
+ end
350
+
351
+ # Returns this number converted to other units.
352
+ # The conversion takes into account the relationship between e.g. mm and cm,
353
+ # as well as between e.g. in and cm.
354
+ #
355
+ # If this number has no units, it will simply return itself
356
+ # with the given units.
357
+ #
358
+ # An incompatible coercion, e.g. between px and cm, will raise an error.
359
+ #
360
+ # @param num_units [Array<String>] The numerator units to coerce this number into.
361
+ # See {\#numerator\_units}
362
+ # @param den_units [Array<String>] The denominator units to coerce this number into.
363
+ # See {\#denominator\_units}
364
+ # @return [Number] The number with the new units
365
+ # @raise [Sass::UnitConversionError] if the given units are incompatible with the number's
366
+ # current units
367
+ def coerce(num_units, den_units)
368
+ Number.new(if unitless?
369
+ value
370
+ else
371
+ value * coercion_factor(@numerator_units, num_units) /
372
+ coercion_factor(@denominator_units, den_units)
373
+ end, num_units, den_units)
374
+ end
375
+
376
+ # @param other [Number] A number to decide if it can be compared with this number.
377
+ # @return [Boolean] Whether or not this number can be compared with the other.
378
+ def comparable_to?(other)
379
+ operate(other, :+)
380
+ true
381
+ rescue Sass::UnitConversionError
382
+ false
383
+ end
384
+
385
+ # Returns a human readable representation of the units in this number.
386
+ # For complex units this takes the form of:
387
+ # numerator_unit1 * numerator_unit2 / denominator_unit1 * denominator_unit2
388
+ # @return [String] a string that represents the units in this number
389
+ def unit_str
390
+ rv = @numerator_units.sort.join("*")
391
+ if @denominator_units.any?
392
+ rv << "/"
393
+ rv << @denominator_units.sort.join("*")
394
+ end
395
+ rv
396
+ end
397
+
398
+ private
399
+
400
+ # @private
401
+ # @see Sass::Script::Number.basically_equal?
402
+ def basically_equal?(num1, num2)
403
+ self.class.basically_equal?(num1, num2)
404
+ end
405
+
406
+ # Checks whether two numbers are within an epsilon of each other.
407
+ # @return [Boolean]
408
+ def self.basically_equal?(num1, num2)
409
+ (num1 - num2).abs < epsilon
410
+ end
411
+
412
+ # @private
413
+ def self.round(num)
414
+ if num.is_a?(Float) && (num.infinite? || num.nan?)
415
+ num
416
+ elsif basically_equal?(num % 1, 0.0)
417
+ num.round
418
+ else
419
+ ((num * precision_factor).round / precision_factor).to_f
420
+ end
421
+ end
422
+
423
+ OPERATIONS = [:+, :-, :<=, :<, :>, :>=, :%]
424
+
425
+ def operate(other, operation)
426
+ this = self
427
+ if OPERATIONS.include?(operation)
428
+ if unitless?
429
+ this = this.coerce(other.numerator_units, other.denominator_units)
430
+ else
431
+ other = other.coerce(@numerator_units, @denominator_units)
432
+ end
433
+ end
434
+ # avoid integer division
435
+ value = :/ == operation ? this.value.to_f : this.value
436
+ result = value.send(operation, other.value)
437
+
438
+ if result.is_a?(Numeric)
439
+ Number.new(result, *compute_units(this, other, operation))
440
+ else # Boolean op
441
+ Bool.new(result)
442
+ end
443
+ end
444
+
445
+ def coercion_factor(from_units, to_units)
446
+ # get a list of unmatched units
447
+ from_units, to_units = sans_common_units(from_units, to_units)
448
+
449
+ if from_units.size != to_units.size || !convertable?(from_units | to_units)
450
+ raise Sass::UnitConversionError.new(
451
+ "Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.")
452
+ end
453
+
454
+ from_units.zip(to_units).inject(1) {|m, p| m * conversion_factor(p[0], p[1])}
455
+ end
456
+
457
+ def compute_units(this, other, operation)
458
+ case operation
459
+ when :*
460
+ [this.numerator_units + other.numerator_units,
461
+ this.denominator_units + other.denominator_units]
462
+ when :/
463
+ [this.numerator_units + other.denominator_units,
464
+ this.denominator_units + other.numerator_units]
465
+ else
466
+ [this.numerator_units, this.denominator_units]
467
+ end
468
+ end
469
+
470
+ def normalize!
471
+ return if unitless?
472
+ @numerator_units, @denominator_units =
473
+ sans_common_units(@numerator_units, @denominator_units)
474
+
475
+ @denominator_units.each_with_index do |d, i|
476
+ next unless convertable?(d) && (u = @numerator_units.find {|n| convertable?([n, d])})
477
+ @value /= conversion_factor(d, u)
478
+ @denominator_units.delete_at(i)
479
+ @numerator_units.delete_at(@numerator_units.index(u))
480
+ end
481
+ end
482
+
483
+ # This is the source data for all the unit logic. It's pre-processed to make
484
+ # it efficient to figure out whether a set of units is mutually compatible
485
+ # and what the conversion ratio is between two units.
486
+ #
487
+ # These come from http://www.w3.org/TR/2012/WD-css3-values-20120308/.
488
+ relative_sizes = [
489
+ {
490
+ 'in' => Rational(1),
491
+ 'cm' => Rational(1, 2.54),
492
+ 'pc' => Rational(1, 6),
493
+ 'mm' => Rational(1, 25.4),
494
+ 'q' => Rational(1, 101.6),
495
+ 'pt' => Rational(1, 72),
496
+ 'px' => Rational(1, 96)
497
+ },
498
+ {
499
+ 'deg' => Rational(1, 360),
500
+ 'grad' => Rational(1, 400),
501
+ 'rad' => Rational(1, 2 * Math::PI),
502
+ 'turn' => Rational(1)
503
+ },
504
+ {
505
+ 's' => Rational(1),
506
+ 'ms' => Rational(1, 1000)
507
+ },
508
+ {
509
+ 'Hz' => Rational(1),
510
+ 'kHz' => Rational(1000)
511
+ },
512
+ {
513
+ 'dpi' => Rational(1),
514
+ 'dpcm' => Rational(254, 100),
515
+ 'dppx' => Rational(96)
516
+ }
517
+ ]
518
+
519
+ # A hash from each known unit to the set of units that it's mutually
520
+ # convertible with.
521
+ MUTUALLY_CONVERTIBLE = {}
522
+ relative_sizes.map do |values|
523
+ set = values.keys.to_set
524
+ values.keys.each {|name| MUTUALLY_CONVERTIBLE[name] = set}
525
+ end
526
+
527
+ # A two-dimensional hash from two units to the conversion ratio between
528
+ # them. Multiply `X` by `CONVERSION_TABLE[X][Y]` to convert it to `Y`.
529
+ CONVERSION_TABLE = {}
530
+ relative_sizes.each do |values|
531
+ values.each do |(name1, value1)|
532
+ CONVERSION_TABLE[name1] ||= {}
533
+ values.each do |(name2, value2)|
534
+ value = value1 / value2
535
+ CONVERSION_TABLE[name1][name2] = value.denominator == 1 ? value.to_i : value.to_f
536
+ end
537
+ end
538
+ end
539
+
540
+ def conversion_factor(from_unit, to_unit)
541
+ CONVERSION_TABLE[from_unit][to_unit]
542
+ end
543
+
544
+ def convertable?(units)
545
+ units = Array(units).to_set
546
+ return true if units.empty?
547
+ return false unless (mutually_convertible = MUTUALLY_CONVERTIBLE[units.first])
548
+ units.subset?(mutually_convertible)
549
+ end
550
+
551
+ def sans_common_units(units1, units2)
552
+ units2 = units2.dup
553
+ # Can't just use -, because we want px*px to coerce properly to px*mm
554
+ units1 = units1.map do |u|
555
+ j = units2.index(u)
556
+ next u unless j
557
+ units2.delete_at(j)
558
+ nil
559
+ end
560
+ units1.compact!
561
+ return units1, units2
562
+ end
563
+ end
564
+ end
@@ -0,0 +1,138 @@
1
+ # -*- coding: utf-8 -*-
2
+ module Sass::Script::Value
3
+ # A SassScript object representing a CSS string *or* a CSS identifier.
4
+ class String < Base
5
+ @@interpolation_deprecation = Sass::Deprecation.new
6
+
7
+ # The Ruby value of the string.
8
+ #
9
+ # @return [String]
10
+ attr_reader :value
11
+
12
+ # Whether this is a CSS string or a CSS identifier.
13
+ # The difference is that strings are written with double-quotes,
14
+ # while identifiers aren't.
15
+ #
16
+ # @return [Symbol] `:string` or `:identifier`
17
+ attr_reader :type
18
+
19
+ def self.value(contents)
20
+ contents.gsub("\\\n", "").gsub(/\\(?:([0-9a-fA-F]{1,6})\s?|(.))/) do
21
+ next $2 if $2
22
+ # Handle unicode escapes as per CSS Syntax Level 3 section 4.3.8.
23
+ code_point = $1.to_i(16)
24
+ if code_point == 0 || code_point > 0x10FFFF ||
25
+ (code_point >= 0xD800 && code_point <= 0xDFFF)
26
+ '�'
27
+ else
28
+ [code_point].pack("U")
29
+ end
30
+ end
31
+ end
32
+
33
+ # Returns the quoted string representation of `contents`.
34
+ #
35
+ # @options opts :quote [String]
36
+ # The preferred quote style for quoted strings. If `:none`, strings are
37
+ # always emitted unquoted. If `nil`, quoting is determined automatically.
38
+ # @options opts :sass [String]
39
+ # Whether to quote strings for Sass source, as opposed to CSS. Defaults to `false`.
40
+ def self.quote(contents, opts = {})
41
+ quote = opts[:quote]
42
+
43
+ # Short-circuit if there are no characters that need quoting.
44
+ unless contents =~ /[\n\\"']|\#\{/
45
+ quote ||= '"'
46
+ return "#{quote}#{contents}#{quote}"
47
+ end
48
+
49
+ if quote.nil?
50
+ if contents.include?('"')
51
+ if contents.include?("'")
52
+ quote = '"'
53
+ else
54
+ quote = "'"
55
+ end
56
+ else
57
+ quote = '"'
58
+ end
59
+ end
60
+
61
+ # Replace single backslashes with multiples.
62
+ contents = contents.gsub("\\", "\\\\\\\\")
63
+
64
+ # Escape interpolation.
65
+ contents = contents.gsub('#{', "\\\#{") if opts[:sass]
66
+
67
+ if quote == '"'
68
+ contents = contents.gsub('"', "\\\"")
69
+ else
70
+ contents = contents.gsub("'", "\\'")
71
+ end
72
+
73
+ contents = contents.gsub(/\n(?![a-fA-F0-9\s])/, "\\a").gsub("\n", "\\a ")
74
+ "#{quote}#{contents}#{quote}"
75
+ end
76
+
77
+ # Creates a new string.
78
+ #
79
+ # @param value [String] See \{#value}
80
+ # @param type [Symbol] See \{#type}
81
+ # @param deprecated_interp_equivalent [String?]
82
+ # If this was created via a potentially-deprecated string interpolation,
83
+ # this is the replacement expression that should be suggested to the user.
84
+ def initialize(value, type = :identifier, deprecated_interp_equivalent = nil)
85
+ super(value)
86
+ @type = type
87
+ @deprecated_interp_equivalent = deprecated_interp_equivalent
88
+ end
89
+
90
+ # @see Value#plus
91
+ def plus(other)
92
+ other_value = if other.is_a?(Sass::Script::Value::String)
93
+ other.value
94
+ else
95
+ other.to_s(:quote => :none)
96
+ end
97
+ Sass::Script::Value::String.new(value + other_value, type)
98
+ end
99
+
100
+ # @see Value#to_s
101
+ def to_s(opts = {})
102
+ return @value.gsub(/\n\s*/, ' ') if opts[:quote] == :none || @type == :identifier
103
+ String.quote(value, opts)
104
+ end
105
+
106
+ # @see Value#to_sass
107
+ def to_sass(opts = {})
108
+ to_s(opts.merge(:sass => true))
109
+ end
110
+
111
+ def separator
112
+ check_deprecated_interp
113
+ super
114
+ end
115
+
116
+ def to_a
117
+ check_deprecated_interp
118
+ super
119
+ end
120
+
121
+ # Prints a warning if this string was created using potentially-deprecated
122
+ # interpolation.
123
+ def check_deprecated_interp
124
+ return unless @deprecated_interp_equivalent
125
+
126
+ @@interpolation_deprecation.warn(source_range.file, source_range.start_pos.line, <<WARNING)
127
+ \#{} interpolation near operators will be simplified in a future version of Sass.
128
+ To preserve the current behavior, use quotes:
129
+
130
+ #{@deprecated_interp_equivalent}
131
+ WARNING
132
+ end
133
+
134
+ def inspect
135
+ String.quote(value)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,13 @@
1
+ module Sass::Script::Value; end
2
+
3
+ require 'sass/script/value/base'
4
+ require 'sass/script/value/string'
5
+ require 'sass/script/value/number'
6
+ require 'sass/script/value/color'
7
+ require 'sass/script/value/bool'
8
+ require 'sass/script/value/null'
9
+ require 'sass/script/value/list'
10
+ require 'sass/script/value/arg_list'
11
+ require 'sass/script/value/map'
12
+ require 'sass/script/value/callable'
13
+ require 'sass/script/value/function'