haml-edge 2.3.179 → 2.3.180

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. data/EDGE_GEM_VERSION +1 -1
  2. data/README.md +88 -149
  3. data/VERSION +1 -1
  4. data/bin/css2sass +7 -1
  5. data/bin/sass-convert +7 -0
  6. data/lib/haml/exec.rb +95 -22
  7. data/lib/haml/template.rb +1 -1
  8. data/lib/haml/util.rb +50 -0
  9. data/lib/sass.rb +1 -1
  10. data/lib/sass/css.rb +38 -210
  11. data/lib/sass/engine.rb +121 -47
  12. data/lib/sass/files.rb +28 -19
  13. data/lib/sass/plugin.rb +32 -43
  14. data/lib/sass/repl.rb +1 -1
  15. data/lib/sass/script.rb +25 -6
  16. data/lib/sass/script/bool.rb +1 -0
  17. data/lib/sass/script/color.rb +2 -2
  18. data/lib/sass/script/css_lexer.rb +22 -0
  19. data/lib/sass/script/css_parser.rb +28 -0
  20. data/lib/sass/script/funcall.rb +17 -9
  21. data/lib/sass/script/functions.rb +46 -1
  22. data/lib/sass/script/interpolation.rb +42 -0
  23. data/lib/sass/script/lexer.rb +142 -34
  24. data/lib/sass/script/literal.rb +28 -12
  25. data/lib/sass/script/node.rb +57 -1
  26. data/lib/sass/script/number.rb +18 -3
  27. data/lib/sass/script/operation.rb +44 -8
  28. data/lib/sass/script/parser.rb +149 -24
  29. data/lib/sass/script/string.rb +50 -2
  30. data/lib/sass/script/unary_operation.rb +25 -10
  31. data/lib/sass/script/variable.rb +20 -11
  32. data/lib/sass/scss.rb +14 -0
  33. data/lib/sass/scss/css_parser.rb +39 -0
  34. data/lib/sass/scss/parser.rb +683 -0
  35. data/lib/sass/scss/rx.rb +112 -0
  36. data/lib/sass/scss/script_lexer.rb +13 -0
  37. data/lib/sass/scss/script_parser.rb +25 -0
  38. data/lib/sass/tree/comment_node.rb +58 -16
  39. data/lib/sass/tree/debug_node.rb +7 -2
  40. data/lib/sass/tree/directive_node.rb +38 -34
  41. data/lib/sass/tree/for_node.rb +6 -0
  42. data/lib/sass/tree/if_node.rb +13 -0
  43. data/lib/sass/tree/import_node.rb +26 -7
  44. data/lib/sass/tree/mixin_def_node.rb +18 -0
  45. data/lib/sass/tree/mixin_node.rb +16 -1
  46. data/lib/sass/tree/node.rb +98 -27
  47. data/lib/sass/tree/prop_node.rb +97 -20
  48. data/lib/sass/tree/root_node.rb +37 -0
  49. data/lib/sass/tree/rule_node.rb +88 -60
  50. data/lib/sass/tree/variable_node.rb +9 -5
  51. data/lib/sass/tree/while_node.rb +4 -0
  52. data/test/haml/results/filters.xhtml +1 -1
  53. data/test/haml/util_test.rb +28 -0
  54. data/test/sass/conversion_test.rb +884 -0
  55. data/test/sass/css2sass_test.rb +46 -21
  56. data/test/sass/engine_test.rb +680 -160
  57. data/test/sass/functions_test.rb +27 -0
  58. data/test/sass/more_results/more_import.css +1 -1
  59. data/test/sass/more_templates/more_import.sass +3 -3
  60. data/test/sass/plugin_test.rb +28 -8
  61. data/test/sass/results/compact.css +1 -1
  62. data/test/sass/results/complex.css +5 -5
  63. data/test/sass/results/compressed.css +1 -1
  64. data/test/sass/results/expanded.css +1 -1
  65. data/test/sass/results/import.css +3 -1
  66. data/test/sass/results/mixins.css +12 -12
  67. data/test/sass/results/nested.css +1 -1
  68. data/test/sass/results/parent_ref.css +4 -4
  69. data/test/sass/results/script.css +3 -3
  70. data/test/sass/results/scss_import.css +15 -0
  71. data/test/sass/results/scss_importee.css +2 -0
  72. data/test/sass/script_conversion_test.rb +153 -0
  73. data/test/sass/script_test.rb +44 -54
  74. data/test/sass/scss/css_test.rb +811 -0
  75. data/test/sass/scss/rx_test.rb +156 -0
  76. data/test/sass/scss/scss_test.rb +871 -0
  77. data/test/sass/scss/test_helper.rb +37 -0
  78. data/test/sass/templates/alt.sass +2 -2
  79. data/test/sass/templates/bork1.sass +1 -1
  80. data/test/sass/templates/import.sass +4 -4
  81. data/test/sass/templates/importee.sass +3 -3
  82. data/test/sass/templates/line_numbers.sass +1 -1
  83. data/test/sass/templates/mixins.sass +2 -2
  84. data/test/sass/templates/nested_mixin_bork.sass +1 -1
  85. data/test/sass/templates/options.sass +1 -1
  86. data/test/sass/templates/parent_ref.sass +2 -2
  87. data/test/sass/templates/script.sass +69 -69
  88. data/test/sass/templates/scss_import.scss +10 -0
  89. data/test/sass/templates/scss_importee.scss +1 -0
  90. data/test/sass/templates/units.sass +10 -10
  91. data/test/test_helper.rb +4 -4
  92. metadata +27 -2
