drnic-haml 2.3.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 (190) hide show
  1. data/.yardopts +5 -0
  2. data/CONTRIBUTING +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +347 -0
  5. data/REVISION +1 -0
  6. data/Rakefile +371 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/css2sass +7 -0
  10. data/bin/haml +9 -0
  11. data/bin/html2haml +7 -0
  12. data/bin/sass +8 -0
  13. data/extra/haml-mode.el +663 -0
  14. data/extra/sass-mode.el +205 -0
  15. data/extra/update_watch.rb +13 -0
  16. data/init.rb +8 -0
  17. data/lib/haml.rb +40 -0
  18. data/lib/haml/buffer.rb +307 -0
  19. data/lib/haml/engine.rb +301 -0
  20. data/lib/haml/error.rb +22 -0
  21. data/lib/haml/exec.rb +470 -0
  22. data/lib/haml/filters.rb +341 -0
  23. data/lib/haml/helpers.rb +560 -0
  24. data/lib/haml/helpers/action_view_extensions.rb +40 -0
  25. data/lib/haml/helpers/action_view_mods.rb +176 -0
  26. data/lib/haml/herb.rb +96 -0
  27. data/lib/haml/html.rb +308 -0
  28. data/lib/haml/precompiler.rb +997 -0
  29. data/lib/haml/shared.rb +78 -0
  30. data/lib/haml/template.rb +51 -0
  31. data/lib/haml/template/patch.rb +58 -0
  32. data/lib/haml/template/plugin.rb +71 -0
  33. data/lib/haml/util.rb +244 -0
  34. data/lib/haml/version.rb +64 -0
  35. data/lib/sass.rb +24 -0
  36. data/lib/sass/css.rb +423 -0
  37. data/lib/sass/engine.rb +491 -0
  38. data/lib/sass/environment.rb +79 -0
  39. data/lib/sass/error.rb +162 -0
  40. data/lib/sass/files.rb +133 -0
  41. data/lib/sass/plugin.rb +170 -0
  42. data/lib/sass/plugin/merb.rb +57 -0
  43. data/lib/sass/plugin/rails.rb +23 -0
  44. data/lib/sass/repl.rb +58 -0
  45. data/lib/sass/script.rb +55 -0
  46. data/lib/sass/script/bool.rb +17 -0
  47. data/lib/sass/script/color.rb +183 -0
  48. data/lib/sass/script/funcall.rb +50 -0
  49. data/lib/sass/script/functions.rb +199 -0
  50. data/lib/sass/script/lexer.rb +191 -0
  51. data/lib/sass/script/literal.rb +177 -0
  52. data/lib/sass/script/node.rb +14 -0
  53. data/lib/sass/script/number.rb +381 -0
  54. data/lib/sass/script/operation.rb +45 -0
  55. data/lib/sass/script/parser.rb +222 -0
  56. data/lib/sass/script/string.rb +12 -0
  57. data/lib/sass/script/unary_operation.rb +34 -0
  58. data/lib/sass/script/variable.rb +31 -0
  59. data/lib/sass/tree/comment_node.rb +84 -0
  60. data/lib/sass/tree/debug_node.rb +30 -0
  61. data/lib/sass/tree/directive_node.rb +70 -0
  62. data/lib/sass/tree/for_node.rb +48 -0
  63. data/lib/sass/tree/if_node.rb +54 -0
  64. data/lib/sass/tree/import_node.rb +69 -0
  65. data/lib/sass/tree/mixin_def_node.rb +29 -0
  66. data/lib/sass/tree/mixin_node.rb +48 -0
  67. data/lib/sass/tree/node.rb +252 -0
  68. data/lib/sass/tree/prop_node.rb +106 -0
  69. data/lib/sass/tree/root_node.rb +56 -0
  70. data/lib/sass/tree/rule_node.rb +220 -0
  71. data/lib/sass/tree/variable_node.rb +34 -0
  72. data/lib/sass/tree/while_node.rb +31 -0
  73. data/rails/init.rb +1 -0
  74. data/test/benchmark.rb +99 -0
  75. data/test/haml/engine_test.rb +1129 -0
  76. data/test/haml/helper_test.rb +282 -0
  77. data/test/haml/html2haml_test.rb +258 -0
  78. data/test/haml/markaby/standard.mab +52 -0
  79. data/test/haml/mocks/article.rb +6 -0
  80. data/test/haml/results/content_for_layout.xhtml +12 -0
  81. data/test/haml/results/eval_suppressed.xhtml +9 -0
  82. data/test/haml/results/filters.xhtml +62 -0
  83. data/test/haml/results/helpers.xhtml +93 -0
  84. data/test/haml/results/helpful.xhtml +10 -0
  85. data/test/haml/results/just_stuff.xhtml +68 -0
  86. data/test/haml/results/list.xhtml +12 -0
  87. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  88. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  89. data/test/haml/results/original_engine.xhtml +20 -0
  90. data/test/haml/results/partial_layout.xhtml +5 -0
  91. data/test/haml/results/partials.xhtml +21 -0
  92. data/test/haml/results/render_layout.xhtml +3 -0
  93. data/test/haml/results/silent_script.xhtml +74 -0
  94. data/test/haml/results/standard.xhtml +162 -0
  95. data/test/haml/results/tag_parsing.xhtml +23 -0
  96. data/test/haml/results/very_basic.xhtml +5 -0
  97. data/test/haml/results/whitespace_handling.xhtml +89 -0
  98. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  99. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  100. data/test/haml/rhtml/action_view.rhtml +62 -0
  101. data/test/haml/rhtml/standard.rhtml +54 -0
  102. data/test/haml/spec_test.rb +44 -0
  103. data/test/haml/template_test.rb +217 -0
  104. data/test/haml/templates/_av_partial_1.haml +9 -0
  105. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  106. data/test/haml/templates/_av_partial_2.haml +5 -0
  107. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  108. data/test/haml/templates/_layout.erb +3 -0
  109. data/test/haml/templates/_layout_for_partial.haml +3 -0
  110. data/test/haml/templates/_partial.haml +8 -0
  111. data/test/haml/templates/_text_area.haml +3 -0
  112. data/test/haml/templates/action_view.haml +47 -0
  113. data/test/haml/templates/action_view_ugly.haml +47 -0
  114. data/test/haml/templates/breakage.haml +8 -0
  115. data/test/haml/templates/content_for_layout.haml +8 -0
  116. data/test/haml/templates/eval_suppressed.haml +11 -0
  117. data/test/haml/templates/filters.haml +66 -0
  118. data/test/haml/templates/helpers.haml +95 -0
  119. data/test/haml/templates/helpful.haml +11 -0
  120. data/test/haml/templates/just_stuff.haml +83 -0
  121. data/test/haml/templates/list.haml +12 -0
  122. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  123. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  124. data/test/haml/templates/original_engine.haml +17 -0
  125. data/test/haml/templates/partial_layout.haml +3 -0
  126. data/test/haml/templates/partialize.haml +1 -0
  127. data/test/haml/templates/partials.haml +12 -0
  128. data/test/haml/templates/render_layout.haml +2 -0
  129. data/test/haml/templates/silent_script.haml +40 -0
  130. data/test/haml/templates/standard.haml +42 -0
  131. data/test/haml/templates/standard_ugly.haml +42 -0
  132. data/test/haml/templates/tag_parsing.haml +21 -0
  133. data/test/haml/templates/very_basic.haml +4 -0
  134. data/test/haml/templates/whitespace_handling.haml +87 -0
  135. data/test/haml/util_test.rb +92 -0
  136. data/test/linked_rails.rb +12 -0
  137. data/test/sass/css2sass_test.rb +294 -0
  138. data/test/sass/engine_test.rb +956 -0
  139. data/test/sass/functions_test.rb +126 -0
  140. data/test/sass/more_results/more1.css +9 -0
  141. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  142. data/test/sass/more_results/more_import.css +29 -0
  143. data/test/sass/more_templates/_more_partial.sass +2 -0
  144. data/test/sass/more_templates/more1.sass +23 -0
  145. data/test/sass/more_templates/more_import.sass +11 -0
  146. data/test/sass/plugin_test.rb +229 -0
  147. data/test/sass/results/alt.css +4 -0
  148. data/test/sass/results/basic.css +9 -0
  149. data/test/sass/results/compact.css +5 -0
  150. data/test/sass/results/complex.css +87 -0
  151. data/test/sass/results/compressed.css +1 -0
  152. data/test/sass/results/expanded.css +19 -0
  153. data/test/sass/results/import.css +29 -0
  154. data/test/sass/results/line_numbers.css +49 -0
  155. data/test/sass/results/mixins.css +95 -0
  156. data/test/sass/results/multiline.css +24 -0
  157. data/test/sass/results/nested.css +22 -0
  158. data/test/sass/results/parent_ref.css +13 -0
  159. data/test/sass/results/script.css +16 -0
  160. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  161. data/test/sass/results/subdir/subdir.css +3 -0
  162. data/test/sass/results/units.css +11 -0
  163. data/test/sass/script_test.rb +261 -0
  164. data/test/sass/templates/_partial.sass +2 -0
  165. data/test/sass/templates/alt.sass +16 -0
  166. data/test/sass/templates/basic.sass +23 -0
  167. data/test/sass/templates/bork1.sass +2 -0
  168. data/test/sass/templates/bork2.sass +2 -0
  169. data/test/sass/templates/bork3.sass +2 -0
  170. data/test/sass/templates/compact.sass +17 -0
  171. data/test/sass/templates/complex.sass +307 -0
  172. data/test/sass/templates/compressed.sass +15 -0
  173. data/test/sass/templates/expanded.sass +17 -0
  174. data/test/sass/templates/import.sass +11 -0
  175. data/test/sass/templates/importee.sass +19 -0
  176. data/test/sass/templates/line_numbers.sass +13 -0
  177. data/test/sass/templates/mixins.sass +76 -0
  178. data/test/sass/templates/multiline.sass +20 -0
  179. data/test/sass/templates/nested.sass +25 -0
  180. data/test/sass/templates/nested_bork1.sass +2 -0
  181. data/test/sass/templates/nested_bork2.sass +2 -0
  182. data/test/sass/templates/nested_bork3.sass +2 -0
  183. data/test/sass/templates/parent_ref.sass +25 -0
  184. data/test/sass/templates/script.sass +101 -0
  185. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  186. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  187. data/test/sass/templates/subdir/subdir.sass +6 -0
  188. data/test/sass/templates/units.sass +11 -0
  189. data/test/test_helper.rb +44 -0
  190. metadata +298 -0
