sass 3.2.19 → 3.4.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -1
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/CONTRIBUTING.md +148 -0
  5. data/MIT-LICENSE +1 -1
  6. data/README.md +87 -61
  7. data/Rakefile +119 -15
  8. data/VERSION +1 -1
  9. data/VERSION_DATE +1 -1
  10. data/VERSION_NAME +1 -1
  11. data/bin/sass +1 -1
  12. data/bin/scss +1 -1
  13. data/extra/sass-spec-ref.sh +32 -0
  14. data/extra/update_watch.rb +1 -1
  15. data/lib/sass/cache_stores/base.rb +2 -2
  16. data/lib/sass/cache_stores/chain.rb +2 -1
  17. data/lib/sass/cache_stores/filesystem.rb +8 -12
  18. data/lib/sass/cache_stores/memory.rb +5 -6
  19. data/lib/sass/cache_stores/null.rb +2 -2
  20. data/lib/sass/callbacks.rb +3 -2
  21. data/lib/sass/css.rb +22 -23
  22. data/lib/sass/deprecation.rb +55 -0
  23. data/lib/sass/engine.rb +487 -191
  24. data/lib/sass/environment.rb +172 -58
  25. data/lib/sass/error.rb +21 -24
  26. data/lib/sass/exec/base.rb +199 -0
  27. data/lib/sass/exec/sass_convert.rb +283 -0
  28. data/lib/sass/exec/sass_scss.rb +440 -0
  29. data/lib/sass/exec.rb +5 -703
  30. data/lib/sass/features.rb +47 -0
  31. data/lib/sass/importers/base.rb +50 -7
  32. data/lib/sass/importers/deprecated_path.rb +51 -0
  33. data/lib/sass/importers/filesystem.rb +54 -21
  34. data/lib/sass/importers.rb +1 -0
  35. data/lib/sass/logger/base.rb +9 -5
  36. data/lib/sass/logger/delayed.rb +50 -0
  37. data/lib/sass/logger/log_level.rb +3 -7
  38. data/lib/sass/logger.rb +9 -7
  39. data/lib/sass/media.rb +20 -23
  40. data/lib/sass/plugin/compiler.rb +321 -145
  41. data/lib/sass/plugin/configuration.rb +45 -34
  42. data/lib/sass/plugin/merb.rb +3 -3
  43. data/lib/sass/plugin/rack.rb +3 -3
  44. data/lib/sass/plugin/rails.rb +1 -1
  45. data/lib/sass/plugin/staleness_checker.rb +6 -6
  46. data/lib/sass/plugin.rb +9 -8
  47. data/lib/sass/repl.rb +3 -3
  48. data/lib/sass/script/css_lexer.rb +8 -4
  49. data/lib/sass/script/css_parser.rb +4 -2
  50. data/lib/sass/script/css_variable_warning.rb +52 -0
  51. data/lib/sass/script/functions.rb +1583 -433
  52. data/lib/sass/script/lexer.rb +198 -79
  53. data/lib/sass/script/parser.rb +463 -133
  54. data/lib/sass/script/tree/funcall.rb +313 -0
  55. data/lib/sass/script/tree/interpolation.rb +223 -0
  56. data/lib/sass/script/tree/list_literal.rb +104 -0
  57. data/lib/sass/script/tree/literal.rb +49 -0
  58. data/lib/sass/script/tree/map_literal.rb +64 -0
  59. data/lib/sass/script/{node.rb → tree/node.rb} +42 -14
  60. data/lib/sass/script/tree/operation.rb +156 -0
  61. data/lib/sass/script/tree/selector.rb +26 -0
  62. data/lib/sass/script/tree/string_interpolation.rb +125 -0
  63. data/lib/sass/script/{unary_operation.rb → tree/unary_operation.rb} +6 -6
  64. data/lib/sass/script/tree/variable.rb +57 -0
  65. data/lib/sass/script/tree.rb +16 -0
  66. data/lib/sass/script/{arg_list.rb → value/arg_list.rb} +9 -25
  67. data/lib/sass/script/value/base.rb +241 -0
  68. data/lib/sass/script/value/bool.rb +35 -0
  69. data/lib/sass/script/value/color.rb +698 -0
  70. data/lib/sass/script/value/helpers.rb +272 -0
  71. data/lib/sass/script/value/list.rb +113 -0
  72. data/lib/sass/script/value/map.rb +70 -0
  73. data/lib/sass/script/{null.rb → value/null.rb} +14 -7
  74. data/lib/sass/script/{number.rb → value/number.rb} +196 -86
  75. data/lib/sass/script/value/string.rb +138 -0
  76. data/lib/sass/script/value.rb +11 -0
  77. data/lib/sass/script.rb +38 -11
  78. data/lib/sass/scss/css_parser.rb +25 -5
  79. data/lib/sass/scss/parser.rb +532 -458
  80. data/lib/sass/scss/rx.rb +21 -14
  81. data/lib/sass/scss/static_parser.rb +328 -9
  82. data/lib/sass/scss.rb +0 -2
  83. data/lib/sass/selector/abstract_sequence.rb +36 -19
  84. data/lib/sass/selector/comma_sequence.rb +125 -26
  85. data/lib/sass/selector/pseudo.rb +266 -0
  86. data/lib/sass/selector/sequence.rb +200 -71
  87. data/lib/sass/selector/simple.rb +30 -32
  88. data/lib/sass/selector/simple_sequence.rb +193 -64
  89. data/lib/sass/selector.rb +65 -194
  90. data/lib/sass/shared.rb +2 -2
  91. data/lib/sass/source/map.rb +213 -0
  92. data/lib/sass/source/position.rb +39 -0
  93. data/lib/sass/source/range.rb +41 -0
  94. data/lib/sass/stack.rb +120 -0
  95. data/lib/sass/supports.rb +19 -23
  96. data/lib/sass/tree/at_root_node.rb +83 -0
  97. data/lib/sass/tree/charset_node.rb +1 -1
  98. data/lib/sass/tree/comment_node.rb +4 -4
  99. data/lib/sass/tree/css_import_node.rb +19 -11
  100. data/lib/sass/tree/debug_node.rb +2 -2
  101. data/lib/sass/tree/directive_node.rb +21 -4
  102. data/lib/sass/tree/each_node.rb +8 -8
  103. data/lib/sass/tree/error_node.rb +18 -0
  104. data/lib/sass/tree/extend_node.rb +14 -7
  105. data/lib/sass/tree/for_node.rb +4 -4
  106. data/lib/sass/tree/function_node.rb +14 -4
  107. data/lib/sass/tree/if_node.rb +1 -1
  108. data/lib/sass/tree/import_node.rb +10 -10
  109. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  110. data/lib/sass/tree/media_node.rb +4 -14
  111. data/lib/sass/tree/mixin_def_node.rb +4 -4
  112. data/lib/sass/tree/mixin_node.rb +21 -8
  113. data/lib/sass/tree/node.rb +59 -15
  114. data/lib/sass/tree/prop_node.rb +42 -24
  115. data/lib/sass/tree/return_node.rb +3 -2
  116. data/lib/sass/tree/root_node.rb +19 -3
  117. data/lib/sass/tree/rule_node.rb +49 -26
  118. data/lib/sass/tree/supports_node.rb +0 -13
  119. data/lib/sass/tree/trace_node.rb +2 -1
  120. data/lib/sass/tree/variable_node.rb +9 -3
  121. data/lib/sass/tree/visitors/base.rb +5 -8
  122. data/lib/sass/tree/visitors/check_nesting.rb +62 -36
  123. data/lib/sass/tree/visitors/convert.rb +111 -76
  124. data/lib/sass/tree/visitors/cssize.rb +206 -74
  125. data/lib/sass/tree/visitors/deep_copy.rb +11 -6
  126. data/lib/sass/tree/visitors/extend.rb +19 -17
  127. data/lib/sass/tree/visitors/perform.rb +308 -190
  128. data/lib/sass/tree/visitors/set_options.rb +21 -7
  129. data/lib/sass/tree/visitors/to_css.rb +273 -92
  130. data/lib/sass/tree/warn_node.rb +2 -2
  131. data/lib/sass/tree/while_node.rb +2 -2
  132. data/lib/sass/util/cross_platform_random.rb +19 -0
  133. data/lib/sass/util/normalized_map.rb +129 -0
  134. data/lib/sass/util/ordered_hash.rb +192 -0
  135. data/lib/sass/util/subset_map.rb +5 -5
  136. data/lib/sass/util/test.rb +0 -1
  137. data/lib/sass/util.rb +620 -193
  138. data/lib/sass/version.rb +22 -24
  139. data/lib/sass.rb +27 -13
  140. data/test/sass/cache_test.rb +62 -20
  141. data/test/sass/callbacks_test.rb +1 -1
  142. data/test/sass/compiler_test.rb +236 -0
  143. data/test/sass/conversion_test.rb +472 -44
  144. data/test/sass/css2sass_test.rb +73 -5
  145. data/test/sass/css_variable_test.rb +132 -0
  146. data/test/sass/encoding_test.rb +219 -0
  147. data/test/sass/engine_test.rb +618 -415
  148. data/test/sass/exec_test.rb +12 -2
  149. data/test/sass/extend_test.rb +419 -168
  150. data/test/sass/functions_test.rb +931 -93
  151. data/test/sass/importer_test.rb +250 -21
  152. data/test/sass/logger_test.rb +1 -1
  153. data/test/sass/more_results/more_import.css +1 -1
  154. data/test/sass/more_templates/more1.sass +10 -10
  155. data/test/sass/more_templates/more_import.sass +2 -2
  156. data/test/sass/plugin_test.rb +26 -34
  157. data/test/sass/results/compact.css +1 -1
  158. data/test/sass/results/complex.css +4 -4
  159. data/test/sass/results/expanded.css +1 -1
  160. data/test/sass/results/import.css +1 -1
  161. data/test/sass/results/import_charset_ibm866.css +2 -2
  162. data/test/sass/results/mixins.css +17 -17
  163. data/test/sass/results/nested.css +1 -1
  164. data/test/sass/results/parent_ref.css +2 -2
  165. data/test/sass/results/script.css +5 -5
  166. data/test/sass/results/scss_import.css +1 -1
  167. data/test/sass/script_conversion_test.rb +97 -39
  168. data/test/sass/script_test.rb +911 -102
  169. data/test/sass/scss/css_test.rb +215 -34
  170. data/test/sass/scss/rx_test.rb +8 -4
  171. data/test/sass/scss/scss_test.rb +2424 -325
  172. data/test/sass/source_map_test.rb +1055 -0
  173. data/test/sass/superselector_test.rb +210 -0
  174. data/test/sass/templates/_partial.sass +1 -1
  175. data/test/sass/templates/basic.sass +10 -10
  176. data/test/sass/templates/bork1.sass +1 -1
  177. data/test/sass/templates/bork5.sass +1 -1
  178. data/test/sass/templates/compact.sass +10 -10
  179. data/test/sass/templates/complex.sass +187 -187
  180. data/test/sass/templates/compressed.sass +10 -10
  181. data/test/sass/templates/expanded.sass +10 -10
  182. data/test/sass/templates/import.sass +2 -2
  183. data/test/sass/templates/importee.sass +3 -3
  184. data/test/sass/templates/mixins.sass +22 -22
  185. data/test/sass/templates/multiline.sass +4 -4
  186. data/test/sass/templates/nested.sass +13 -13
  187. data/test/sass/templates/parent_ref.sass +12 -12
  188. data/test/sass/templates/script.sass +70 -70
  189. data/test/sass/templates/scss_import.scss +2 -1
  190. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +1 -1
  191. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +2 -2
  192. data/test/sass/templates/subdir/subdir.sass +3 -3
  193. data/test/sass/templates/units.sass +10 -10
  194. data/test/sass/test_helper.rb +1 -1
  195. data/test/sass/util/multibyte_string_scanner_test.rb +11 -3
  196. data/test/sass/util/normalized_map_test.rb +51 -0
  197. data/test/sass/util/subset_map_test.rb +2 -2
  198. data/test/sass/util_test.rb +99 -43
  199. data/test/sass/value_helpers_test.rb +179 -0
  200. data/test/sass-spec.yml +3 -0
  201. data/test/test_helper.rb +42 -12
  202. data/vendor/listen/CHANGELOG.md +1 -228
  203. data/vendor/listen/Gemfile +5 -15
  204. data/vendor/listen/README.md +111 -77
  205. data/vendor/listen/Rakefile +0 -42
  206. data/vendor/listen/lib/listen/adapter.rb +195 -82
  207. data/vendor/listen/lib/listen/adapters/bsd.rb +27 -64
  208. data/vendor/listen/lib/listen/adapters/darwin.rb +21 -58
  209. data/vendor/listen/lib/listen/adapters/linux.rb +23 -55
  210. data/vendor/listen/lib/listen/adapters/polling.rb +25 -34
  211. data/vendor/listen/lib/listen/adapters/windows.rb +50 -46
  212. data/vendor/listen/lib/listen/directory_record.rb +96 -61
  213. data/vendor/listen/lib/listen/listener.rb +135 -37
  214. data/vendor/listen/lib/listen/turnstile.rb +9 -5
  215. data/vendor/listen/lib/listen/version.rb +1 -1
  216. data/vendor/listen/lib/listen.rb +33 -19
  217. data/vendor/listen/listen.gemspec +6 -0
  218. data/vendor/listen/spec/listen/adapter_spec.rb +43 -77
  219. data/vendor/listen/spec/listen/adapters/polling_spec.rb +8 -8
  220. data/vendor/listen/spec/listen/directory_record_spec.rb +81 -56
  221. data/vendor/listen/spec/listen/listener_spec.rb +128 -39
  222. data/vendor/listen/spec/listen_spec.rb +15 -21
  223. data/vendor/listen/spec/spec_helper.rb +4 -0
  224. data/vendor/listen/spec/support/adapter_helper.rb +52 -15
  225. data/vendor/listen/spec/support/directory_record_helper.rb +7 -5
  226. data/vendor/listen/spec/support/listeners_helper.rb +30 -7
  227. metadata +161 -111
  228. data/CONTRIBUTING +0 -3
  229. data/lib/sass/script/bool.rb +0 -18
  230. data/lib/sass/script/color.rb +0 -606
  231. data/lib/sass/script/funcall.rb +0 -245
  232. data/lib/sass/script/interpolation.rb +0 -79
  233. data/lib/sass/script/list.rb +0 -85
  234. data/lib/sass/script/literal.rb +0 -221
  235. data/lib/sass/script/operation.rb +0 -110
  236. data/lib/sass/script/string.rb +0 -51
  237. data/lib/sass/script/string_interpolation.rb +0 -103
  238. data/lib/sass/script/variable.rb +0 -58
  239. data/lib/sass/scss/script_lexer.rb +0 -15
  240. data/lib/sass/scss/script_parser.rb +0 -25
  241. data/test/Gemfile +0 -3
  242. data/test/Gemfile.lock +0 -10
  243. data/vendor/listen/lib/listen/dependency_manager.rb +0 -126
  244. data/vendor/listen/lib/listen/multi_listener.rb +0 -143
  245. data/vendor/listen/spec/listen/dependency_manager_spec.rb +0 -107
  246. data/vendor/listen/spec/listen/multi_listener_spec.rb +0 -174
