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
@@ -1,3 +1,7 @@
1
1
  module Haml
2
+ # The root directory of the Haml source tree.
3
+ # This may be overridden by the package manager
4
+ # if the lib directory is separated from the main source tree.
5
+ # @api public
2
6
  ROOT_DIR = File.expand_path("../../..", __FILE__)
3
7
  end
@@ -1,4 +1,6 @@
1
1
  require 'haml/engine'
2
+ require 'haml/helpers/action_view_mods'
3
+ require 'haml/helpers/action_view_extensions'
2
4
 
3
5
  module Haml
4
6
  # The class that keeps track of the global options for Haml within Rails.
@@ -3,6 +3,7 @@ require 'set'
3
3
  require 'enumerator'
4
4
  require 'stringio'
5
5
  require 'haml/root'
6
+ require 'haml/util/subset_map'
6
7
 
7
8
  module Haml
8
9
  # A module containing various useful functions.
@@ -10,6 +11,7 @@ module Haml
10
11
  extend self
11
12
 
12
13
  # An array of ints representing the Ruby version number.
14
+ # @api public
13
15
  RUBY_VERSION = ::RUBY_VERSION.split(".").map {|s| s.to_i}
14
16
 
15
17
  # Returns the path of a file relative to the Haml root directory.
@@ -135,6 +137,33 @@ module Haml
135
137
  end
136
138
  end
137
139
 
140
+ # Intersperses a value in an enumerable, as would be done with `Array#join`
141
+ # but without concatenating the array together afterwards.
142
+ #
143
+ # @param enum [Enumerable]
144
+ # @param val
145
+ # @return [Array]
146
+ def intersperse(enum, val)
147
+ enum.inject([]) {|a, e| a << e << val}[0...-1]
148
+ end
149
+
150
+ # Substitutes a sub-array of one array with another sub-array.
151
+ #
152
+ # @param ary [Array] The array in which to make the substitution
153
+ # @param from [Array] The sequence of elements to replace with `to`
154
+ # @param to [Array] The sequence of elements to replace `from` with
155
+ def substitute(ary, from, to)
156
+ res = ary.dup
157
+ i = 0
158
+ while i < res.size
159
+ if res[i...i+from.size] == from
160
+ res[i...i+from.size] = to
161
+ end
162
+ i += 1
163
+ end
164
+ res
165
+ end
166
+
138
167
  # Destructively strips whitespace from the beginning and end
139
168
  # of the first and last elements, respectively,
140
169
  # in the array (if those elements are strings).
@@ -147,6 +176,23 @@ module Haml
147
176
  arr
148
177
  end
149
178
 
179
+ # Return an array of all possible paths through the given arrays.
180
+ #
181
+ # @param arrs [Array<Array>]
182
+ # @return [Array<Arrays>]
183
+ #
184
+ # @example
185
+ # paths([[1, 2], [3, 4], [5]]) #=>
186
+ # # [[1, 3, 5],
187
+ # # [2, 3, 5],
188
+ # # [1, 4, 5],
189
+ # # [2, 4, 5]]
190
+ def paths(arrs)
191
+ arrs.inject([[]]) do |paths, arr|
192
+ arr.map {|e| paths.map {|path| path + [e]}}.flatten(1)
193
+ end
194
+ end
195
+
150
196
  # Returns information about the caller of the previous method.
151
197
  #
152
198
  # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
@@ -237,6 +283,30 @@ module Haml
237
283
  ActionPack::VERSION::TINY == "0.beta")
238
284
  end
239
285
 
286
+ # Returns whether this environment is using ActionPack
287
+ # version 3.0.0.beta.3 or greater.
288
+ #
289
+ # @return [Boolean]
290
+ def ap_geq_3_beta_3?
291
+ # The ActionPack module is always loaded automatically in Rails >= 3
292
+ return false unless defined?(ActionPack) && defined?(ActionPack::VERSION)
293
+
294
+ version =
295
+ if defined?(ActionPack::VERSION::MAJOR)
296
+ ActionPack::VERSION::MAJOR
297
+ else
298
+ # Rails 1.2
299
+ ActionPack::VERSION::Major
300
+ end
301
+ version >= 3 &&
302
+ ((defined?(ActionPack::VERSION::TINY) &&
303
+ ActionPack::VERSION::TINY.is_a?(Fixnum) &&
304
+ ActionPack::VERSION::TINY >= 1) ||
305
+ (defined?(ActionPack::VERSION::BUILD) &&
306
+ ActionPack::VERSION::BUILD =~ /beta(\d+)/ &&
307
+ $1.to_i >= 3))
308
+ end
309
+
240
310
  # Returns an ActionView::Template* class.
241
311
  # In pre-3.0 versions of Rails, most of these classes
242
312
  # were of the form `ActionView::TemplateFoo`,
