haml 4.0.7 → 5.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +18 -0
- data/.gitmodules +3 -0
- data/.travis.yml +54 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +96 -4
- data/FAQ.md +4 -14
- data/Gemfile +19 -0
- data/MIT-LICENSE +1 -1
- data/README.md +80 -42
- data/REFERENCE.md +116 -64
- data/Rakefile +46 -54
- data/TODO +24 -0
- data/benchmark.rb +66 -0
- data/haml.gemspec +38 -0
- data/lib/haml/.gitattributes +1 -0
- data/lib/haml/attribute_builder.rb +163 -0
- data/lib/haml/attribute_compiler.rb +223 -0
- data/lib/haml/attribute_parser.rb +148 -0
- data/lib/haml/buffer.rb +22 -132
- data/lib/haml/compiler.rb +89 -298
- data/lib/haml/engine.rb +25 -41
- data/lib/haml/error.rb +3 -0
- data/lib/haml/escapable.rb +49 -0
- data/lib/haml/exec.rb +38 -19
- data/lib/haml/filters.rb +18 -24
- data/lib/haml/generator.rb +41 -0
- data/lib/haml/helpers/action_view_extensions.rb +3 -2
- data/lib/haml/helpers/action_view_mods.rb +42 -60
- data/lib/haml/helpers/action_view_xss_mods.rb +1 -0
- data/lib/haml/helpers/safe_erubi_template.rb +19 -0
- data/lib/haml/helpers/safe_erubis_template.rb +4 -1
- data/lib/haml/helpers/xss_mods.rb +18 -12
- data/lib/haml/helpers.rb +132 -89
- data/lib/haml/options.rb +41 -47
- data/lib/haml/parser.rb +278 -216
- data/lib/haml/{template/plugin.rb → plugin.rb} +8 -15
- data/lib/haml/railtie.rb +38 -12
- data/lib/haml/sass_rails_filter.rb +17 -4
- data/lib/haml/template/options.rb +12 -2
- data/lib/haml/template.rb +12 -6
- data/lib/haml/temple_engine.rb +121 -0
- data/lib/haml/temple_line_counter.rb +29 -0
- data/lib/haml/util.rb +80 -199
- data/lib/haml/version.rb +2 -1
- data/lib/haml.rb +1 -0
- data/yard/default/.gitignore +1 -0
- data/yard/default/fulldoc/html/css/common.sass +15 -0
- data/yard/default/layout/html/footer.erb +12 -0
- metadata +50 -111
- data/test/engine_test.rb +0 -2013
- data/test/erb/_av_partial_1.erb +0 -12
- data/test/erb/_av_partial_2.erb +0 -8
- data/test/erb/action_view.erb +0 -62
- data/test/erb/standard.erb +0 -55
- data/test/filters_test.rb +0 -254
- data/test/gemfiles/Gemfile.rails-3.0.x +0 -5
- data/test/gemfiles/Gemfile.rails-3.1.x +0 -6
- data/test/gemfiles/Gemfile.rails-3.2.x +0 -5
- data/test/gemfiles/Gemfile.rails-4.0.x +0 -5
- data/test/haml-spec/LICENSE +0 -14
- data/test/haml-spec/README.md +0 -106
- data/test/haml-spec/lua_haml_spec.lua +0 -38
- data/test/haml-spec/perl_haml_test.pl +0 -81
- data/test/haml-spec/ruby_haml_test.rb +0 -23
- data/test/haml-spec/tests.json +0 -660
- data/test/helper_test.rb +0 -583
- data/test/markaby/standard.mab +0 -52
- data/test/mocks/article.rb +0 -6
- data/test/parser_test.rb +0 -105
- data/test/results/content_for_layout.xhtml +0 -12
- data/test/results/eval_suppressed.xhtml +0 -9
- data/test/results/helpers.xhtml +0 -70
- data/test/results/helpful.xhtml +0 -10
- data/test/results/just_stuff.xhtml +0 -70
- data/test/results/list.xhtml +0 -12
- data/test/results/nuke_inner_whitespace.xhtml +0 -40
- data/test/results/nuke_outer_whitespace.xhtml +0 -148
- data/test/results/original_engine.xhtml +0 -20
- data/test/results/partial_layout.xhtml +0 -5
- data/test/results/partial_layout_erb.xhtml +0 -5
- data/test/results/partials.xhtml +0 -21
- data/test/results/render_layout.xhtml +0 -3
- data/test/results/silent_script.xhtml +0 -74
- data/test/results/standard.xhtml +0 -162
- data/test/results/tag_parsing.xhtml +0 -23
- data/test/results/very_basic.xhtml +0 -5
- data/test/results/whitespace_handling.xhtml +0 -90
- data/test/template_test.rb +0 -354
- data/test/templates/_av_partial_1.haml +0 -9
- data/test/templates/_av_partial_1_ugly.haml +0 -9
- data/test/templates/_av_partial_2.haml +0 -5
- data/test/templates/_av_partial_2_ugly.haml +0 -5
- data/test/templates/_layout.erb +0 -3
- data/test/templates/_layout_for_partial.haml +0 -3
- data/test/templates/_partial.haml +0 -8
- data/test/templates/_text_area.haml +0 -3
- data/test/templates/_text_area_helper.html.haml +0 -4
- data/test/templates/action_view.haml +0 -47
- data/test/templates/action_view_ugly.haml +0 -47
- data/test/templates/breakage.haml +0 -8
- data/test/templates/content_for_layout.haml +0 -8
- data/test/templates/eval_suppressed.haml +0 -11
- data/test/templates/helpers.haml +0 -55
- data/test/templates/helpful.haml +0 -11
- data/test/templates/just_stuff.haml +0 -85
- data/test/templates/list.haml +0 -12
- data/test/templates/nuke_inner_whitespace.haml +0 -32
- data/test/templates/nuke_outer_whitespace.haml +0 -144
- data/test/templates/original_engine.haml +0 -17
- data/test/templates/partial_layout.haml +0 -3
- data/test/templates/partial_layout_erb.erb +0 -4
- data/test/templates/partialize.haml +0 -1
- data/test/templates/partials.haml +0 -12
- data/test/templates/render_layout.haml +0 -2
- data/test/templates/silent_script.haml +0 -45
- data/test/templates/standard.haml +0 -43
- data/test/templates/standard_ugly.haml +0 -43
- data/test/templates/tag_parsing.haml +0 -21
- data/test/templates/very_basic.haml +0 -4
- data/test/templates/whitespace_handling.haml +0 -87
- data/test/test_helper.rb +0 -81
- data/test/util_test.rb +0 -63
data/lib/haml/parser.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: false
|
1
2
|
require 'strscan'
|
2
3
|
|
3
4
|
module Haml
|
@@ -71,7 +72,7 @@ module Haml
|
|
71
72
|
# foo.each do | bar |
|
72
73
|
# = bar
|
73
74
|
#
|
74
|
-
BLOCK_WITH_SPACES = /do
|
75
|
+
BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
|
75
76
|
|
76
77
|
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
|
77
78
|
START_BLOCK_KEYWORDS = %w[if begin case unless]
|
@@ -80,17 +81,16 @@ module Haml
|
|
80
81
|
BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
|
81
82
|
|
82
83
|
# The Regex that matches a Doctype command.
|
83
|
-
DOCTYPE_REGEX = /(\d(?:\.\d)?)
|
84
|
+
DOCTYPE_REGEX = /(\d(?:\.\d)?)?\s*([a-z]*)\s*([^ ]+)?/i
|
84
85
|
|
85
86
|
# The Regex that matches a literal string or symbol value
|
86
87
|
LITERAL_VALUE_REGEX = /:(\w*)|(["'])((?!\\|\#\{|\#@|\#\$|\2).|\\.)*\2/
|
87
88
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
@
|
93
|
-
@index = 0
|
89
|
+
ID_KEY = 'id'.freeze
|
90
|
+
CLASS_KEY = 'class'.freeze
|
91
|
+
|
92
|
+
def initialize(options)
|
93
|
+
@options = Options.wrap(options)
|
94
94
|
# Record the indent levels of "if" statements to validate the subsequent
|
95
95
|
# elsif and else statements are indented at the appropriate level.
|
96
96
|
@script_level_stack = []
|
@@ -98,15 +98,27 @@ module Haml
|
|
98
98
|
@template_tabs = 0
|
99
99
|
end
|
100
100
|
|
101
|
-
def
|
101
|
+
def call(template)
|
102
|
+
match = template.rstrip.scan(/(([ \t]+)?(.*?))(?:\Z|\r\n|\r|\n)/m)
|
103
|
+
# discard the last match which is always blank
|
104
|
+
match.pop
|
105
|
+
@template = match.each_with_index.map do |(full, whitespace, text), index|
|
106
|
+
Line.new(whitespace, text.rstrip, full, index, self, false)
|
107
|
+
end
|
108
|
+
# Append special end-of-document marker
|
109
|
+
@template << Line.new(nil, '-#', '-#', @template.size, self, true)
|
110
|
+
|
102
111
|
@root = @parent = ParseNode.new(:root)
|
103
|
-
@
|
112
|
+
@flat = false
|
113
|
+
@filter_buffer = nil
|
104
114
|
@indentation = nil
|
105
115
|
@line = next_line
|
106
116
|
|
107
117
|
raise SyntaxError.new(Error.message(:indenting_at_start), @line.index) if @line.tabs != 0
|
108
118
|
|
109
|
-
|
119
|
+
loop do
|
120
|
+
next_line
|
121
|
+
|
110
122
|
process_indent(@line) unless @line.text.empty?
|
111
123
|
|
112
124
|
if flat?
|
@@ -118,61 +130,66 @@ module Haml
|
|
118
130
|
end
|
119
131
|
|
120
132
|
@tab_up = nil
|
121
|
-
process_line(@line
|
122
|
-
if
|
133
|
+
process_line(@line) unless @line.text.empty?
|
134
|
+
if block_opened? || @tab_up
|
123
135
|
@template_tabs += 1
|
124
136
|
@parent = @parent.children.last
|
125
137
|
end
|
126
138
|
|
127
|
-
if
|
139
|
+
if !flat? && @next_line.tabs - @line.tabs > 1
|
128
140
|
raise SyntaxError.new(Error.message(:deeper_indenting, @next_line.tabs - @line.tabs), @next_line.index)
|
129
141
|
end
|
130
142
|
|
131
143
|
@line = @next_line
|
132
144
|
end
|
133
|
-
|
134
145
|
# Close all the open tags
|
135
146
|
close until @parent.type == :root
|
136
147
|
@root
|
137
148
|
rescue Haml::Error => e
|
138
|
-
e.backtrace.unshift "#{@options
|
149
|
+
e.backtrace.unshift "#{@options.filename}:#{(e.line ? e.line + 1 : @line.index + 1) + @options.line - 1}"
|
139
150
|
raise
|
140
151
|
end
|
141
152
|
|
153
|
+
def compute_tabs(line)
|
154
|
+
return 0 if line.text.empty? || !line.whitespace
|
155
|
+
|
156
|
+
if @indentation.nil?
|
157
|
+
@indentation = line.whitespace
|
158
|
+
|
159
|
+
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
160
|
+
raise SyntaxError.new(Error.message(:cant_use_tabs_and_spaces), line.index)
|
161
|
+
end
|
162
|
+
|
163
|
+
@flat_spaces = @indentation * (@template_tabs+1) if flat?
|
164
|
+
return 1
|
165
|
+
end
|
166
|
+
|
167
|
+
tabs = line.whitespace.length / @indentation.length
|
168
|
+
return tabs if line.whitespace == @indentation * tabs
|
169
|
+
return @template_tabs + 1 if flat? && line.whitespace =~ /^#{@flat_spaces}/
|
170
|
+
|
171
|
+
message = Error.message(:inconsistent_indentation,
|
172
|
+
human_indentation(line.whitespace),
|
173
|
+
human_indentation(@indentation)
|
174
|
+
)
|
175
|
+
raise SyntaxError.new(message, line.index)
|
176
|
+
end
|
142
177
|
|
143
178
|
private
|
144
179
|
|
145
180
|
# @private
|
146
|
-
class Line < Struct.new(:
|
181
|
+
class Line < Struct.new(:whitespace, :text, :full, :index, :parser, :eod)
|
147
182
|
alias_method :eod?, :eod
|
148
183
|
|
149
184
|
# @private
|
150
185
|
def tabs
|
151
|
-
|
152
|
-
|
153
|
-
break 0 if line.text.empty? || !(whitespace = line.full[/^\s+/])
|
154
|
-
|
155
|
-
if @indentation.nil?
|
156
|
-
@indentation = whitespace
|
157
|
-
|
158
|
-
if @indentation.include?(?\s) && @indentation.include?(?\t)
|
159
|
-
raise SyntaxError.new(Error.message(:cant_use_tabs_and_spaces), line.index)
|
160
|
-
end
|
161
|
-
|
162
|
-
@flat_spaces = @indentation * (@template_tabs+1) if flat?
|
163
|
-
break 1
|
164
|
-
end
|
165
|
-
|
166
|
-
tabs = whitespace.length / @indentation.length
|
167
|
-
break tabs if whitespace == @indentation * tabs
|
168
|
-
break @template_tabs + 1 if flat? && whitespace =~ /^#{@flat_spaces}/
|
186
|
+
@tabs ||= parser.compute_tabs(self)
|
187
|
+
end
|
169
188
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
raise SyntaxError.new(message, line.index)
|
175
|
-
end
|
189
|
+
def strip!(from)
|
190
|
+
self.text = text[from..-1]
|
191
|
+
self.text.lstrip!
|
192
|
+
self
|
176
193
|
end
|
177
194
|
end
|
178
195
|
|
@@ -184,9 +201,31 @@ module Haml
|
|
184
201
|
end
|
185
202
|
|
186
203
|
def inspect
|
187
|
-
|
188
|
-
|
189
|
-
|
204
|
+
%Q[(#{type} #{value.inspect}#{children.each_with_object('') {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})]
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# @param [String] new - Hash literal including dynamic values.
|
209
|
+
# @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
|
210
|
+
class DynamicAttributes < Struct.new(:new, :old)
|
211
|
+
def old=(value)
|
212
|
+
unless value =~ /\A{.*}\z/m
|
213
|
+
raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
|
214
|
+
end
|
215
|
+
super
|
216
|
+
end
|
217
|
+
|
218
|
+
# This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
|
219
|
+
def to_literal
|
220
|
+
[new, stripped_old].compact.join(', ')
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
|
225
|
+
# For `%foo{ { foo: 1 }, bar: 2 }`, :old is "{ { foo: 1 }, bar: 2 }" and this method returns " { foo: 1 }, bar: 2 " for last argument.
|
226
|
+
def stripped_old
|
227
|
+
return nil if old.nil?
|
228
|
+
old.sub!(/\A{/, '').sub!(/}\z/m, '')
|
190
229
|
end
|
191
230
|
end
|
192
231
|
|
@@ -195,44 +234,55 @@ module Haml
|
|
195
234
|
return unless line.tabs <= @template_tabs && @template_tabs > 0
|
196
235
|
|
197
236
|
to_close = @template_tabs - line.tabs
|
198
|
-
to_close.times {|i| close unless to_close - 1 - i == 0 &&
|
237
|
+
to_close.times {|i| close unless to_close - 1 - i == 0 && continuation_script?(line.text)}
|
238
|
+
end
|
239
|
+
|
240
|
+
def continuation_script?(text)
|
241
|
+
text[0] == SILENT_SCRIPT && mid_block_keyword?(text)
|
242
|
+
end
|
243
|
+
|
244
|
+
def mid_block_keyword?(text)
|
245
|
+
MID_BLOCK_KEYWORDS.include?(block_keyword(text))
|
199
246
|
end
|
200
247
|
|
201
248
|
# Processes a single line of Haml.
|
202
249
|
#
|
203
250
|
# This method doesn't return anything; it simply processes the line and
|
204
251
|
# adds the appropriate code to `@precompiled`.
|
205
|
-
def process_line(
|
206
|
-
|
207
|
-
|
208
|
-
case text[0]
|
209
|
-
when DIV_CLASS; push div(text)
|
252
|
+
def process_line(line)
|
253
|
+
case line.text[0]
|
254
|
+
when DIV_CLASS; push div(line)
|
210
255
|
when DIV_ID
|
211
|
-
return push plain(
|
212
|
-
push div(
|
213
|
-
when ELEMENT; push tag(
|
214
|
-
when COMMENT; push comment(text[1..-1].
|
256
|
+
return push plain(line) if %w[{ @ $].include?(line.text[1])
|
257
|
+
push div(line)
|
258
|
+
when ELEMENT; push tag(line)
|
259
|
+
when COMMENT; push comment(line.text[1..-1].lstrip)
|
215
260
|
when SANITIZE
|
216
|
-
return push plain(
|
217
|
-
return push script(
|
218
|
-
return push flat_script(
|
219
|
-
return push plain(
|
220
|
-
push plain(
|
261
|
+
return push plain(line.strip!(3), :escape_html) if line.text[1, 2] == '=='
|
262
|
+
return push script(line.strip!(2), :escape_html) if line.text[1] == SCRIPT
|
263
|
+
return push flat_script(line.strip!(2), :escape_html) if line.text[1] == FLAT_SCRIPT
|
264
|
+
return push plain(line.strip!(1), :escape_html) if line.text[1] == ?\s || line.text[1..2] == '#{'
|
265
|
+
push plain(line)
|
221
266
|
when SCRIPT
|
222
|
-
return push plain(
|
223
|
-
|
224
|
-
|
225
|
-
when
|
226
|
-
when
|
267
|
+
return push plain(line.strip!(2)) if line.text[1] == SCRIPT
|
268
|
+
line.text = line.text[1..-1]
|
269
|
+
push script(line)
|
270
|
+
when FLAT_SCRIPT; push flat_script(line.strip!(1))
|
271
|
+
when SILENT_SCRIPT
|
272
|
+
return push haml_comment(line.text[2..-1]) if line.text[1] == SILENT_COMMENT
|
273
|
+
push silent_script(line)
|
274
|
+
when FILTER; push filter(line.text[1..-1].downcase)
|
227
275
|
when DOCTYPE
|
228
|
-
return push doctype(text) if text[0
|
229
|
-
return push plain(
|
230
|
-
return push script(
|
231
|
-
return push flat_script(
|
232
|
-
return push plain(
|
233
|
-
push plain(
|
234
|
-
when ESCAPE
|
235
|
-
|
276
|
+
return push doctype(line.text) if line.text[0, 3] == '!!!'
|
277
|
+
return push plain(line.strip!(3), false) if line.text[1, 2] == '=='
|
278
|
+
return push script(line.strip!(2), false) if line.text[1] == SCRIPT
|
279
|
+
return push flat_script(line.strip!(2), false) if line.text[1] == FLAT_SCRIPT
|
280
|
+
return push plain(line.strip!(1), false) if line.text[1] == ?\s || line.text[1..2] == '#{'
|
281
|
+
push plain(line)
|
282
|
+
when ESCAPE
|
283
|
+
line.text = line.text[1..-1]
|
284
|
+
push plain(line)
|
285
|
+
else; push plain(line)
|
236
286
|
end
|
237
287
|
end
|
238
288
|
|
@@ -241,52 +291,47 @@ module Haml
|
|
241
291
|
keyword[0] || keyword[1]
|
242
292
|
end
|
243
293
|
|
244
|
-
def mid_block_keyword?(text)
|
245
|
-
MID_BLOCK_KEYWORDS.include?(block_keyword(text))
|
246
|
-
end
|
247
|
-
|
248
294
|
def push(node)
|
249
295
|
@parent.children << node
|
250
296
|
node.parent = @parent
|
251
297
|
end
|
252
298
|
|
253
|
-
def plain(
|
299
|
+
def plain(line, escape_html = nil)
|
254
300
|
if block_opened?
|
255
301
|
raise SyntaxError.new(Error.message(:illegal_nesting_plain), @next_line.index)
|
256
302
|
end
|
257
303
|
|
258
|
-
unless contains_interpolation?(text)
|
259
|
-
return ParseNode.new(:plain,
|
304
|
+
unless contains_interpolation?(line.text)
|
305
|
+
return ParseNode.new(:plain, line.index + 1, :text => line.text)
|
260
306
|
end
|
261
307
|
|
262
|
-
escape_html = @options
|
263
|
-
|
308
|
+
escape_html = @options.escape_html if escape_html.nil?
|
309
|
+
line.text = unescape_interpolation(line.text, escape_html)
|
310
|
+
script(line, false)
|
264
311
|
end
|
265
312
|
|
266
|
-
def script(
|
267
|
-
raise SyntaxError.new(Error.message(:no_ruby_code, '=')) if text.empty?
|
268
|
-
|
269
|
-
escape_html = @options
|
313
|
+
def script(line, escape_html = nil, preserve = false)
|
314
|
+
raise SyntaxError.new(Error.message(:no_ruby_code, '=')) if line.text.empty?
|
315
|
+
line = handle_ruby_multiline(line)
|
316
|
+
escape_html = @options.escape_html if escape_html.nil?
|
270
317
|
|
271
|
-
keyword = block_keyword(text)
|
318
|
+
keyword = block_keyword(line.text)
|
272
319
|
check_push_script_stack(keyword)
|
273
320
|
|
274
|
-
ParseNode.new(:script,
|
321
|
+
ParseNode.new(:script, line.index + 1, :text => line.text, :escape_html => escape_html,
|
275
322
|
:preserve => preserve, :keyword => keyword)
|
276
323
|
end
|
277
324
|
|
278
|
-
def flat_script(
|
279
|
-
raise SyntaxError.new(Error.message(:no_ruby_code, '~')) if text.empty?
|
280
|
-
script(
|
325
|
+
def flat_script(line, escape_html = nil)
|
326
|
+
raise SyntaxError.new(Error.message(:no_ruby_code, '~')) if line.text.empty?
|
327
|
+
script(line, escape_html, :preserve)
|
281
328
|
end
|
282
329
|
|
283
|
-
def silent_script(
|
284
|
-
|
285
|
-
|
286
|
-
raise SyntaxError.new(Error.message(:no_end), @index - 1) if text[1..-1].strip == "end"
|
330
|
+
def silent_script(line)
|
331
|
+
raise SyntaxError.new(Error.message(:no_end), line.index) if line.text[1..-1].strip == 'end'
|
287
332
|
|
288
|
-
|
289
|
-
keyword = block_keyword(text)
|
333
|
+
line = handle_ruby_multiline(line)
|
334
|
+
keyword = block_keyword(line.text)
|
290
335
|
|
291
336
|
check_push_script_stack(keyword)
|
292
337
|
|
@@ -308,8 +353,8 @@ module Haml
|
|
308
353
|
end
|
309
354
|
end
|
310
355
|
|
311
|
-
ParseNode.new(:silent_script, @index,
|
312
|
-
:text => text[1..-1], :keyword => keyword)
|
356
|
+
ParseNode.new(:silent_script, @line.index + 1,
|
357
|
+
:text => line.text[1..-1], :keyword => keyword)
|
313
358
|
end
|
314
359
|
|
315
360
|
def check_push_script_stack(keyword)
|
@@ -323,18 +368,25 @@ module Haml
|
|
323
368
|
end
|
324
369
|
|
325
370
|
def haml_comment(text)
|
326
|
-
|
327
|
-
|
371
|
+
if filter_opened?
|
372
|
+
@flat = true
|
373
|
+
@filter_buffer = String.new
|
374
|
+
@filter_buffer << "#{text}\n" unless text.empty?
|
375
|
+
text = @filter_buffer
|
376
|
+
# If we don't know the indentation by now, it'll be set in Line#tabs
|
377
|
+
@flat_spaces = @indentation * (@template_tabs+1) if @indentation
|
378
|
+
end
|
379
|
+
|
380
|
+
ParseNode.new(:haml_comment, @line.index + 1, :text => text)
|
328
381
|
end
|
329
382
|
|
330
383
|
def tag(line)
|
331
384
|
tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
332
|
-
nuke_inner_whitespace, action, value, last_line = parse_tag(line)
|
385
|
+
nuke_inner_whitespace, action, value, last_line = parse_tag(line.text)
|
333
386
|
|
334
|
-
preserve_tag = @options
|
387
|
+
preserve_tag = @options.preserve.include?(tag_name)
|
335
388
|
nuke_inner_whitespace ||= preserve_tag
|
336
|
-
|
337
|
-
escape_html = (action == '&' || (action != '!' && @options[:escape_html]))
|
389
|
+
escape_html = (action == '&' || (action != '!' && @options.escape_html))
|
338
390
|
|
339
391
|
case action
|
340
392
|
when '/'; self_closing = true
|
@@ -369,22 +421,20 @@ module Haml
|
|
369
421
|
end
|
370
422
|
|
371
423
|
attributes = Parser.parse_class_and_id(attributes)
|
372
|
-
|
424
|
+
dynamic_attributes = DynamicAttributes.new
|
373
425
|
|
374
426
|
if attributes_hashes[:new]
|
375
427
|
static_attributes, attributes_hash = attributes_hashes[:new]
|
376
|
-
|
377
|
-
|
428
|
+
AttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
|
429
|
+
dynamic_attributes.new = attributes_hash
|
378
430
|
end
|
379
431
|
|
380
432
|
if attributes_hashes[:old]
|
381
433
|
static_attributes = parse_static_hash(attributes_hashes[:old])
|
382
|
-
|
383
|
-
|
434
|
+
AttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
|
435
|
+
dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
|
384
436
|
end
|
385
437
|
|
386
|
-
attributes_list.compact!
|
387
|
-
|
388
438
|
raise SyntaxError.new(Error.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
|
389
439
|
raise SyntaxError.new(Error.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
|
390
440
|
raise SyntaxError.new(Error.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
|
@@ -393,56 +443,70 @@ module Haml
|
|
393
443
|
raise SyntaxError.new(Error.message(:illegal_nesting_line, tag_name), @next_line.index)
|
394
444
|
end
|
395
445
|
|
396
|
-
self_closing ||= !!(!block_opened? && value.empty? && @options
|
446
|
+
self_closing ||= !!(!block_opened? && value.empty? && @options.autoclose.any? {|t| t === tag_name})
|
397
447
|
value = nil if value.empty? && (block_opened? || self_closing)
|
398
|
-
|
448
|
+
line.text = value
|
449
|
+
line = handle_ruby_multiline(line) if parse
|
399
450
|
|
400
|
-
ParseNode.new(:tag,
|
401
|
-
:
|
451
|
+
ParseNode.new(:tag, line.index + 1, :name => tag_name, :attributes => attributes,
|
452
|
+
:dynamic_attributes => dynamic_attributes, :self_closing => self_closing,
|
402
453
|
:nuke_inner_whitespace => nuke_inner_whitespace,
|
403
454
|
:nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
|
404
455
|
:escape_html => escape_html, :preserve_tag => preserve_tag,
|
405
|
-
:preserve_script => preserve_script, :parse => parse, :value =>
|
456
|
+
:preserve_script => preserve_script, :parse => parse, :value => line.text)
|
406
457
|
end
|
407
458
|
|
408
459
|
# Renders a line that creates an XHTML tag and has an implicit div because of
|
409
460
|
# `.` or `#`.
|
410
461
|
def div(line)
|
411
|
-
|
462
|
+
line.text = "%div#{line.text}"
|
463
|
+
tag(line)
|
412
464
|
end
|
413
465
|
|
414
466
|
# Renders an XHTML comment.
|
415
|
-
def comment(
|
416
|
-
|
417
|
-
|
418
|
-
|
467
|
+
def comment(text)
|
468
|
+
if text[0..1] == '!['
|
469
|
+
revealed = true
|
470
|
+
text = text[1..-1]
|
471
|
+
else
|
472
|
+
revealed = false
|
473
|
+
end
|
419
474
|
|
420
|
-
|
475
|
+
conditional, text = balance(text, ?[, ?]) if text[0] == ?[
|
476
|
+
text.strip!
|
477
|
+
|
478
|
+
if contains_interpolation?(text)
|
479
|
+
parse = true
|
480
|
+
text = unescape_interpolation(text)
|
481
|
+
else
|
482
|
+
parse = false
|
483
|
+
end
|
484
|
+
|
485
|
+
if block_opened? && !text.empty?
|
421
486
|
raise SyntaxError.new(Haml::Error.message(:illegal_nesting_content), @next_line.index)
|
422
487
|
end
|
423
488
|
|
424
|
-
ParseNode.new(:comment, @index, :conditional => conditional, :text =>
|
489
|
+
ParseNode.new(:comment, @line.index + 1, :conditional => conditional, :text => text, :revealed => revealed, :parse => parse)
|
425
490
|
end
|
426
491
|
|
427
492
|
# Renders an XHTML doctype or XML shebang.
|
428
|
-
def doctype(
|
493
|
+
def doctype(text)
|
429
494
|
raise SyntaxError.new(Error.message(:illegal_nesting_header), @next_line.index) if block_opened?
|
430
|
-
version, type, encoding =
|
431
|
-
ParseNode.new(:doctype, @index, :version => version, :type => type, :encoding => encoding)
|
495
|
+
version, type, encoding = text[3..-1].strip.downcase.scan(DOCTYPE_REGEX)[0]
|
496
|
+
ParseNode.new(:doctype, @line.index + 1, :version => version, :type => type, :encoding => encoding)
|
432
497
|
end
|
433
498
|
|
434
499
|
def filter(name)
|
435
500
|
raise Error.new(Error.message(:invalid_filter_name, name)) unless name =~ /^\w+$/
|
436
501
|
|
437
|
-
@filter_buffer = String.new
|
438
|
-
|
439
502
|
if filter_opened?
|
440
503
|
@flat = true
|
504
|
+
@filter_buffer = String.new
|
441
505
|
# If we don't know the indentation by now, it'll be set in Line#tabs
|
442
506
|
@flat_spaces = @indentation * (@template_tabs+1) if @indentation
|
443
507
|
end
|
444
508
|
|
445
|
-
ParseNode.new(:filter, @index, :name => name, :text => @filter_buffer)
|
509
|
+
ParseNode.new(:filter, @line.index + 1, :name => name, :text => @filter_buffer)
|
446
510
|
end
|
447
511
|
|
448
512
|
def close
|
@@ -452,13 +516,17 @@ module Haml
|
|
452
516
|
end
|
453
517
|
|
454
518
|
def close_filter(_)
|
455
|
-
|
456
|
-
@flat_spaces = nil
|
457
|
-
@filter_buffer = nil
|
519
|
+
close_flat_section
|
458
520
|
end
|
459
521
|
|
460
522
|
def close_haml_comment(_)
|
461
|
-
|
523
|
+
close_flat_section
|
524
|
+
end
|
525
|
+
|
526
|
+
def close_flat_section
|
527
|
+
@flat = false
|
528
|
+
@flat_spaces = nil
|
529
|
+
@filter_buffer = nil
|
462
530
|
end
|
463
531
|
|
464
532
|
def close_silent_script(node)
|
@@ -485,23 +553,33 @@ module Haml
|
|
485
553
|
# that can then be merged with another attributes hash.
|
486
554
|
def self.parse_class_and_id(list)
|
487
555
|
attributes = {}
|
488
|
-
|
556
|
+
return attributes if list.empty?
|
557
|
+
|
558
|
+
list.scan(/([#.])([-:_a-zA-Z0-9\@]+)/) do |type, property|
|
489
559
|
case type
|
490
560
|
when '.'
|
491
|
-
if attributes[
|
492
|
-
attributes[
|
561
|
+
if attributes[CLASS_KEY]
|
562
|
+
attributes[CLASS_KEY] += " "
|
493
563
|
else
|
494
|
-
attributes[
|
564
|
+
attributes[CLASS_KEY] = ""
|
495
565
|
end
|
496
|
-
attributes[
|
497
|
-
when '#'; attributes[
|
566
|
+
attributes[CLASS_KEY] += property
|
567
|
+
when '#'; attributes[ID_KEY] = property
|
498
568
|
end
|
499
569
|
end
|
500
570
|
attributes
|
501
571
|
end
|
502
572
|
|
573
|
+
# This method doesn't use Haml::AttributeParser because currently it depends on Ripper and Rubinius doesn't provide it.
|
574
|
+
# Ideally this logic should be placed in Haml::AttributeParser instead of here and this method should use it.
|
575
|
+
#
|
576
|
+
# @param [String] text - Hash literal or text inside old attributes
|
577
|
+
# @return [Hash,nil] - Return nil if text is not static Hash literal
|
503
578
|
def parse_static_hash(text)
|
504
579
|
attributes = {}
|
580
|
+
return attributes if text.empty?
|
581
|
+
|
582
|
+
text = text[1...-1] # strip brackets
|
505
583
|
scanner = StringScanner.new(text)
|
506
584
|
scanner.scan(/\s+/)
|
507
585
|
until scanner.eos?
|
@@ -515,20 +593,20 @@ module Haml
|
|
515
593
|
end
|
516
594
|
|
517
595
|
# Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
|
518
|
-
def parse_tag(
|
519
|
-
match =
|
520
|
-
raise SyntaxError.new(Error.message(:invalid_tag,
|
596
|
+
def parse_tag(text)
|
597
|
+
match = text.scan(/%([-:\w]+)([-:\w.#\@]*)(.+)?/)[0]
|
598
|
+
raise SyntaxError.new(Error.message(:invalid_tag, text)) unless match
|
521
599
|
|
522
600
|
tag_name, attributes, rest = match
|
523
601
|
|
524
|
-
if attributes =~ /[
|
602
|
+
if !attributes.empty? && (attributes =~ /[.#](\.|#|\z)/)
|
525
603
|
raise SyntaxError.new(Error.message(:illegal_element))
|
526
604
|
end
|
527
605
|
|
528
606
|
new_attributes_hash = old_attributes_hash = last_line = nil
|
529
|
-
object_ref =
|
607
|
+
object_ref = :nil
|
530
608
|
attributes_hashes = {}
|
531
|
-
while rest
|
609
|
+
while rest && !rest.empty?
|
532
610
|
case rest[0]
|
533
611
|
when ?{
|
534
612
|
break if old_attributes_hash
|
@@ -539,38 +617,46 @@ module Haml
|
|
539
617
|
new_attributes_hash, rest, last_line = parse_new_attributes(rest)
|
540
618
|
attributes_hashes[:new] = new_attributes_hash
|
541
619
|
when ?[
|
542
|
-
break unless object_ref ==
|
620
|
+
break unless object_ref == :nil
|
543
621
|
object_ref, rest = balance(rest, ?[, ?])
|
544
622
|
else; break
|
545
623
|
end
|
546
624
|
end
|
547
625
|
|
548
|
-
if rest
|
626
|
+
if rest && !rest.empty?
|
549
627
|
nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0]
|
550
|
-
nuke_whitespace
|
551
|
-
|
552
|
-
|
628
|
+
if nuke_whitespace
|
629
|
+
nuke_outer_whitespace = nuke_whitespace.include? '>'
|
630
|
+
nuke_inner_whitespace = nuke_whitespace.include? '<'
|
631
|
+
end
|
553
632
|
end
|
554
633
|
|
555
|
-
if @options
|
634
|
+
if @options.remove_whitespace
|
556
635
|
nuke_outer_whitespace = true
|
557
636
|
nuke_inner_whitespace = true
|
558
637
|
end
|
559
638
|
|
560
|
-
|
639
|
+
if value.nil?
|
640
|
+
value = ''
|
641
|
+
else
|
642
|
+
value.strip!
|
643
|
+
end
|
561
644
|
[tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace,
|
562
|
-
nuke_inner_whitespace, action, value, last_line || @index]
|
645
|
+
nuke_inner_whitespace, action, value, last_line || @line.index + 1]
|
563
646
|
end
|
564
647
|
|
565
|
-
|
566
|
-
|
567
|
-
|
648
|
+
# @return [String] attributes_hash - Hash literal starting with `{` and ending with `}`
|
649
|
+
# @return [String] rest
|
650
|
+
# @return [Integer] last_line
|
651
|
+
def parse_old_attributes(text)
|
652
|
+
text = text.dup
|
653
|
+
last_line = @line.index + 1
|
568
654
|
|
569
655
|
begin
|
570
|
-
attributes_hash, rest = balance(
|
656
|
+
attributes_hash, rest = balance(text, ?{, ?})
|
571
657
|
rescue SyntaxError => e
|
572
|
-
if
|
573
|
-
|
658
|
+
if text.strip[-1] == ?, && e.message == Error.message(:unbalanced_brackets)
|
659
|
+
text << "\n#{@next_line.text}"
|
574
660
|
last_line += 1
|
575
661
|
next_line
|
576
662
|
retry
|
@@ -579,14 +665,15 @@ module Haml
|
|
579
665
|
raise e
|
580
666
|
end
|
581
667
|
|
582
|
-
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
583
668
|
return attributes_hash, rest, last_line
|
584
669
|
end
|
585
670
|
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
671
|
+
# @return [Array<Hash,String,nil>] - [static_attributes (Hash), dynamic_attributes (nil or String starting with `{` and ending with `}`)]
|
672
|
+
# @return [String] rest
|
673
|
+
# @return [Integer] last_line
|
674
|
+
def parse_new_attributes(text)
|
675
|
+
scanner = StringScanner.new(text)
|
676
|
+
last_line = @line.index + 1
|
590
677
|
attributes = {}
|
591
678
|
|
592
679
|
scanner.scan(/\(\s*/)
|
@@ -595,14 +682,15 @@ module Haml
|
|
595
682
|
break if name.nil?
|
596
683
|
|
597
684
|
if name == false
|
598
|
-
|
685
|
+
scanned = Haml::Util.balance(text, ?(, ?))
|
686
|
+
text = scanned ? scanned.first : text
|
599
687
|
raise Haml::SyntaxError.new(Error.message(:invalid_attribute_list, text.inspect), last_line - 1)
|
600
688
|
end
|
601
689
|
attributes[name] = value
|
602
690
|
scanner.scan(/\s*/)
|
603
691
|
|
604
692
|
if scanner.eos?
|
605
|
-
|
693
|
+
text << " #{@next_line.text}"
|
606
694
|
last_line += 1
|
607
695
|
next_line
|
608
696
|
scanner.scan(/\s*/)
|
@@ -615,7 +703,7 @@ module Haml
|
|
615
703
|
if type == :static
|
616
704
|
static_attributes[name] = val
|
617
705
|
else
|
618
|
-
dynamic_attributes << inspect_obj(name)
|
706
|
+
dynamic_attributes << "#{inspect_obj(name)} => #{val},"
|
619
707
|
end
|
620
708
|
end
|
621
709
|
dynamic_attributes << "}"
|
@@ -650,35 +738,16 @@ module Haml
|
|
650
738
|
|
651
739
|
return name, [:static, content.first[1]] if content.size == 1
|
652
740
|
return name, [:dynamic,
|
653
|
-
|
654
|
-
end
|
655
|
-
|
656
|
-
def raw_next_line
|
657
|
-
text = @template.shift
|
658
|
-
return unless text
|
659
|
-
|
660
|
-
index = @template_index
|
661
|
-
@template_index += 1
|
662
|
-
|
663
|
-
return text, index
|
741
|
+
%!"#{content.each_with_object('') {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
664
742
|
end
|
665
743
|
|
666
744
|
def next_line
|
667
|
-
|
668
|
-
return unless text
|
669
|
-
|
670
|
-
# :eod is a special end-of-document marker
|
671
|
-
line =
|
672
|
-
if text == :eod
|
673
|
-
Line.new '-#', '-#', '-#', index, self, true
|
674
|
-
else
|
675
|
-
Line.new text.strip, text.lstrip.chomp, text, index, self, false
|
676
|
-
end
|
745
|
+
line = @template.shift || raise(StopIteration)
|
677
746
|
|
678
747
|
# `flat?' here is a little outdated,
|
679
748
|
# so we have to manually check if either the previous or current line
|
680
749
|
# closes the flat block, as well as whether a new block is opened.
|
681
|
-
line_defined = instance_variable_defined?(
|
750
|
+
line_defined = instance_variable_defined?(:@line)
|
682
751
|
@line.tabs if line_defined
|
683
752
|
unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) ||
|
684
753
|
(line_defined && @line.text[0] == ?: && line.full =~ %r[^#{@line.full[/^\s+/]}\s])
|
@@ -694,21 +763,17 @@ module Haml
|
|
694
763
|
line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/
|
695
764
|
end
|
696
765
|
|
697
|
-
def un_next_line(line)
|
698
|
-
@template.unshift line
|
699
|
-
@template_index -= 1
|
700
|
-
end
|
701
|
-
|
702
766
|
def handle_multiline(line)
|
703
767
|
return unless is_multiline?(line.text)
|
704
768
|
line.text.slice!(-1)
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
769
|
+
loop do
|
770
|
+
new_line = @template.first
|
771
|
+
break if new_line.eod?
|
772
|
+
next @template.shift if new_line.text.strip.empty?
|
773
|
+
break unless is_multiline?(new_line.text.strip)
|
774
|
+
line.text << new_line.text.strip[0...-1]
|
775
|
+
@template.shift
|
710
776
|
end
|
711
|
-
un_next_line new_line
|
712
777
|
end
|
713
778
|
|
714
779
|
# Checks whether or not `line` is in a multiline sequence.
|
@@ -716,18 +781,18 @@ module Haml
|
|
716
781
|
text && text.length > 1 && text[-1] == MULTILINE_CHAR_VALUE && text[-2] == ?\s && text !~ BLOCK_WITH_SPACES
|
717
782
|
end
|
718
783
|
|
719
|
-
def handle_ruby_multiline(
|
720
|
-
text
|
721
|
-
return
|
722
|
-
un_next_line @next_line.full
|
784
|
+
def handle_ruby_multiline(line)
|
785
|
+
line.text.rstrip!
|
786
|
+
return line unless is_ruby_multiline?(line.text)
|
723
787
|
begin
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
788
|
+
# Use already fetched @next_line in the first loop. Otherwise, fetch next
|
789
|
+
new_line = new_line.nil? ? @next_line : @template.shift
|
790
|
+
break if new_line.eod?
|
791
|
+
next if new_line.text.empty?
|
792
|
+
line.text << " #{new_line.text.rstrip}"
|
793
|
+
end while is_ruby_multiline?(new_line.text)
|
729
794
|
next_line
|
730
|
-
|
795
|
+
line
|
731
796
|
end
|
732
797
|
|
733
798
|
# `text' is a Ruby multiline block if it:
|
@@ -735,16 +800,13 @@ module Haml
|
|
735
800
|
# - but not "?," which is a character literal
|
736
801
|
# (however, "x?," is a method call and not a literal)
|
737
802
|
# - and not "?\," which is a character literal
|
738
|
-
#
|
739
803
|
def is_ruby_multiline?(text)
|
740
804
|
text && text.length > 1 && text[-1] == ?, &&
|
741
|
-
!((text[-3
|
805
|
+
!((text[-3, 2] =~ /\W\?/) || text[-3, 2] == "?\\")
|
742
806
|
end
|
743
807
|
|
744
808
|
def balance(*args)
|
745
|
-
|
746
|
-
return res if res
|
747
|
-
raise SyntaxError.new(Error.message(:unbalanced_brackets))
|
809
|
+
Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
|
748
810
|
end
|
749
811
|
|
750
812
|
def block_opened?
|
@@ -754,7 +816,7 @@ module Haml
|
|
754
816
|
# Same semantics as block_opened?, except that block_opened? uses Line#tabs,
|
755
817
|
# which doesn't interact well with filter lines
|
756
818
|
def filter_opened?
|
757
|
-
@next_line.full =~ (@indentation ? /^#{@indentation * @template_tabs}/ : /^\s/)
|
819
|
+
@next_line.full =~ (@indentation ? /^#{@indentation * (@template_tabs + 1)}/ : /^\s/)
|
758
820
|
end
|
759
821
|
|
760
822
|
def flat?
|