haml 3.0.0.beta.3 → 3.0.0.rc.1

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 (82) hide show
  1. data/.yardopts +2 -0
  2. data/REMEMBER +4 -11
  3. data/Rakefile +24 -2
  4. data/VERSION +1 -1
  5. data/lib/haml.rb +5 -2
  6. data/lib/haml/exec.rb +11 -4
  7. data/lib/haml/filters.rb +3 -0
  8. data/lib/haml/helpers.rb +2 -10
  9. data/lib/haml/helpers/action_view_extensions.rb +4 -2
  10. data/lib/haml/helpers/action_view_mods.rb +6 -4
  11. data/lib/haml/html.rb +0 -1
  12. data/lib/haml/precompiler.rb +37 -30
  13. data/lib/haml/railtie.rb +6 -2
  14. data/lib/haml/root.rb +4 -0
  15. data/lib/haml/template.rb +2 -0
  16. data/lib/haml/util.rb +74 -0
  17. data/lib/haml/util/subset_map.rb +101 -0
  18. data/lib/sass.rb +1 -0
  19. data/lib/sass/engine.rb +36 -31
  20. data/lib/sass/files.rb +1 -1
  21. data/lib/sass/plugin.rb +21 -0
  22. data/lib/sass/plugin/staleness_checker.rb +9 -9
  23. data/lib/sass/script.rb +1 -2
  24. data/lib/sass/script/color.rb +4 -3
  25. data/lib/sass/script/css_lexer.rb +11 -1
  26. data/lib/sass/script/css_parser.rb +4 -1
  27. data/lib/sass/script/funcall.rb +9 -0
  28. data/lib/sass/script/interpolation.rb +21 -0
  29. data/lib/sass/script/lexer.rb +30 -13
  30. data/lib/sass/script/node.rb +1 -1
  31. data/lib/sass/script/number.rb +4 -5
  32. data/lib/sass/script/parser.rb +13 -14
  33. data/lib/sass/script/string.rb +8 -2
  34. data/lib/sass/script/string_interpolation.rb +27 -4
  35. data/lib/sass/scss.rb +3 -0
  36. data/lib/sass/scss/css_parser.rb +5 -3
  37. data/lib/sass/scss/parser.rb +146 -64
  38. data/lib/sass/scss/rx.rb +9 -1
  39. data/lib/sass/scss/sass_parser.rb +11 -0
  40. data/lib/sass/scss/script_lexer.rb +2 -0
  41. data/lib/sass/scss/static_parser.rb +48 -0
  42. data/lib/sass/selector.rb +353 -0
  43. data/lib/sass/selector/abstract_sequence.rb +40 -0
  44. data/lib/sass/selector/comma_sequence.rb +80 -0
  45. data/lib/sass/selector/sequence.rb +194 -0
  46. data/lib/sass/selector/simple.rb +107 -0
  47. data/lib/sass/selector/simple_sequence.rb +161 -0
  48. data/lib/sass/tree/comment_node.rb +1 -0
  49. data/lib/sass/tree/debug_node.rb +1 -0
  50. data/lib/sass/tree/directive_node.rb +1 -0
  51. data/lib/sass/tree/extend_node.rb +60 -0
  52. data/lib/sass/tree/for_node.rb +1 -0
  53. data/lib/sass/tree/if_node.rb +2 -0
  54. data/lib/sass/tree/import_node.rb +2 -0
  55. data/lib/sass/tree/mixin_def_node.rb +1 -0
  56. data/lib/sass/tree/mixin_node.rb +21 -5
  57. data/lib/sass/tree/node.rb +59 -12
  58. data/lib/sass/tree/prop_node.rb +20 -21
  59. data/lib/sass/tree/root_node.rb +8 -17
  60. data/lib/sass/tree/rule_node.rb +49 -100
  61. data/lib/sass/tree/variable_node.rb +1 -0
  62. data/lib/sass/tree/warn_node.rb +1 -0
  63. data/lib/sass/tree/while_node.rb +1 -0
  64. data/test/haml/engine_test.rb +185 -3
  65. data/test/haml/helper_test.rb +25 -2
  66. data/test/haml/template_test.rb +2 -2
  67. data/test/haml/templates/helpers.haml +13 -0
  68. data/test/haml/util/subset_map_test.rb +91 -0
  69. data/test/haml/util_test.rb +25 -0
  70. data/test/sass/conversion_test.rb +23 -3
  71. data/test/sass/engine_test.rb +50 -7
  72. data/test/sass/extend_test.rb +1045 -0
  73. data/test/sass/results/complex.css +0 -1
  74. data/test/sass/results/script.css +1 -1
  75. data/test/sass/script_conversion_test.rb +16 -0
  76. data/test/sass/script_test.rb +37 -4
  77. data/test/sass/scss/css_test.rb +17 -3
  78. data/test/sass/scss/rx_test.rb +1 -1
  79. data/test/sass/scss/scss_test.rb +30 -0
  80. data/test/sass/templates/complex.sass +0 -2
  81. data/test/test_helper.rb +5 -0
  82. metadata +18 -4
