haml 3.0.25 → 3.1.0.alpha.2

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 (212) hide show
  1. data/.yardopts +1 -1
  2. data/CONTRIBUTING +0 -1
  3. data/EDGE_GEM_VERSION +1 -0
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +10 -175
  6. data/Rakefile +56 -84
  7. data/VERSION +1 -1
  8. data/VERSION_NAME +1 -1
  9. data/init.rb +1 -1
  10. data/lib/haml.rb +14 -12
  11. data/lib/haml/engine.rb +1 -1
  12. data/lib/haml/exec.rb +19 -316
  13. data/lib/haml/helpers/action_view_extensions.rb +1 -1
  14. data/lib/haml/html.rb +69 -76
  15. data/lib/haml/precompiler.rb +34 -41
  16. data/lib/haml/railtie.rb +4 -6
  17. data/lib/haml/template/plugin.rb +6 -16
  18. data/lib/haml/util.rb +91 -107
  19. data/lib/haml/version.rb +7 -0
  20. data/test/benchmark.rb +2 -9
  21. data/test/haml/engine_test.rb +195 -92
  22. data/test/haml/html2haml/erb_tests.rb +0 -14
  23. data/test/haml/util_test.rb +32 -0
  24. data/test/test_helper.rb +0 -39
  25. metadata +96 -324
  26. data/bin/css2sass +0 -13
  27. data/bin/sass +0 -8
  28. data/bin/sass-convert +0 -7
  29. data/extra/haml-mode.el +0 -753
  30. data/extra/sass-mode.el +0 -207
  31. data/lib/haml/util/subset_map.rb +0 -101
  32. data/lib/sass.rb +0 -29
  33. data/lib/sass/callbacks.rb +0 -52
  34. data/lib/sass/css.rb +0 -294
  35. data/lib/sass/engine.rb +0 -720
  36. data/lib/sass/environment.rb +0 -143
  37. data/lib/sass/error.rb +0 -198
  38. data/lib/sass/files.rb +0 -160
  39. data/lib/sass/less.rb +0 -382
  40. data/lib/sass/plugin.rb +0 -279
  41. data/lib/sass/plugin/configuration.rb +0 -221
  42. data/lib/sass/plugin/generic.rb +0 -15
  43. data/lib/sass/plugin/merb.rb +0 -37
  44. data/lib/sass/plugin/rack.rb +0 -47
  45. data/lib/sass/plugin/rails.rb +0 -32
  46. data/lib/sass/plugin/staleness_checker.rb +0 -123
  47. data/lib/sass/repl.rb +0 -58
  48. data/lib/sass/script.rb +0 -63
  49. data/lib/sass/script/bool.rb +0 -18
  50. data/lib/sass/script/color.rb +0 -491
  51. data/lib/sass/script/css_lexer.rb +0 -29
  52. data/lib/sass/script/css_parser.rb +0 -31
  53. data/lib/sass/script/funcall.rb +0 -77
  54. data/lib/sass/script/functions.rb +0 -861
  55. data/lib/sass/script/interpolation.rb +0 -70
  56. data/lib/sass/script/lexer.rb +0 -337
  57. data/lib/sass/script/literal.rb +0 -236
  58. data/lib/sass/script/node.rb +0 -112
  59. data/lib/sass/script/number.rb +0 -423
  60. data/lib/sass/script/operation.rb +0 -95
  61. data/lib/sass/script/parser.rb +0 -401
  62. data/lib/sass/script/string.rb +0 -67
  63. data/lib/sass/script/string_interpolation.rb +0 -93
  64. data/lib/sass/script/unary_operation.rb +0 -57
  65. data/lib/sass/script/variable.rb +0 -48
  66. data/lib/sass/scss.rb +0 -17
  67. data/lib/sass/scss/css_parser.rb +0 -46
  68. data/lib/sass/scss/parser.rb +0 -855
  69. data/lib/sass/scss/rx.rb +0 -126
  70. data/lib/sass/scss/sass_parser.rb +0 -11
  71. data/lib/sass/scss/script_lexer.rb +0 -15
  72. data/lib/sass/scss/script_parser.rb +0 -25
  73. data/lib/sass/scss/static_parser.rb +0 -40
  74. data/lib/sass/selector.rb +0 -361
  75. data/lib/sass/selector/abstract_sequence.rb +0 -62
  76. data/lib/sass/selector/comma_sequence.rb +0 -82
  77. data/lib/sass/selector/sequence.rb +0 -237
  78. data/lib/sass/selector/simple.rb +0 -113
  79. data/lib/sass/selector/simple_sequence.rb +0 -136
  80. data/lib/sass/tree/charset_node.rb +0 -37
  81. data/lib/sass/tree/comment_node.rb +0 -128
  82. data/lib/sass/tree/debug_node.rb +0 -36
  83. data/lib/sass/tree/directive_node.rb +0 -75
  84. data/lib/sass/tree/extend_node.rb +0 -65
  85. data/lib/sass/tree/for_node.rb +0 -55
  86. data/lib/sass/tree/if_node.rb +0 -69
  87. data/lib/sass/tree/import_node.rb +0 -102
  88. data/lib/sass/tree/mixin_def_node.rb +0 -48
  89. data/lib/sass/tree/mixin_node.rb +0 -111
  90. data/lib/sass/tree/node.rb +0 -464
  91. data/lib/sass/tree/prop_node.rb +0 -220
  92. data/lib/sass/tree/root_node.rb +0 -163
  93. data/lib/sass/tree/rule_node.rb +0 -261
  94. data/lib/sass/tree/variable_node.rb +0 -39
  95. data/lib/sass/tree/warn_node.rb +0 -42
  96. data/lib/sass/tree/while_node.rb +0 -36
  97. data/test/haml/util/subset_map_test.rb +0 -91
  98. data/test/sass/callbacks_test.rb +0 -61
  99. data/test/sass/conversion_test.rb +0 -1218
  100. data/test/sass/css2sass_test.rb +0 -364
  101. data/test/sass/data/hsl-rgb.txt +0 -319
  102. data/test/sass/engine_test.rb +0 -2267
  103. data/test/sass/extend_test.rb +0 -1348
  104. data/test/sass/functions_test.rb +0 -556
  105. data/test/sass/less_conversion_test.rb +0 -653
  106. data/test/sass/more_results/more1.css +0 -9
  107. data/test/sass/more_results/more1_with_line_comments.css +0 -26
  108. data/test/sass/more_results/more_import.css +0 -29
  109. data/test/sass/more_templates/_more_partial.sass +0 -2
  110. data/test/sass/more_templates/more1.sass +0 -23
  111. data/test/sass/more_templates/more_import.sass +0 -11
  112. data/test/sass/plugin_test.rb +0 -433
  113. data/test/sass/results/alt.css +0 -4
  114. data/test/sass/results/basic.css +0 -9
  115. data/test/sass/results/compact.css +0 -5
  116. data/test/sass/results/complex.css +0 -86
  117. data/test/sass/results/compressed.css +0 -1
  118. data/test/sass/results/expanded.css +0 -19
  119. data/test/sass/results/import.css +0 -31
  120. data/test/sass/results/import_charset.css +0 -4
  121. data/test/sass/results/import_charset_1_8.css +0 -4
  122. data/test/sass/results/import_charset_ibm866.css +0 -4
  123. data/test/sass/results/line_numbers.css +0 -49
  124. data/test/sass/results/mixins.css +0 -95
  125. data/test/sass/results/multiline.css +0 -24
  126. data/test/sass/results/nested.css +0 -22
  127. data/test/sass/results/options.css +0 -1
  128. data/test/sass/results/parent_ref.css +0 -13
  129. data/test/sass/results/script.css +0 -16
  130. data/test/sass/results/scss_import.css +0 -31
  131. data/test/sass/results/scss_importee.css +0 -2
  132. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +0 -1
  133. data/test/sass/results/subdir/subdir.css +0 -3
  134. data/test/sass/results/units.css +0 -11
  135. data/test/sass/results/warn.css +0 -0
  136. data/test/sass/results/warn_imported.css +0 -0
  137. data/test/sass/script_conversion_test.rb +0 -314
  138. data/test/sass/script_test.rb +0 -470
  139. data/test/sass/scss/css_test.rb +0 -916
  140. data/test/sass/scss/rx_test.rb +0 -156
  141. data/test/sass/scss/scss_test.rb +0 -1122
  142. data/test/sass/scss/test_helper.rb +0 -37
  143. data/test/sass/templates/_imported_charset_ibm866.sass +0 -4
  144. data/test/sass/templates/_imported_charset_utf8.sass +0 -4
  145. data/test/sass/templates/_partial.sass +0 -2
  146. data/test/sass/templates/alt.sass +0 -16
  147. data/test/sass/templates/basic.sass +0 -23
  148. data/test/sass/templates/bork1.sass +0 -2
  149. data/test/sass/templates/bork2.sass +0 -2
  150. data/test/sass/templates/bork3.sass +0 -2
  151. data/test/sass/templates/bork4.sass +0 -2
  152. data/test/sass/templates/compact.sass +0 -17
  153. data/test/sass/templates/complex.sass +0 -305
  154. data/test/sass/templates/compressed.sass +0 -15
  155. data/test/sass/templates/expanded.sass +0 -17
  156. data/test/sass/templates/import.sass +0 -12
  157. data/test/sass/templates/import_charset.sass +0 -7
  158. data/test/sass/templates/import_charset_1_8.sass +0 -4
  159. data/test/sass/templates/import_charset_ibm866.sass +0 -9
  160. data/test/sass/templates/importee.less +0 -2
  161. data/test/sass/templates/importee.sass +0 -19
  162. data/test/sass/templates/line_numbers.sass +0 -13
  163. data/test/sass/templates/mixin_bork.sass +0 -5
  164. data/test/sass/templates/mixins.sass +0 -76
  165. data/test/sass/templates/multiline.sass +0 -20
  166. data/test/sass/templates/nested.sass +0 -25
  167. data/test/sass/templates/nested_bork1.sass +0 -2
  168. data/test/sass/templates/nested_bork2.sass +0 -2
  169. data/test/sass/templates/nested_bork3.sass +0 -2
  170. data/test/sass/templates/nested_bork4.sass +0 -2
  171. data/test/sass/templates/nested_mixin_bork.sass +0 -6
  172. data/test/sass/templates/options.sass +0 -2
  173. data/test/sass/templates/parent_ref.sass +0 -25
  174. data/test/sass/templates/script.sass +0 -101
  175. data/test/sass/templates/scss_import.scss +0 -11
  176. data/test/sass/templates/scss_importee.scss +0 -1
  177. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +0 -2
  178. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +0 -3
  179. data/test/sass/templates/subdir/subdir.sass +0 -6
  180. data/test/sass/templates/units.sass +0 -11
  181. data/test/sass/templates/warn.sass +0 -3
  182. data/test/sass/templates/warn_imported.sass +0 -4
  183. data/vendor/fssm/LICENSE +0 -20
  184. data/vendor/fssm/README.markdown +0 -55
  185. data/vendor/fssm/Rakefile +0 -59
  186. data/vendor/fssm/VERSION.yml +0 -5
  187. data/vendor/fssm/example.rb +0 -9
  188. data/vendor/fssm/fssm.gemspec +0 -77
  189. data/vendor/fssm/lib/fssm.rb +0 -33
  190. data/vendor/fssm/lib/fssm/backends/fsevents.rb +0 -36
  191. data/vendor/fssm/lib/fssm/backends/inotify.rb +0 -26
  192. data/vendor/fssm/lib/fssm/backends/polling.rb +0 -25
  193. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +0 -131
  194. data/vendor/fssm/lib/fssm/monitor.rb +0 -26
  195. data/vendor/fssm/lib/fssm/path.rb +0 -91
  196. data/vendor/fssm/lib/fssm/pathname.rb +0 -502
  197. data/vendor/fssm/lib/fssm/state/directory.rb +0 -57
  198. data/vendor/fssm/lib/fssm/state/file.rb +0 -24
  199. data/vendor/fssm/lib/fssm/support.rb +0 -63
  200. data/vendor/fssm/lib/fssm/tree.rb +0 -176
  201. data/vendor/fssm/profile/prof-cache.rb +0 -40
  202. data/vendor/fssm/profile/prof-fssm-pathname.html +0 -1231
  203. data/vendor/fssm/profile/prof-pathname.rb +0 -68
  204. data/vendor/fssm/profile/prof-plain-pathname.html +0 -988
  205. data/vendor/fssm/profile/prof.html +0 -2379
  206. data/vendor/fssm/spec/path_spec.rb +0 -75
  207. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  208. data/vendor/fssm/spec/root/file.css +0 -0
  209. data/vendor/fssm/spec/root/file.rb +0 -0
  210. data/vendor/fssm/spec/root/file.yml +0 -0
  211. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  212. data/vendor/fssm/spec/spec_helper.rb +0 -14
