haml 3.0.0.beta.1 → 3.0.0.beta.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (67) hide show
  1. data/README.md +7 -7
  2. data/REMEMBER +2 -1
  3. data/Rakefile +6 -4
  4. data/VERSION +1 -1
  5. data/lib/haml/exec.rb +119 -24
  6. data/lib/haml/filters.rb +5 -1
  7. data/lib/haml/helpers.rb +4 -2
  8. data/lib/haml/helpers/action_view_mods.rb +2 -3
  9. data/lib/haml/precompiler.rb +1 -0
  10. data/lib/sass.rb +1 -1
  11. data/lib/sass/css.rb +25 -6
  12. data/lib/sass/engine.rb +23 -7
  13. data/lib/sass/environment.rb +47 -1
  14. data/lib/sass/files.rb +9 -10
  15. data/lib/sass/plugin.rb +6 -27
  16. data/lib/sass/plugin/merb.rb +1 -0
  17. data/lib/sass/plugin/rails.rb +1 -0
  18. data/lib/sass/plugin/staleness_checker.rb +123 -0
  19. data/lib/sass/script/bool.rb +1 -1
  20. data/lib/sass/script/color.rb +1 -1
  21. data/lib/sass/script/css_lexer.rb +1 -4
  22. data/lib/sass/script/funcall.rb +2 -2
  23. data/lib/sass/script/functions.rb +102 -7
  24. data/lib/sass/script/interpolation.rb +5 -5
  25. data/lib/sass/script/lexer.rb +10 -8
  26. data/lib/sass/script/literal.rb +11 -1
  27. data/lib/sass/script/node.rb +10 -1
  28. data/lib/sass/script/number.rb +25 -10
  29. data/lib/sass/script/operation.rb +7 -6
  30. data/lib/sass/script/parser.rb +37 -28
  31. data/lib/sass/script/string.rb +12 -7
  32. data/lib/sass/script/string_interpolation.rb +70 -0
  33. data/lib/sass/script/unary_operation.rb +3 -3
  34. data/lib/sass/script/variable.rb +2 -2
  35. data/lib/sass/scss/css_parser.rb +1 -0
  36. data/lib/sass/scss/parser.rb +58 -44
  37. data/lib/sass/scss/rx.rb +1 -0
  38. data/lib/sass/tree/comment_node.rb +3 -2
  39. data/lib/sass/tree/debug_node.rb +1 -1
  40. data/lib/sass/tree/for_node.rb +1 -1
  41. data/lib/sass/tree/if_node.rb +1 -1
  42. data/lib/sass/tree/import_node.rb +3 -0
  43. data/lib/sass/tree/mixin_def_node.rb +3 -3
  44. data/lib/sass/tree/mixin_node.rb +7 -3
  45. data/lib/sass/tree/node.rb +8 -0
  46. data/lib/sass/tree/prop_node.rb +21 -16
  47. data/lib/sass/tree/rule_node.rb +3 -3
  48. data/lib/sass/tree/variable_node.rb +1 -1
  49. data/lib/sass/tree/warn_node.rb +41 -0
  50. data/lib/sass/tree/while_node.rb +1 -1
  51. data/test/haml/engine_test.rb +9 -0
  52. data/test/sass/conversion_test.rb +127 -15
  53. data/test/sass/css2sass_test.rb +34 -3
  54. data/test/sass/engine_test.rb +82 -5
  55. data/test/sass/functions_test.rb +31 -0
  56. data/test/sass/plugin_test.rb +25 -24
  57. data/test/sass/results/script.css +4 -4
  58. data/test/sass/results/warn.css +0 -0
  59. data/test/sass/results/warn_imported.css +0 -0
  60. data/test/sass/script_conversion_test.rb +43 -1
  61. data/test/sass/script_test.rb +3 -3
  62. data/test/sass/scss/css_test.rb +46 -5
  63. data/test/sass/scss/scss_test.rb +72 -0
  64. data/test/sass/templates/import.sass +1 -1
  65. data/test/sass/templates/warn.sass +3 -0
  66. data/test/sass/templates/warn_imported.sass +4 -0
  67. metadata +10 -3
@@ -9,16 +9,16 @@ module Sass::Script
9
9
  end
10
10
 
11
11
  def inspect
12
- "(interpolation #{@before.inspect} #{@mid.inspect} #{after.inspect})"
12
+ "(interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
13
13
  end
