haml-edge 2.3.179 → 2.3.180

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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