@@ -1,6 +1,4 @@
1
- require 'sass/script/literal'
2
-
3
- module Sass::Script
1
+ module Sass::Script::Value
4
2
  # A SassScript object representing a number.
5
3
  # SassScript numbers can have decimal values,
6
4
  # and can also have units.
@@ -9,7 +7,7 @@ module Sass::Script
9
7
  #
10
8
  # Numbers can also have more complex units, such as `1px*em/in`.
11
9
  # These cannot be inputted directly in Sass code at the moment.
12
- class Number < Literal
10
+ class Number < Base
13
11
  # The Ruby value of the number.
14
12
  #
15
13
  # @return [Numeric]
@@ -17,7 +15,7 @@ module Sass::Script
17
15
 
18
16
  # A list of units in the numerator of the number.
19
17
  # For example, `1px*em/in*cm` would return `["px", "em"]`
20
- # @return [Array<String>]
18
+ # @return [Array<String>]
21
19
  attr_reader :numerator_units
22
20
 
23
21
  # A list of units in the denominator of the number.
@@ -36,44 +34,46 @@ module Sass::Script
36
34
  attr_accessor :original
37
35
 
38
36
  def self.precision
39
- @precision ||= 5
37
+ Thread.current[:sass_numeric_precision] || Thread.main[:sass_numeric_precision] || 5
40
38
  end