@@ -282,6 +352,10 @@ module Haml
282
352
  raise Haml::Error.new("Expected #{text.inspect} to be HTML-safe.")
283
353
  end
284
354
 
355
+ # The class for the Rails SafeBuffer XSS protection class.
356
+ # This varies depending on Rails version.
357
+ #
358
+ # @return [Class]
285
359
  def rails_safe_buffer_class
286
360
  return ActionView::SafeBuffer if defined?(ActionView::SafeBuffer)
287
361
  ActiveSupport::SafeBuffer
@@ -0,0 +1,101 @@
1
+ require 'set'
2
+
3
+ module Haml
4
+ module Util
5
+ # A map from sets to values.
6
+ # A value is \{#\[]= set} by providing a set (the "set-set") and a value,
7
+ # which is then recorded as corresponding to that set.
8
+ # Values are \{#\[] accessed} by providing a set (the "get-set")
9
+ # and returning all values that correspond to set-sets
10
+ # that are subsets of the get-set.
11
+ #
12
+ # SubsetMap preserves the order of values as they're inserted.
13
+ #
14
+ # @example
15
+ # ssm = SubsetMap.new
16
+ # ssm[Set[1, 2]] = "Foo"
17
+ # ssm[Set[2, 3]] = "Bar"
18
+ # ssm[Set[1, 2, 3]] = "Baz"
19
+ #
20
+ # ssm[Set[1, 2, 3]] #=> ["Foo", "Bar", "Baz"]
21
+ class SubsetMap
22
+ # Creates a new, empty SubsetMap.
23
+ def initialize
24
+ @hash = {}
25
+ @vals = []
26
+ end
27
+
28
+ # Whether or not this SubsetMap has any key-value pairs.
29
+ #
30
+ # @return [Boolean]
31
+ def empty?
32
+ @hash.empty?
33
+ end
34
+
35
+ # Associates a value with a set.
36
+ # When `set` or any of its supersets is accessed,
37
+ # `value` will be among the values returned.
38
+ #
39
+ # Note that if the same `set` is passed to this method multiple times,
40
+ # all given `value`s will be associated with that `set`.
41
+ #
42
+ # This runs in `O(n)` time, where `n` is the size of `set`.
43
+ #
44
+ # @param set [#to_set] The set to use as the map key. May not be empty.
45
+ # @param value [Object] The value to associate with `set`.
46
+ # @raise [ArgumentError] If `set` is empty.
47
+ def []=(set, value)
48
+ raise ArgumentError.new("SubsetMap keys may not be empty.") if set.empty?
49
+
50
+ index = @vals.size
51
+ @vals << value
52
+ set.each do |k|
53
+ @hash[k] ||= []
54
+ @hash[k] << [set, set.to_set, index]
55
+ end
56
+ end
57
+
58
+ # Returns all values associated with subsets of `set`.
59
+ #
60
+ # In the worst case, this runs in `O(m*max(n, log m))` time,
61
+ # where `n` is the size of `set`
62
+ # and `m` is the number of assocations in the map.
63
+ # However, unless many keys in the map overlap with `set`,
64
+ # `m` will typically be much smaller.
65
+ #
66
+ # @param set [Set] The set to use as the map key.
67
+ # @return [Array<(Object, #to_set)>] An array of pairs,
68
+ # where the first value is the value associated with a subset of `set`,
69
+ # and the second value is that subset of `set`
70
+ # (or whatever `#to_set` object was used to set the value)
71
+ # This array is in insertion order.
72
+ # @see #[]
73
+ def get(set)
74
+ res = set.map do |k|
75
+ next unless subsets = @hash[k]
76
+ subsets.map do |subenum, subset, index|
77
+ next unless subset.subset?(set)
78
+ [index, subenum]
79
+ end
80
+ end
81
+ res.flatten!(1)
82
+ res.compact!
83
+ res.uniq!
84
+ res.sort!
85
+ res.map! {|i, s| [@vals[i], s]}
86
+ return res
87
+ end
88
+
89
+ # Same as \{#get}, but doesn't return the subsets of the argument
90
+ # for which values were found.
91
+ #
92
+ # @param set [Set] The set to use as the map key.
93
+ # @return [Array] The array of all values
94
+ # associated with subsets of `set`, in insertion order.
95
+ # @see #get
96
+ def [](set)
97
+ get(set).map {|v, _| v}
98
+ end
99
+ end
100
+ end
101
+ end
@@ -16,6 +16,7 @@ module Sass
16
16
 
17
17
  # A string representing the version of Sass.
18
18
  # A more fine-grained representation is available from {Haml::Version#version Sass.version}.
19
+ # @api public
19
20
  VERSION = version[:string] unless defined?(Sass::VERSION)
20
21
  end
21
22
 