@@ -22,6 +22,7 @@ module Sass
22
22
  # *WARNING*: It is important not to retain the instance for too long,
23
23
  # as its instance-level caches are never explicitly expired.
24
24
  class StalenessChecker
25
+ DELETED = 1.0/0.0 # positive Infinity
25
26
  @dependencies_cache = {}
26
27
 
27
28
  class << self
@@ -47,15 +48,9 @@ module Sass
47
48
  # @param template_file [String] The location of the Sass or SCSS template
48
49
  # that is compiled to `css_file`.
49
50
  def stylesheet_needs_update?(css_file, template_file)
50
- template_file = File.expand_path(template_file)
51
+ template_file, css_mtime = File.expand_path(template_file), mtime(css_file)
51
52
 
52
- unless File.exists?(css_file) && File.exists?(template_file)
53
- @dependencies.delete(template_file)
54
- true
55
- else
56
- css_mtime = mtime(css_file)
57
- mtime(template_file) > css_mtime || dependencies_stale?(template_file, css_mtime)
58
- end
53
+ css_mtime == DELETED || dependency_updated?(css_mtime).call(template_file)
59
54
  end
60
55
 
61
56
  # Returns whether or not a given CSS file is out of date
@@ -87,7 +82,12 @@ module Sass
87
82
  end
88
83
 
89
84
  def mtime(filename)
90
- @mtimes[filename] ||= File.mtime(filename)
85
+ @mtimes[filename] ||= begin
86
+ File.mtime(filename).to_i
87
+ rescue Errno::ENOENT
88
+ @dependencies.delete(filename)
89
+ DELETED
90
+ end
91
91
  end
92
92
 
93
93
  def dependencies(filename)
@@ -13,11 +13,9 @@ module Sass
13
13
  # This module contains code that handles the parsing and evaluation of SassScript.
14
14
  module Script
15
15
  # The regular expression used to parse variables.