@@ -1,720 +0,0 @@
1
- require 'strscan'
2
- require 'digest/sha1'
3
- require 'sass/tree/node'
4
- require 'sass/tree/root_node'
5
- require 'sass/tree/rule_node'
6
- require 'sass/tree/comment_node'
7
- require 'sass/tree/prop_node'
8
- require 'sass/tree/directive_node'
9
- require 'sass/tree/variable_node'
10
- require 'sass/tree/mixin_def_node'
11
- require 'sass/tree/mixin_node'
12
- require 'sass/tree/extend_node'
13
- require 'sass/tree/if_node'
14
- require 'sass/tree/while_node'
15
- require 'sass/tree/for_node'
16
- require 'sass/tree/debug_node'
17
- require 'sass/tree/warn_node'
18
- require 'sass/tree/import_node'
19
- require 'sass/tree/charset_node'
20
- require 'sass/selector'
21
- require 'sass/environment'
22
- require 'sass/script'
23
- require 'sass/scss'
24
- require 'sass/error'
25
- require 'sass/files'
26
- require 'haml/shared'
27
-
28
- module Sass
29
- # A Sass mixin.
30
- #
31
- # `name`: `String`
32
- # : The name of the mixin.
33
- #
34
- # `args`: `Array<(String, Script::Node)>`
35
- # : The arguments for the mixin.
36
- # Each element is a tuple containing the name of the argument
37
- # and the parse tree for the default value of the argument.
38
- #
39
- # `environment`: {Sass::Environment}
40
- # : The environment in which the mixin was defined.
41
- # This is captured so that the mixin can have access
42
- # to local variables defined in its scope.
43
- #
44
- # `tree`: {Sass::Tree::Node}
45
- # : The parse tree for the mixin.
46
- Mixin = Struct.new(:name, :args, :environment, :tree)
47
-
48
- # This class handles the parsing and compilation of the Sass template.
49
- # Example usage:
50
- #
51
- # template = File.load('stylesheets/sassy.sass')
52
- # sass_engine = Sass::Engine.new(template)
53
- # output = sass_engine.render
54
- # puts output
55
- class Engine
56
- include Haml::Util
57
-
58
- # A line of Sass code.
59
- #
60
- # `text`: `String`
61
- # : The text in the line, without any whitespace at the beginning or end.
62
- #
63
- # `tabs`: `Fixnum`
64
- # : The level of indentation of the line.
65
- #
66
- # `index`: `Fixnum`
67
- # : The line number in the original document.
68
- #
69
- # `offset`: `Fixnum`
70
- # : The number of bytes in on the line that the text begins.
71
- # This ends up being the number of bytes of leading whitespace.
72
- #
73
- # `filename`: `String`
74
- # : The name of the file in which this line appeared.
75
- #
76
- # `children`: `Array<Line>`
77
- # : The lines nested below this one.
78
- class Line < Struct.new(:text, :tabs, :index, :offset, :filename, :children)
79
- def comment?
80
- text[0] == COMMENT_CHAR && (text[1] == SASS_COMMENT_CHAR || text[1] == CSS_COMMENT_CHAR)
81
- end
82
- end
83
-
84
- # The character that begins a CSS property.
85
- PROPERTY_CHAR = ?:
86
-
87
- # The character that designates that
88
- # a property should be assigned to a SassScript expression.
89
- SCRIPT_CHAR = ?=
90
-
91
- # The character that designates the beginning of a comment,
92
- # either Sass or CSS.
93
- COMMENT_CHAR = ?/
94
-
95
- # The character that follows the general COMMENT_CHAR and designates a Sass comment,
96
- # which is not output as a CSS comment.
97
- SASS_COMMENT_CHAR = ?/
98
-
99
- # The character that follows the general COMMENT_CHAR and designates a CSS comment,
100
- # which is embedded in the CSS document.
101
- CSS_COMMENT_CHAR = ?*
102
-
103
- # The character used to denote a compiler directive.
104
- DIRECTIVE_CHAR = ?@
105
-
106
- # Designates a non-parsed rule.
107
- ESCAPE_CHAR = ?\\
108
-
109
- # Designates block as mixin definition rather than CSS rules to output
110
- MIXIN_DEFINITION_CHAR = ?=
111
-
112
- # Includes named mixin declared using MIXIN_DEFINITION_CHAR
113
- MIXIN_INCLUDE_CHAR = ?+
114
-
115
- # The regex that matches properties of the form `name: prop`.
116
- PROPERTY_NEW_MATCHER = /^[^\s:"\[]+\s*[=:](\s|$)/
117
-
118
- # The regex that matches and extracts data from
119
- # properties of the form `name: prop`.
120
- PROPERTY_NEW = /^([^\s=:"]+)\s*(=|:)(?:\s+|$)(.*)/
121
-
122
- # The regex that matches and extracts data from
123
- # properties of the form `:name prop`.
124
- PROPERTY_OLD = /^:([^\s=:"]+)\s*(=?)(?:\s+|$)(.*)/
125
-
126
- # The default options for Sass::Engine.
127
- # @api public
128
- DEFAULT_OPTIONS = {
129
- :style => :nested,
130
- :load_paths => ['.'],
131
- :cache => true,
132
- :cache_location => './.sass-cache',
133
- :syntax => :sass,
134
- }.freeze
135
-
136
- # @param template [String] The Sass template.
137
- # This template can be encoded using any encoding
138
- # that can be converted to Unicode.
139
- # If the template contains an `@charset` declaration,
140
- # that overrides the Ruby encoding
141
- # (see {file:SASS_REFERENCE.md#encodings the encoding documentation})
142
- # @param options [{Symbol => Object}] An options hash;
143
- # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
144
- def initialize(template, options={})
145
- @options = DEFAULT_OPTIONS.merge(options.reject {|k, v| v.nil?})
146
- @template = template
147
-
148
- # Support both, because the docs said one and the other actually worked
149
- # for quite a long time.
150
- @options[:line_comments] ||= @options[:line_numbers]
151
-
152
- # Backwards compatibility
153
- @options[:property_syntax] ||= @options[:attribute_syntax]
154
- case @options[:property_syntax]
155
- when :alternate; @options[:property_syntax] = :new
156
- when :normal; @options[:property_syntax] = :old
157
- end
158
- end
159
-
160
- # Render the template to CSS.
161
- #
162
- # @return [String] The CSS
163
- # @raise [Sass::SyntaxError] if there's an error in the document
164
- # @raise [Encoding::UndefinedConversionError] if the source encoding
165
- # cannot be converted to UTF-8
166
- # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
167
- def render
168
- return _render unless @options[:quiet]
169
- Haml::Util.silence_haml_warnings {_render}
170
- end
171
- alias_method :to_css, :render
172
-
173
- # Parses the document into its parse tree.
174
- #
175
- # @return [Sass::Tree::Node] The root of the parse tree.
176
- # @raise [Sass::SyntaxError] if there's an error in the document
177
- def to_tree
178
- return _to_tree unless @options[:quiet]
179
- Haml::Util.silence_haml_warnings {_to_tree}
180
- end
181
-
182
- # Returns the original encoding of the document,
183
- # or `nil` under Ruby 1.8.
184
- #
185
- # @return [Encoding, nil]
186
- # @raise [Encoding::UndefinedConversionError] if the source encoding
187
- # cannot be converted to UTF-8
188
- # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
189
- def source_encoding
190
- check_encoding!
191
- @original_encoding
192
- end
193
-
194
- private
195
-
196
- def _render
197
- rendered = _to_tree.render
198
- return rendered if ruby1_8?
199
- begin
200
- # Try to convert the result to the original encoding,
201
- # but if that doesn't work fall back on UTF-8
202
- rendered = rendered.encode(source_encoding)
203
- rescue EncodingError
204
- end
205
- rendered.gsub(Regexp.new('\A@charset "(.*?)"'.encode(source_encoding)),
206
- "@charset \"#{source_encoding.name}\"".encode(source_encoding))
207
- end
208
-
209
- def _to_tree
210
- check_encoding!
211
-
212
- if @options[:syntax] == :scss
213
- root = Sass::SCSS::Parser.new(@template).parse
214
- else
215
- root = Tree::RootNode.new(@template)
216
- append_children(root, tree(tabulate(@template)).first, true)
217
- end
218
-
219
- root.options = @options
220
- root
221
- rescue SyntaxError => e
222
- e.modify_backtrace(:filename => @options[:filename], :line => @line)
223
- e.sass_template = @template
224
- raise e
225
- end
226
-
227
- def check_encoding!
228
- return if @checked_encoding
229
- @checked_encoding = true
230
- @template, @original_encoding = check_sass_encoding(@template) do |msg, line|
231
- raise Sass::SyntaxError.new(msg, :line => line)
232
- end
233
- end
234
-
235
- def tabulate(string)
236
- tab_str = nil
237
- comment_tab_str = nil
238
- first = true
239
- lines = []
240
- string.gsub(/\r|\n|\r\n|\r\n/, "\n").scan(/^[^\n]*?$/).each_with_index do |line, index|
241
- index += (@options[:line] || 1)
242
- if line.strip.empty?
243
- lines.last.text << "\n" if lines.last && lines.last.comment?
244
- next
245
- end
246
-
247
- line_tab_str = line[/^\s*/]
248
- unless line_tab_str.empty?
249
- if tab_str.nil?
250
- comment_tab_str ||= line_tab_str
251
- next if try_comment(line, lines.last, "", comment_tab_str, index)
252
- comment_tab_str = nil
253
- end
254
-
255
- tab_str ||= line_tab_str
256
-
257
- raise SyntaxError.new("Indenting at the beginning of the document is illegal.",
258
- :line => index) if first
259
-
260
- raise SyntaxError.new("Indentation can't use both tabs and spaces.",
261
- :line => index) if tab_str.include?(?\s) && tab_str.include?(?\t)
262
- end
263
- first &&= !tab_str.nil?
264
- if tab_str.nil?
265
- lines << Line.new(line.strip, 0, index, 0, @options[:filename], [])
266
- next
267
- end
268
-
269
- comment_tab_str ||= line_tab_str
270
- if try_comment(line, lines.last, tab_str * lines.last.tabs, comment_tab_str, index)
271
- next
272
- else
273
- comment_tab_str = nil
274
- end
275
-
276
- line_tabs = line_tab_str.scan(tab_str).size
277
- if tab_str * line_tabs != line_tab_str
278
- message = <<END.strip.gsub("\n", ' ')
279
- Inconsistent indentation: #{Haml::Shared.human_indentation line_tab_str, true} used for indentation,
280
- but the rest of the document was indented using #{Haml::Shared.human_indentation tab_str}.
281
- END
282
- raise SyntaxError.new(message, :line => index)
283
- end
284
-
285
- lines << Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
286
- end
287
- lines
288
- end
289
-
290
- def try_comment(line, last, tab_str, comment_tab_str, index)
291
- return unless last && last.comment?
292
- # Nested comment stuff must be at least one whitespace char deeper
293
- # than the normal indentation
294
- return unless line =~ /^#{tab_str}\s/
295
- unless line =~ /^(?:#{comment_tab_str})(.*)$/
296
- raise SyntaxError.new(<<MSG.strip.gsub("\n", " "), :line => index)
297
- Inconsistent indentation:
298
- previous line was indented by #{Haml::Shared.human_indentation comment_tab_str},
299
- but this line was indented by #{Haml::Shared.human_indentation line[/^\s*/]}.
300
- MSG
301
- end
302
-
303
- last.text << "\n" << $1
304
- true
305
- end
306
-
307
- def tree(arr, i = 0)
308
- return [], i if arr[i].nil?
309
-
310
- base = arr[i].tabs
311
- nodes = []
312
- while (line = arr[i]) && line.tabs >= base
313
- if line.tabs > base
314
- raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.",
315
- :line => line.index) if line.tabs > base + 1
316
-
317
- nodes.last.children, i = tree(arr, i)
318
- else
319
- nodes << line
320
- i += 1
321
- end
322
- end
323
- return nodes, i
324
- end
325
-
326
- def build_tree(parent, line, root = false)
327
- @line = line.index
328
- node_or_nodes = parse_line(parent, line, root)
329
-
330
- Array(node_or_nodes).each do |node|
331
- # Node is a symbol if it's non-outputting, like a variable assignment
332
- next unless node.is_a? Tree::Node
333
-
334
- node.line = line.index
335
- node.filename = line.filename
336
-
337
- append_children(node, line.children, false)
338
- end
339
-
340
- node_or_nodes
341
- end
342
-
343
- def append_children(parent, children, root)
344
- continued_rule = nil
345
- continued_comment = nil
346
- children.each do |line|
347
- child = build_tree(parent, line, root)
348
-
349
- if child.is_a?(Tree::RuleNode) && child.continued?
350
- raise SyntaxError.new("Rules can't end in commas.",
351
- :line => child.line) unless child.children.empty?
352
- if continued_rule
353
- continued_rule.add_rules child
354
- else
355
- continued_rule = child
356
- end
357
- next
358
- end
359
-
360
- if continued_rule
361
- raise SyntaxError.new("Rules can't end in commas.",
362
- :line => continued_rule.line) unless child.is_a?(Tree::RuleNode)
363
- continued_rule.add_rules child
364
- continued_rule.children = child.children
365
- continued_rule, child = nil, continued_rule
366
- end
367
-
368
- if child.is_a?(Tree::CommentNode) && child.silent
369
- if continued_comment &&
370
- child.line == continued_comment.line +
371
- continued_comment.value.count("\n") + 1
372
- continued_comment.value << "\n" << child.value
373
- next
374
- end
375
-
376
- continued_comment = child
377
- end
378
-
379
- check_for_no_children(child)
380
- validate_and_append_child(parent, child, line, root)
381
- end
382
-
383
- raise SyntaxError.new("Rules can't end in commas.",
384
- :line => continued_rule.line) if continued_rule
385
-
386
- parent
387
- end
388
-
389
- def validate_and_append_child(parent, child, line, root)
390
- case child
391
- when Array
392
- child.each {|c| validate_and_append_child(parent, c, line, root)}
393
- when Tree::Node
394
- parent << child
395
- end
396
- end
397
-
398
- def check_for_no_children(node)
399
- return unless node.is_a?(Tree::RuleNode) && node.children.empty?
400
- Haml::Util.haml_warn(<<WARNING.strip)
401
- WARNING on line #{node.line}#{" of #{node.filename}" if node.filename}:
402
- This selector doesn't have any properties and will not be rendered.
403
- WARNING
404
- end
405
-
406
- def parse_line(parent, line, root)
407
- case line.text[0]
408
- when PROPERTY_CHAR
409
- if line.text[1] == PROPERTY_CHAR ||
410
- (@options[:property_syntax] == :new &&
411
- line.text =~ PROPERTY_OLD && $3.empty?)
412
- # Support CSS3-style pseudo-elements,
413
- # which begin with ::,
414
- # as well as pseudo-classes
415
- # if we're using the new property syntax
416
- Tree::RuleNode.new(parse_interp(line.text))
417
- else
418
- name, eq, value = line.text.scan(PROPERTY_OLD)[0]
419
- raise SyntaxError.new("Invalid property: \"#{line.text}\".",
420
- :line => @line) if name.nil? || value.nil?
421
- parse_property(name, parse_interp(name), eq, value, :old, line)
422
- end
423
- when ?!, ?$
424
- parse_variable(line)
425
- when COMMENT_CHAR
426
- parse_comment(line.text)
427
- when DIRECTIVE_CHAR
428
- parse_directive(parent, line, root)
429
- when ESCAPE_CHAR
430
- Tree::RuleNode.new(parse_interp(line.text[1..-1]))
431
- when MIXIN_DEFINITION_CHAR
432
- parse_mixin_definition(line)
433
- when MIXIN_INCLUDE_CHAR
434
- if line.text[1].nil? || line.text[1] == ?\s
435
- Tree::RuleNode.new(parse_interp(line.text))
436
- else
437
- parse_mixin_include(line, root)
438
- end
439
- else
440
- parse_property_or_rule(line)
441
- end
442
- end
443
-
444
- def parse_property_or_rule(line)
445
- scanner = StringScanner.new(line.text)
446
- hack_char = scanner.scan(/[:\*\.]|\#(?!\{)/)
447
- parser = Sass::SCSS::SassParser.new(scanner, @line)
448
-
449
- unless res = parser.parse_interp_ident
450
- return Tree::RuleNode.new(parse_interp(line.text))
451
- end
452
- res.unshift(hack_char) if hack_char
453
- if comment = scanner.scan(Sass::SCSS::RX::COMMENT)
454
- res << comment
455
- end
456
-
457
- name = line.text[0...scanner.pos]
458
- if scanner.scan(/\s*([:=])(?:\s|$)/)
459
- parse_property(name, res, scanner[1], scanner.rest, :new, line)
460
- else
461
- res.pop if comment
462
- Tree::RuleNode.new(res + parse_interp(scanner.rest))
463
- end
464
- end
465
-
466
- def parse_property(name, parsed_name, eq, value, prop, line)
467
- if value.strip.empty?
468
- expr = Sass::Script::String.new("")
469
- else
470
- expr = parse_script(value, :offset => line.offset + line.text.index(value))
471
-
472
- if eq.strip[0] == SCRIPT_CHAR
473
- expr.context = :equals
474
- Script.equals_warning("properties", name,
475
- Sass::Tree::PropNode.val_to_sass(expr, @options), false,
476
- @line, line.offset + 1, @options[:filename])
477
- end
478
- end
479
- Tree::PropNode.new(parse_interp(name), expr, prop)
480
- end
481
-
482
- def parse_variable(line)
483
- name, op, value, default = line.text.scan(Script::MATCH)[0]
484
- guarded = op =~ /^\|\|/
485
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.",
486
- :line => @line + 1) unless line.children.empty?
487
- raise SyntaxError.new("Invalid variable: \"#{line.text}\".",
488
- :line => @line) unless name && value
489
- Script.var_warning(name, @line, line.offset + 1, @options[:filename]) if line.text[0] == ?!
490
-
491
- expr = parse_script(value, :offset => line.offset + line.text.index(value))
492
- if op =~ /=$/
493
- expr.context = :equals
494
- type = guarded ? "variable defaults" : "variables"
495
- Script.equals_warning(type, "$#{name}", expr.to_sass,
496
- guarded, @line, line.offset + 1, @options[:filename])
497
- end
498
-
499
- Tree::VariableNode.new(name, expr, default || guarded)
500
- end
501
-
502
- def parse_comment(line)
503
- if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
504
- silent = line[1] == SASS_COMMENT_CHAR
505
- Tree::CommentNode.new(
506
- format_comment_text(line[2..-1], silent),
507
- silent)
508
- else
509
- Tree::RuleNode.new(parse_interp(line))
510
- end
511
- end
512
-
513
- def parse_directive(parent, line, root)
514
- directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
515
- offset = directive.size + whitespace.size + 1 if whitespace
516
-
517
- # If value begins with url( or ",
518
- # it's a CSS @import rule and we don't want to touch it.
519
- if directive == "import"
520
- parse_import(line, value)
521
- elsif directive == "mixin"
522
- parse_mixin_definition(line)
523
- elsif directive == "include"
524
- parse_mixin_include(line, root)
525
- elsif directive == "for"
526
- parse_for(line, root, value)
527
- elsif directive == "else"
528
- parse_else(parent, line, value)
529
- elsif directive == "while"
530
- raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
531
- Tree::WhileNode.new(parse_script(value, :offset => offset))
532
- elsif directive == "if"
533
- raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
534
- Tree::IfNode.new(parse_script(value, :offset => offset))
535
- elsif directive == "debug"
536
- raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
537
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.",
538
- :line => @line + 1) unless line.children.empty?
539
- offset = line.offset + line.text.index(value).to_i
540
- Tree::DebugNode.new(parse_script(value, :offset => offset))
541
- elsif directive == "extend"
542
- raise SyntaxError.new("Invalid extend directive '@extend': expected expression.") unless value
543
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath extend directives.",
544
- :line => @line + 1) unless line.children.empty?
545
- offset = line.offset + line.text.index(value).to_i
546
- Tree::ExtendNode.new(parse_interp(value, offset))
547
- elsif directive == "warn"
548
- raise SyntaxError.new("Invalid warn directive '@warn': expected expression.") unless value
549
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath warn directives.",
550
- :line => @line + 1) unless line.children.empty?
551
- offset = line.offset + line.text.index(value).to_i
552
- Tree::WarnNode.new(parse_script(value, :offset => offset))
553
- elsif directive == "charset"
554
- name = value && value[/\A(["'])(.*)\1\Z/, 2] #"
555
- raise SyntaxError.new("Invalid charset directive '@charset': expected string.") unless name
556
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath charset directives.",
557
- :line => @line + 1) unless line.children.empty?
558
- Tree::CharsetNode.new(name)
559
- else
560
- Tree::DirectiveNode.new(line.text)
561
- end
562
- end
563
-
564
- def parse_for(line, root, text)
565
- var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
566
-
567
- if var.nil? # scan failed, try to figure out why for error message
568
- if text !~ /^[^\s]+/
569
- expected = "variable name"
570
- elsif text !~ /^[^\s]+\s+from\s+.+/
571
- expected = "'from <expr>'"
572
- else
573
- expected = "'to <expr>' or 'through <expr>'"
574
- end
575
- raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.")
576
- end
577
- raise SyntaxError.new("Invalid variable \"#{var}\".") unless var =~ Script::VALIDATE
578
- if var.slice!(0) == ?!
579
- offset = line.offset + line.text.index("!" + var) + 1
580
- Script.var_warning(var, @line, offset, @options[:filename])
581
- end
582
-
583
- parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
584
- parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
585
- Tree::ForNode.new(var, parsed_from, parsed_to, to_name == 'to')
586
- end
587
-
588
- def parse_else(parent, line, text)
589
- previous = parent.children.last
590
- raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
591
-
592
- if text
593
- if text !~ /^if\s+(.+)/
594
- raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.")
595
- end
596
- expr = parse_script($1, :offset => line.offset + line.text.index($1))
597
- end
598
-
599
- node = Tree::IfNode.new(expr)
600
- append_children(node, line.children, false)
601
- previous.add_else node
602
- nil
603
- end
604
-
605
- def parse_import(line, value)
606
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
607
- :line => @line + 1) unless line.children.empty?
608
-
609
- scanner = StringScanner.new(value)
610
- values = []
611
-
612
- loop do
613
- unless node = parse_import_arg(scanner)
614
- raise SyntaxError.new("Invalid @import: expected file to import, was #{scanner.rest.inspect}",
615
- :line => @line)
616
- end
617
- values << node
618
- break unless scanner.scan(/,\s*/)
619
- end
620
-
621
- return values
622
- end
623
-
624
- def parse_import_arg(scanner)
625
- return if scanner.eos?
626
- unless (str = scanner.scan(Sass::SCSS::RX::STRING)) ||
627
- (uri = scanner.scan(Sass::SCSS::RX::URI))
628
- return Tree::ImportNode.new(scanner.scan(/[^,]+/))
629
- end
630
-
631
- val = scanner[1] || scanner[2]
632
- scanner.scan(/\s*/)
633
- if media = scanner.scan(/[^,].*/)
634
- Tree::DirectiveNode.new("@import #{str || uri} #{media}")
635
- elsif uri
636
- Tree::DirectiveNode.new("@import #{uri}")
637
- elsif val =~ /^http:\/\//
638
- Tree::DirectiveNode.new("@import url(#{val})")
639
- else
640
- Tree::ImportNode.new(val)
641
- end
642
- end
643
-
644
- MIXIN_DEF_RE = /^(?:=|@mixin)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
645
- def parse_mixin_definition(line)
646
- name, arg_string = line.text.scan(MIXIN_DEF_RE).first
647
- raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".") if name.nil?
648
-
649
- offset = line.offset + line.text.size - arg_string.size
650
- args = Script::Parser.new(arg_string.strip, @line, offset, @options).
651
- parse_mixin_definition_arglist
652
- default_arg_found = false
653
- Tree::MixinDefNode.new(name, args)
654
- end
655
-
656
- MIXIN_INCLUDE_RE = /^(?:\+|@include)\s*(#{Sass::SCSS::RX::IDENT})(.*)$/
657
- def parse_mixin_include(line, root)
658
- name, arg_string = line.text.scan(MIXIN_INCLUDE_RE).first
659
- raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
660
-
661
- offset = line.offset + line.text.size - arg_string.size
662
- args = Script::Parser.new(arg_string.strip, @line, offset, @options).
663
- parse_mixin_include_arglist
664
- raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.",
665
- :line => @line + 1) unless line.children.empty?
666
- Tree::MixinNode.new(name, args)
667
- end
668
-
669
- def parse_script(script, options = {})
670
- line = options[:line] || @line
671
- offset = options[:offset] || 0
672
- Script.parse(script, line, offset, @options)
673
- end
674
-
675
- def format_comment_text(text, silent)
676
- content = text.split("\n")
677
-
678
- if content.first && content.first.strip.empty?
679
- removed_first = true
680
- content.shift
681
- end
682
-
683
- return silent ? "//" : "/* */" if content.empty?
684
- content.last.gsub!(%r{ ?\*/ *$}, '')
685
- content.map! {|l| l.gsub!(/^\*( ?)/, '\1') || (l.empty? ? "" : " ") + l}
686
- content.first.gsub!(/^ /, '') unless removed_first
687
- if silent
688
- "//" + content.join("\n//")
689
- else
690
- # The #gsub fixes the case of a trailing */
691
- "/*" + content.join("\n *").gsub(/ \*\Z/, '') + " */"
692
- end
693
- end
694
-
695
- def parse_interp(text, offset = 0)
696
- self.class.parse_interp(text, @line, offset, :filename => @filename)
697
- end
698
-
699
- # It's important that this have strings (at least)
700
- # at the beginning, the end, and between each Script::Node.
701
- #
702
- # @private
703
- def self.parse_interp(text, line, offset, options)
704
- res = []
705
- rest = Haml::Shared.handle_interpolation text do |scan|
706
- escapes = scan[2].size
707
- res << scan.matched[0...-2 - escapes]
708
- if escapes % 2 == 1
709
- res << "\\" * (escapes - 1) << '#{'
710
- else
711
- res << "\\" * [0, escapes - 1].max
712
- res << Script::Parser.new(
713
- scan, line, offset + scan.pos - scan.matched_size, options).
714
- parse_interpolated
715
- end
716
- end
717
- res << rest
718
- end
719
- end
720
- end