41
39
 
42
40
  # Sets the number of digits of precision
43
41
  # For example, if this is `3`,
44
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
45
  def self.precision=(digits)
46
- @precision = digits.round
47
- @precision_factor = 10.0**@precision
46
+ Thread.current[:sass_numeric_precision] = digits.round
47
+ Thread.current[:sass_numeric_precision_factor] = nil
48
+ Thread.current[:sass_numeric_epsilon] = nil
48
49
  end
49
50
 
50
51
  # the precision factor used in numeric output
51
52
  # it is derived from the `precision` method.
52
53
  def self.precision_factor
53
- @precision_factor ||= 10.0**precision
54
+ Thread.current[:sass_numeric_precision_factor] ||= 10.0**precision
54
55
  end
55
56
 
56
- # Handles the deprecation warning for the PRECISION constant
57
- # This can be removed in 3.2.
58
- def self.const_missing(const)
59
- if const == :PRECISION
60
- Sass::Util.sass_warn("Sass::Script::Number::PRECISION is deprecated and will be removed in a future release. Use Sass::Script::Number.precision_factor instead.")
61
- const_set(:PRECISION, self.precision_factor)
62
- else
63
- super
64
- end
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)
65
62
  end
66
63
 
67
64
  # Used so we don't allocate two new arrays for each new number.
