haml 2.2.24 → 3.0.0.beta.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 (168) hide show
  1. data/.yardopts +0 -1
  2. data/README.md +91 -151
  3. data/REMEMBER +11 -1
  4. data/Rakefile +73 -55
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/css2sass +7 -1
  8. data/bin/sass-convert +7 -0
  9. data/extra/haml-mode.el +2 -1
  10. data/lib/haml/buffer.rb +22 -4
  11. data/lib/haml/engine.rb +5 -1
  12. data/lib/haml/exec.rb +231 -46
  13. data/lib/haml/filters.rb +19 -8
  14. data/lib/haml/helpers.rb +47 -20
  15. data/lib/haml/helpers/action_view_extensions.rb +2 -4
  16. data/lib/haml/helpers/action_view_mods.rb +11 -8
  17. data/lib/haml/helpers/xss_mods.rb +13 -2
  18. data/lib/haml/html.rb +179 -48
  19. data/lib/haml/html/erb.rb +141 -0
  20. data/lib/haml/precompiler.rb +40 -15
  21. data/lib/haml/railtie.rb +1 -5
  22. data/lib/haml/root.rb +3 -0
  23. data/lib/haml/template.rb +4 -14
  24. data/lib/haml/util.rb +120 -30
  25. data/lib/haml/version.rb +25 -2
  26. data/lib/sass.rb +5 -1
  27. data/lib/sass/callbacks.rb +50 -0
  28. data/lib/sass/css.rb +40 -191
  29. data/lib/sass/engine.rb +170 -74
  30. data/lib/sass/environment.rb +8 -2
  31. data/lib/sass/error.rb +163 -25
  32. data/lib/sass/files.rb +31 -28
  33. data/lib/sass/plugin.rb +268 -87
  34. data/lib/sass/plugin/rails.rb +9 -4
  35. data/lib/sass/repl.rb +1 -1
  36. data/lib/sass/script.rb +31 -29
  37. data/lib/sass/script/bool.rb +1 -0
  38. data/lib/sass/script/color.rb +290 -23
  39. data/lib/sass/script/css_lexer.rb +22 -0
  40. data/lib/sass/script/css_parser.rb +28 -0
  41. data/lib/sass/script/funcall.rb +22 -3
  42. data/lib/sass/script/functions.rb +523 -33
  43. data/lib/sass/script/interpolation.rb +42 -0
  44. data/lib/sass/script/lexer.rb +169 -52
  45. data/lib/sass/script/literal.rb +58 -9
  46. data/lib/sass/script/node.rb +79 -1
  47. data/lib/sass/script/number.rb +20 -5
  48. data/lib/sass/script/operation.rb +49 -3
  49. data/lib/sass/script/parser.rb +162 -28
  50. data/lib/sass/script/string.rb +50 -2
  51. data/lib/sass/script/unary_operation.rb +25 -2
  52. data/lib/sass/script/variable.rb +21 -4
  53. data/lib/sass/scss.rb +14 -0
  54. data/lib/sass/scss/css_parser.rb +39 -0
  55. data/lib/sass/scss/parser.rb +683 -0
  56. data/lib/sass/scss/rx.rb +112 -0
  57. data/lib/sass/scss/script_lexer.rb +13 -0
  58. data/lib/sass/scss/script_parser.rb +25 -0
  59. data/lib/sass/tree/comment_node.rb +69 -27
  60. data/lib/sass/tree/debug_node.rb +7 -2
  61. data/lib/sass/tree/directive_node.rb +41 -35
  62. data/lib/sass/tree/for_node.rb +6 -0
  63. data/lib/sass/tree/if_node.rb +13 -1
  64. data/lib/sass/tree/import_node.rb +52 -27
  65. data/lib/sass/tree/mixin_def_node.rb +18 -0
  66. data/lib/sass/tree/mixin_node.rb +41 -6
  67. data/lib/sass/tree/node.rb +197 -70
  68. data/lib/sass/tree/prop_node.rb +152 -57
  69. data/lib/sass/tree/root_node.rb +118 -0
  70. data/lib/sass/tree/rule_node.rb +193 -96
  71. data/lib/sass/tree/variable_node.rb +9 -5
  72. data/lib/sass/tree/while_node.rb +4 -0
  73. data/test/benchmark.rb +5 -5
  74. data/test/haml/engine_test.rb +147 -10
  75. data/test/haml/{rhtml/_av_partial_1.rhtml → erb/_av_partial_1.erb} +1 -1
  76. data/test/haml/{rhtml/_av_partial_2.rhtml → erb/_av_partial_2.erb} +1 -1
  77. data/test/haml/{rhtml/action_view.rhtml → erb/action_view.erb} +1 -1
  78. data/test/haml/{rhtml/standard.rhtml → erb/standard.erb} +0 -0
  79. data/test/haml/helper_test.rb +91 -24
  80. data/test/haml/html2haml/erb_tests.rb +410 -0
  81. data/test/haml/html2haml_test.rb +210 -66
  82. data/test/haml/results/filters.xhtml +1 -1
  83. data/test/haml/results/just_stuff.xhtml +2 -0
  84. data/test/haml/spec_test.rb +44 -0
  85. data/test/haml/template_test.rb +22 -2
  86. data/test/haml/templates/helpers.haml +0 -13
  87. data/test/haml/templates/just_stuff.haml +2 -0
  88. data/test/haml/util_test.rb +48 -0
  89. data/test/sass/callbacks_test.rb +61 -0
  90. data/test/sass/conversion_test.rb +884 -0
  91. data/test/sass/css2sass_test.rb +99 -18
  92. data/test/sass/data/hsl-rgb.txt +319 -0
  93. data/test/sass/engine_test.rb +1049 -131
  94. data/test/sass/functions_test.rb +398 -47
  95. data/test/sass/more_results/more_import.css +1 -1
  96. data/test/sass/more_templates/more_import.sass +3 -3
  97. data/test/sass/plugin_test.rb +184 -10
  98. data/test/sass/results/compact.css +1 -1
  99. data/test/sass/results/complex.css +5 -5
  100. data/test/sass/results/compressed.css +1 -1
  101. data/test/sass/results/expanded.css +1 -1
  102. data/test/sass/results/import.css +3 -1
  103. data/test/sass/results/mixins.css +12 -12
  104. data/test/sass/results/nested.css +1 -1
  105. data/test/sass/results/options.css +1 -0
  106. data/test/sass/results/parent_ref.css +4 -4
  107. data/test/sass/results/script.css +3 -3
  108. data/test/sass/results/scss_import.css +15 -0
  109. data/test/sass/results/scss_importee.css +2 -0
  110. data/test/sass/script_conversion_test.rb +153 -0
  111. data/test/sass/script_test.rb +137 -70
  112. data/test/sass/scss/css_test.rb +811 -0
  113. data/test/sass/scss/rx_test.rb +156 -0
  114. data/test/sass/scss/scss_test.rb +871 -0
  115. data/test/sass/scss/test_helper.rb +37 -0
  116. data/test/sass/templates/alt.sass +2 -2
  117. data/test/sass/templates/bork1.sass +2 -0
  118. data/test/sass/templates/bork3.sass +2 -0
  119. data/test/sass/templates/bork4.sass +2 -0
  120. data/test/sass/templates/import.sass +4 -4
  121. data/test/sass/templates/importee.sass +3 -3
  122. data/test/sass/templates/line_numbers.sass +1 -1
  123. data/test/sass/templates/mixin_bork.sass +5 -0
  124. data/test/sass/templates/mixins.sass +2 -2
  125. data/test/sass/templates/nested_bork1.sass +2 -0
  126. data/test/sass/templates/nested_bork2.sass +2 -0
  127. data/test/sass/templates/nested_bork3.sass +2 -0
  128. data/test/sass/templates/nested_bork4.sass +2 -0
  129. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  130. data/test/sass/templates/options.sass +2 -0
  131. data/test/sass/templates/parent_ref.sass +2 -2
  132. data/test/sass/templates/script.sass +69 -69
  133. data/test/sass/templates/scss_import.scss +10 -0
  134. data/test/sass/templates/scss_importee.scss +1 -0
  135. data/test/sass/templates/units.sass +10 -10
  136. data/test/test_helper.rb +20 -8
  137. data/vendor/fssm/LICENSE +20 -0
  138. data/vendor/fssm/README.markdown +55 -0
  139. data/vendor/fssm/Rakefile +59 -0
  140. data/vendor/fssm/VERSION.yml +5 -0
  141. data/vendor/fssm/example.rb +9 -0
  142. data/vendor/fssm/fssm.gemspec +77 -0
  143. data/vendor/fssm/lib/fssm.rb +33 -0
  144. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  145. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  146. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  147. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  148. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  149. data/vendor/fssm/lib/fssm/path.rb +91 -0
  150. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  151. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  152. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  153. data/vendor/fssm/lib/fssm/support.rb +63 -0
  154. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  155. data/vendor/fssm/profile/prof-cache.rb +40 -0
  156. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  157. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  158. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  159. data/vendor/fssm/profile/prof.html +2379 -0
  160. data/vendor/fssm/spec/path_spec.rb +75 -0
  161. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  162. data/vendor/fssm/spec/root/file.css +0 -0
  163. data/vendor/fssm/spec/root/file.rb +0 -0
  164. data/vendor/fssm/spec/root/file.yml +0 -0
  165. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  166. data/vendor/fssm/spec/spec_helper.rb +14 -0
  167. metadata +94 -14
  168. data/test/sass/templates/bork.sass +0 -2
