drnic-haml 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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