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