@@ -23,11 +23,27 @@ module Haml
23
23
  # :major => 2, :minor => 1, :teeny => 0
24
24
  # }
25
25
  #
26
+ # If a prerelease version of Haml is being used,
27
+ # the `:string` and `:number` fields will reflect the full version
28
+ # (e.g. `"2.2.beta.1"`), and the `:tiny` field will be `-1`.
29
+ # A `:prerelease` key will contain the name of the prerelease (e.g. `"beta"`),
30
+ # and a `:prerelease_number` key will contain the rerelease number.
31
+ # For example:
32
+ #
33
+ # {
34
+ # :string => "3.0.beta.1",
35
+ # :number => "3.0.beta.1",
36
+ # :major => 3, :minor => 0, :tiny => -1,
37
+ # :prerelease => "beta",
38
+ # :prerelease_number => 1
39
+ # }
40
+ #
26
41
  # @return [{Symbol => String/Fixnum}] The version hash
27
42
  def version
28
43
  return @@version if defined?(@@version)
29
44
 
30
- numbers = File.read(scope('VERSION')).strip.split('.').map { |n| n.to_i }
45
+ numbers = File.read(scope('VERSION')).strip.split('.').
46
+ map {|n| n =~ /^[0-9]+$/ ? n.to_i : n}
31
47
  name = File.read(scope('VERSION_NAME')).strip