data/lib/haml/template.rb CHANGED
@@ -77,7 +77,7 @@ if Haml::Util.rails_root
77
77
  FileUtils.cp(haml_init_file, rails_init_file) unless FileUtils.cmp(rails_init_file, haml_init_file)
78
78
  end
79
79
  rescue SystemCallError
80
- warn <<END
80
+ Haml::Util.haml_warn <<END
81
81
  HAML WARNING:
82
82
  #{rails_init_file} is out of date and couldn't be automatically updated.
83
83
  Please run `haml --rails #{File.expand_path(Haml::Util.rails_root)}' to update it.
data/lib/haml/util.rb CHANGED
@@ -135,6 +135,18 @@ module Haml
135
135
  end
136
136
  end
137
137
 
138
+ # Destructively strips whitespace from the beginning and end
139
+ # of the first and last elements, respectively,
140
+ # in the array (if those elements are strings).
141
+ #
142
+ # @param arr [Array]
143
+ # @return [Array] `arr`
144
+ def strip_string_array(arr)
145
+ arr.first.lstrip! if arr.first.is_a?(String)
146
+ arr.last.rstrip! if arr.last.is_a?(String)
147
+ arr
148
+ end
149
+
138
150
  # Returns information about the caller of the previous method.
139
151
  #
140
152
  # @param entry [String] An entry in the `#caller` list, or a similarly formatted string
@@ -156,6 +168,26 @@ module Haml
156
168
  $stderr = the_real_stderr
157
169
  end
158
170
 
171
+ @@silence_warnings = false
172
+ # Silences all Haml warnings within a block.
173
+ #
174
+ # @yield A block in which no Haml warnings will be printed
175
+ def silence_haml_warnings
176
+ old_silence_warnings = @@silence_warnings
177
+ @@silence_warnings = true
178
+ yield
179
+ ensure
180
+ @@silence_warnings = old_silence_warnings
181
+ end
182
+
183
+ # The same as `Kernel#warn`, but is silenced by \{#silence\_haml\_warnings}.
184
+ #
185
+ # @param msg [String]
186
+ def haml_warn(msg)
187
+ return if @@silence_warnings
188
+ warn(msg)
189
+ end
190
+
159
191
  ## Cross Rails Version Compatibility
160
192
 
161
193
  # Returns the root of the Rails application,
@@ -326,6 +358,24 @@ MSG
326
358
  ruby1_8? ? enum.enum_with_index : enum.each_with_index
327
359
  end
328
360
 
361
+ # A version of `Enumerable#enum_cons` that works in Ruby 1.8 and 1.9.
362
+ #
363
+ # @param enum [Enumerable] The enumerable to get the enumerator for
364
+ # @param n [Fixnum] The size of each cons
365
+ # @return [Enumerator] The consed enumerator
366
+ def enum_cons(enum, n)
367
+ ruby1_8? ? enum.enum_cons(n) : enum.each_cons(n)
368
+ end
369
+
370
+ # A version of `Enumerable#enum_slice` that works in Ruby 1.8 and 1.9.
371
+ #
372
+ # @param enum [Enumerable] The enumerable to get the enumerator for
373
+ # @param n [Fixnum] The size of each slice
374
+ # @return [Enumerator] The consed enumerator
375
+ def enum_slice(enum, n)
376
+ ruby1_8? ? enum.enum_slice(n) : enum.each_slice(n)
377
+ end
378
+
329
379
  # Returns the ASCII code of the given character.