@@ -9,12 +9,14 @@ require 'sass/tree/directive_node'
9
9
  require 'sass/tree/variable_node'
10
10
  require 'sass/tree/mixin_def_node'
11
11
  require 'sass/tree/mixin_node'
12
+ require 'sass/tree/extend_node'
12
13
  require 'sass/tree/if_node'
13
14
  require 'sass/tree/while_node'
14
15
  require 'sass/tree/for_node'
15
16
  require 'sass/tree/debug_node'
16
17
  require 'sass/tree/warn_node'
17
18
  require 'sass/tree/import_node'
19
+ require 'sass/selector'
18
20
  require 'sass/environment'
19
21
  require 'sass/script'
20
22
  require 'sass/scss'
@@ -79,60 +81,49 @@ module Sass
79
81
  end
80
82
 
81
83
  # The character that begins a CSS property.
82
- # @private
83
84
  PROPERTY_CHAR = ?:
84
85
 
85
86
  # The character that designates that
86
87
  # a property should be assigned to a SassScript expression.
87
- # @private
88
88
  SCRIPT_CHAR = ?=
89
89
 
90
90
  # The character that designates the beginning of a comment,
91
91
  # either Sass or CSS.
92
- # @private
93
92
  COMMENT_CHAR = ?/
94
93
 
95
94
  # The character that follows the general COMMENT_CHAR and designates a Sass comment,
96
95
  # which is not output as a CSS comment.
97
- # @private
98
96
  SASS_COMMENT_CHAR = ?/
99
97
 
100
98
  # The character that follows the general COMMENT_CHAR and designates a CSS comment,
101
99
  # which is embedded in the CSS document.
102
- # @private
103
100
  CSS_COMMENT_CHAR = ?*
104
101
 
105
102
  # The character used to denote a compiler directive.
106
- # @private
107
103
  DIRECTIVE_CHAR = ?@
108
104
 
109
105
  # Designates a non-parsed rule.
110
- # @private
111
106
  ESCAPE_CHAR = ?\\
112
107
 
113
108
  # Designates block as mixin definition rather than CSS rules to output
114
- # @private
115
109
  MIXIN_DEFINITION_CHAR = ?=
116
110
 
117
111
  # Includes named mixin declared using MIXIN_DEFINITION_CHAR
118
- # @private
119
112
  MIXIN_INCLUDE_CHAR = ?+
120
113
 
121
114
  # The regex that matches properties of the form `name: prop`.