32
48
  @@version = {
33
49
  :major => numbers[0],
@@ -35,7 +51,14 @@ module Haml
35
51
  :teeny => numbers[2],
36
52
  :name => name
37
53
  }
38
- @@version[:number] = [:major, :minor, :teeny].map { |comp| @@version[comp] }.compact.join('.')
54
+
55
+ if numbers[3].is_a?(String)
56
+ @@version[:teeny] = -1
57
+ @@version[:prerelease] = numbers[3]
58
+ @@version[:prerelease_number] = numbers[4]
59
+ end
60
+
61
+ @@version[:number] = numbers.join('.')
39
62
  @@version[:string] = @@version[:number].dup
40
63
 
41
64
  if rev = revision_number
@@ -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.
@@ -20,5 +20,9 @@ module Sass
20
20
  end
21
21
 
22
22
  require 'haml/util'
23
+
24
+ dir = Haml::Util.scope("vendor/fssm/lib")
25
+ $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
26
+
23
27
  require 'sass/engine'
24
28
  require 'sass/plugin' if defined?(Merb::Plugins)
@@ -0,0 +1,50 @@
1
+ # A lightweight infrastructure for defining and running callbacks.
2
+ # Callbacks are defined using \{#define\_callback\} at the class level,
3
+ # and called using `run_#{name}` at the instance level.
4
+ #
5
+ # Clients can add callbacks by calling the generated `on_#{name}` method,
6
+ # and passing in a block that's run when the callback is activated.
7
+ #
8
+ # @example Define a callback
9
+ # class Munger
10
+ # extend Sass::Callbacks
11
+ # define_callback :string_munged
12
+ #
13
+ # def munge(str)
14
+ # res = str.gsub(/[a-z]/, '\1\1')
15
+ # run_string_munged str, res
16
+ # res
17
+ # end
18
+ # end
19
+ #
20
+ # @example Use a callback
21
+ # m = Munger.new
22
+ # m.on_string_munged {|str, res| puts "#{str} was munged into #{res}!"}
23
+ # m.munge "bar" #=> bar was munged into bbaarr!
24
+ module Sass::Callbacks
25
+ protected
26
+
27
+ # Define a callback with the given name.
28
+ # This will define an `on_#{name}` method
29
+ # that registers a block,
30
+ # and a `run_#{name}` method that runs that block
31
+ # (optionall with some arguments).
32
+ #
33
+ # @param name [Symbol] The name of the callback
34
+ # @return [void]
35
+ def define_callback(name)
36
+ class_eval <<RUBY
37
+ def on_#{name}(&block)
38
+ @_sass_callbacks ||= {}
39
+ (@_sass_callbacks[#{name.inspect}] ||= []) << block
40
+ end
41
+
42
+ def run_#{name}(*args)
43
+ return unless @_sass_callbacks
44
+ return unless @_sass_callbacks[#{name.inspect}]
45
+ @_sass_callbacks[#{name.inspect}].each {|c| c[*args]}
46
+ end
47
+ private :run_#{name}
48
+ RUBY
49
+ end
50
+ end
@@ -1,69 +1,25 @@
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`).
21
+ # This is only meaningful when generating Sass code,
22
+ # rather than SCSS.
67
23
  def initialize(template, options = {})
68
24
  if template.is_a? IO
69
25
  template = template.read
@@ -72,21 +28,23 @@ module Sass
72
28
  @options = options.dup
73
29
  # Backwards compatibility
74
30
  @options[:old] = true if @options[:alternate] == false
75
- @template = StringScanner.new(template)
31
+ @template = template
76
32
  end
77
33
 
78
- # Converts the CSS template into Sass code.
34
+ # Converts the CSS template into Sass or SCSS code.
79
35
  #
80
- # @return [String] The resulting Sass code
81
- def render
82
- begin
83
- build_tree.to_sass(0, @options).strip + "\n"
84
- rescue Exception => err
85
- line = @template.string[0...@template.pos].split("\n").size
86
-
87
- err.backtrace.unshift "(css):#{line}"
88
- raise err
36
+ # @param fmt [Symbol] `:sass` or `:scss`, designating the format to return.
37
+ # @return [String] The resulting Sass or SCSS code
38
+ # @raise [Sass::SyntaxError] if there's an error parsing the CSS template
39
+ def render(fmt = :sass)
40
+ Haml::Util.check_encoding(@template) do |msg, line|
41
+ raise Sass::SyntaxError.new(msg, :line => line)
89
42
  end
43
+
44
+ build_tree.send("to_#{fmt}", @options).strip + "\n"
45
+ rescue Sass::SyntaxError => err
46
+ err.modify_backtrace(:filename => @options[:filename] || '(css)')
47
+ raise err
90
48
  end
91
49
 
92
50
  private
@@ -95,9 +53,7 @@ module Sass
95
53
  #
96
54
  # @return [Tree::Node] The root node of the parsed tree
97
55
  def build_tree
98
- root = Tree::Node.new
99
- whitespace
100
- rules root
56
+ root = Sass::SCSS::CssParser.new(@template).parse
101
57
  expand_commas root
102
58
  parent_ref_rules root
103
59
  remove_parent_refs root
@@ -106,117 +62,6 @@ module Sass
106
62
  root
107
63
  end
108
64
 
109
- # Parses a set of CSS rules.
110
- #
111
- # @param root [Tree::Node] The parent node of the rules
112
- def rules(root)
113
- while r = rule
114
- root << r
115
- whitespace
116
- end
117
- end
118
-
119
- # Parses a single CSS rule.
120
- #
121
- # @return [Tree::Node] The parsed rule
122
- def rule
123
- rule = ""
124
- loop do
125
- token = @template.scan(/(?:[^\{\};\/\s]|\/[^*])+/)
126
- if token.nil?
127
- return if rule.empty?
128
- break
129
- end
130
- rule << token
131
- break unless @template.match?(/\s|\/\*/)
132
- whitespace
133
- rule << " "
134
- end
135
-
136
- rule.strip!
137
- directive = rule[0] == ?@
138
-
139
- if directive
140
- node = Tree::DirectiveNode.new(rule)
141
- return node if @template.scan(/;/)
142
-
143
- assert_match /\{/
144
- whitespace
145
-
146
- rules(node)
147
- return node
148
- end
149
-
150
- assert_match /\{/
151
- node = Tree::RuleNode.new(rule)
152
- properties(node)
153
- return node
154
- end
155
-
156
- # Parses a set of CSS properties within a rule.
157
- #
158
- # @param rule [Tree::RuleNode] The parent node of the properties
159
- def properties(rule)
160
- while @template.scan(/[^:\}\s]+/)
161
- name = @template[0]
162
- whitespace
163
-
164
- assert_match /:/
165
-
166
- value = ''
167
- while @template.scan(/[^;\s\}]+/)
168
- value << @template[0] << whitespace
169
- end
170
-
171
- assert_match /(;|(?=\}))/
172
- rule << Tree::PropNode.new(name, value, nil)
173
- end
174
-
175
- assert_match /\}/
176
- end
177
-
178
- # Moves the scanner over a section of whitespace or comments.
179
- #
180
- # @return [String] The ignored whitespace
181
- def whitespace
182
- space = @template.scan(/\s*/) || ''
183
-
184
- # If we've hit a comment,
185
- # go past it and look for more whitespace
186
- if @template.scan(/\/\*/)
187
- @template.scan_until(/\*\//)
188
- return space + whitespace
189
- end
190
- return space
191
- end
192
-
193
- # Moves the scanner over a regular expression,
194
- # raising an exception if it doesn't match.
195
- #
196
- # @param re [Regexp] The regular expression to assert
197
- def assert_match(re)
198
- if @template.scan(re)
199
- whitespace
200
- return
201
- end
202
-
203
- line = @template.string[0..@template.pos].count "\n"
204
- pos = @template.pos
205
-
206
- after = @template.string[pos - 15...pos]
207
- after = "..." + after if pos >= 15
208
-
209
- # Display basic regexps as plain old strings
210
- expected = re.source == Regexp.escape(re.source) ? "\"#{re.source}\"" : re.inspect
211
-
212
- was = @template.rest[0...15]
213
- was += "..." if @template.rest.size >= 15
214
- raise Exception.new(<<MESSAGE)
215
- Invalid CSS on line #{line + 1} after #{after.inspect}:
216
- expected #{expected}, was #{was.inspect}
217
- MESSAGE
218
- end
219
-
220
65
  # Transform
221
66
  #
222
67
  # foo, bar, baz
@@ -234,9 +79,9 @@ MESSAGE
234
79
  # @param root [Tree::Node] The parent node
235
80
  def expand_commas(root)
236
81
  root.children.map! do |child|
237
- next child unless Tree::RuleNode === child && child.rules.first.include?(',')
238
- child.rules.first.split(',').map do |rule|
239
- 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])
240
85
  node.children = child.children
241
86
  node
242
87
  end
@@ -280,22 +125,26 @@ MESSAGE
280
125
  # @param root [Tree::Node] The parent node
281
126
  def parent_ref_rules(root)
282
127
  current_rule = nil
283
- root.children.select { |c| Tree::RuleNode === c }.each do |child|
284
- root.children.delete child
285
- first, rest = child.rules.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
128
+ root.children.map! do |child|
129
+ next child unless child.is_a?(Tree::RuleNode)
286
130
 
287
- if current_rule.nil? || current_rule.rules.first != first
288
- current_rule = Tree::RuleNode.new(first)
289
- root << current_rule
131
+ first, rest = child.rule.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
132
+
133
+ if current_rule.nil? || current_rule.rule.first != first
134
+ current_rule = Tree::RuleNode.new([first])
290
135
  end
291
136
 
292
137
  if rest
293
- child.rules = ["&" + rest]
138
+ child.rule = ["&" + rest]
294
139
  current_rule << child
295
140
  else
296
141
  current_rule.children += child.children
297
142
  end
143
+
144
+ current_rule
298
145
  end
146
+ root.children.compact!
147
+ root.children.uniq!
299
148
 
300
149
  root.children.each { |v| parent_ref_rules(v) }
301
150
  end
@@ -316,7 +165,7 @@ MESSAGE
316
165
  def remove_parent_refs(root)
317
166
  root.children.each do |child|
318
167
  if child.is_a?(Tree::RuleNode)
319
- child.rules.first.gsub! /^& +/, ''
168
+ child.rule.first.gsub! /^& +/, ''
320
169
  remove_parent_refs child
321
170
  end
322
171
  end
@@ -357,10 +206,10 @@ MESSAGE
357
206
  while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
358
207
  child = rule.children.first
359
208
 
360
- if child.rules.first[0] == ?&
361
- 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)]
362
211
  else
363
- rule.rules = ["#{rule.rules.first} #{child.rules.first}"]
212
+ rule.rule = ["#{rule.rule.first} #{child.rule.first}"]
364
213
  end
365
214
 
366
215
  rule.children = child.children
@@ -390,7 +239,7 @@ MESSAGE
390
239
  next child unless child.is_a?(Tree::RuleNode)
391
240
 
392
241
  if prev_rule && prev_rule.children == child.children
393
- prev_rule.rules.first << ", #{child.rules.first}"
242
+ prev_rule.rule.first << ", #{child.rule.first}"
394
243
  next nil
395
244
  end
396
245
 
@@ -1,6 +1,7 @@
1
1
  require 'strscan'
2
2
  require 'digest/sha1'
3
3
  require 'sass/tree/node'
4
+ require 'sass/tree/root_node'
4
5
  require 'sass/tree/rule_node'
5
6
  require 'sass/tree/comment_node'
6
7
  require 'sass/tree/prop_node'
@@ -15,6 +16,7 @@ require 'sass/tree/debug_node'
15
16
  require 'sass/tree/import_node'
16
17
  require 'sass/environment'
17
18
  require 'sass/script'
19
+ require 'sass/scss'
18
20
  require 'sass/error'
19
21
  require 'sass/files'
20
22
  require 'haml/shared'
@@ -122,7 +124,7 @@ module Sass
122
124
  # The regex that matches and extracts data from
123
125
  # properties of the form `name: prop`.
124
126
  # @private
125
- PROPERTY_NEW = /^([^\s=:"]+)(\s*=|:)(?:\s+|$)(.*)/
127
+ PROPERTY_NEW = /^([^\s=:"]+)\s*(=|:)(?:\s+|$)(.*)/
126
128
 
127
129
  # The regex that matches and extracts data from
128
130
  # properties of the form `:name prop`.
@@ -135,6 +137,7 @@ module Sass
135
137
  :load_paths => ['.'],
136
138
  :cache => true,
137
139
  :cache_location => './.sass-cache',
140
+ :syntax => :sass,
138
141
  }.freeze
139
142
 
140
143
  # @param template [String] The Sass template.
@@ -171,11 +174,21 @@ module Sass
171
174
  # @return [Sass::Tree::Node] The root of the parse tree.
172
175
  # @raise [Sass::SyntaxError] if there's an error in the document
173
176
  def to_tree
174
- root = Tree::Node.new
175
- append_children(root, tree(tabulate(@template)).first, true)
177
+ @template = check_encoding(@template) {|msg, line| raise Sass::SyntaxError.new(msg, :line => line)}
178
+
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
+
176
186
  root.options = @options
177
187
  root
178
- rescue SyntaxError => e; e.add_metadata(@options[:filename], @line)
188
+ rescue SyntaxError => e
189
+ e.modify_backtrace(:filename => @options[:filename], :line => @line)
190
+ e.sass_template = @template
191
+ raise e
179
192
  end
180
193
 
181
194
  private
@@ -202,10 +215,11 @@ module Sass
202
215
 
203
216
  tab_str ||= line_tab_str
204
217
 
205
- raise SyntaxError.new("Indenting at the beginning of the document is illegal.", index) if first
206
- if tab_str.include?(?\s) && tab_str.include?(?\t)
207
- raise SyntaxError.new("Indentation can't use both tabs and spaces.", index)
208
- end
218
+ raise SyntaxError.new("Indenting at the beginning of the document is illegal.",
219
+ :line => index) if first
220
+
221
+ raise SyntaxError.new("Indentation can't use both tabs and spaces.",
222
+ :line => index) if tab_str.include?(?\s) && tab_str.include?(?\t)
209
223
  end
210
224
  first &&= !tab_str.nil?
211
225
  if tab_str.nil?
@@ -221,10 +235,14 @@ module Sass
221
235
  end
222
236
 
223
237
  line_tabs = line_tab_str.scan(tab_str).size
224
- raise SyntaxError.new(<<END.strip.gsub("\n", ' '), index) if tab_str * line_tabs != line_tab_str
238
+ if tab_str * line_tabs != line_tab_str
239
+ message = <<END.strip.gsub("\n", ' ')
225
240
  Inconsistent indentation: #{Haml::Shared.human_indentation line_tab_str, true} used for indentation,
226
241
  but the rest of the document was indented using #{Haml::Shared.human_indentation tab_str}.
227
242
  END
243
+ raise SyntaxError.new(message, :line => index)
244
+ end
245
+
228
246
  lines << Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
229
247
  end
230
248
  lines
@@ -234,7 +252,7 @@ END
234
252
  return unless last && last.comment?
235
253
  return unless line =~ /^#{tab_str}/
236
254
  unless line =~ /^(?:#{comment_tab_str})(.*)$/
237
- raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), index)
255
+ raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), :line => index)
238
256
  Inconsistent indentation:
239
257
  previous line was indented by #{Haml::Shared.human_indentation comment_tab_str},
240
258
  but this line was indented by #{Haml::Shared.human_indentation line[/^\s*/]}.
@@ -252,9 +270,8 @@ MSG
252
270
  nodes = []
253
271
  while (line = arr[i]) && line.tabs >= base
254
272
  if line.tabs > base
255
- if line.tabs > base + 1
256
- raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.", line.index)
257
- end
273
+ raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.",
274
+ :line => line.index) if line.tabs > base + 1
258
275
 
259
276
  nodes.last.children, i = tree(arr, i)
260
277
  else
@@ -276,11 +293,7 @@ MSG
276
293
  node.line = line.index
277
294
  node.filename = line.filename
278
295
 
279
- if node.is_a?(Tree::CommentNode)
280
- node.lines = line.children
281
- else
282
- append_children(node, line.children, false)
283
- end
296
+ append_children(node, line.children, false)
284
297
  end
285
298
 
286
299
  node_or_nodes
@@ -288,11 +301,13 @@ MSG
288
301
 
289
302
  def append_children(parent, children, root)
290
303
  continued_rule = nil
304
+ continued_comment = nil
291
305
  children.each do |line|
292
306
  child = build_tree(parent, line, root)
293
307
 
294
308
  if child.is_a?(Tree::RuleNode) && child.continued?
295
- raise SyntaxError.new("Rules can't end in commas.", child.line) unless child.children.empty?
309
+ raise SyntaxError.new("Rules can't end in commas.",
310
+ :line => child.line) unless child.children.empty?
296
311
  if continued_rule
297
312
  continued_rule.add_rules child
298
313
  else
@@ -302,31 +317,35 @@ MSG
302
317
  end
303
318
 
304
319
  if continued_rule
305
- raise SyntaxError.new("Rules can't end in commas.", continued_rule.line) unless child.is_a?(Tree::RuleNode)
320
+ raise SyntaxError.new("Rules can't end in commas.",
321
+ :line => continued_rule.line) unless child.is_a?(Tree::RuleNode)
306
322
  continued_rule.add_rules child
307
323
  continued_rule.children = child.children
308
324
  continued_rule, child = nil, continued_rule
309
325
  end
310
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
+
311
338
  check_for_no_children(child)
312
339
  validate_and_append_child(parent, child, line, root)
313
340
  end
314
341
 
315
- raise SyntaxError.new("Rules can't end in commas.", continued_rule.line) if continued_rule
342
+ raise SyntaxError.new("Rules can't end in commas.",
343
+ :line => continued_rule.line) if continued_rule
316
344
 
317
345
  parent
318
346
  end
319
347
 
320
348
  def validate_and_append_child(parent, child, line, root)
321
- unless root
322
- case child
323
- when Tree::MixinDefNode
324
- raise SyntaxError.new("Mixins may only be defined at the root of a document.", line.index)
325
- when Tree::ImportNode
326
- raise SyntaxError.new("Import directives may only be used at the root of a document.", line.index)
327
- end
328
- end
329
-
330
349
  case child
331
350
  when Array
332
351
  child.each {|c| validate_and_append_child(parent, c, line, root)}
@@ -337,18 +356,10 @@ MSG
337
356
 
338
357
  def check_for_no_children(node)
339
358
  return unless node.is_a?(Tree::RuleNode) && node.children.empty?
340
- warning = (node.rules.size == 1) ? <<SHORT : <<LONG
359
+ Haml::Util.haml_warn(<<WARNING.strip)
341
360
  WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
342
- Selector #{node.rules.first.inspect} doesn't have any properties and will not be rendered.
343
- SHORT
344
-
345
- WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
346
- Selector
347
- #{node.rules.join("\n ")}
348
- doesn't have any properties and will not be rendered.
349
- LONG
350
-
351
- warn(warning.strip)
361
+ This selector doesn't have any properties and will not be rendered.
362
+ WARNING
352
363
  end
353
364
 
354
365
  def parse_line(parent, line, root)
@@ -361,23 +372,23 @@ LONG
361
372
  # which begin with ::,
362
373
  # as well as pseudo-classes
363
374
  # if we're using the new property syntax
364
- Tree::RuleNode.new(line.text)
375
+ Tree::RuleNode.new(parse_interp(line.text))
365
376
  else
366
377
  parse_property(line, PROPERTY_OLD)
367
378
  end
368
- when Script::VARIABLE_CHAR
379
+ when ?!, ?$
369
380
  parse_variable(line)
370
381
  when COMMENT_CHAR
371
382
  parse_comment(line.text)
372
383
  when DIRECTIVE_CHAR
373
384
  parse_directive(parent, line, root)
374
385
  when ESCAPE_CHAR
375
- Tree::RuleNode.new(line.text[1..-1])
386
+ Tree::RuleNode.new(parse_interp(line.text[1..-1]))
376
387
  when MIXIN_DEFINITION_CHAR
377
388
  parse_mixin_definition(line)
378
389
  when MIXIN_INCLUDE_CHAR
379
390
  if line.text[1].nil? || line.text[1] == ?\s
380
- Tree::RuleNode.new(line.text)
391
+ Tree::RuleNode.new(parse_interp(line.text))
381
392
  else
382
393
  parse_mixin_include(line, root)
383
394
  end
@@ -385,7 +396,7 @@ LONG
385
396
  if line.text =~ PROPERTY_NEW_MATCHER
386
397
  parse_property(line, PROPERTY_NEW)
387
398
  else
388
- Tree::RuleNode.new(line.text)
399
+ Tree::RuleNode.new(parse_interp(line.text))
389
400
  end
390
401
  end
391
402
  end
@@ -393,30 +404,54 @@ LONG
393
404
  def parse_property(line, property_regx)
394
405
  name, eq, value = line.text.scan(property_regx)[0]
395
406
 
396
- if name.nil? || value.nil?
397
- raise SyntaxError.new("Invalid property: \"#{line.text}\".", @line)
398
- end
399
- expr = if (eq.strip[0] == SCRIPT_CHAR)
400
- parse_script(value, :offset => line.offset + line.text.index(value))
407
+ raise SyntaxError.new("Invalid property: \"#{line.text}\".",
408
+ :line => @line) if name.nil? || value.nil?
409
+
410
+ if value.strip.empty?
411
+ expr = Sass::Script::String.new("")
401
412
  else
402
- 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
403
421
  end
404
- 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)
405
425
  end
406
426
 
407
427
  def parse_variable(line)
408
- name, op, value = line.text.scan(Script::MATCH)[0]
409
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.", @line + 1) unless line.children.empty?
410
- raise SyntaxError.new("Invalid variable: \"#{line.text}\".", @line) unless name && value
428
+ name, op, value, default = line.text.scan(Script::MATCH)[0]
429
+ guarded = op =~ /^\|\|/
430
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
431
+ :line => @line + 1) unless line.children.empty?
432
+ raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
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
411
443
 
412
- Tree::VariableNode.new(name, parse_script(value, :offset => line.offset + line.text.index(value)), op == '||=')
444
+ Tree::VariableNode.new(name, expr, default || guarded)
413
445
  end
414
446
 
415
447
  def parse_comment(line)
416
448
  if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
417
- 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)
418
453
  else
419
- Tree::RuleNode.new(line)
454
+ Tree::RuleNode.new(parse_interp(line))
420
455
  end
421
456
  end
422
457
 
@@ -427,8 +462,13 @@ LONG
427
462
  # If value begins with url( or ",
428
463
  # it's a CSS @import rule and we don't want to touch it.
429
464
  if directive == "import" && value !~ /^(url\(|")/
430
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.", @line + 1) unless line.children.empty?
465
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
466
+ :line => @line + 1) unless line.children.empty?
431
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)
432
472
  elsif directive == "for"
433
473
  parse_for(line, root, value)
434
474
  elsif directive == "else"
@@ -441,7 +481,8 @@ LONG
441
481
  Tree::IfNode.new(parse_script(value, :offset => offset))
442
482
  elsif directive == "debug"
443
483
  raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
444
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.", @line + 1) unless line.children.empty?
484
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
485
+ :line => @line + 1) unless line.children.empty?
445
486
  offset = line.offset + line.text.index(value).to_i
446
487
  Tree::DebugNode.new(parse_script(value, :offset => offset))
447
488
  else
@@ -460,22 +501,26 @@ LONG
460
501
  else
461
502
  expected = "'to <expr>' or 'through <expr>'"
462
503
  end
463
- raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.", @line)
504
+ raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
505
+ end
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])
464
510
  end
465
- raise SyntaxError.new("Invalid variable \"#{var}\".", @line) unless var =~ Script::VALIDATE
466
511
 
467
512
  parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
468
513
  parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
469
- 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')
470
515
  end
471
516
 
472
517
  def parse_else(parent, line, text)
473
- previous = parent.last
518
+ previous = parent.children.last
474
519
  raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
475
520
 
476
521
  if text
477
522
  if text !~ /^if\s+(.+)/
478
- raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.", @line)
523
+ raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
479
524
  end
480
525
  expr = parse_script($1, :offset => line.offset + line.text.index($1))
481
526
  end
@@ -486,30 +531,81 @@ LONG
486
531
  nil
487
532
  end
488
533
 
534
+ # @private
535
+ MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
489
536
  def parse_mixin_definition(line)
490
- name, arg_string = line.text.scan(/^=\s*([^(]+)(.*)$/).first
491
- raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".", @line) if name.nil?
537
+ name, arg_string = line.text.scan(MIXIN_DEF_RE).first
538
+ raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
492
539
 
493
540
  offset = line.offset + line.text.size - arg_string.size
494
- args = Script::Parser.new(arg_string.strip, @line, offset).parse_mixin_definition_arglist
541
+ args = Script::Parser.new(arg_string.strip, @line, offset, @options).
542
+ parse_mixin_definition_arglist
495
543
  default_arg_found = false
496
544
  Tree::MixinDefNode.new(name, args)
497
545
  end
498
546
 
547
+ # @private
548
+ MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
499
549
  def parse_mixin_include(line, root)
500
- name, arg_string = line.text.scan(/^\+\s*([^(]+)(.*)$/).first
501
- raise SyntaxError.new("Invalid mixin include \"#{line.text}\".", @line) if name.nil?
550
+ name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
551
+ raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
502
552
 
503
553
  offset = line.offset + line.text.size - arg_string.size
504
- args = Script::Parser.new(arg_string.strip, @line, offset).parse_mixin_include_arglist
505
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.", @line + 1) unless line.children.empty?
554
+ args = Script::Parser.new(arg_string.strip, @line, offset, @options).
555
+ parse_mixin_include_arglist
556
+ raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.",
557
+ :line => @line + 1) unless line.children.empty?
506
558
  Tree::MixinNode.new(name, args)
507
559
  end
508
560
 
509
561
  def parse_script(script, options = {})
510
562
  line = options[:line] || @line
511
563
  offset = options[:offset] || 0
512
- Script.parse(script, line, offset, @options[:filename])
564
+ Script.parse(script, line, offset, @options)
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
513
609
  end
514
610
  end
515
611
  end