330
380
  #
331
381
  # @param c [String] All characters but the first are ignored.
data/lib/sass.rb CHANGED
@@ -5,7 +5,7 @@ require 'haml/version'
5
5
 
6
6
  # The module that contains everything Sass-related:
7
7
  #
8
- # * {Sass::Engine} is the class used to render Sass within Ruby code.
8
+ # * {Sass::Engine} is the class used to render Sass/SCSS within Ruby code.
9
9
  # * {Sass::Plugin} is interfaces with web frameworks (Rails and Merb in particular).
10
10
  # * {Sass::SyntaxError} is raised when Sass encounters an error.
11
11
  # * {Sass::CSS} handles conversion of CSS to Sass.
data/lib/sass/css.rb CHANGED
@@ -1,96 +1,49 @@
1
1
  require File.dirname(__FILE__) + '/../sass'
2
2
  require 'sass/tree/node'
3
+ require 'sass/scss/css_parser'
3
4
  require 'strscan'
4
5
 
5
6
  module Sass
6
- module Tree
7
- class Node
8
- # Converts a node to Sass code that will generate it.
9
- #
10
- # @param tabs [Fixnum] The amount of tabulation to use for the Sass code
11
- # @param opts [{Symbol => Object}] An options hash (see {Sass::CSS#initialize})
12
- # @return [String] The Sass code corresponding to the node
13
- def to_sass(tabs = 0, opts = {})
14
- result = ''
15
-
16
- children.each do |child|
17
- result << "#{' ' * tabs}#{child.to_sass(0, opts)}\n"
18
- end
19
-
20
- result
21
- end
22
- end
23
-
24
- class RuleNode
25
- # @see Node#to_sass
26
- def to_sass(tabs, opts = {})
27
- name = rules.first
28
- name = "\\" + name if name[0] == ?:
29
- str = "\n#{' ' * tabs}#{name}#{children.any? { |c| c.is_a? PropNode } ? "\n" : ''}"
30
-
31
- children.each do |child|
32
- str << "#{child.to_sass(tabs + 1, opts)}"
33
- end
34
-
35
- str
36
- end
37
- end
38
-
39
- class PropNode
40
- # @see Node#to_sass
41
- def to_sass(tabs, opts = {})
42
- "#{' ' * tabs}#{opts[:old] ? ':' : ''}#{name}#{opts[:old] ? '' : ':'} #{value}\n"
43
- end
44
- end
45
-
46
- class DirectiveNode
47
- # @see Node#to_sass
48
- def to_sass(tabs, opts = {})
49
- "#{' ' * tabs}#{value}#{children.map {|c| c.to_sass(tabs + 1, opts)}}\n"
50
- end
51
- end
52
- end
53
-
54
- # This class converts CSS documents into Sass templates.
7
+ # This class converts CSS documents into Sass or SCSS templates.
55
8
  # It works by parsing the CSS document into a {Sass::Tree} structure,
56
9
  # and then applying various transformations to the structure
57
- # to produce more concise and idiomatic Sass.
10
+ # to produce more concise and idiomatic Sass/SCSS.
58
11
  #
59
12
  # Example usage:
60
13
  #
61
- # Sass::CSS.new("p { color: blue }").render #=> "p\n color: blue"
14
+ # Sass::CSS.new("p { color: blue }").render(:sass) #=> "p\n color: blue"
15
+ # Sass::CSS.new("p { color: blue }").render(:scss) #=> "p {\n color: blue; }"
62
16
  class CSS
63
17
  # @param template [String] The CSS code
64
18
  # @option options :old [Boolean] (false)
65
19
  # Whether or not to output old property syntax
66
20
  # (`:color blue` as opposed to `color: blue`).
67
- # @option options :filename [String]
68
- # The filename of the CSS file being processed.
69
- # Used for error reporting
21
+ # This is only meaningful when generating Sass code,
22
+ # rather than SCSS.
70
23
  def initialize(template, options = {})
71
24
  if template.is_a? IO
72
25
  template = template.read
73
26
  end
74
27
 
75
- @line = 1
76
28
  @options = options.dup
77
29
  # Backwards compatibility
78
30
  @options[:old] = true if @options[:alternate] == false