14
14
 
15
- def to_sass
15
+ def to_sass(opts = {})
16
16
  res = ""
17
- res << @before.to_sass if @before
17
+ res << @before.to_sass(opts) if @before
18
18
  res << ' ' if @before && @whitespace_before
19
- res << '#{' << @mid.to_sass << '}'
19
+ res << '#{' << @mid.to_sass(opts) << '}'
20
20
  res << ' ' if @after && @whitespace_after
21
- res << @after.to_sass if @after
21
+ res << @after.to_sass(opts) if @after
22
22
  res
23
23
  end
24
24
 
@@ -111,7 +111,7 @@ module Sass
111
111
 
112
112
  # A hash of regular expressions that are used for tokenizing strings.
113
113
  #
114
- # The key is a [Symbol, Boolean] pair.
114
+ # The key is a `[Symbol, Boolean]` pair.
115
115
  # The symbol represents which style of quotation to use,
116
116
  # while the boolean represents whether or not the string
117
117
  # is following an interpolated segment.
@@ -196,11 +196,12 @@ module Sass
196
196
  def read_token
197
197
  return if done?
198
198
  return unless value = token
199
+ type, val, size = value
200
+ size ||= @scanner.matched_size
199
201
 
200
- value.last.line = @line if value.last.is_a?(Script::Node)
201
- Token.new(value.first, value.last, @line,
202
- current_position - @scanner.matched_size,
203
- @scanner.pos - @scanner.matched_size)
202
+ val.line = @line if val.is_a?(Script::Node)
203
+ Token.new(type, val, @line,
204
+ current_position - size, @scanner.pos - size)
204
205
  end
205
206
 
206
207
  def whitespace
@@ -236,13 +237,13 @@ module Sass
236
237
 
237
238
  def ident
238
239
  return unless s = scan(REGULAR_EXPRESSIONS[:ident])
239
- [:ident, s.gsub(/\\(.)/, '\1')]
240
+ [:ident, s]
240
241
  end
241
242
 
242
243
  def string(re, open)
243
244
  return unless scan(STRING_REGULAR_EXPRESSIONS[[re, open]])
244
245
  @interpolation_stack << re if @scanner[2].empty? # Started an interpolated section
245
- [:string, Script::String.new(@scanner[1].gsub(/\\([^0-9a-f])/, '\1').gsub(/\\([0-9a-f]{1,4})/, "\\\\\\1"), :string)]
246
+ [:string, Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)]
246
247
  end
247
248
 
248
249
  def number
@@ -272,7 +273,8 @@ module Sass
272
273
  @offset = (c == 0 ? @offset + str2.size : str2[/\n(.*)/, 1].size)
273
274
  [:special_fun,
274
275
  Haml::Util.merge_adjacent_strings(
275
- [str1] + Sass::Engine.parse_interp(str2, @line, @options))]
276
+ [str1] + Sass::Engine.parse_interp(str2, @line, @options)),
277
+ str1.size + str2.size]
276
278
  end
277
279
 
278
280
  def ident_op
@@ -120,6 +120,16 @@ MSG
120
120
  Sass::Script::String.new("#{self.to_s}, #{other.to_s}")
121
121
  end
122
122
 
123
+ # The SassScript `=` operation
124
+ # (used for proprietary MS syntax like `alpha(opacity=20)`).
125
+ #
126
+ # @param other [Literal] The right-hand side of the operator
127
+ # @return [Script::String] A string containing both literals
128
+ # separated by `"="`
129
+ def single_eq(other)
130
+ Sass::Script::String.new("#{self.to_s}=#{other.to_s}")
131
+ end
132
+
123
133
  # The SassScript `+` operation.
124
134
  #
125
135
  # @param other [Literal] The right-hand side of the operator
@@ -208,7 +218,7 @@ MSG
208
218
  # as it would be output to the CSS document.
209
219
  #
210
220
  # @return [String]
211
- def to_s
221
+ def to_s(opts = {})
212
222
  raise Sass::SyntaxError.new("[BUG] All subclasses of Sass::Literal must implement #to_s.")
213
223
  end
214
224
  alias_method :to_sass, :to_s
@@ -74,12 +74,21 @@ module Sass::Script
74
74
  # Returns the text of this SassScript expression.
75
75
  #
76
76
  # @return [String]
77
- def to_sass
77
+ def to_sass(opts = {})
78
78
  raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #to_sass.")
