drnic-haml 2.3.0

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 (190) hide show
  1. data/.yardopts +5 -0
  2. data/CONTRIBUTING +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +347 -0
  5. data/REVISION +1 -0
  6. data/Rakefile +371 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/css2sass +7 -0
  10. data/bin/haml +9 -0
  11. data/bin/html2haml +7 -0
  12. data/bin/sass +8 -0
  13. data/extra/haml-mode.el +663 -0
  14. data/extra/sass-mode.el +205 -0
  15. data/extra/update_watch.rb +13 -0
  16. data/init.rb +8 -0
  17. data/lib/haml.rb +40 -0
  18. data/lib/haml/buffer.rb +307 -0
  19. data/lib/haml/engine.rb +301 -0
  20. data/lib/haml/error.rb +22 -0
  21. data/lib/haml/exec.rb +470 -0
  22. data/lib/haml/filters.rb +341 -0
  23. data/lib/haml/helpers.rb +560 -0
  24. data/lib/haml/helpers/action_view_extensions.rb +40 -0
  25. data/lib/haml/helpers/action_view_mods.rb +176 -0
  26. data/lib/haml/herb.rb +96 -0
  27. data/lib/haml/html.rb +308 -0
  28. data/lib/haml/precompiler.rb +997 -0
  29. data/lib/haml/shared.rb +78 -0
  30. data/lib/haml/template.rb +51 -0
  31. data/lib/haml/template/patch.rb +58 -0
  32. data/lib/haml/template/plugin.rb +71 -0
  33. data/lib/haml/util.rb +244 -0
  34. data/lib/haml/version.rb +64 -0
  35. data/lib/sass.rb +24 -0
  36. data/lib/sass/css.rb +423 -0
  37. data/lib/sass/engine.rb +491 -0
  38. data/lib/sass/environment.rb +79 -0
  39. data/lib/sass/error.rb +162 -0
  40. data/lib/sass/files.rb +133 -0
  41. data/lib/sass/plugin.rb +170 -0
  42. data/lib/sass/plugin/merb.rb +57 -0
  43. data/lib/sass/plugin/rails.rb +23 -0
  44. data/lib/sass/repl.rb +58 -0
  45. data/lib/sass/script.rb +55 -0
  46. data/lib/sass/script/bool.rb +17 -0
  47. data/lib/sass/script/color.rb +183 -0
  48. data/lib/sass/script/funcall.rb +50 -0
  49. data/lib/sass/script/functions.rb +199 -0
  50. data/lib/sass/script/lexer.rb +191 -0
  51. data/lib/sass/script/literal.rb +177 -0
  52. data/lib/sass/script/node.rb +14 -0
  53. data/lib/sass/script/number.rb +381 -0
  54. data/lib/sass/script/operation.rb +45 -0
  55. data/lib/sass/script/parser.rb +222 -0
  56. data/lib/sass/script/string.rb +12 -0
  57. data/lib/sass/script/unary_operation.rb +34 -0
  58. data/lib/sass/script/variable.rb +31 -0
  59. data/lib/sass/tree/comment_node.rb +84 -0
  60. data/lib/sass/tree/debug_node.rb +30 -0
  61. data/lib/sass/tree/directive_node.rb +70 -0
  62. data/lib/sass/tree/for_node.rb +48 -0
  63. data/lib/sass/tree/if_node.rb +54 -0
  64. data/lib/sass/tree/import_node.rb +69 -0
  65. data/lib/sass/tree/mixin_def_node.rb +29 -0
  66. data/lib/sass/tree/mixin_node.rb +48 -0
  67. data/lib/sass/tree/node.rb +252 -0
  68. data/lib/sass/tree/prop_node.rb +106 -0
  69. data/lib/sass/tree/root_node.rb +56 -0
  70. data/lib/sass/tree/rule_node.rb +220 -0
  71. data/lib/sass/tree/variable_node.rb +34 -0
  72. data/lib/sass/tree/while_node.rb +31 -0
  73. data/rails/init.rb +1 -0
  74. data/test/benchmark.rb +99 -0
  75. data/test/haml/engine_test.rb +1129 -0
  76. data/test/haml/helper_test.rb +282 -0
  77. data/test/haml/html2haml_test.rb +258 -0
  78. data/test/haml/markaby/standard.mab +52 -0
  79. data/test/haml/mocks/article.rb +6 -0
  80. data/test/haml/results/content_for_layout.xhtml +12 -0
  81. data/test/haml/results/eval_suppressed.xhtml +9 -0
  82. data/test/haml/results/filters.xhtml +62 -0
  83. data/test/haml/results/helpers.xhtml +93 -0
  84. data/test/haml/results/helpful.xhtml +10 -0
  85. data/test/haml/results/just_stuff.xhtml +68 -0
  86. data/test/haml/results/list.xhtml +12 -0
  87. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  88. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  89. data/test/haml/results/original_engine.xhtml +20 -0
  90. data/test/haml/results/partial_layout.xhtml +5 -0
  91. data/test/haml/results/partials.xhtml +21 -0
  92. data/test/haml/results/render_layout.xhtml +3 -0
  93. data/test/haml/results/silent_script.xhtml +74 -0
  94. data/test/haml/results/standard.xhtml +162 -0
  95. data/test/haml/results/tag_parsing.xhtml +23 -0
  96. data/test/haml/results/very_basic.xhtml +5 -0
  97. data/test/haml/results/whitespace_handling.xhtml +89 -0
  98. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  99. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  100. data/test/haml/rhtml/action_view.rhtml +62 -0
  101. data/test/haml/rhtml/standard.rhtml +54 -0
  102. data/test/haml/spec_test.rb +44 -0
  103. data/test/haml/template_test.rb +217 -0
  104. data/test/haml/templates/_av_partial_1.haml +9 -0
  105. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  106. data/test/haml/templates/_av_partial_2.haml +5 -0
  107. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  108. data/test/haml/templates/_layout.erb +3 -0
  109. data/test/haml/templates/_layout_for_partial.haml +3 -0
  110. data/test/haml/templates/_partial.haml +8 -0
  111. data/test/haml/templates/_text_area.haml +3 -0
  112. data/test/haml/templates/action_view.haml +47 -0
  113. data/test/haml/templates/action_view_ugly.haml +47 -0
  114. data/test/haml/templates/breakage.haml +8 -0
  115. data/test/haml/templates/content_for_layout.haml +8 -0
  116. data/test/haml/templates/eval_suppressed.haml +11 -0
  117. data/test/haml/templates/filters.haml +66 -0
  118. data/test/haml/templates/helpers.haml +95 -0
  119. data/test/haml/templates/helpful.haml +11 -0
  120. data/test/haml/templates/just_stuff.haml +83 -0
  121. data/test/haml/templates/list.haml +12 -0
  122. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  123. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  124. data/test/haml/templates/original_engine.haml +17 -0
  125. data/test/haml/templates/partial_layout.haml +3 -0
  126. data/test/haml/templates/partialize.haml +1 -0
  127. data/test/haml/templates/partials.haml +12 -0
  128. data/test/haml/templates/render_layout.haml +2 -0
  129. data/test/haml/templates/silent_script.haml +40 -0
  130. data/test/haml/templates/standard.haml +42 -0
  131. data/test/haml/templates/standard_ugly.haml +42 -0
  132. data/test/haml/templates/tag_parsing.haml +21 -0
  133. data/test/haml/templates/very_basic.haml +4 -0
  134. data/test/haml/templates/whitespace_handling.haml +87 -0
  135. data/test/haml/util_test.rb +92 -0
  136. data/test/linked_rails.rb +12 -0
  137. data/test/sass/css2sass_test.rb +294 -0
  138. data/test/sass/engine_test.rb +956 -0
  139. data/test/sass/functions_test.rb +126 -0
  140. data/test/sass/more_results/more1.css +9 -0
  141. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  142. data/test/sass/more_results/more_import.css +29 -0
  143. data/test/sass/more_templates/_more_partial.sass +2 -0
  144. data/test/sass/more_templates/more1.sass +23 -0
  145. data/test/sass/more_templates/more_import.sass +11 -0
  146. data/test/sass/plugin_test.rb +229 -0
  147. data/test/sass/results/alt.css +4 -0
  148. data/test/sass/results/basic.css +9 -0
  149. data/test/sass/results/compact.css +5 -0
  150. data/test/sass/results/complex.css +87 -0
  151. data/test/sass/results/compressed.css +1 -0
  152. data/test/sass/results/expanded.css +19 -0
  153. data/test/sass/results/import.css +29 -0
  154. data/test/sass/results/line_numbers.css +49 -0
  155. data/test/sass/results/mixins.css +95 -0
  156. data/test/sass/results/multiline.css +24 -0
  157. data/test/sass/results/nested.css +22 -0
  158. data/test/sass/results/parent_ref.css +13 -0
  159. data/test/sass/results/script.css +16 -0
  160. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  161. data/test/sass/results/subdir/subdir.css +3 -0
  162. data/test/sass/results/units.css +11 -0
  163. data/test/sass/script_test.rb +261 -0
  164. data/test/sass/templates/_partial.sass +2 -0
  165. data/test/sass/templates/alt.sass +16 -0
  166. data/test/sass/templates/basic.sass +23 -0
  167. data/test/sass/templates/bork1.sass +2 -0
  168. data/test/sass/templates/bork2.sass +2 -0
  169. data/test/sass/templates/bork3.sass +2 -0
  170. data/test/sass/templates/compact.sass +17 -0
  171. data/test/sass/templates/complex.sass +307 -0
  172. data/test/sass/templates/compressed.sass +15 -0
  173. data/test/sass/templates/expanded.sass +17 -0
  174. data/test/sass/templates/import.sass +11 -0
  175. data/test/sass/templates/importee.sass +19 -0
  176. data/test/sass/templates/line_numbers.sass +13 -0
  177. data/test/sass/templates/mixins.sass +76 -0
  178. data/test/sass/templates/multiline.sass +20 -0
  179. data/test/sass/templates/nested.sass +25 -0
  180. data/test/sass/templates/nested_bork1.sass +2 -0
  181. data/test/sass/templates/nested_bork2.sass +2 -0
  182. data/test/sass/templates/nested_bork3.sass +2 -0
  183. data/test/sass/templates/parent_ref.sass +25 -0
  184. data/test/sass/templates/script.sass +101 -0
  185. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  186. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  187. data/test/sass/templates/subdir/subdir.sass +6 -0
  188. data/test/sass/templates/units.sass +11 -0
  189. data/test/test_helper.rb +44 -0
  190. metadata +298 -0