79
- @template_str = template
31
+ @template = template
80
32
  end
81
33
 
82
- # Converts the CSS template into Sass code.
34
+ # Converts the CSS template into Sass or SCSS code.
83
35
  #
84
- # @return [String] The resulting Sass code
36
+ # @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
37
+ # @return [String] The resulting Sass or SCSS code
85
38
  # @raise [Sass::SyntaxError] if there's an error parsing the CSS template
86
- def render
87
- @template = StringScanner.new(
88
- Haml::Util.check_encoding(@template_str) do |msg, line|
89
- raise Sass::SyntaxError.new(msg, :line => line)
90
- end)
91
- build_tree.to_sass(0, @options).strip + "\n"
39
+ def render(fmt = :sass)
40
+ Haml::Util.check_encoding(@template) do |msg, line|
41
+ raise Sass::SyntaxError.new(msg, :line => line)
42
+ end
43
+
44
+ build_tree.send("to_#{fmt}", @options).strip + "\n"
92
45
  rescue Sass::SyntaxError => err
93
- err.modify_backtrace(:filename => @options[:filename] || '(css)', :line => @line)
46
+ err.modify_backtrace(:filename => @options[:filename] || '(css)')
94
47
  raise err
95
48
  end
96
49
 
@@ -100,9 +53,7 @@ module Sass
100
53
  #
101
54
  # @return [Tree::Node] The root node of the parsed tree
102
55
  def build_tree
103
- root = Tree::RootNode.new(@template.string)
104
- whitespace
105
- rules root
56
+ root = Sass::SCSS::CssParser.new(@template).parse
106
57
  expand_commas root
107
58
  parent_ref_rules root
108
59
  remove_parent_refs root
@@ -111,133 +62,6 @@ module Sass
111
62
  root
112
63
  end
113
64
 