79
79
  end
80
80
 
81
81
  protected
82
82
 
83
+ # Converts underscores to dashes if the :dasherize option is set.
84
+ def dasherize(s, opts)
85
+ if opts[:dasherize]
86
+ s.gsub(/_/,'-')
87
+ else
88
+ s
89
+ end
90
+ end
91
+
83
92
  # Evaluates this node.
84
93
  #
85
94
  # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
@@ -238,7 +238,7 @@ module Sass::Script
238
238
  # as long as there is only one unit.
239
239
  #
240
240
  # @return [String] The representation
241
- def inspect
241
+ def inspect(opts = {})
242
242
  value =
243
243
  if self.value.is_a?(Float) && (self.value.infinite? || self.value.nan?)
244
244
  self.value
@@ -299,6 +299,30 @@ module Sass::Script
299
299
  end, num_units, den_units)
300
300
  end
301
301
 
302
+ # @param other [Number] A number to decide if it can be compared with this number.
303
+ # @return [Boolean] Whether or not this number can be compared with the other.
304
+ def comparable_to?(other)
305
+ begin
306
+ self.operate(other, :+)
307
+ true
308
+ rescue Sass::UnitConversionError
309
+ false
310
+ end
311
+ end
312
+
313
+ # Returns a human readable representation of the units in this number.
314
+ # For complex units this takes the form of:
315
+ # numerator_unit1 * numerator_unit2 / denominator_unit1 * denominator_unit2
316
+ # @return [String] a string that represents the units in this number
317
+ def unit_str
318
+ rv = numerator_units.sort.join("*")
319
+ if denominator_units.any?
320
+ rv << "/"
321
+ rv << denominator_units.sort.join("*")
322
+ end
323
+ rv
324
+ end
325
+
302
326
  protected
303
327
 
304
328
  def operate(other, operation)
@@ -343,15 +367,6 @@ module Sass::Script
343
367
  end
344
368
  end
345
369
 
346
- def unit_str
347
- rv = numerator_units.join("*")
348
- if denominator_units.any?
349
- rv << "/"
350
- rv << denominator_units.join("*")
351
- end
352
- rv
353
- end
354
-
355
370
  def normalize!
356
371
  return if unitless?
357
372
  @numerator_units, @denominator_units = sans_common_units(numerator_units, denominator_units)
@@ -5,6 +5,7 @@ require 'sass/script/color'
5
5
  require 'sass/script/functions'
6
6
  require 'sass/script/unary_operation'
7
7
  require 'sass/script/interpolation'
8
+ require 'sass/script/string_interpolation'
8
9
 
9
10
  module Sass::Script
10
11
  # A SassScript parse node representing a binary operation,
@@ -33,10 +34,10 @@ module Sass::Script
33
34
  end
34
35
 
35
36
  # @see Node#to_sass
36
- def to_sass
37
+ def to_sass(opts = {})
37
38
  pred = Sass::Script::Parser.precedence_of(@operator)
38
- o1 = operand_to_sass pred, @operand1
39
- o2 = operand_to_sass pred, @operand2
39
+ o1 = operand_to_sass pred, @operand1, opts
40
+ o2 = operand_to_sass pred, @operand2, opts
40
41
  sep =
41
42
  case @operator
42
43
  when :comma; ", "
@@ -82,10 +83,10 @@ module Sass::Script
82
83
 
83
84
  private
84
85
 
85
- def operand_to_sass(pred, op)
86
- return "(#{op.to_sass})" if op.is_a?(Operation) &&
86
+ def operand_to_sass(pred, op, opts = {})
87
+ return "(#{op.to_sass(opts)})" if op.is_a?(Operation) &&
87
88
  Sass::Script::Parser.precedence_of(op.operator) < pred
88
- op.to_sass
89
+ op.to_sass(opts)
89
90
  end
90
91
  end
91
92
  end
@@ -97,12 +97,7 @@ module Sass
97
97
  # @return [Array<Script::Node>] The root nodes of the arguments.
98
98
  # @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
99
99
  def parse_mixin_definition_arglist
100
- args = []
101
-
102
- if try_tok(:lparen)
103
- args = defn_arglist(false) || args
104
- assert_tok(:rparen)
105
- end
100
+ args = defn_arglist!(false)
106
101
  assert_done
107
102
 
108
103
  args.each do |k, v|