16
- # @private
17
16
  MATCH = /^[!\$](#{Sass::SCSS::RX::IDENT})\s*((?:\|\|)?=|:)\s*(.+?)(!(?i:default))?$/
18
17
 
19
18
  # The regular expression used to validate variables without matching.
20
- # @private
21
19
  VALIDATE = /^[!\$]#{Sass::SCSS::RX::IDENT}$/
22
20
 
23
21
  # Parses a string of SassScript
@@ -50,6 +48,7 @@ You can use `sass-convert --in-place --from sass2 file.sass' to convert files au
50
48
  MESSAGE
51
49
  end
52
50
 
51
+ # @private
53
52
  def self.equals_warning(types, name, val, guarded, line, offset, filename)
54
53
  Haml::Util.haml_warn <<MESSAGE
55
54
  DEPRECATION WARNING:
@@ -19,7 +19,6 @@ module Sass::Script
19
19
  class << self; include Haml::Util; end
20
20
 
21
21
  # A hash from color names to `[red, green, blue]` value arrays.
22
- # @private
23
22
  HTML4_COLORS = map_vals({
24
23
  'black' => 0x000000,
25
24
  'silver' => 0xc0c0c0,
@@ -39,7 +38,6 @@ module Sass::Script
39
38
  'aqua' => 0x00ffff
40
39
  }) {|color| (0..2).map {|n| color >> (n << 3) & 0xff}.reverse}
41
40
  # A hash from `[red, green, blue]` value arrays to color names.
42
- # @private
43
41
  HTML4_COLORS_REVERSE = map_hash(HTML4_COLORS) {|k, v| [v, k]}
44
42
 
45
43
  # Constructs an RGB or HSL color object,
@@ -108,7 +106,10 @@ module Sass::Script
108
106
  end
109
107
 
110
108
  [:saturation, :lightness].each do |k|
111
- next if @attrs[k].nil? || (0..100).include?(@attrs[k])
109
+ next if @attrs[k].nil?
110
+ @attrs[k] = 0 if @attrs[k] < 0.00001 && @attrs[k] > -0.00001
111
+ @attrs[k] = 100 if @attrs[k] - 100 < 0.00001 && @attrs[k] - 100 > -0.00001
112
+ next if (0..100).include?(@attrs[k])
112
113
  raise Sass::SyntaxError.new("#{k.to_s.capitalize} must be between 0 and 100")
113
114
  end
114
115
 
@@ -1,11 +1,21 @@
1
1
  module Sass
2
2
  module Script
3
+ # This is a subclass of {Lexer} for use in parsing plain CSS properties.
4
+ #
5
+ # @see Sass::SCSS::CssParser
3
6
  class CssLexer < Lexer
7
+ private
8
+
4
9
  def token
5
10
  important || super
6
11
  end
7
12
 
8
- def string(*args)
13
+ def string(re, *args)
14
+ if re == :uri
15
+ return unless uri = scan(URI)
16
+ return [:string, Script::String.new(uri)]
17
+ end
18
+
9
19
  return unless scan(STRING)
10
20
  [:string, Script::String.new((@scanner[1] || @scanner[2]).gsub(/\\(['"])/, '\1'), :string)]
11
21
  end
@@ -3,6 +3,9 @@ require 'sass/script/css_lexer'
3
3
 
4
4
  module Sass
5
5
  module Script
6
+ # This is a subclass of {Parser} for use in parsing plain CSS properties.
7
+ #
8
+ # @see Sass::SCSS::CssParser
6
9
  class CssParser < Parser
7
10
  private
8
11
 
@@ -21,7 +24,7 @@ module Sass
21
24
  # Short-circuit all the SassScript-only productions
22
25
  alias_method :interpolation, :concat
23
26
  alias_method :or_expr, :div
24
- alias_method :unary_div, :funcall
27
+ alias_method :unary_div, :ident
25
28
  alias_method :paren, :string
26
29
  end
27
30
  end
@@ -17,6 +17,15 @@ module Sass
17
17
  # @return [Array<Script::Node>]
18
18
  attr_reader :args
19
19
 
20
+ # Don't set the context for child nodes if this is `url()`,
21
+ # since `url()` allows quoted strings.
22
+ #
23
+ # @param context [Symbol]
24
+ # @see Node#context=
25
+ def context=(context)
26
+ super unless @name == "url"
27
+ end
28
+
20
29
  # @param name [String] See \{#name}
21
30
  # @param name [Array<Script::Node>] See \{#args}
22
31
  def initialize(name, args)
@@ -1,5 +1,15 @@
1
1
  module Sass::Script
2
+ # A SassScript object representing `#{}` interpolation outside a string.
3
+ #
4
+ # @see StringInterpolation
2
5
  class Interpolation < Node
6
+ # Interpolation in a property is of the form `before #{mid} after`.
7
+ #
8
+ # @param before [Node] The SassScript before the interpolation
9
+ # @param mid [Node] The SassScript within the interpolation
10
+ # @param after [Node] The SassScript after the interpolation
11
+ # @param wb [Boolean] Whether there was whitespace between `before` and `#{`
12
+ # @param wa [Boolean] Whether there was whitespace between `}` and `after`
3
13
  def initialize(before, mid, after, wb, wa)
4
14
  @before = before
5
15
  @mid = mid
@@ -8,10 +18,12 @@ module Sass::Script
8
18
  @whitespace_after = wa
9
19
  end
10
20
 
21
+ # @return [String] A human-readable s-expression representation of the interpolation
11
22
  def inspect
12
23
  "(interpolation #{@before.inspect} #{@mid.inspect} #{@after.inspect})"
13
24
  end
14
25
 
26
+ # @see Node#to_sass
15
27
  def to_sass(opts = {})
16
28
  res = ""
17
29
  res << @before.to_sass(opts) if @before
@@ -22,12 +34,21 @@ module Sass::Script
22
34
  res
23
35
  end
24
36
 
37
+ # Returns the three components of the interpolation, `before`, `mid`, and `after`.
38
+ #
39
+ # @return [Array<Node>]
40
+ # @see #initialize
41
+ # @see Node#children
25
42
  def children
26
43
  [@before, @mid, @after].compact
27
44
  end
28
45
 
29
46
  protected
30
47
 
48
+ # Evaluates the interpolation.
49
+ #
50
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
51
+ # @return [Sass::Script::String] The SassScript string that is the value of the interpolation
31
52
  def _perform(environment)
32
53
  res = ""
33
54
  res << @before.perform(environment).to_s if @before
@@ -40,7 +40,6 @@ module Sass
40
40
  attr_reader :offset
41
41
 
42
42
  # A hash from operator strings to the corresponding token types.
43
- # @private
44
43
  OPERATORS = {
45
44
  '+' => :plus,
46
45
  '-' => :minus,
@@ -67,10 +66,8 @@ module Sass
67
66
  '{' => :lcurly,
68
67
  }
69
68
 
70
- # @private
71
69
  OPERATORS_REVERSE = Haml::Util.map_hash(OPERATORS) {|k, v| [v, k]}
72
70
 
73
- # @private
74
71
  TOKEN_NAMES = Haml::Util.map_hash(OPERATORS_REVERSE) {|k, v| [k, v.inspect]}.merge({
75
72
  :const => "variable (e.g. $foo)",
76
73
  :ident => "identifier (e.g. middle)",
@@ -79,22 +76,19 @@ module Sass
79
76
 
80
77
  # A list of operator strings ordered with longer names first
81
78
  # so that `>` and `<` don't clobber `>=` and `<=`.
82
- # @private
83
79
  OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
84
80
 
85
81
  # A sub-list of {OP_NAMES} that only includes operators
86
82
  # with identifier names.
87
- # @private
88
83
  IDENT_OP_NAMES = OP_NAMES.select {|k, v| k =~ /^\w+/}
89
84
 
90
85
  # A hash of regular expressions that are used for tokenizing.
91
- # @private
92
86
  REGULAR_EXPRESSIONS = {
93
87
  :whitespace => /\s+/,
94
88
  :comment => COMMENT,
95
89
  :single_line_comment => SINGLE_LINE_COMMENT,
96
90
  :variable => /([!\$])(#{IDENT})/,
97
- :ident => IDENT,
91
+ :ident => /(#{IDENT})(\()?/,
98
92
  :number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
99
93
  :color => HEXCOLOR,
100
94
  :bool => /(true|false)\b/,
@@ -120,6 +114,8 @@ module Sass
120
114
  [:single, false] => string_re("'", "'"),
121
115
  [:double, true] => string_re('', '"'),
122
116
  [:single, true] => string_re('', "'"),
117
+ [:uri, false] => /url\(#{W}(#{URLCHAR}*?)(#{W}\)|(?=#\{))/,
118
+ [:uri, true] => /(#{URLCHAR}*?)(#{W}\)|(?=#\{))/,
123
119
  }
124
120
 
125
121
  # @param str [String, StringScanner] The source text to lex
@@ -179,11 +175,24 @@ module Sass
179
175
  @scanner.eos? && @tok.nil?
180
176
  end
181
177
 
178
+ # Raise an error to the effect that `name` was expected in the input stream
179
+ # and wasn't found.
180
+ #
181
+ # This calls \{#unpeek!} to rewind the scanner to immediately after
182
+ # the last returned token.
183
+ #
184
+ # @param name [String] The name of the entity that was expected but not found
185
+ # @raise [Sass::SyntaxError]
182
186
  def expected!(name)
183
187
  unpeek!
184
188
  Sass::SCSS::Parser.expected(@scanner, name, @line)
185
189
  end
186
190
 
191
+ # Records all non-comment text the lexer consumes within the block
192
+ # and returns it as a string.
193
+ #
194
+ # @yield A block in which text is recorded
195
+ # @return [String]
187
196
  def str
188
197
  old_pos = @tok ? @tok.pos : @scanner.pos
189
198
  yield
@@ -216,8 +225,8 @@ module Sass
216
225
  end
217
226
 
218
227
  variable || string(:double, false) || string(:single, false) || number ||
219
- color || bool || raw(URI) || raw(UNICODERANGE) || special_fun ||
220
- ident_op || ident || op
228
+ color || bool || string(:uri, false) || raw(UNICODERANGE) ||
229
+ special_fun || ident_op || ident || op
221
230
  end
222
231
 
223
232
  def variable
@@ -236,14 +245,20 @@ module Sass
236
245
  end
237
246
 
238
247
  def ident
239
- return unless s = scan(REGULAR_EXPRESSIONS[:ident])
240
- [:ident, s]
248
+ return unless scan(REGULAR_EXPRESSIONS[:ident])
249
+ [@scanner[2] ? :funcall : :ident, @scanner[1]]
241
250
  end
242
251
 
243
252
  def string(re, open)
244
253
  return unless scan(STRING_REGULAR_EXPRESSIONS[[re, open]])
245
254
  @interpolation_stack << re if @scanner[2].empty? # Started an interpolated section
246
- [:string, Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)]
255
+ str =
256
+ if re == :uri
257
+ Script::String.new("#{'url(' unless open}#{@scanner[1]}#{')' unless @scanner[2].empty?}")
258
+ else
259
+ Script::String.new(@scanner[1].gsub(/\\(['"]|\#\{)/, '\1'), :string)
260
+ end
261
+ [:string, str]
247
262
  end
248
263
 
249
264
  def number
@@ -269,11 +284,13 @@ module Sass
269
284
  return unless str1 = scan(/(calc|expression|progid:[a-z\.]*)\(/i)
270
285
  str2, _ = Haml::Shared.balance(@scanner, ?(, ?), 1)
271
286
  c = str2.count("\n")
287
+ old_line = @line
288
+ old_offset = @offset
272
289
  @line += c
273
290
  @offset = (c == 0 ? @offset + str2.size : str2[/\n(.*)/, 1].size)
274
291
  [:special_fun,
275
292
  Haml::Util.merge_adjacent_strings(
276
- [str1] + Sass::Engine.parse_interp(str2, @line, @options)),
293
+ [str1] + Sass::Engine.parse_interp(str2, old_line, old_offset, @options)),
277
294
  str1.size + str2.size]
278
295
  end
279
296
 
@@ -35,7 +35,7 @@ module Sass::Script
35
35
  children.each {|c| c.options = options}
36
36
  end
37
37
 
38
- # Sets the options hash for this node,
38
+ # Sets the context for this node,
39
39
  # as well as for all child nodes.
40
40
  #
41
41
  # @param context [Symbol]
@@ -30,6 +30,7 @@ module Sass::Script
30
30
  # The precision with which numbers will be printed to CSS files.
31
31
  # For example, if this is `1000.0`,
32
32
  # `3.1415926` will be printed as `3.142`.
33
+ # @api public
33
34
  PRECISION = 1000.0
34
35
 
35
36
  # @param value [Numeric] The value of the number
@@ -115,7 +116,7 @@ module Sass::Script
115
116
  # @raise [NoMethodError] if `other` is an invalid type
116
117
  def times(other)
117
118
  if other.is_a? Number
118
- self.operate(other, :*)
119
+ operate(other, :*)
119
120
  elsif other.is_a? Color
120
121
  other.times(self)
121
122
  else
@@ -303,7 +304,7 @@ module Sass::Script
303
304
  # @return [Boolean] Whether or not this number can be compared with the other.
304
305
  def comparable_to?(other)
305
306
  begin
306
- self.operate(other, :+)
307
+ operate(other, :+)
307
308
  true
308
309
  rescue Sass::UnitConversionError
309
310
  false
@@ -323,7 +324,7 @@ module Sass::Script
323
324
  rv
324
325
  end
325
326
 
326
- protected
327
+ private
327
328
 
328
329
  def operate(other, operation)
329
330
  this = self
@@ -381,9 +382,7 @@ module Sass::Script
381
382
  end
382
383
 
383
384
  # A hash of unit names to their index in the conversion table
384
- # @private
385
385
  CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4}
386
- # @private
387
386
  CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 ], # in
388
387
  [ nil, 1, 2.36220473, 10, 28.3464567], # cm
389
388
  [ nil, nil, 1, 4.23333333, 12 ], # pc
@@ -120,7 +120,6 @@ module Sass
120
120
  new(*args).parse
121
121
  end
122
122
 
123
- # @private
124
123
  PRECEDENCE = [
125
124
  :comma, :single_eq, :concat, :or, :and,
126
125
  [:eq, :neq],
@@ -214,24 +213,24 @@ RUBY
214
213
  unary :plus, :unary_minus
215
214
  unary :minus, :unary_div
216
215
  unary :div, :unary_not # For strings, so /foo/bar works
217
- unary :not, :funcall
216
+ unary :not, :ident
218
217
 
219
- def funcall
220
- return raw unless @lexer.peek && @lexer.peek.type == :ident
218
+ def ident
219
+ return funcall unless @lexer.peek && @lexer.peek.type == :ident
221
220
  return if @stop_at && @stop_at.include?(@lexer.peek.value)
222
221
 
223
222
  name = @lexer.next
224
- # An identifier without arguments is just a string
225
- unless try_tok(:lparen)
226
- if color = Color::HTML4_COLORS[name.value]
227
- return node(Color.new(color))
228
- end
229
- node(Script::String.new(name.value, :identifier))
230
- else
231
- args = fn_arglist || []
232
- assert_tok(:rparen)
233
- node(Script::Funcall.new(name.value, args))
223
+ if color = Color::HTML4_COLORS[name.value]
224
+ return node(Color.new(color))
234
225
  end
226
+ node(Script::String.new(name.value, :identifier))
227
+ end
228
+
229
+ def funcall
230
+ return raw unless tok = try_tok(:funcall)
231
+ args = fn_arglist || []
232
+ assert_tok(:rparen)
233
+ node(Script::Funcall.new(tok.value, args))
235
234
  end
236
235
 
237
236
  def defn_arglist!(must_have_default)
@@ -15,6 +15,10 @@ module Sass::Script
15
15
  # @return [Symbol] `:string` or `:identifier`
16
16
  attr_reader :type
17
17
 
18
+ # In addition to setting the \{#context} of the string,
19
+ # this sets the string to be an identifier if the context is `:equals`.
20
+ #
21
+ # @see Node#context=
18
22
  def context=(context)
19
23
  super
20
24
  @type = :identifier if context == :equals
@@ -29,6 +33,7 @@ module Sass::Script
29
33
  @type = type
30
34
  end
31
35
 
36
+ # @see Literal#plus
32
37
  def plus(other)
33
38
  other_str = other.is_a?(Sass::Script::String) ? other.value : other.to_s
34
39
  Sass::Script::String.new(self.value + other_str, self.type)
@@ -47,12 +52,13 @@ module Sass::Script
47
52
  def to_sass(opts = {})
48
53
  type = opts[:type] || self.type
49
54
  if type == :identifier
50
- if context == :equals && Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
55
+ if context == :equals && self.value !~ Sass::SCSS::RX::URI &&
56
+ Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
51
57
  return "unquote(#{Sass::Script::String.new(self.value, :string).to_sass})"
52
58
  elsif context == :equals && self.value.size == 0
53
59
  return %q{""}
54
60
  end
55
- return self.value
61
+ return self.value.gsub("\n", " ")
56
62
  end
57
63
 
58
64
  return "\"#{value.gsub('"', "\\\"")}\"" if opts[:quote] == %q{"}