68
- NO_UNITS = []
65
+ NO_UNITS = []
69
66
 
70
67
  # @param value [Numeric] The value of the number
71
- # @param numerator_units [Array<String>] See \{#numerator\_units}
72
- # @param denominator_units [Array<String>] See \{#denominator\_units}
68
+ # @param numerator_units [::String, Array<::String>] See \{#numerator\_units}
69
+ # @param denominator_units [::String, Array<::String>] See \{#denominator\_units}
73
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)
74
73
  super(value)
75
74
  @numerator_units = numerator_units
76
75
  @denominator_units = denominator_units
76
+ @options = nil
77
77
  normalize!
78
78
  end
79
79
 
@@ -86,11 +86,11 @@ module Sass::Script
86
86
  # {Color}
87
87
  # : Adds this number to each of the RGB color channels.
88
88
  #
89
- # {Literal}
90
- # : See {Literal#plus}.
89
+ # {Value}
90
+ # : See {Value::Base#plus}.
91
91
  #
92
- # @param other [Literal] The right-hand side of the operator
93
- # @return [Literal] The result of the operation
92
+ # @param other [Value] The right-hand side of the operator
93
+ # @return [Value] The result of the operation
94
94
  # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
95
95
  def plus(other)
96
96
  if other.is_a? Number