@@ -0,0 +1,14 @@
1
+ module Sass::Script
2
+ # The abstract superclass for SassScript parse tree nodes.
3
+ #
4
+ # Use \{#perform} to evaluate a parse tree.
5
+ class Node
6
+ # Evaluates the node.
7
+ #
8
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
9
+ # @return [Literal] The SassScript object that is the value of the SassScript
10
+ def perform(environment)
11
+ raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #perform.")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,381 @@
1
+ require 'sass/script/literal'
2
+
3
+ module Sass::Script
4
+ # A SassScript object representing a number.
5
+ # SassScript numbers can have decimal values,
6
+ # and can also have units.
7
+ # For example, `12`, `1px`, and `10.45em`
8
+ # are all valid values.
9
+ #
10
+ # Numbers can also have more complex units, such as `1px*em/in`.
11
+ # These cannot be inputted directly in Sass code at the moment.
12
+ class Number < Literal
13
+ # The Ruby value of the number.
14
+ #
15
+ # @return [Numeric]
16
+ attr_reader :value
17
+
18
+ # A list of units in the numerator of the number.
19
+ # For example, `1px*em/in*cm` would return `["px", "em"]`
20
+ # @return [Array<String>]
21
+ attr_reader :numerator_units
22
+
23
+ # A list of units in the denominator of the number.
24
+ # For example, `1px*em/in*cm` would return `["in", "cm"]`
25
+ # @return [Array<String>]
26
+ attr_reader :denominator_units
27
+
28
+ # The precision with which numbers will be printed to CSS files.
29
+ # For example, if this is `1000.0`,
30
+ # `3.1415926` will be printed as `3.142`.
31
+ PRECISION = 1000.0
32
+
33
+ # @param value [Numeric] The value of the number
34
+ # @param numerator_units [Array<String>] See \{#numerator\_units}
35
+ # @param denominator_units [Array<String>] See \{#denominator\_units}
36
+ def initialize(value, numerator_units = [], denominator_units = [])
37
+ super(value)
38
+ @numerator_units = numerator_units
39
+ @denominator_units = denominator_units
40
+ normalize!
41
+ end
42
+
43
+ # The SassScript `+` operation.
44
+ # Its functionality depends on the type of its argument:
45
+ #
46
+ # {Number}
47
+ # : Adds the two numbers together, converting units if possible.
48
+ #
49
+ # {Color}
50
+ # : Adds this number to each of the RGB color channels.
51
+ #
52
+ # {Literal}
53
+ # : See {Literal#plus}.
54
+ #
55
+ # @param other [Literal] The right-hand side of the operator
56
+ # @return [Literal] The result of the operation
57
+ # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
58
+ def plus(other)
59
+ if other.is_a? Number
60
+ operate(other, :+)
61
+ elsif other.is_a?(Color)
62
+ other.plus(self)
63
+ else
64
+ super
65
+ end
66
+ end
67
+
68
+ # The SassScript binary `-` operation (e.g. `!a - !b`).
69
+ # Its functionality depends on the type of its argument:
70
+ #
71
+ # {Number}
72
+ # : Subtracts this number from the other, converting units if possible.
73
+ #
74
+ # {Literal}
75
+ # : See {Literal#minus}.
76
+ #
77
+ # @param other [Literal] The right-hand side of the operator
78
+ # @return [Literal] The result of the operation
79
+ # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
80
+ def minus(other)
81
+ if other.is_a? Number
82
+ operate(other, :-)
83
+ else
84
+ super
85
+ end
86
+ end
87
+
88
+ # The SassScript unary `-` operation (e.g. `-!a`).
89
+ #
90
+ # @return [Number] The negative value of this number
91
+ def unary_minus
92
+ Number.new(-value, numerator_units, denominator_units)
93
+ end
94
+
95
+ # The SassScript `*` operation.
96
+ # Its functionality depends on the type of its argument:
97
+ #
98
+ # {Number}
99
+ # : Multiplies the two numbers together, converting units appropriately.
100
+ #
101
+ # {Color}
102
+ # : Multiplies each of the RGB color channels by this number.
103
+ #
104
+ # @param other [Number, Color] The right-hand side of the operator
105
+ # @return [Number, Color] The result of the operation
106
+ # @raise [NoMethodError] if `other` is an invalid type
107
+ def times(other)
108
+ if other.is_a? Number
109
+ self.operate(other, :*)
110
+ elsif other.is_a? Color
111
+ other.times(self)
112
+ else
113
+ raise NoMethodError.new(nil, :times)
114
+ end
115
+ end
116
+
117
+ # The SassScript `/` operation.
118
+ # Its functionality depends on the type of its argument:
119
+ #
120
+ # {Number}
121
+ # : Divides this number by the other, converting units appropriately.
122
+ #
123
+ # {Literal}
124
+ # : See {Literal#div}.
125
+ #
126
+ # @param other [Literal] The right-hand side of the operator
127
+ # @return [Literal] The result of the operation
128
+ def div(other)
129
+ if other.is_a? Number
130
+ operate(other, :/)
131
+ else
132
+ super
133
+ end
134
+ end
135
+
136
+ # The SassScript `%` operation.
137
+ #
138
+ # @param other [Number] The right-hand side of the operator
139
+ # @return [Number] This number modulo the other
140
+ # @raise [NoMethodError] if `other` is an invalid type
141
+ # @raise [Sass::UnitConversionError] if `other` has any units
142
+ def mod(other)
143
+ if other.is_a?(Number)
144
+ unless other.unitless?
145
+ raise Sass::UnitConversionError.new("Cannot modulo by a number with units: #{other.inspect}.")
146
+ end
147
+ operate(other, :%)
148
+ else
149
+ raise NoMethodError.new(nil, :mod)
150
+ end
151
+ end
152
+
153
+ # The SassScript `==` operation.
154
+ #
155
+ # @param other [Literal] The right-hand side of the operator
156
+ # @return [Boolean] Whether this number is equal to the other object
157
+ def eq(other)
158
+ return Sass::Script::Bool.new(false) unless other.is_a?(Sass::Script::Number)
159
+ this = self
160
+ begin
161
+ if unitless?
162
+ this = this.coerce(other.numerator_units, other.denominator_units)
163
+ else
164
+ other = other.coerce(numerator_units, denominator_units)
165
+ end
166
+ rescue Sass::UnitConversionError
167
+ return Sass::Script::Bool.new(false)
168
+ end
169
+
170
+ Sass::Script::Bool.new(this.value == other.value)
171
+ end
172
+
173
+ # The SassScript `>` operation.
174
+ #
175
+ # @param other [Number] The right-hand side of the operator
176
+ # @return [Boolean] Whether this number is greater than the other
177
+ # @raise [NoMethodError] if `other` is an invalid type
178
+ def gt(other)
179
+ raise NoMethodError.new(nil, :gt) unless other.is_a?(Number)
180
+ operate(other, :>)
181
+ end
182
+
183
+ # The SassScript `>=` operation.
184
+ #
185
+ # @param other [Number] The right-hand side of the operator
186
+ # @return [Boolean] Whether this number is greater than or equal to the other
187
+ # @raise [NoMethodError] if `other` is an invalid type
188
+ def gte(other)
189
+ raise NoMethodError.new(nil, :gte) unless other.is_a?(Number)
190
+ operate(other, :>=)
191
+ end
192
+
193
+ # The SassScript `<` operation.
194
+ #
195
+ # @param other [Number] The right-hand side of the operator
196
+ # @return [Boolean] Whether this number is less than the other
197
+ # @raise [NoMethodError] if `other` is an invalid type
198
+ def lt(other)
199
+ raise NoMethodError.new(nil, :lt) unless other.is_a?(Number)
200
+ operate(other, :<)
201
+ end
202
+
203
+ # The SassScript `<=` operation.
204
+ #
205
+ # @param other [Number] The right-hand side of the operator
206
+ # @return [Boolean] Whether this number is less than or equal to the other
207
+ # @raise [NoMethodError] if `other` is an invalid type
208
+ def lte(other)
209
+ raise NoMethodError.new(nil, :lte) unless other.is_a?(Number)
210
+ operate(other, :<=)
211
+ end
212
+
213
+ # @return [String] The CSS representation of this number
214
+ # @raise [Sass::SyntaxError] if this number has units that can't be used in CSS
215
+ # (e.g. `px*in`)
216
+ def to_s
217
+ raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") unless legal_units?
218
+ inspect
219
+ end
220
+
221
+ # Returns a readable representation of this number.
222
+ #
223
+ # This representation is valid CSS (and valid SassScript)
224
+ # as long as there is only one unit.
225
+ #
226
+ # @return [String] The representation
227
+ def inspect
228
+ value =
229
+ if self.value.is_a?(Float) && (self.value.infinite? || self.value.nan?)
230
+ self.value
231
+ elsif int?
232
+ self.value.to_i
233
+ else
234
+ (self.value * PRECISION).round / PRECISION
235
+ end
236
+ "#{value}#{unit_str}"
237
+ end
238
+
239
+ # @return [Fixnum] The integer value of the number
240
+ # @raise [Sass::SyntaxError] if the number isn't an integer
241
+ def to_i
242
+ super unless int?
243
+ return value
244
+ end
245
+
246
+ # @return [Boolean] Whether or not this number is an integer.
247
+ def int?
248
+ value % 1 == 0.0
249
+ end
250
+
251
+ # @return [Boolean] Whether or not this number has no units.
252
+ def unitless?
253
+ numerator_units.empty? && denominator_units.empty?
254
+ end
255
+
256
+ # @return [Boolean] Whether or not this number has units that can be represented in CSS
257
+ # (that is, zero or one \{#numerator\_units}).
258
+ def legal_units?
259
+ (numerator_units.empty? || numerator_units.size == 1) && denominator_units.empty?
260
+ end
261
+
262
+ # Returns this number converted to other units.
263
+ # The conversion takes into account the relationship between e.g. mm and cm,
264
+ # as well as between e.g. in and cm.
265
+ #
266
+ # If this number has no units, it will simply return itself
267
+ # with the given units.
268
+ #
269
+ # An incompatible coercion, e.g. between px and cm, will raise an error.
270
+ #
271
+ # @param num_units [Array<String>] The numerator units to coerce this number into.
272
+ # See {\#numerator\_units}
273
+ # @param den_units [Array<String>] The denominator units to coerce this number into.
274
+ # See {\#denominator\_units}
275
+ # @return [Number] The number with the new units
276
+ # @raise [Sass::UnitConversionError] if the given units are incompatible with the number's
277
+ # current units
278
+ def coerce(num_units, den_units)
279
+ Number.new(if unitless?
280
+ self.value
281
+ else
282
+ self.value * coercion_factor(self.numerator_units, num_units) /
283
+ coercion_factor(self.denominator_units, den_units)
284
+ end, num_units, den_units)
285
+ end
286
+
287
+ protected
288
+
289
+ def operate(other, operation)
290
+ this = self
291
+ if [:+, :-, :<=, :<, :>, :>=].include?(operation)
292
+ if unitless?
293
+ this = this.coerce(other.numerator_units, other.denominator_units)
294
+ else
295
+ other = other.coerce(numerator_units, denominator_units)
296
+ end
297
+ end
298
+ # avoid integer division
299
+ value = (:/ == operation) ? this.value.to_f : this.value
300
+ result = value.send(operation, other.value)
301
+
302
+ if result.is_a?(Numeric)
303
+ Number.new(result, *compute_units(this, other, operation))
304
+ else # Boolean op
305
+ Bool.new(result)
306
+ end
307
+ end
308
+
309
+ def coercion_factor(from_units, to_units)
310
+ # get a list of unmatched units
311
+ from_units, to_units = sans_common_units(from_units, to_units)
312
+
313
+ if from_units.size != to_units.size || !convertable?(from_units | to_units)
314
+ raise Sass::UnitConversionError.new("Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.")
315
+ end
316
+
317
+ from_units.zip(to_units).inject(1) {|m,p| m * conversion_factor(p[0], p[1]) }
318
+ end
319
+
320
+ def compute_units(this, other, operation)
321
+ case operation
322
+ when :*
323
+ [this.numerator_units + other.numerator_units, this.denominator_units + other.denominator_units]
324
+ when :/
325
+ [this.numerator_units + other.denominator_units, this.denominator_units + other.numerator_units]
326
+ else
327
+ [this.numerator_units, this.denominator_units]
328
+ end
329
+ end
330
+
331
+ def unit_str
332
+ rv = numerator_units.join("*")
333
+ if denominator_units.any?
334
+ rv << "/"
335
+ rv << denominator_units.join("*")
336
+ end
337
+ rv
338
+ end
339
+
340
+ def normalize!
341
+ return if unitless?
342
+ @numerator_units, @denominator_units = sans_common_units(numerator_units, denominator_units)
343
+
344
+ @denominator_units.each_with_index do |d, i|
345
+ if convertable?(d) && (u = @numerator_units.detect(&method(:convertable?)))
346
+ @value /= conversion_factor(d, u)
347
+ @denominator_units.delete_at(i)
348
+ @numerator_units.delete_at(@numerator_units.index(u))
349
+ end
350
+ end
351
+ end
352
+
353
+ # A hash of unit names to their index in the conversion table
354
+ CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4}
355
+ CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 ], # in
356
+ [ nil, 1, 2.36220473, 10, 28.3464567], # cm
357
+ [ nil, nil, 1, 4.23333333, 12 ], # pc
358
+ [ nil, nil, nil, 1, 2.83464567], # mm
359
+ [ nil, nil, nil, nil, 1 ]] # pt
360
+
361
+ def conversion_factor(from_unit, to_unit)
362
+ res = CONVERSION_TABLE[CONVERTABLE_UNITS[from_unit]][CONVERTABLE_UNITS[to_unit]]
363
+ return 1.0 / conversion_factor(to_unit, from_unit) if res.nil?
364
+ res
365
+ end
366
+
367
+ def convertable?(units)
368
+ Array(units).all?(&CONVERTABLE_UNITS.method(:include?))
369
+ end
370
+
371
+ def sans_common_units(units1, units2)
372
+ units2 = units2.dup
373
+ # Can't just use -, because we want px*px to coerce properly to px*mm
374
+ return units1.map do |u|
375
+ next u unless j = units2.index(u)
376
+ units2.delete_at(j)
377
+ nil
378
+ end.compact, units2
379
+ end
380
+ end
381
+ end
@@ -0,0 +1,45 @@
1
+ require 'set'
2
+ require 'sass/script/string'
3
+ require 'sass/script/number'
4
+ require 'sass/script/color'
5
+ require 'sass/script/functions'
6
+ require 'sass/script/unary_operation'
7
+
8
+ module Sass::Script
9
+ # A SassScript parse node representing a binary operation,
10
+ # such as `!a + !b` or `"foo" + 1`.
11
+ class Operation < Node
12
+ # @param operand1 [Script::Node] The parse-tree node
13
+ # for the right-hand side of the operator
14
+ # @param operand2 [Script::Node] The parse-tree node
15
+ # for the left-hand side of the operator
16
+ # @param operator [Symbol] The operator to perform.
17
+ # This should be one of the binary operator names in {Lexer::OPERATORS}
18
+ def initialize(operand1, operand2, operator)
19
+ @operand1 = operand1
20
+ @operand2 = operand2
21
+ @operator = operator
22
+ end
23
+
24
+ # @return [String] A human-readable s-expression representation of the operation
25
+ def inspect
26
+ "(#{@operator.inspect} #{@operand1.inspect} #{@operand2.inspect})"
27
+ end
28
+
29
+ # Evaluates the operation.
30
+ #
31
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
32
+ # @return [Literal] The SassScript object that is the value of the operation
33
+ # @raise [Sass::SyntaxError] if the operation is undefined for the operands
34
+ def perform(environment)
35
+ literal1 = @operand1.perform(environment)
36
+ literal2 = @operand2.perform(environment)
37
+ begin
38
+ literal1.send(@operator, literal2)
39
+ rescue NoMethodError => e
40
+ raise e unless e.name.to_s == @operator.to_s
41
+ raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\".")
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,222 @@
1
+ require 'sass/script/lexer'
2
+
3
+ module Sass
4
+ module Script
5
+ # The parser for SassScript.
6
+ # It parses a string of code into a tree of {Script::Node}s.
7
+ class Parser
8
+ # @param str [String, StringScanner] The source text to parse
9
+ # @param line [Fixnum] The line on which the SassScript appears.
10
+ # Used for error reporting
11
+ # @param offset [Fixnum] The number of characters in on which the SassScript appears.
12
+ # Used for error reporting
13
+ # @param filename [String] The name of the file in which the SassScript appears.
14
+ # Used for error reporting
15
+ def initialize(str, line, offset, filename = nil)
16
+ @filename = filename
17
+ @lexer = Lexer.new(str, line, offset, filename)
18
+ end
19
+
20
+ # Parses a SassScript expression within an interpolated segment (`#{}`).
21
+ # This means that it stops when it comes across an unmatched `}`,
22
+ # which signals the end of an interpolated segment,
23
+ # it returns rather than throwing an error.
24
+ #
25
+ # @return [Script::Node] The root node of the parse tree
26
+ # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
27
+ def parse_interpolated
28
+ expr = assert_expr :expr
29
+ assert_tok :end_interpolation
30
+ expr
31
+ end
32
+
33
+ # Parses a SassScript expression.
34
+ #
35
+ # @return [Script::Node] The root node of the parse tree
36
+ # @raise [Sass::SyntaxError] if the expression isn't valid SassScript
37
+ def parse
38
+ expr = assert_expr :expr
39
+ assert_done
40
+ expr
41
+ end
42
+
43
+ # Parses the argument list for a mixin include.
44
+ #
45
+ # @return [Array<Script::Node>] The root nodes of the arguments.
46
+ # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
47
+ def parse_mixin_include_arglist
48
+ args = []
49
+
50
+ if try_tok(:lparen)
51
+ args = arglist || args
52
+ assert_tok(:rparen)
53
+ end
54
+ assert_done
55
+
56
+ args
57
+ end
58
+
59
+ # Parses the argument list for a mixin definition.
60
+ #
61
+ # @return [Array<Script::Node>] The root nodes of the arguments.
62
+ # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
63
+ def parse_mixin_definition_arglist
64
+ args = []
65
+
66
+ if try_tok(:lparen)
67
+ args = defn_arglist(false) || args
68
+ assert_tok(:rparen)
69
+ end
70
+ assert_done
71
+
72
+ args
73
+ end
74
+
75
+ # Parses a SassScript expression.
76
+ #
77
+ # @overload parse(str, line, offset, filename = nil)
78
+ # @return [Script::Node] The root node of the parse tree
79
+ # @see Parser#initialize
80
+ # @see Parser#parse
81
+ def self.parse(*args)
82
+ new(*args).parse
83
+ end
84
+
85
+ class << self
86
+ private
87
+
88
+ # Defines a simple left-associative production.
89
+ # name is the name of the production,
90
+ # sub is the name of the production beneath it,
91
+ # and ops is a list of operators for this precedence level
92
+ def production(name, sub, *ops)
93
+ class_eval <<RUBY
94
+ def #{name}
95
+ return unless e = #{sub}
96
+ while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
97
+ e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
98
+ end
99
+ e
100
+ end
101
+ RUBY
102
+ end
103
+
104
+ def unary(op, sub)
105
+ class_eval <<RUBY
106
+ def unary_#{op}
107
+ return #{sub} unless try_tok(:#{op})
108
+ UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
109
+ end
110
+ RUBY
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ production :expr, :concat, :comma
117
+
118
+ def concat
119
+ return unless e = or_expr
120
+ while sub = or_expr
121
+ e = Operation.new(e, sub, :concat)
122
+ end
123
+ e
124
+ end
125
+
126
+ production :or_expr, :and_expr, :or
127
+ production :and_expr, :eq_or_neq, :and
128
+ production :eq_or_neq, :relational, :eq, :neq
129
+ production :relational, :plus_or_minus, :gt, :gte, :lt, :lte
130
+ production :plus_or_minus, :times_div_or_mod, :plus, :minus
131
+ production :times_div_or_mod, :unary_minus, :times, :div, :mod
132
+
133
+ unary :minus, :unary_div
134
+ unary :div, :unary_not # For strings, so /foo/bar works
135
+ unary :not, :funcall
136
+
137
+ def funcall
138
+ return paren unless name = try_tok(:ident)
139
+ # An identifier without arguments is just a string
140
+ unless try_tok(:lparen)
141
+ warn(<<END)
142
+ DEPRECATION WARNING:
143
+ On line #{name.line}, character #{name.offset}#{" of '#{@filename}'" if @filename}
144
+ Implicit strings have been deprecated and will be removed in version 2.4.
145
+ '#{name.value}' was not quoted. Please add double quotes (e.g. "#{name.value}").
146
+ END
147
+ Script::String.new(name.value)
148
+ else
149
+ args = arglist || []
150
+ assert_tok(:rparen)
151
+ Script::Funcall.new(name.value, args)
152
+ end
153
+ end
154
+
155
+ def defn_arglist(must_have_default)
156
+ return unless c = try_tok(:const)
157
+ var = Script::Variable.new(c.value)
158
+ if try_tok(:single_eq)
159
+ val = assert_expr(:concat)
160
+ elsif must_have_default
161
+ raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
162
+ end
163
+
164
+ return [[var, val]] unless try_tok(:comma)
165
+ [[var, val], *defn_arglist(val)]
166
+ end
167
+
168
+ def arglist
169
+ return unless e = concat
170
+ return [e] unless try_tok(:comma)
171
+ [e, *arglist]
172
+ end
173
+
174
+ def paren
175
+ return variable unless try_tok(:lparen)
176
+ e = assert_expr(:expr)
177
+ assert_tok(:rparen)
178
+ return e
179
+ end
180
+
181
+ def variable
182
+ return string unless c = try_tok(:const)
183
+ Variable.new(c.value)
184
+ end
185
+
186
+ def string
187
+ return literal unless first = try_tok(:string)
188
+ return first.value unless try_tok(:begin_interpolation)
189
+ mid = parse_interpolated
190
+ last = assert_expr(:string)
191
+ Operation.new(first.value, Operation.new(mid, last, :plus), :plus)
192
+ end
193
+
194
+ def literal
195
+ (t = try_tok(:number, :color, :bool)) && (return t.value)
196
+ end
197
+
198
+ # It would be possible to have unified #assert and #try methods,
199
+ # but detecting the method/token difference turns out to be quite expensive.
200
+
201
+ def assert_expr(name)
202
+ (e = send(name)) && (return e)
203
+ raise Sass::SyntaxError.new("Expected expression, was #{@lexer.done? ? 'end of text' : "#{@lexer.peek.type} token"}.")
204
+ end
205
+
206
+ def assert_tok(*names)
207
+ (t = try_tok(*names)) && (return t)
208
+ raise Sass::SyntaxError.new("Expected #{names.join(' or ')} token, was #{@lexer.done? ? 'end of text' : "#{@lexer.peek.type} token"}.")
209
+ end
210
+
211
+ def try_tok(*names)
212
+ peeked = @lexer.peek
213
+ peeked && names.include?(peeked.type) && @lexer.next
214
+ end
215
+
216
+ def assert_done
217
+ return if @lexer.done?
218
+ raise Sass::SyntaxError.new("Unexpected #{@lexer.peek.type} token.")
219
+ end
220
+ end
221
+ end
222
+ end