@@ -127,7 +122,7 @@ module Sass
127
122
 
128
123
  # @private
129
124
  PRECEDENCE = [
130
- :comma, :concat, :or, :and,
125
+ :comma, :single_eq, :concat, :or, :and,
131
126
  [:eq, :neq],
132
127
  [:gt, :gte, :lt, :lte],
133
128
  [:plus, :minus],
@@ -186,6 +181,7 @@ RUBY
186
181
  def lexer_class; Lexer; end
187
182
 
188
183
  production :expr, :interpolation, :comma
184
+ production :equals, :interpolation, :single_eq
189
185
 
190
186
  def interpolation
191
187
  e = concat
@@ -232,32 +228,45 @@ RUBY
232
228
  end
233
229
  node(Script::String.new(name.value, :identifier))
234
230
  else
235
- args = arglist || []
231
+ args = fn_arglist || []
236
232
  assert_tok(:rparen)
237
233
  node(Script::Funcall.new(name.value, args))
238
234
  end
239
235
  end
240
236
 
241
- def defn_arglist(must_have_default)
242
- line = @lexer.line
243
- offset = @lexer.offset + 1
244
- return unless c = try_tok(:const)
245
- var = Script::Variable.new(c.value)
246
- if tok = (try_tok(:colon) || try_tok(:single_eq))
247
- val = assert_expr(:concat)
248
-
249
- if tok.type == :single_eq
250
- val.context = :equals
251
- val.options = @options
252
- Script.equals_warning("mixin argument defaults", "$#{c.value}",
253
- val.to_sass, false, line, offset, @options[:filename])
237
+ def defn_arglist!(must_have_default)
238
+ return [] unless try_tok(:lparen)
239
+ return [] if try_tok(:rparen)
240
+ res = []
241
+ loop do
242
+ line = @lexer.line
243
+ offset = @lexer.offset + 1
244
+ c = assert_tok(:const)
245
+ var = Script::Variable.new(c.value)
246
+ if tok = (try_tok(:colon) || try_tok(:single_eq))
247
+ val = assert_expr(:concat)
248
+
249
+ if tok.type == :single_eq
250
+ val.context = :equals
251
+ val.options = @options
252
+ Script.equals_warning("mixin argument defaults", "$#{c.value}",
253
+ val.to_sass, false, line, offset, @options[:filename])
254
+ end
255
+ must_have_default = true
256
+ elsif must_have_default
257
+ raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
254
258
  end
255
- elsif must_have_default
256
- raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
259
+ res << [var, val]
260
+ break unless try_tok(:comma)
257
261
  end
262
+ assert_tok(:rparen)
263
+ res
264
+ end
258
265
 
259
- return [[var, val]] unless try_tok(:comma)
260
- [[var, val], *defn_arglist(val)]
266
+ def fn_arglist
267
+ return unless e = equals
268
+ return [e] unless try_tok(:comma)
269
+ [e, *fn_arglist]
261
270
  end
262
271
 
263
272
  def arglist
@@ -303,9 +312,9 @@ RUBY
303
312
  line = @lexer.line
304
313
  mid = parse_interpolated
305
314
  last = assert_expr(:string)
306
- op = Operation.new(first.value, node(Operation.new(mid, last, :plus)), :plus)
307
- op.line = line
308
- op
315
+ interp = StringInterpolation.new(first.value, mid, last)
316
+ interp.line = line
317
+ interp
309
318
  end
310
319
 
311
320
  def number
@@ -35,23 +35,28 @@ module Sass::Script
35
35
  end
36
36
 
37
37
  # @see Node#to_s
38
- def to_s
39
- to_sass
38
+ def to_s(opts = {})
39
+ to_sass(opts)
40
40
  end
41
41
 
42
- # @param type [Symbol] The type of string to render this as.
43
- # `:string`s have double quotes, `:identifier`s do not.
42
+ # @param opts [{Symbol => Object}]
43
+ # `opts[:type]` -- The type of string to render this as.
44
+ # `:string`s have double quotes, `:identifier`s do not.
45
+ # Defaults to `:identifier`.
44
46
  # @see Node#to_sass
45
- def to_sass(type = self.type)
47
+ def to_sass(opts = {})
48
+ type = opts[:type] || self.type
46
49
  if type == :identifier
47
50
  if context == :equals && Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
48
51
  return "unquote(#{Sass::Script::String.new(self.value, :string).to_sass})"
52
+ elsif context == :equals && self.value.size == 0
53
+ return %q{""}
49
54
  end
50
55
  return self.value
51
56
  end
52
57
 
53
- # Replace single backslashes with double. Really.
54
- value = self.value.gsub("\\", "\\\\\\\\")
58
+ return "\"#{value.gsub('"', "\\\"")}\"" if opts[:quote] == %q{"}
59
+ return "'#{value.gsub("'", "\\'")}'" if opts[:quote] == %q{'}
55
60
  return "\"#{value}\"" unless value.include?('"')
56
61
  return "'#{value}'" unless value.include?("'")
57
62
  "\"#{value.gsub('"', "\\\"")}\"" #'
@@ -0,0 +1,70 @@
1
+ module Sass::Script
2
+ class StringInterpolation < Node
3
+ def initialize(before, mid, after)
4
+ @before = before
5
+ @mid = mid
6
+ @after = after
7
+ end
8
+
9
+ def inspect
10
+ "(string_interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
11
+ end
12
+
13
+ def to_sass(opts = {})
14
+ # We can get rid of all of this when we remove the deprecated :equals context
15
+ before_unquote, before_quote_char, before_str = parse_str(@before.to_sass(opts))
16
+ after_unquote, after_quote_char, after_str = parse_str(@after.to_sass(opts))
17
+ unquote = before_unquote || after_unquote ||
18
+ (before_quote_char && !after_quote_char && !after_str.empty?) ||
19
+ (!before_quote_char && after_quote_char && !before_str.empty?)
20
+ quote_char =
21
+ if before_quote_char && after_quote_char && before_quote_char != after_quote_char
22
+ before_str.gsub!("\\'", "'")
23
+ before_str.gsub!('"', "\\\"")
24
+ after_str.gsub!("\\'", "'")
25
+ after_str.gsub!('"', "\\\"")
26
+ '"'
27
+ else
28
+ before_quote_char || after_quote_char
29
+ end
30
+
31
+ res = ""
32
+ res << 'unquote(' if unquote
33
+ res << quote_char if quote_char
34
+ res << before_str
35
+ res << '#{' << @mid.to_sass(opts) << '}'
36
+ res << after_str
37
+ res << quote_char if quote_char
38
+ res << ')' if unquote
39
+ res
40
+ end
41
+
42
+ def children
43
+ [@before, @mid, @after].compact
44
+ end
45
+
46
+ protected
47
+
48
+ def _perform(environment)
49
+ res = ""
50
+ res << @before.perform(environment).value
51
+ val = @mid.perform(environment)
52
+ res << (val.is_a?(Sass::Script::String) ? val.value : val.to_s)
53
+ res << @after.perform(environment).value
54
+ Sass::Script::String.new(res, :string)
55
+ end
56
+
57
+ def parse_str(str)
58
+ case str
59
+ when /^unquote\((["'])(.*)\1\)$/
60
+ return true, $1, $2
61
+ when '""'
62
+ return false, nil, ""
63
+ when /^(["'])(.*)\1$/
64
+ return false, $1, $2
65
+ else
66
+ return false, nil, str
67
+ end
68
+ end
69
+ end
70
+ end
@@ -19,12 +19,12 @@ module Sass::Script
19
19
  end
20
20
 
21
21
  # @see Node#to_sass
22
- def to_sass
23
- operand = @operand.to_sass
22
+ def to_sass(opts = {})
23
+ operand = @operand.to_sass(opts)
24
24
  if @operand.is_a?(Operation) ||
25
25
  (@operator == :minus &&
26
26
  (operand =~ Sass::SCSS::RX::IDENT) == 0)
27
- operand = "(#{@operand.to_sass})"
27
+ operand = "(#{@operand.to_sass(opts)})"
28
28
  end
29
29
  op = Lexer::OPERATORS_REVERSE[@operator]
30
30
  op + (op =~ /[a-z]/ ? " " : "") + operand
@@ -14,9 +14,9 @@ module Sass
14
14
  end
15
15
 
16
16
  # @return [String] A string representation of the variable
17
- def inspect
17
+ def inspect(opts = {})
18
18
  return "!important" if name == "important"
19
- "$#{name}"
19
+ "$#{dasherize(name, opts)}"
20
20
  end
21
21
  alias_method :to_sass, :inspect
22
22