@@ -108,11 +108,11 @@ module Sass::Script
108
108
  # {Number}
109
109
  # : Subtracts this number from the other, converting units if possible.
110
110
  #
111
- # {Literal}
112
- # : See {Literal#minus}.
111
+ # {Value}
112
+ # : See {Value::Base#minus}.
113
113
  #
114
- # @param other [Literal] The right-hand side of the operator
115
- # @return [Literal] The result of the operation
114
+ # @param other [Value] The right-hand side of the operator
115
+ # @return [Value] The result of the operation
116
116
  # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
117
117
  def minus(other)
118
118
  if other.is_a? Number
@@ -164,16 +164,16 @@ module Sass::Script
164
164
  # {Number}
165
165
  # : Divides this number by the other, converting units appropriately.
166
166
  #
167
- # {Literal}
168
- # : See {Literal#div}.
167
+ # {Value}
168
+ # : See {Value::Base#div}.
169
169
  #
170
- # @param other [Literal] The right-hand side of the operator
171
- # @return [Literal] The result of the operation
170
+ # @param other [Value] The right-hand side of the operator
171
+ # @return [Value] The result of the operation
172
172
  def div(other)
173
173
  if other.is_a? Number
174
174
  res = operate(other, :/)
175
- if self.original && other.original
176
- res.original = "#{self.original}/#{other.original}"
175
+ if original && other.original
176
+ res.original = "#{original}/#{other.original}"
177
177
  end
178
178
  res
179
179
  else
@@ -186,12 +186,9 @@ module Sass::Script
186
186
  # @param other [Number] The right-hand side of the operator
187
187
  # @return [Number] This number modulo the other
188
188
  # @raise [NoMethodError] if `other` is an invalid type
189
- # @raise [Sass::UnitConversionError] if `other` has any units
189
+ # @raise [Sass::UnitConversionError] if `other` has incompatible units
190
190
  def mod(other)
191
191
  if other.is_a?(Number)
192
- unless other.unitless?
193
- raise Sass::UnitConversionError.new("Cannot modulo by a number with units: #{other.inspect}.")
194
- end
195
192
  operate(other, :%)
196
193
  else
197
194
  raise NoMethodError.new(nil, :mod)
@@ -200,10 +197,10 @@ module Sass::Script
200
197
 
201
198
  # The SassScript `==` operation.
202
199
  #
203
- # @param other [Literal] The right-hand side of the operator
200
+ # @param other [Value] The right-hand side of the operator
204
201
  # @return [Boolean] Whether this number is equal to the other object
205
202
  def eq(other)