114
- # Parses a set of CSS rules.
115
- #
116
- # @param root [Tree::Node] The parent node of the rules
117
- def rules(root)
118
- while r = rule
119
- root << r
120
- whitespace
121
- end
122
- end
123
-
124
- # Parses a single CSS rule.
125
- #
126
- # @return [Tree::Node] The parsed rule
127
- def rule
128
- rule = ""
129
- loop do
130
- token = scan(/(?:[^\{\};\/\s]|\/[^*])+/)
131
- if token.nil?
132
- return if rule.empty?
133
- break
134
- end
135
- rule << token
136
- break unless @template.match?(/\s|\/\*/)
137
- whitespace
138
- rule << " "
139
- end
140
-
141
- rule.strip!
142
- directive = rule[0] == ?@
143
-
144
- if directive
145
- node = Tree::DirectiveNode.new(rule)
146
- return node if scan(/;/)
147
-
148
- assert_match /\{/
149
- whitespace
150
-
151
- rules(node)
152
- return node
153
- end
154
-
155
- assert_match /\{/
156
- node = Tree::RuleNode.new(rule)
157
- properties(node)
158
- return node
159
- end
160
-
161
- # Parses a set of CSS properties within a rule.
162
- #
163
- # @param rule [Tree::RuleNode] The parent node of the properties
164
- def properties(rule)
165
- while scan(/[^:\}\s]+/)
166
- name = @template[0]
167
- whitespace
168
-
169
- assert_match /:/
170
-
171
- value = ''
172
- while scan(/[^;\s\}]+/)
173
- value << @template[0] << whitespace
174
- end
175
-
176
- assert_match /(;|(?=\}))/
177
- rule << Tree::PropNode.new(name, value, nil)
178
- end
179
-
180
- assert_match /\}/
181
- end
182
-
183
- # Moves the scanner over a section of whitespace or comments.
184
- #
185
- # @return [String] The ignored whitespace
186
- def whitespace
187
- space = scan(/\s*/) || ''
188
-
189
- # If we've hit a comment,
190
- # go past it and look for more whitespace
191
- if scan(/\/\*/)
192
- scan_until(/\*\//)
193
- return space + whitespace
194
- end
195
- return space
196
- end
197
-
198
- # Moves the scanner over a regular expression,
199
- # raising an exception if it doesn't match.
200
- #
201
- # @param re [Regexp] The regular expression to assert
202
- def assert_match(re)
203
- if scan(re)
204
- whitespace
205
- return
206
- end
207
-
208
- pos = @template.pos
209
-
210
- after = @template.string[[pos - 15, 0].max...pos].gsub(/.*\n/m, '')
211
- after = "..." + after if pos >= 15
212
-
213
- # Display basic regexps as plain old strings
214
- string = re.source.gsub(/\\(.)/, '\1')
215
- expected = re.source == Regexp.escape(string) ? string.inspect : re.inspect
216
-
217
- was = @template.rest[0...15].gsub(/\n.*/m, '')
218
- was += "..." if @template.rest.size >= 15
219
- raise Sass::SyntaxError.new(
220
- "Invalid CSS after #{after.inspect}: expected #{expected}, was #{was.inspect}")
221
- end
222
-
223
- # Identical to `@template.scan`, except that it increments the line count.
224
- # `@template.scan` should never be called directly;
225
- # this should be used instead.
226
- def scan(re)
227
- str = @template.scan(re)
228
- @line += str.count "\n" if str
229
- str
230
- end
231
-
232
- # Identical to `@template.scan_until`, except that it increments the line count.
233
- # `@template.scan_until` should never be called directly;
234
- # this should be used instead.
235
- def scan_until(re)
236
- str = @template.scan_until(re)
237
- @line += str.count "\n" if str
238
- str
239
- end
240
-
241
65
  # Transform
242
66
  #
243
67
  # foo, bar, baz
@@ -255,9 +79,9 @@ module Sass
255
79
  # @param root [Tree::Node] The parent node
256
80
  def expand_commas(root)
257
81
  root.children.map! do |child|
258
- next child unless Tree::RuleNode === child && child.rules.first.include?(',')
259
- child.rules.first.split(',').map do |rule|
260
- node = Tree::RuleNode.new(rule.strip)
82
+ next child unless Tree::RuleNode === child && child.rule.first.include?(',')
83
+ child.rule.first.split(',').map do |rule|
84
+ node = Tree::RuleNode.new([rule.strip])
261
85
  node.children = child.children
262
86
  node
263
87
  end
@@ -301,22 +125,26 @@ module Sass
301
125
  # @param root [Tree::Node] The parent node
302
126
  def parent_ref_rules(root)
303
127
  current_rule = nil
304
- root.children.select { |c| Tree::RuleNode === c }.each do |child|
305
- root.children.delete child
306
- first, rest = child.rules.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
128
+ root.children.map! do |child|
129
+ next child unless child.is_a?(Tree::RuleNode)
130
+
131
+ first, rest = child.rule.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
307
132
 
308
- if current_rule.nil? || current_rule.rules.first != first
309
- current_rule = Tree::RuleNode.new(first)
310
- root << current_rule
133
+ if current_rule.nil? || current_rule.rule.first != first
134
+ current_rule = Tree::RuleNode.new([first])
311
135
  end
312
136
 
313
137
  if rest
314
- child.rules = ["&" + rest]
138
+ child.rule = ["&" + rest]
315
139
  current_rule << child
316
140
  else
317
141
  current_rule.children += child.children
318
142
  end
143
+
144
+ current_rule
319
145
  end
146
+ root.children.compact!
147
+ root.children.uniq!
320
148
 
321
149
  root.children.each { |v| parent_ref_rules(v) }
322
150
  end
@@ -337,7 +165,7 @@ module Sass
337
165
  def remove_parent_refs(root)
338
166
  root.children.each do |child|
339
167
  if child.is_a?(Tree::RuleNode)
340
- child.rules.first.gsub! /^& +/, ''
168
+ child.rule.first.gsub! /^& +/, ''
341
169
  remove_parent_refs child
342
170
  end
343
171
  end
@@ -378,10 +206,10 @@ module Sass
378
206
  while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
379
207
  child = rule.children.first
380
208
 
381
- if child.rules.first[0] == ?&
382
- rule.rules = [child.rules.first.gsub(/^&/, rule.rules.first)]
209
+ if child.rule.first[0] == ?&
210
+ rule.rule = [child.rule.first.gsub(/^&/, rule.rule.first)]
383
211
  else
384
- rule.rules = ["#{rule.rules.first} #{child.rules.first}"]
212
+ rule.rule = ["#{rule.rule.first} #{child.rule.first}"]
385
213
  end
386
214
 
387
215
  rule.children = child.children
@@ -411,7 +239,7 @@ module Sass
411
239
  next child unless child.is_a?(Tree::RuleNode)
412
240
 
413
241
  if prev_rule && prev_rule.children == child.children
414
- prev_rule.rules.first << ", #{child.rules.first}"
242
+ prev_rule.rule.first << ", #{child.rule.first}"
415
243
  next nil
416
244
  end
417
245
 
data/lib/sass/engine.rb CHANGED
@@ -16,6 +16,7 @@ require 'sass/tree/debug_node'
16
16
  require 'sass/tree/import_node'
17
17
  require 'sass/environment'
18
18
  require 'sass/script'
19
+ require 'sass/scss'
19
20
  require 'sass/error'
20
21
  require 'sass/files'
21
22
  require 'haml/shared'
@@ -123,7 +124,7 @@ module Sass
123
124
  # The regex that matches and extracts data from
124
125
  # properties of the form `name: prop`.
125
126
  # @private
126
- PROPERTY_NEW = /^([^\s=:"]+)(\s*=|:)(?:\s+|$)(.*)/
127
+ PROPERTY_NEW = /^([^\s=:"]+)\s*(=|:)(?:\s+|$)(.*)/
127
128
 
128
129
  # The regex that matches and extracts data from
129
130
  # properties of the form `:name prop`.
@@ -136,6 +137,7 @@ module Sass
136
137
  :load_paths => ['.'],
137
138
  :cache => true,
138
139
  :cache_location => './.sass-cache',
140
+ :syntax => :sass,
139
141
  }.freeze
140
142
 
141
143
  # @param template [String] The Sass template.
@@ -174,8 +176,13 @@ module Sass
174
176
  def to_tree
175
177
  @template = check_encoding(@template) {|msg, line| raise Sass::SyntaxError.new(msg, :line => line)}
176
178
 
177
- root = Tree::RootNode.new(@template)
178
- append_children(root, tree(tabulate(@template)).first, true)
179
+ if @options[:syntax] == :scss
180
+ root = Sass::SCSS::Parser.new(@template).parse
181
+ else
182
+ root = Tree::RootNode.new(@template)
183
+ append_children(root, tree(tabulate(@template)).first, true)
184
+ end
185
+
179
186
  root.options = @options
180
187
  root
181
188
  rescue SyntaxError => e
@@ -286,11 +293,7 @@ MSG
286
293
  node.line = line.index
287
294
  node.filename = line.filename
288
295
 
289
- if node.is_a?(Tree::CommentNode)
290
- node.lines = line.children
291
- else
292
- append_children(node, line.children, false)
293
- end
296
+ append_children(node, line.children, false)
294
297
  end
295
298
 
296
299
  node_or_nodes
@@ -298,6 +301,7 @@ MSG
298
301
 
299
302
  def append_children(parent, children, root)
300
303
  continued_rule = nil
304
+ continued_comment = nil
301
305
  children.each do |line|
302
306
  child = build_tree(parent, line, root)
303
307
 
@@ -320,6 +324,17 @@ MSG
320
324
  continued_rule, child = nil, continued_rule
321
325
  end
322
326
 
327
+ if child.is_a?(Tree::CommentNode) && child.silent
328
+ if continued_comment &&
329
+ child.line == continued_comment.line +
330
+ continued_comment.value.count("\n") + 1
331
+ continued_comment.value << "\n" << child.value
332
+ next
333
+ end
334
+
335
+ continued_comment = child
336
+ end
337
+
323
338
  check_for_no_children(child)
324
339
  validate_and_append_child(parent, child, line, root)
325
340
  end
@@ -331,17 +346,6 @@ MSG
331
346
  end
332
347
 
333
348
  def validate_and_append_child(parent, child, line, root)
334
- unless root
335
- case child
336
- when Tree::MixinDefNode
337
- raise SyntaxError.new("Mixins may only be defined at the root of a document.",
338
- :line => line.index)
339
- when Tree::ImportNode
340
- raise SyntaxError.new("Import directives may only be used at the root of a document.",
341
- :line => line.index)
342
- end
343
- end
344
-
345
349
  case child
346
350
  when Array
347
351
  child.each {|c| validate_and_append_child(parent, c, line, root)}
@@ -352,18 +356,10 @@ MSG
352
356
 
353
357
  def check_for_no_children(node)
354
358
  return unless node.is_a?(Tree::RuleNode) && node.children.empty?
355
- warning = (node.rules.size == 1) ? <<SHORT : <<LONG
359
+ Haml::Util.haml_warn(<<WARNING.strip)
356
360
  WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
357
- Selector #{node.rules.first.inspect} doesn't have any properties and will not be rendered.
358
- SHORT
359
-
360
- WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
361
- Selector
362
- #{node.rules.join("\n ")}
363
- doesn't have any properties and will not be rendered.
364
- LONG
365
-
366
- warn(warning.strip)
361
+ This selector doesn't have any properties and will not be rendered.
362
+ WARNING
367
363
  end
368
364
 
369
365
  def parse_line(parent, line, root)
@@ -376,23 +372,23 @@ LONG
376
372
  # which begin with ::,
377
373
  # as well as pseudo-classes
378
374
  # if we're using the new property syntax
379
- Tree::RuleNode.new(line.text)
375
+ Tree::RuleNode.new(parse_interp(line.text))
380
376
  else
381
377
  parse_property(line, PROPERTY_OLD)
382
378
  end
383
- when Script::VARIABLE_CHAR
379
+ when ?!, ?$
384
380
  parse_variable(line)
385
381
  when COMMENT_CHAR
386
382
  parse_comment(line.text)
387
383
  when DIRECTIVE_CHAR
388
384
  parse_directive(parent, line, root)
389
385
  when ESCAPE_CHAR
390
- Tree::RuleNode.new(line.text[1..-1])
386
+ Tree::RuleNode.new(parse_interp(line.text[1..-1]))
391
387
  when MIXIN_DEFINITION_CHAR
392
388
  parse_mixin_definition(line)
393
389
  when MIXIN_INCLUDE_CHAR
394
390
  if line.text[1].nil? || line.text[1] == ?\s
395
- Tree::RuleNode.new(line.text)
391
+ Tree::RuleNode.new(parse_interp(line.text))
396
392
  else
397
393
  parse_mixin_include(line, root)
398
394
  end
@@ -400,7 +396,7 @@ LONG
400
396
  if line.text =~ PROPERTY_NEW_MATCHER
401
397
  parse_property(line, PROPERTY_NEW)
402
398
  else
403
- Tree::RuleNode.new(line.text)
399
+ Tree::RuleNode.new(parse_interp(line.text))
404
400
  end
405
401
  end
406
402
  end
@@ -411,29 +407,51 @@ LONG
411
407
  raise SyntaxError.new("Invalid property: \"#{line.text}\".",
412
408
  :line => @line) if name.nil? || value.nil?
413
409
 
414
- expr = if (eq.strip[0] == SCRIPT_CHAR)
415
- parse_script(value, :offset => line.offset + line.text.index(value))
410
+ if value.strip.empty?
411
+ expr = Sass::Script::String.new("")
416
412
  else
417
- value
413
+ expr = parse_script(value, :offset => line.offset + line.text.index(value))
414
+
415
+ if eq.strip[0] == SCRIPT_CHAR
416
+ expr.context = :equals
417
+ Script.equals_warning("properties", name,
418
+ Sass::Tree::PropNode.val_to_sass(expr), false,
419
+ @line, line.offset + 1, @options[:filename])
420
+ end
418
421
  end
419
- Tree::PropNode.new(name, expr, property_regx == PROPERTY_OLD ? :old : :new)
422
+ Tree::PropNode.new(
423
+ parse_interp(name), expr,
424
+ property_regx == PROPERTY_OLD ? :old : :new)
420
425
  end
421
426
 
422
427
  def parse_variable(line)
423
- name, op, value = line.text.scan(Script::MATCH)[0]
428
+ name, op, value, default = line.text.scan(Script::MATCH)[0]
429
+ guarded = op =~ /^\|\|/
424
430
  raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
425
431
  :line => @line + 1) unless line.children.empty?
426
432
  raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
427
433
  :line => @line) unless name && value
434
+ Script.var_warning(name, @line, line.offset + 1, @options[:filename]) if line.text[0] == ?!
435
+
436
+ expr = parse_script(value, :offset => line.offset + line.text.index(value))
437
+ if op =~ /=$/
438
+ expr.context = :equals
439
+ type = guarded ? "variable defaults" : "variables"
440
+ Script.equals_warning(type, "$#{name}", expr.to_sass,
441
+ guarded, @line, line.offset + 1, @options[:filename])
442
+ end
428
443
 
429
- Tree::VariableNode.new(name, parse_script(value, :offset => line.offset + line.text.index(value)), op == '||=')
444
+ Tree::VariableNode.new(name, expr, default || guarded)
430
445
  end
431
446
 
432
447
  def parse_comment(line)
433
448
  if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
434
- Tree::CommentNode.new(line, line[1] == SASS_COMMENT_CHAR)
449
+ silent = line[1] == SASS_COMMENT_CHAR
450
+ Tree::CommentNode.new(
451
+ format_comment_text(line[2..-1], silent),
452
+ silent)
435
453
  else
436
- Tree::RuleNode.new(line)
454
+ Tree::RuleNode.new(parse_interp(line))
437
455
  end
438
456
  end
439
457
 
@@ -447,6 +465,10 @@ LONG
447
465
  raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
448
466
  :line => @line + 1) unless line.children.empty?
449
467
  value.split(/,\s*/).map {|f| Tree::ImportNode.new(f)}
468
+ elsif directive == "mixin"
469
+ parse_mixin_definition(line)
470
+ elsif directive == "include"
471
+ parse_mixin_include(line, root)
450
472
  elsif directive == "for"
451
473
  parse_for(line, root, value)
452
474
  elsif directive == "else"
@@ -482,14 +504,18 @@ LONG
482
504
  raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
483
505
  end
484
506
  raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
507
+ if var.slice!(0) == ?!
508
+ offset = line.offset + line.text.index("!" + var) + 1
509
+ Script.var_warning(var, @line, offset, @options[:filename])
510
+ end
485
511
 
486
512
  parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
487
513
  parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
488
- Tree::ForNode.new(var[1..-1], parsed_from, parsed_to, to_name == 'to')
514
+ Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
489
515
  end
490
516
 
491
517
  def parse_else(parent, line, text)
492
- previous = parent.last
518
+ previous = parent.children.last
493
519
  raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
494
520
 
495
521
  if text
@@ -505,8 +531,10 @@ LONG
505
531
  nil
506
532
  end
507
533
 
534
+ # @private
535
+ MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
508
536
  def parse_mixin_definition(line)
509
- name, arg_string = line.text.scan(/^=\s*([^(]+)(.*)$/).first
537
+ name, arg_string = line.text.scan(MIXIN_DEF_RE).first
510
538
  raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
511
539
 
512
540
  offset = line.offset + line.text.size - arg_string.size
@@ -516,8 +544,10 @@ LONG
516
544
  Tree::MixinDefNode.new(name, args)
517
545
  end
518
546
 
547
+ # @private
548
+ MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
519
549
  def parse_mixin_include(line, root)
520
- name, arg_string = line.text.scan(/^\+\s*([^(]+)(.*)$/).first
550
+ name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
521
551
  raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
522
552
 
523
553
  offset = line.offset + line.text.size - arg_string.size
@@ -533,5 +563,49 @@ LONG
533
563
  offset = options[:offset] || 0
534
564
  Script.parse(script, line, offset, @options)
535
565
  end
566
+
567
+ def format_comment_text(text, silent)
568
+ content = text.split("\n")
569
+
570
+ if content.first && content.first.strip.empty?
571
+ removed_first = true
572
+ content.shift
573
+ end
574
+
575
+ return silent ? "//" : "/* */" if content.empty?
576
+ content.map! {|l| (l.empty? ? "" : " ") + l}
577
+ content.first.gsub!(/^ /, '') unless removed_first
578
+ content.last.gsub!(%r{ ?\*/ *$}, '')
579
+ if silent
580
+ "//" + content.join("\n//")
581
+ else
582
+ "/*" + content.join("\n *") + " */"
583
+ end
584
+ end
585
+
586
+ def parse_interp(text)
587
+ self.class.parse_interp(text, @line, :filename => @filename)
588
+ end
589
+
590
+ # It's important that this have strings (at least)
591
+ # at the beginning, the end, and between each Script::Node.
592
+ #
593
+ # @private
594
+ def self.parse_interp(text, line, options)
595
+ res = []
596
+ rest = Haml::Shared.handle_interpolation text do |scan|
597
+ escapes = scan[2].size
598
+ res << scan.matched[0...-2 - escapes]
599
+ if escapes % 2 == 1
600
+ res << "\\" * (escapes - 1) << '#{'
601
+ else
602
+ res << "\\" * [0, escapes - 1].max
603
+ res << Script::Parser.new(
604
+ scan, line, scan.pos - scan.matched_size, options).
605
+ parse_interpolated
606
+ end
607
+ end
608
+ res << rest
609
+ end
536
610
  end
537
611
  end