@@ -0,0 +1,64 @@
1
+ require 'haml/util'
2
+
3
+ module Haml
4
+ # Handles Haml version-reporting.
5
+ # Haml not only reports the standard three version numbers,
6
+ # but its Git revision hash as well,
7
+ # if it was installed from Git.
8
+ module Version
9
+ include Haml::Util
10
+
11
+ # Returns a hash representing the version of Haml.
12
+ # The `:major`, `:minor`, and `:teeny` keys have their respective numbers as Fixnums.
13
+ # The `:name` key has the name of the version.
14
+ # The `:string` key contains a human-readable string representation of the version.
15
+ # The `:number` key is the major, minor, and teeny keys separated by periods.
16
+ # If Haml is checked out from Git, the `:rev` key will have the revision hash.
17
+ # For example:
18
+ #
19
+ # {
20
+ # :string => "2.1.0.9616393",
21
+ # :rev => "9616393b8924ef36639c7e82aa88a51a24d16949",
22
+ # :number => "2.1.0",
23
+ # :major => 2, :minor => 1, :teeny => 0
24
+ # }
25
+ #
26
+ # @return [Hash<Symbol, String/Fixnum>] The version hash
27
+ def version
28
+ return @@version if defined?(@@version)
29
+
30
+ numbers = File.read(scope('VERSION')).strip.split('.').map { |n| n.to_i }
31
+ name = File.read(scope('VERSION_NAME')).strip
32
+ @@version = {
33
+ :major => numbers[0],
34
+ :minor => numbers[1],
35
+ :teeny => numbers[2],
36
+ :name => name
37
+ }
38
+ @@version[:number] = [:major, :minor, :teeny].map { |comp| @@version[comp] }.compact.join('.')
39
+ @@version[:string] = @@version[:number].dup
40
+
41
+ if File.exists?(scope('REVISION'))
42
+ rev = File.read(scope('REVISION')).strip
43
+ rev = nil if rev !~ /^([a-f0-9]+|\(.*\))$/
44
+ end
45
+
46
+ if (rev.nil? || rev == '(unknown)') && File.exists?(scope('.git/HEAD'))
47
+ rev = File.read(scope('.git/HEAD')).strip
48
+ if rev =~ /^ref: (.*)$/
49
+ rev = File.read(scope(".git/#{$1}")).strip
50
+ end
51
+ end
52
+
53
+ if rev
54
+ @@version[:rev] = rev
55
+ unless rev[0] == ?(
56
+ @@version[:string] << "." << rev[0...7]
57
+ end
58
+ @@version[:string] << " (#{name})"
59
+ end
60
+
61
+ @@version
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,24 @@
1
+ dir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
3
+
4
+ require 'haml/version'
5
+
6
+ # The module that contains everything Sass-related:
7
+ #
8
+ # * {Sass::Engine} is the class used to render Sass within Ruby code.
9
+ # * {Sass::Plugin} is interfaces with web frameworks (Rails and Merb in particular).
10
+ # * {Sass::SyntaxError} is raised when Sass encounters an error.
11
+ # * {Sass::CSS} handles conversion of CSS to Sass.
12
+ #
13
+ # Also see the {file:SASS_REFERENCE.md full Sass reference}.
14
+ module Sass
15
+ extend Haml::Version
16
+
17
+ # A string representing the version of Sass.
18
+ # A more fine-grained representation is available from {Sass.version}.
19
+ VERSION = version[:string] unless defined?(Sass::VERSION)
20
+ end
21
+
22
+ require 'haml/util'
23
+ require 'sass/engine'
24
+ require 'sass/plugin' if defined?(Merb::Plugins)
@@ -0,0 +1,423 @@
1
+ require File.dirname(__FILE__) + '/../sass'
2
+ require 'sass/tree/node'
3
+ require 'strscan'
4
+
5
+ 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 [Hash<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
+ str = "\n#{' ' * tabs}#{rules.first}#{children.any? { |c| c.is_a? PropNode } ? "\n" : ''}"
28
+
29
+ children.each do |child|
30
+ str << "#{child.to_sass(tabs + 1, opts)}"
31
+ end
32
+
33
+ str
34
+ end
35
+ end
36
+
37
+ class PropNode
38
+ # @see Node#to_sass
39
+ def to_sass(tabs, opts = {})
40
+ "#{' ' * tabs}#{opts[:old] ? ':' : ''}#{name}#{opts[:old] ? '' : ':'} #{value}\n"
41
+ end
42
+ end
43
+
44
+ class DirectiveNode
45
+ # @see Node#to_sass
46
+ def to_sass(tabs, opts = {})
47
+ "#{' ' * tabs}#{value}#{children.map {|c| c.to_sass(tabs + 1, opts)}}\n"
48
+ end
49
+ end
50
+ end
51
+
52
+ # This class converts CSS documents into Sass templates.
53
+ # It works by parsing the CSS document into a {Sass::Tree} structure,
54
+ # and then applying various transformations to the structure
55
+ # to produce more concise and idiomatic Sass.
56
+ #
57
+ # Example usage:
58
+ #
59
+ # Sass::CSS.new("p { color: blue }").render #=> "p\n color: blue"
60
+ class CSS
61
+ # @param template [String] The CSS code
62
+ # @option options :old [Boolean] (false)
63
+ # Whether or not to output old property syntax
64
+ # (`:color blue` as opposed to `color: blue`).
65
+ # @option options :filename [String]
66
+ # The filename of the CSS file being processed.
67
+ # Used for error reporting
68
+ def initialize(template, options = {})
69
+ if template.is_a? IO
70
+ template = template.read
71
+ end
72
+
73
+ @line = 1
74
+ @options = options.dup
75
+ # Backwards compatibility
76
+ @options[:old] = true if @options[:alternate] == false
77
+ @template = StringScanner.new(template)
78
+ end
79
+
80
+ # Converts the CSS template into Sass code.
81
+ #
82
+ # @return [String] The resulting Sass code
83
+ # @raise [Sass::SyntaxError] if there's an error parsing the CSS template
84
+ def render
85
+ Haml::Util.check_encoding(@template.string) do |msg, line|
86
+ raise Sass::SyntaxError.new(msg, :line => line)
87
+ end
88
+
89
+ build_tree.to_sass(0, @options).strip + "\n"
90
+ rescue Sass::SyntaxError => err
91
+ err.modify_backtrace(:filename => @options[:filename] || '(css)', :line => @line)
92
+ raise err
93
+ end
94
+
95
+ private
96
+
97
+ # Parses the CSS template and applies various transformations
98
+ #
99
+ # @return [Tree::Node] The root node of the parsed tree
100
+ def build_tree
101
+ root = Tree::Node.new
102
+ whitespace
103
+ rules root
104
+ expand_commas root
105
+ parent_ref_rules root
106
+ remove_parent_refs root
107
+ flatten_rules root
108
+ fold_commas root
109
+ root
110
+ end
111
+
112
+ # Parses a set of CSS rules.
113
+ #
114
+ # @param root [Tree::Node] The parent node of the rules
115
+ def rules(root)
116
+ while r = rule
117
+ root << r
118
+ whitespace
119
+ end
120
+ end
121
+
122
+ # Parses a single CSS rule.
123
+ #
124
+ # @return [Tree::Node] The parsed rule
125
+ def rule
126
+ rule = ""
127
+ loop do
128
+ token = scan(/(?:[^\{\};\/\s]|\/[^*])+/)
129
+ if token.nil?
130
+ return if rule.empty?
131
+ break
132
+ end
133
+ rule << token
134
+ break unless @template.match?(/\s|\/\*/)
135
+ whitespace
136
+ rule << " "
137
+ end
138
+
139
+ rule.strip!
140
+ directive = rule[0] == ?@
141
+
142
+ if directive
143
+ node = Tree::DirectiveNode.new(rule)
144
+ return node if scan(/;/)
145
+
146
+ assert_match /\{/
147
+ whitespace
148
+
149
+ rules(node)
150
+ return node
151
+ end
152
+
153
+ assert_match /\{/
154
+ node = Tree::RuleNode.new(rule)
155
+ properties(node)
156
+ return node
157
+ end
158
+
159
+ # Parses a set of CSS properties within a rule.
160
+ #
161
+ # @param rule [Tree::RuleNode] The parent node of the properties
162
+ def properties(rule)
163
+ while scan(/[^:\}\s]+/)
164
+ name = @template[0]
165
+ whitespace
166
+
167
+ assert_match /:/
168
+
169
+ value = ''
170
+ while scan(/[^;\s\}]+/)
171
+ value << @template[0] << whitespace
172
+ end
173
+
174
+ assert_match /(;|(?=\}))/
175
+ rule << Tree::PropNode.new(name, value, nil)
176
+ end
177
+
178
+ assert_match /\}/
179
+ end
180
+
181
+ # Moves the scanner over a section of whitespace or comments.
182
+ #
183
+ # @return [String] The ignored whitespace
184
+ def whitespace
185
+ space = scan(/\s*/) || ''
186
+
187
+ # If we've hit a comment,
188
+ # go past it and look for more whitespace
189
+ if scan(/\/\*/)
190
+ scan_until(/\*\//)
191
+ return space + whitespace
192
+ end
193
+ return space
194
+ end
195
+
196
+ # Moves the scanner over a regular expression,
197
+ # raising an exception if it doesn't match.
198
+ #
199
+ # @param re [Regexp] The regular expression to assert
200
+ def assert_match(re)
201
+ if scan(re)
202
+ whitespace
203
+ return
204
+ end
205
+
206
+ pos = @template.pos
207
+
208
+ after = @template.string[[pos - 15, 0].max...pos].gsub(/.*\n/m, '')
209
+ after = "..." + after if pos >= 15
210
+
211
+ # Display basic regexps as plain old strings
212
+ string = re.source.gsub(/\\(.)/, '\1')
213
+ expected = re.source == Regexp.escape(string) ? string.inspect : re.inspect
214
+
215
+ was = @template.rest[0...15].gsub(/\n.*/m, '')
216
+ was += "..." if @template.rest.size >= 15
217
+ raise Sass::SyntaxError.new(
218
+ "Invalid CSS after #{after.inspect}: expected #{expected}, was #{was.inspect}")
219
+ end
220
+
221
+ # Identical to `@template.scan`, except that it increments the line count.
222
+ # `@template.scan` should never be called directly;
223
+ # this should be used instead.
224
+ def scan(re)
225
+ str = @template.scan(re)
226
+ @line += str.count "\n" if str
227
+ str
228
+ end
229
+
230
+ # Identical to `@template.scan_until`, except that it increments the line count.
231
+ # `@template.scan_until` should never be called directly;
232
+ # this should be used instead.
233
+ def scan_until(re)
234
+ str = @template.scan_until(re)
235
+ @line += str.count "\n" if str
236
+ str
237
+ end
238
+
239
+ # Transform
240
+ #
241
+ # foo, bar, baz
242
+ # color: blue
243
+ #
244
+ # into
245
+ #
246
+ # foo
247
+ # color: blue
248
+ # bar
249
+ # color: blue
250
+ # baz
251
+ # color: blue
252
+ #
253
+ # @param root [Tree::Node] The parent node
254
+ def expand_commas(root)
255
+ root.children.map! do |child|
256
+ next child unless Tree::RuleNode === child && child.rules.first.include?(',')
257
+ child.rules.first.split(',').map do |rule|
258
+ node = Tree::RuleNode.new(rule.strip)
259
+ node.children = child.children
260
+ node
261
+ end
262
+ end
263
+ root.children.flatten!
264
+ end
265
+
266
+ # Make rules use parent refs so that
267
+ #
268
+ # foo
269
+ # color: green
270
+ # foo.bar
271
+ # color: blue
272
+ #
273
+ # becomes
274
+ #
275
+ # foo
276
+ # color: green
277
+ # &.bar
278
+ # color: blue
279
+ #
280
+ # This has the side effect of nesting rules,
281
+ # so that
282
+ #
283
+ # foo
284
+ # color: green
285
+ # foo bar
286
+ # color: red
287
+ # foo baz
288
+ # color: blue
289
+ #
290
+ # becomes
291
+ #
292
+ # foo
293
+ # color: green
294
+ # & bar
295
+ # color: red
296
+ # & baz
297
+ # color: blue
298
+ #
299
+ # @param root [Tree::Node] The parent node
300
+ def parent_ref_rules(root)
301
+ current_rule = nil
302
+ root.children.select { |c| Tree::RuleNode === c }.each do |child|
303
+ root.children.delete child
304
+ first, rest = child.rules.first.scan(/^(&?(?: .|[^ ])[^.#: \[]*)([.#: \[].*)?$/).first
305
+
306
+ if current_rule.nil? || current_rule.rules.first != first
307
+ current_rule = Tree::RuleNode.new(first)
308
+ root << current_rule
309
+ end
310
+
311
+ if rest
312
+ child.rules = ["&" + rest]
313
+ current_rule << child
314
+ else
315
+ current_rule.children += child.children
316
+ end
317
+ end
318
+
319
+ root.children.each { |v| parent_ref_rules(v) }
320
+ end
321
+
322
+ # Remove useless parent refs so that
323
+ #
324
+ # foo
325
+ # & bar
326
+ # color: blue
327
+ #
328
+ # becomes
329
+ #
330
+ # foo
331
+ # bar
332
+ # color: blue
333
+ #
334
+ # @param root [Tree::Node] The parent node
335
+ def remove_parent_refs(root)
336
+ root.children.each do |child|
337
+ if child.is_a?(Tree::RuleNode)
338
+ child.rules.first.gsub! /^& +/, ''
339
+ remove_parent_refs child
340
+ end
341
+ end
342
+ end
343
+
344
+ # Flatten rules so that
345
+ #
346
+ # foo
347
+ # bar
348
+ # color: red
349
+ #
350
+ # becomes
351
+ #
352
+ # foo bar
353
+ # color: red
354
+ #
355
+ # and
356
+ #
357
+ # foo
358
+ # &.bar
359
+ # color: blue
360
+ #
361
+ # becomes
362
+ #
363
+ # foo.bar
364
+ # color: blue
365
+ #
366
+ # @param root [Tree::Node] The parent node
367
+ def flatten_rules(root)
368
+ root.children.each { |child| flatten_rule(child) if child.is_a?(Tree::RuleNode) }
369
+ end
370
+
371
+ # Flattens a single rule
372
+ #
373
+ # @param rule [Tree::RuleNode] The candidate for flattening
374
+ # @see #flatten_rules
375
+ def flatten_rule(rule)
376
+ while rule.children.size == 1 && rule.children.first.is_a?(Tree::RuleNode)
377
+ child = rule.children.first
378
+
379
+ if child.rules.first[0] == ?&
380
+ rule.rules = [child.rules.first.gsub(/^&/, rule.rules.first)]
381
+ else
382
+ rule.rules = ["#{rule.rules.first} #{child.rules.first}"]
383
+ end
384
+
385
+ rule.children = child.children
386
+ end
387
+
388
+ flatten_rules(rule)
389
+ end
390
+
391
+ # Transform
392
+ #
393
+ # foo
394
+ # bar
395
+ # color: blue
396
+ # baz
397
+ # color: blue
398
+ #
399
+ # into
400
+ #
401
+ # foo
402
+ # bar, baz
403
+ # color: blue
404
+ #
405
+ # @param rule [Tree::RuleNode] The candidate for flattening
406
+ def fold_commas(root)
407
+ prev_rule = nil
408
+ root.children.map! do |child|
409
+ next child unless child.is_a?(Tree::RuleNode)
410
+
411
+ if prev_rule && prev_rule.children == child.children
412
+ prev_rule.rules.first << ", #{child.rules.first}"
413
+ next nil
414
+ end
415
+
416
+ fold_commas(child)
417
+ prev_rule = child
418
+ child
419
+ end
420
+ root.children.compact!
421
+ end
422
+ end
423
+ end