206
- return Sass::Script::Bool.new(false) unless other.is_a?(Sass::Script::Number)
203
+ return Bool::FALSE unless other.is_a?(Sass::Script::Value::Number)
207
204
  this = self
208
205
  begin
209
206
  if unitless?
@@ -212,10 +209,21 @@ module Sass::Script
212
209
  other = other.coerce(@numerator_units, @denominator_units)
213
210
  end
214
211
  rescue Sass::UnitConversionError
215
- return Sass::Script::Bool.new(false)
212
+ return Bool::FALSE
216
213
  end
214
+ Bool.new(basically_equal?(this.value, other.value))
215
+ end
217
216
 
218
- Sass::Script::Bool.new(this.value == other.value)
217
+ def hash
218
+ [value, numerator_units, denominator_units].hash
219
+ end
220
+
221
+ # Hash-equality works differently than `==` equality for numbers.
222
+ # Hash-equality must be transitive, so it just compares the exact value,
223
+ # numerator units, and denominator units.
224
+ def eql?(other)
225
+ basically_equal?(value, other.value) && numerator_units == other.numerator_units &&
226
+ denominator_units == other.denominator_units
219
227
  end
220
228
 
221
229
  # The SassScript `>` operation.
@@ -274,21 +282,40 @@ module Sass::Script
274
282
  #
275
283
  # @return [String] The representation
276
284
  def inspect(opts = {})
285
+ return original if original
286
+
277
287
  value = self.class.round(self.value)
278
- unitless? ? value.to_s : "#{value}#{unit_str}"
288
+ str = value.to_s
289
+
290
+ # Ruby will occasionally print in scientific notation if the number is
291
+ # small enough. That's technically valid CSS, but it's not well-supported
292
+ # and confusing.
293
+ str = ("%0.#{self.class.precision}f" % value).gsub(/0*$/, '') if str.include?('e')
294
+
295
+ # Sometimes numeric formatting will result in a decimal number with a trailing zero (x.0)
296
+ if str =~ /(.*)\.0$/
297
+ str = $1
298
+ end
299
+
300
+ # We omit a leading zero before the decimal point in compressed mode.
301
+ if @options && options[:style] == :compressed
302
+ str.sub!(/^(-)?0\./, '\1.')
303
+ end
304
+
305
+ unitless? ? str : "#{str}#{unit_str}"
279
306
  end
280
307
  alias_method :to_sass, :inspect
281
308
 
282
- # @return [Fixnum] The integer value of the number
309
+ # @return [Integer] The integer value of the number
283
310
  # @raise [Sass::SyntaxError] if the number isn't an integer
284
311
  def to_i
285
312
  super unless int?
286
- return value
313
+ value.to_i
287
314
  end
288
315
 
289
316
  # @return [Boolean] Whether or not this number is an integer.
290
317
  def int?
291
- value % 1 == 0.0
318
+ basically_equal?(value % 1, 0.0)
292
319
  end
293
320
 
294
321
  # @return [Boolean] Whether or not this number has no units.
@@ -296,6 +323,24 @@ module Sass::Script
296
323
  @numerator_units.empty? && @denominator_units.empty?
297
324
  end
298
325
 
326
+ # Checks whether the number has the numerator unit specified.
327
+ #
328
+ # @example
329
+ # number = Sass::Script::Value::Number.new(10, "px")
330
+ # number.is_unit?("px") => true
331
+ # number.is_unit?(nil) => false
332
+ #
333
+ # @param unit [::String, nil] The unit the number should have or nil if the number
334
+ # should be unitless.
335
+ # @see Number#unitless? The unitless? method may be more readable.
336
+ def is_unit?(unit)
337
+ if unit
338
+ denominator_units.size == 0 && numerator_units.size == 1 && numerator_units.first == unit
339
+ else
340
+ unitless?
341
+ end
342
+ end
343
+
299
344
  # @return [Boolean] Whether or not this number has units that can be represented in CSS
300
345
  # (that is, zero or one \{#numerator\_units}).
301
346
  def legal_units?
@@ -320,9 +365,9 @@ module Sass::Script
320
365
  # current units
321
366
  def coerce(num_units, den_units)
322
367
  Number.new(if unitless?
323
- self.value
368
+ value
324
369
  else
325
- self.value * coercion_factor(@numerator_units, num_units) /
370
+ value * coercion_factor(@numerator_units, num_units) /
326
371
  coercion_factor(@denominator_units, den_units)
327
372
  end, num_units, den_units)