122
- # @private
123
115
  PROPERTY_NEW_MATCHER = /^[^\s:"\[]+\s*[=:](\s|$)/
124
116
 
125
117
  # The regex that matches and extracts data from
126
118
  # properties of the form `name: prop`.
127
- # @private
128
119
  PROPERTY_NEW = /^([^\s=:"]+)\s*(=|:)(?:\s+|$)(.*)/
129
120
 
130
121
  # The regex that matches and extracts data from
131
122
  # properties of the form `:name prop`.
132
- # @private
133
123
  PROPERTY_OLD = /^:([^\s=:"]+)\s*(=?)(?:\s+|$)(.*)/
134
124
 
135
125
  # The default options for Sass::Engine.
126
+ # @api public
136
127
  DEFAULT_OPTIONS = {
137
128
  :style => :nested,
138
129
  :load_paths => ['.'],
@@ -382,7 +373,10 @@ WARNING
382
373
  # if we're using the new property syntax
383
374
  Tree::RuleNode.new(parse_interp(line.text))
384
375
  else
385
- parse_property(line, PROPERTY_OLD)
376
+ name, eq, value = line.text.scan(PROPERTY_OLD)[0]
377
+ raise SyntaxError.new("Invalid property: \"#{line.text}\".",
378
+ :line => @line) if name.nil? || value.nil?
379
+ parse_property(name, parse_interp(name), eq, value, :old, line)
386
380
  end
387
381
  when ?!, ?$
388
382
  parse_variable(line)
@@ -401,20 +395,29 @@ WARNING
401
395
  parse_mixin_include(line, root)
402
396
  end
403
397
  else
404
- if line.text =~ PROPERTY_NEW_MATCHER
405
- parse_property(line, PROPERTY_NEW)
406
- else
407
- Tree::RuleNode.new(parse_interp(line.text))
408
- end
398
+ parse_property_or_rule(line)
409
399
  end
410
400
  end
411
401
 
412
- def parse_property(line, property_regx)
413
- name, eq, value = line.text.scan(property_regx)[0]
402
+ def parse_property_or_rule(line)
403
+ scanner = StringScanner.new(line.text)
404
+ hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
405
+ parser = Sass::SCSS::SassParser.new(scanner, @line)
414
406
 
415
- raise SyntaxError.new("Invalid property: \"#{line.text}\".",
416
- :line => @line) if name.nil? || value.nil?
407
+ unless res = parser.parse_interp_ident
408
+ return Tree::RuleNode.new(parse_interp(line.text))
409
+ end
410
+ res.unshift(hack_char) if hack_char
411
+
412
+ name = line.text[0...scanner.pos]
413
+ if scanner.scan(/\s*([:=])(?:\s|$)/)
414
+ parse_property(name, res, scanner[1], scanner.rest, :new, line)
415
+ else
416
+ Tree::RuleNode.new(res + parse_interp(scanner.rest))
417
+ end
418
+ end
417
419
 
420
+ def parse_property(name, parsed_name, eq, value, prop, line)
418
421
  if value.strip.empty?
419
422
  expr = Sass::Script::String.new("")
420
423
  else
@@ -427,9 +430,7 @@ WARNING
427
430
  @line, line.offset + 1, @options[:filename])
428
431
  end
429
432
  end
430
- Tree::PropNode.new(
431
- parse_interp(name), expr,
432
- property_regx == PROPERTY_OLD ? :old : :new)
433
+ Tree::PropNode.new(parse_interp(name), expr, prop)
433
434
  end
434
435
 
435
436
  def parse_variable(line)
@@ -500,6 +501,12 @@ WARNING
500
501
  :line => @line + 1) unless line.children.empty?
501
502
  offset = line.offset + line.text.index(value).to_i
502
503
  Tree::DebugNode.new(parse_script(value, :offset => offset))
504
+ elsif directive == "extend"
505
+ raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
506
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
507
+ :line => @line + 1) unless line.children.empty?
508
+ offset = line.offset + line.text.index(value).to_i
509
+ Tree::ExtendNode.new(parse_interp(value, offset))
503
510
  elsif directive == "warn"
504
511
  raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
505
512
  raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
@@ -552,7 +559,6 @@ WARNING
552
559
  nil
553
560
  end
554
561
 
555
- # @private
556
562
  MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
557
563
  def parse_mixin_definition(line)
558
564
  name, arg_string = line.text.scan(MIXIN_DEF_RE).first
@@ -565,7 +571,6 @@ WARNING
565
571
  Tree::MixinDefNode.new(name, args)
566
572
  end
567
573
 
568
- # @private
569
574
  MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
570
575
  def parse_mixin_include(line, root)
571
576
  name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
@@ -605,15 +610,15 @@ WARNING
605
610
  end
606
611
  end
607
612
 
608
- def parse_interp(text)
609
- self.class.parse_interp(text, @line, :filename => @filename)
613
+ def parse_interp(text, offset = 0)
614
+ self.class.parse_interp(text, @line, offset, :filename => @filename)
610
615
  end
611
616
 
612
617
  # It's important that this have strings (at least)
613
618
  # at the beginning, the end, and between each Script::Node.
614
619
  #
615
620
  # @private
616
- def self.parse_interp(text, line, options)
621
+ def self.parse_interp(text, line, offset, options)
617
622
  res = []
618
623
  rest = Haml::Shared.handle_interpolation text do |scan|
619
624
  escapes = scan[2].size
@@ -623,7 +628,7 @@ WARNING
623
628
  else
624
629
  res << "\\" * [0, escapes - 1].max
625
630
  res << Script::Parser.new(
626
- scan, line, scan.pos - scan.matched_size, options).
631
+ scan, line, offset + scan.pos - scan.matched_size, options).
627
632
  parse_interpolated
628
633
  end
629
634
  end
@@ -20,7 +20,7 @@ module Sass
20
20
  options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
21
21
  text = File.read(filename)
22
22
 
23
- if options[:cache]
23
+ if options[:cache] || options[:read_cache]
24
24
  compiled_filename = sassc_filename(filename, options)
25
25
  sha = Digest::SHA1.hexdigest(text)
26
26
 
@@ -231,6 +231,27 @@ module Sass
231
231
  end
232
232
  end
233
233
 
234
+ # Updates all stylesheets, even those that aren't out-of-date.
235
+ # Ignores the cache.
236
+ #
237
+ # @param individual_files [Array<(String, String)>]
238
+ # A list of files to check for updates
239
+ # **in addition to those specified by the
240
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
241
+ # The first string in each pair is the location of the Sass/SCSS file,
242
+ # the second is the location of the CSS file that it should be compiled to.
243
+ # @see #update_stylesheets
244
+ def force_update_stylesheets(individual_files = [])
245
+ old_options = options
246
+ self.options = options.dup
247
+ options[:never_update] = false
248
+ options[:always_update] = true
249
+ options[:cache] = false
250
+ update_stylesheets(individual_files)
251
+ ensure
252
+ self.options = old_options
253
+ end
254
+
234
255
  # Watches the template directory (or directories)
235
256
  # and updates the CSS files whenever the related Sass/SCSS files change.
236
257
  # `watch` never returns.