328
373
  end
@@ -330,12 +375,10 @@ module Sass::Script
330
375
  # @param other [Number] A number to decide if it can be compared with this number.
331
376
  # @return [Boolean] Whether or not this number can be compared with the other.
332
377
  def comparable_to?(other)
333
- begin
334
- operate(other, :+)
335
- true
336
- rescue Sass::UnitConversionError
337
- false
338
- end
378
+ operate(other, :+)
379
+ true
380
+ rescue Sass::UnitConversionError
381
+ false
339
382
  end
340
383
 
341
384
  # Returns a human readable representation of the units in this number.
@@ -353,18 +396,30 @@ module Sass::Script
353
396
 
354
397
  private
355
398
 
399
+ # @private
400
+ # @see Sass::Script::Number.basically_equal?
401
+ def basically_equal?(num1, num2)
402
+ self.class.basically_equal?(num1, num2)
403
+ end
404
+
405
+ # Checks whether two numbers are within an epsilon of each other.
406
+ # @return [Boolean]
407
+ def self.basically_equal?(num1, num2)
408
+ (num1 - num2).abs < epsilon
409
+ end
410
+
356
411
  # @private
357
412
  def self.round(num)
358
413
  if num.is_a?(Float) && (num.infinite? || num.nan?)
359
414
  num
360
- elsif num % 1 == 0.0
361
- num.to_i
415
+ elsif basically_equal?(num % 1, 0.0)
416
+ num.round
362
417
  else
363
- ((num * self.precision_factor).round / self.precision_factor).to_f
418
+ ((num * precision_factor).round / precision_factor).to_f
364
419
  end
365
420
  end
366
421
 
367
- OPERATIONS = [:+, :-, :<=, :<, :>, :>=]
422
+ OPERATIONS = [:+, :-, :<=, :<, :>, :>=, :%]
368
423
 
369
424
  def operate(other, operation)
370
425
  this = self
@@ -376,7 +431,7 @@ module Sass::Script
376
431
  end
377
432
  end
378
433
  # avoid integer division
379
- value = (:/ == operation) ? this.value.to_f : this.value
434
+ value = :/ == operation ? this.value.to_f : this.value
380
435
  result = value.send(operation, other.value)
381
436
 
382
437
  if result.is_a?(Numeric)
@@ -391,63 +446,118 @@ module Sass::Script
391
446
  from_units, to_units = sans_common_units(from_units, to_units)
392
447
 
393
448
  if from_units.size != to_units.size || !convertable?(from_units | to_units)
394
- raise Sass::UnitConversionError.new("Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.")
449
+ raise Sass::UnitConversionError.new(
450
+ "Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.")
395
451
  end
396
452
 
397
- from_units.zip(to_units).inject(1) {|m,p| m * conversion_factor(p[0], p[1]) }
453
+ from_units.zip(to_units).inject(1) {|m, p| m * conversion_factor(p[0], p[1])}
398
454
  end
399
455
 
400
456
  def compute_units(this, other, operation)
401
457
  case operation
402
458
  when :*
403
- [this.numerator_units + other.numerator_units, this.denominator_units + other.denominator_units]
459
+ [this.numerator_units + other.numerator_units,
460
+ this.denominator_units + other.denominator_units]
404
461
  when :/
405
- [this.numerator_units + other.denominator_units, this.denominator_units + other.numerator_units]
406
- else
462
+ [this.numerator_units + other.denominator_units,
463
+ this.denominator_units + other.numerator_units]
464
+ else
407
465
  [this.numerator_units, this.denominator_units]
408
466
  end
409
467
  end
410
468
 
411
469
  def normalize!
412
470
  return if unitless?
413
- @numerator_units, @denominator_units = sans_common_units(@numerator_units, @denominator_units)
471
+ @numerator_units, @denominator_units =
472
+ sans_common_units(@numerator_units, @denominator_units)
414
473
 
415
474
  @denominator_units.each_with_index do |d, i|
416
- if convertable?(d) && (u = @numerator_units.detect(&method(:convertable?)))
417
- @value /= conversion_factor(d, u)
418
- @denominator_units.delete_at(i)
419
- @numerator_units.delete_at(@numerator_units.index(u))
420
- end
475
+ next unless convertable?(d) && (u = @numerator_units.find(&method(:convertable?)))
476
+ @value /= conversion_factor(d, u)
477
+ @denominator_units.delete_at(i)
478
+ @numerator_units.delete_at(@numerator_units.index(u))
421
479
  end
422
480
  end
423
481
 
424
- # A hash of unit names to their index in the conversion table
425
- CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4, "px" => 5 }
426
- CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 , 96 ], # in
427
- [ nil, 1, 2.36220473, 10, 28.3464567, 37.795275591], # cm
428
- [ nil, nil, 1, 4.23333333, 12 , 16 ], # pc
429
- [ nil, nil, nil, 1, 2.83464567, 3.7795275591], # mm
430
- [ nil, nil, nil, nil, 1 , 1.3333333333], # pt
431
- [ nil, nil, nil, nil, nil , 1 ]] # px
482
+ # This is the source data for all the unit logic. It's pre-processed to make
483
+ # it efficient to figure out whether a set of units is mutually compatible
484
+ # and what the conversion ratio is between two units.
485
+ #
486
+ # These come from http://www.w3.org/TR/2012/WD-css3-values-20120308/.
487
+ relative_sizes = [
488
+ {
489
+ 'in' => Rational(1),
490
+ 'cm' => Rational(1, 2.54),
491
+ 'pc' => Rational(1, 6),
492
+ 'mm' => Rational(1, 25.4),
493
+ 'q' => Rational(1, 101.6),
494
+ 'pt' => Rational(1, 72),
495
+ 'px' => Rational(1, 96)
496
+ },
497
+ {
498
+ 'deg' => Rational(1, 360),
499
+ 'grad' => Rational(1, 400),
500
+ 'rad' => Rational(1, 2 * Math::PI),
501
+ 'turn' => Rational(1)
502
+ },
503
+ {
504
+ 's' => Rational(1),
505
+ 'ms' => Rational(1, 1000)
506
+ },
507
+ {
508
+ 'Hz' => Rational(1),
509
+ 'kHz' => Rational(1000)
510
+ },
511
+ {
512
+ 'dpi' => Rational(1),
513
+ 'dpcm' => Rational(254, 100),
514
+ 'dppx' => Rational(96)
515
+ }
516
+ ]
517
+
518
+ # A hash from each known unit to the set of units that it's mutually
519
+ # convertible with.
520
+ MUTUALLY_CONVERTIBLE = {}
521
+ relative_sizes.map do |values|
522
+ set = values.keys.to_set
523
+ values.keys.each {|name| MUTUALLY_CONVERTIBLE[name] = set}
524
+ end
525
+
526
+ # A two-dimensional hash from two units to the conversion ratio between
527
+ # them. Multiply `X` by `CONVERSION_TABLE[X][Y]` to convert it to `Y`.
528
+ CONVERSION_TABLE = {}
529
+ relative_sizes.each do |values|
530
+ values.each do |(name1, value1)|
531
+ CONVERSION_TABLE[name1] ||= {}
532
+ values.each do |(name2, value2)|
533
+ value = value1 / value2
534
+ CONVERSION_TABLE[name1][name2] = value.denominator == 1 ? value.to_i : value.to_f
535
+ end
536
+ end
537
+ end
432
538
 
433
539
  def conversion_factor(from_unit, to_unit)
434
- res = CONVERSION_TABLE[CONVERTABLE_UNITS[from_unit]][CONVERTABLE_UNITS[to_unit]]
435
- return 1.0 / conversion_factor(to_unit, from_unit) if res.nil?
436
- res
540
+ CONVERSION_TABLE[from_unit][to_unit]
437
541
  end
438
542
 
439
543
  def convertable?(units)
440
- Array(units).all? {|u| CONVERTABLE_UNITS.include?(u)}
544
+ units = Array(units).to_set
545
+ return true if units.empty?
546
+ return false unless (mutually_convertible = MUTUALLY_CONVERTIBLE[units.first])
547
+ units.subset?(mutually_convertible)
441
548
  end
442
549
 
443
550
  def sans_common_units(units1, units2)
444
551
  units2 = units2.dup
445
552
  # Can't just use -, because we want px*px to coerce properly to px*mm
446
- return units1.map do |u|
447
- next u unless j = units2.index(u)
553
+ units1 = units1.map do |u|
554
+ j = units2.index(u)
555
+ next u unless j
448
556
  units2.delete_at(j)
449
557
  nil
450
- end.compact, units2
558
+ end
559
+ units1.compact!
560
+ return units1, units2
451
561
  end
452
562
  end
453
563
  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,11 @@
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'