haml 5.0.4 → 5.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +78 -21
- data/.yardopts +1 -2
- data/CHANGELOG.md +43 -1
- data/Gemfile +2 -5
- data/MIT-LICENSE +2 -2
- data/README.md +4 -5
- data/REFERENCE.md +36 -9
- data/Rakefile +2 -13
- data/benchmark.rb +13 -9
- data/haml.gemspec +10 -3
- data/lib/haml.rb +1 -0
- data/lib/haml/attribute_builder.rb +4 -3
- data/lib/haml/attribute_compiler.rb +43 -31
- data/lib/haml/attribute_parser.rb +3 -1
- data/lib/haml/buffer.rb +4 -1
- data/lib/haml/compiler.rb +23 -24
- data/lib/haml/engine.rb +12 -3
- data/lib/haml/error.rb +25 -24
- data/lib/haml/escapable.rb +39 -11
- data/lib/haml/exec.rb +5 -6
- data/lib/haml/filters.rb +12 -11
- data/lib/haml/generator.rb +2 -1
- data/lib/haml/helpers.rb +15 -13
- data/lib/haml/helpers/action_view_extensions.rb +1 -0
- data/lib/haml/helpers/action_view_mods.rb +4 -1
- data/lib/haml/helpers/action_view_xss_mods.rb +1 -0
- data/lib/haml/helpers/safe_erubi_template.rb +1 -0
- data/lib/haml/helpers/safe_erubis_template.rb +1 -0
- data/lib/haml/helpers/xss_mods.rb +7 -3
- data/lib/haml/options.rb +36 -36
- data/lib/haml/parser.rb +52 -22
- data/lib/haml/plugin.rb +7 -4
- data/lib/haml/railtie.rb +4 -4
- data/lib/haml/sass_rails_filter.rb +1 -0
- data/lib/haml/template.rb +1 -0
- data/lib/haml/template/options.rb +1 -0
- data/lib/haml/temple_engine.rb +11 -9
- data/lib/haml/temple_line_counter.rb +1 -0
- data/lib/haml/util.rb +6 -6
- data/lib/haml/version.rb +2 -1
- metadata +28 -9
data/lib/haml/exec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'optparse'
|
3
4
|
require 'rbconfig'
|
4
5
|
require 'pp'
|
@@ -120,7 +121,7 @@ module Haml
|
|
120
121
|
@options[:input], @options[:output] = input, output
|
121
122
|
end
|
122
123
|
|
123
|
-
COLORS = {
|
124
|
+
COLORS = {red: 31, green: 32, yellow: 33}.freeze
|
124
125
|
|
125
126
|
# Prints a status message about performing the given action,
|
126
127
|
# colored using the given color (via terminal escapes) if possible.
|
@@ -337,11 +338,9 @@ END
|
|
337
338
|
end
|
338
339
|
|
339
340
|
def validate_ruby(code)
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
$!
|
344
|
-
end
|
341
|
+
eval("BEGIN {return nil}; #{code}", binding, @options[:filename] || "")
|
342
|
+
rescue ::SyntaxError # Not to be confused with Haml::SyntaxError
|
343
|
+
$!
|
345
344
|
end
|
346
345
|
end
|
347
346
|
end
|
data/lib/haml/filters.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
# frozen_string_literal:
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require "tilt"
|
3
4
|
|
4
5
|
module Haml
|
@@ -119,7 +120,7 @@ module Haml
|
|
119
120
|
# @param text [String] The source text for the filter to process
|
120
121
|
# @return [String] The filtered result
|
121
122
|
# @raise [Haml::Error] if it's not overridden
|
122
|
-
def render(
|
123
|
+
def render(_text)
|
123
124
|
raise Error.new("#{self.inspect}#render not defined!")
|
124
125
|
end
|
125
126
|
|
@@ -130,7 +131,7 @@ module Haml
|
|
130
131
|
# @param text [String] The source text for the filter to process
|
131
132
|
# @return [String] The filtered result
|
132
133
|
# @raise [Haml::Error] if it or \{#render} isn't overridden
|
133
|
-
def render_with_options(text,
|
134
|
+
def render_with_options(text, _options)
|
134
135
|
render(text)
|
135
136
|
end
|
136
137
|
|
@@ -164,7 +165,11 @@ module Haml
|
|
164
165
|
if contains_interpolation?(text)
|
165
166
|
return if options[:suppress_eval]
|
166
167
|
|
167
|
-
|
168
|
+
escape = options[:escape_filter_interpolations]
|
169
|
+
# `escape_filter_interpolations` defaults to `escape_html` if unset.
|
170
|
+
escape = options[:escape_html] if escape.nil?
|
171
|
+
|
172
|
+
text = unescape_interpolation(text, escape).gsub(/(\\+)n/) do |s|
|
168
173
|
escapes = $1.size
|
169
174
|
next s if escapes % 2 == 0
|
170
175
|
"#{'\\' * (escapes - 1)}\n"
|
@@ -182,9 +187,8 @@ RUBY
|
|
182
187
|
return
|
183
188
|
end
|
184
189
|
|
185
|
-
rendered = Haml::Helpers::find_and_preserve(filter.render_with_options(text, compiler.options), compiler.options[:preserve])
|
186
|
-
rendered.rstrip
|
187
|
-
push_text("#{rendered}\n")
|
190
|
+
rendered = Haml::Helpers::find_and_preserve(filter.render_with_options(text.to_s, compiler.options), compiler.options[:preserve])
|
191
|
+
push_text("#{rendered.rstrip}\n")
|
188
192
|
end
|
189
193
|
end
|
190
194
|
end
|
@@ -247,10 +251,7 @@ RUBY
|
|
247
251
|
|
248
252
|
# @see Base#render
|
249
253
|
def render(text)
|
250
|
-
|
251
|
-
text.rstrip!
|
252
|
-
text.gsub!("\n", "\n ")
|
253
|
-
"<![CDATA[#{text}\n]]>"
|
254
|
+
"<![CDATA[#{"\n#{text.rstrip}".gsub("\n", "\n ")}\n]]>"
|
254
255
|
end
|
255
256
|
end
|
256
257
|
|
data/lib/haml/generator.rb
CHANGED
data/lib/haml/helpers.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
# frozen_string_literal:
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'erb'
|
3
4
|
|
4
5
|
module Haml
|
@@ -109,10 +110,7 @@ MESSAGE
|
|
109
110
|
# @yield The block within which to escape newlines
|
110
111
|
def find_and_preserve(input = nil, tags = haml_buffer.options[:preserve], &block)
|
111
112
|
return find_and_preserve(capture_haml(&block), input || tags) if block
|
112
|
-
tags = tags.
|
113
|
-
s << '|' unless s.empty?
|
114
|
-
s << Regexp.escape(t)
|
115
|
-
end
|
113
|
+
tags = tags.map { |tag| Regexp.escape(tag) }.join('|')
|
116
114
|
re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
|
117
115
|
input.to_s.gsub(re) do |s|
|
118
116
|
s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
|
@@ -200,8 +198,8 @@ MESSAGE
|
|
200
198
|
# @yield [item] A block which contains Haml code that goes within list items
|
201
199
|
# @yieldparam item An element of `enum`
|
202
200
|
def list_of(enum, opts={}, &block)
|
203
|
-
opts_attributes = opts.
|
204
|
-
enum.
|
201
|
+
opts_attributes = opts.map { |k, v| " #{k}='#{v}'" }.join
|
202
|
+
enum.map do |i|
|
205
203
|
result = capture_haml(i, &block)
|
206
204
|
|
207
205
|
if result.count("\n") > 1
|
@@ -211,9 +209,8 @@ MESSAGE
|
|
211
209
|
result.strip!
|
212
210
|
end
|
213
211
|
|
214
|
-
|
215
|
-
|
216
|
-
end
|
212
|
+
%Q!<li#{opts_attributes}>#{result}</li>!
|
213
|
+
end.join("\n")
|
217
214
|
end
|
218
215
|
|
219
216
|
# Returns a hash containing default assignments for the `xmlns`, `lang`, and `xml:lang`
|
@@ -596,7 +593,7 @@ MESSAGE
|
|
596
593
|
end
|
597
594
|
|
598
595
|
# Characters that need to be escaped to HTML entities from user input
|
599
|
-
HTML_ESCAPE = {
|
596
|
+
HTML_ESCAPE = {'&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => '''}.freeze
|
600
597
|
|
601
598
|
HTML_ESCAPE_REGEX = /['"><&]/
|
602
599
|
|
@@ -610,9 +607,12 @@ MESSAGE
|
|
610
607
|
# @param text [String] The string to sanitize
|
611
608
|
# @return [String] The sanitized string
|
612
609
|
def html_escape(text)
|
613
|
-
|
610
|
+
CGI.escapeHTML(text.to_s)
|
614
611
|
end
|
615
612
|
|
613
|
+
# Always escape text regardless of html_safe?
|
614
|
+
alias_method :html_escape_without_haml_xss, :html_escape
|
615
|
+
|
616
616
|
HTML_ESCAPE_ONCE_REGEX = /['"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
|
617
617
|
|
618
618
|
# Escapes HTML entities in `text`, but without escaping an ampersand
|
@@ -625,6 +625,9 @@ MESSAGE
|
|
625
625
|
text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
|
626
626
|
end
|
627
627
|
|
628
|
+
# Always escape text once regardless of html_safe?
|
629
|
+
alias_method :escape_once_without_haml_xss, :escape_once
|
630
|
+
|
628
631
|
# Returns whether or not the current template is a Haml template.
|
629
632
|
#
|
630
633
|
# This function, unlike other {Haml::Helpers} functions,
|
@@ -704,4 +707,3 @@ class Object
|
|
704
707
|
false
|
705
708
|
end
|
706
709
|
end
|
707
|
-
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Haml
|
3
4
|
module Helpers
|
4
5
|
module ActionViewMods
|
@@ -52,10 +53,12 @@ module ActionView
|
|
52
53
|
end
|
53
54
|
|
54
55
|
module TagHelper
|
56
|
+
DEFAULT_PRESERVE_OPTIONS = %w(textarea pre code).freeze
|
57
|
+
|
55
58
|
def content_tag_with_haml(name, *args, &block)
|
56
59
|
return content_tag_without_haml(name, *args, &block) unless is_haml?
|
57
60
|
|
58
|
-
preserve = haml_buffer.options.fetch(:preserve,
|
61
|
+
preserve = haml_buffer.options.fetch(:preserve, DEFAULT_PRESERVE_OPTIONS).include?(name.to_s)
|
59
62
|
|
60
63
|
if block_given? && block_is_haml?(block) && preserve
|
61
64
|
return content_tag_without_haml(name, *args) do
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Haml
|
3
4
|
module Helpers
|
4
5
|
# This module overrides Haml helpers to work properly
|
@@ -7,12 +8,15 @@ module Haml
|
|
7
8
|
# to work with Rails' XSS protection methods.
|
8
9
|
module XssMods
|
9
10
|
def self.included(base)
|
10
|
-
%w[
|
11
|
-
precede succeed capture_haml haml_concat haml_internal_concat haml_indent
|
12
|
-
escape_once].each do |name|
|
11
|
+
%w[find_and_preserve preserve list_of surround
|
12
|
+
precede succeed capture_haml haml_concat haml_internal_concat haml_indent].each do |name|
|
13
13
|
base.send(:alias_method, "#{name}_without_haml_xss", name)
|
14
14
|
base.send(:alias_method, name, "#{name}_with_haml_xss")
|
15
15
|
end
|
16
|
+
# Those two always have _without_haml_xss
|
17
|
+
%w[html_escape escape_once].each do |name|
|
18
|
+
base.send(:alias_method, name, "#{name}_with_haml_xss")
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
# Don't escape text that's already safe,
|
data/lib/haml/options.rb
CHANGED
@@ -1,47 +1,44 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Haml
|
3
4
|
# This class encapsulates all of the configuration options that Haml
|
4
5
|
# understands. Please see the {file:REFERENCE.md#options Haml Reference} to
|
5
6
|
# learn how to set the options.
|
6
7
|
class Options
|
7
|
-
|
8
8
|
@valid_formats = [:html4, :html5, :xhtml]
|
9
|
-
|
10
9
|
@buffer_option_keys = [:autoclose, :preserve, :attr_wrapper, :format,
|
11
|
-
:encoding, :escape_html, :escape_attrs, :hyphenate_data_attrs, :cdata]
|
10
|
+
:encoding, :escape_html, :escape_filter_interpolations, :escape_attrs, :hyphenate_data_attrs, :cdata]
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
class << self
|
13
|
+
# The default option values.
|
14
|
+
# @return Hash
|
15
|
+
def defaults
|
16
|
+
@defaults ||= Haml::TempleEngine.options.to_hash.merge(encoding: 'UTF-8')
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
@valid_formats
|
23
|
-
end
|
19
|
+
# An array of valid values for the `:format` option.
|
20
|
+
# @return Array
|
21
|
+
attr_reader :valid_formats
|
24
22
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
@buffer_option_keys
|
30
|
-
end
|
23
|
+
# An array of keys that will be used to provide a hash of options to
|
24
|
+
# {Haml::Buffer}.
|
25
|
+
# @return Hash
|
26
|
+
attr_reader :buffer_option_keys
|
31
27
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
# Returns a subset of defaults: those that {Haml::Buffer} cares about.
|
29
|
+
# @return [{Symbol => Object}] The options hash
|
30
|
+
def buffer_defaults
|
31
|
+
@buffer_defaults ||= buffer_option_keys.inject({}) do |hash, key|
|
32
|
+
hash.merge(key => defaults[key])
|
33
|
+
end
|
37
34
|
end
|
38
|
-
end
|
39
35
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
def wrap(options)
|
37
|
+
if options.is_a?(Options)
|
38
|
+
options
|
39
|
+
else
|
40
|
+
Options.new(options)
|
41
|
+
end
|
45
42
|
end
|
46
43
|
end
|
47
44
|
|
@@ -85,6 +82,13 @@ module Haml
|
|
85
82
|
# Defaults to false.
|
86
83
|
attr_accessor :escape_html
|
87
84
|
|
85
|
+
# Sets whether or not to escape HTML-sensitive characters in interpolated strings.
|
86
|
+
# See also {file:REFERENCE.md#escaping_html Escaping HTML} and
|
87
|
+
# {file:REFERENCE.md#unescaping_html Unescaping HTML}.
|
88
|
+
#
|
89
|
+
# Defaults to the current value of `escape_html`.
|
90
|
+
attr_accessor :escape_filter_interpolations
|
91
|
+
|
88
92
|
# The name of the Haml file being parsed.
|
89
93
|
# This is only used as information when exceptions are raised. This is
|
90
94
|
# automatically assigned when working through ActionView, so it's really
|
@@ -131,7 +135,7 @@ module Haml
|
|
131
135
|
# formatting errors.
|
132
136
|
#
|
133
137
|
# Defaults to `false`.
|
134
|
-
|
138
|
+
attr_accessor :remove_whitespace
|
135
139
|
|
136
140
|
# Whether or not attribute hashes and Ruby scripts designated by `=` or `~`
|
137
141
|
# should be evaluated. If this is `true`, said scripts are rendered as empty
|
@@ -167,7 +171,7 @@ module Haml
|
|
167
171
|
# Key is filter name in String and value is Class to use. Defaults to {}.
|
168
172
|
attr_accessor :filters
|
169
173
|
|
170
|
-
def initialize(values = {}
|
174
|
+
def initialize(values = {})
|
171
175
|
defaults.each {|k, v| instance_variable_set :"@#{k}", v}
|
172
176
|
values.each {|k, v| send("#{k}=", v) if defaults.has_key?(k) && !v.nil?}
|
173
177
|
yield if block_given?
|
@@ -237,10 +241,6 @@ module Haml
|
|
237
241
|
xhtml? || @cdata
|
238
242
|
end
|
239
243
|
|
240
|
-
def remove_whitespace=(value)
|
241
|
-
@remove_whitespace = value
|
242
|
-
end
|
243
|
-
|
244
244
|
def encoding=(value)
|
245
245
|
return unless value
|
246
246
|
@encoding = value.is_a?(Encoding) ? value.name : value.to_s
|
data/lib/haml/parser.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
# frozen_string_literal:
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ripper'
|
2
4
|
require 'strscan'
|
3
5
|
|
4
6
|
module Haml
|
@@ -60,7 +62,7 @@ module Haml
|
|
60
62
|
SILENT_SCRIPT,
|
61
63
|
ESCAPE,
|
62
64
|
FILTER
|
63
|
-
]
|
65
|
+
].freeze
|
64
66
|
|
65
67
|
# The value of the character that designates that a line is part
|
66
68
|
# of a multiline string.
|
@@ -74,8 +76,8 @@ module Haml
|
|
74
76
|
#
|
75
77
|
BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
|
76
78
|
|
77
|
-
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
|
78
|
-
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
|
79
81
|
# Try to parse assignments to block starters as best as possible
|
80
82
|
START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
|
81
83
|
BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
|
@@ -89,6 +91,9 @@ module Haml
|
|
89
91
|
ID_KEY = 'id'.freeze
|
90
92
|
CLASS_KEY = 'class'.freeze
|
91
93
|
|
94
|
+
# Used for scanning old attributes, substituting the first '{'
|
95
|
+
METHOD_CALL_PREFIX = 'a('
|
96
|
+
|
92
97
|
def initialize(options)
|
93
98
|
@options = Options.wrap(options)
|
94
99
|
# Record the indent levels of "if" statements to validate the subsequent
|
@@ -178,7 +183,7 @@ module Haml
|
|
178
183
|
private
|
179
184
|
|
180
185
|
# @private
|
181
|
-
|
186
|
+
Line = Struct.new(:whitespace, :text, :full, :index, :parser, :eod) do
|
182
187
|
alias_method :eod?, :eod
|
183
188
|
|
184
189
|
# @private
|
@@ -194,25 +199,26 @@ module Haml
|
|
194
199
|
end
|
195
200
|
|
196
201
|
# @private
|
197
|
-
|
202
|
+
ParseNode = Struct.new(:type, :line, :value, :parent, :children) do
|
198
203
|
def initialize(*args)
|
199
204
|
super
|
200
205
|
self.children ||= []
|
201
206
|
end
|
202
207
|
|
203
208
|
def inspect
|
204
|
-
%Q[(#{type} #{value.inspect}#{children.each_with_object('') {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})]
|
209
|
+
%Q[(#{type} #{value.inspect}#{children.each_with_object(''.dup) {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})].dup
|
205
210
|
end
|
206
211
|
end
|
207
212
|
|
208
213
|
# @param [String] new - Hash literal including dynamic values.
|
209
214
|
# @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
|
210
|
-
|
215
|
+
DynamicAttributes = Struct.new(:new, :old) do
|
216
|
+
undef :old=
|
211
217
|
def old=(value)
|
212
218
|
unless value =~ /\A{.*}\z/m
|
213
219
|
raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
|
214
220
|
end
|
215
|
-
|
221
|
+
self[:old] = value
|
216
222
|
end
|
217
223
|
|
218
224
|
# This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
|
@@ -287,7 +293,7 @@ module Haml
|
|
287
293
|
end
|
288
294
|
|
289
295
|
def block_keyword(text)
|
290
|
-
return unless keyword = text.scan(BLOCK_KEYWORD_REGEX)[0]
|
296
|
+
return unless (keyword = text.scan(BLOCK_KEYWORD_REGEX)[0])
|
291
297
|
keyword[0] || keyword[1]
|
292
298
|
end
|
293
299
|
|
@@ -305,7 +311,7 @@ module Haml
|
|
305
311
|
return ParseNode.new(:plain, line.index + 1, :text => line.text)
|
306
312
|
end
|
307
313
|
|
308
|
-
escape_html = @options.escape_html if escape_html.nil?
|
314
|
+
escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
|
309
315
|
line.text = unescape_interpolation(line.text, escape_html)
|
310
316
|
script(line, false)
|
311
317
|
end
|
@@ -534,7 +540,7 @@ module Haml
|
|
534
540
|
|
535
541
|
# Post-process case statements to normalize the nesting of "when" clauses
|
536
542
|
return unless node.value[:keyword] == "case"
|
537
|
-
return unless first = node.children.first
|
543
|
+
return unless (first = node.children.first)
|
538
544
|
return unless first.type == :silent_script && first.value[:keyword] == "when"
|
539
545
|
return if first.children.empty?
|
540
546
|
# If the case node has a "when" child with children, it's the
|
@@ -583,9 +589,9 @@ module Haml
|
|
583
589
|
scanner = StringScanner.new(text)
|
584
590
|
scanner.scan(/\s+/)
|
585
591
|
until scanner.eos?
|
586
|
-
return unless key = scanner.scan(LITERAL_VALUE_REGEX)
|
592
|
+
return unless (key = scanner.scan(LITERAL_VALUE_REGEX))
|
587
593
|
return unless scanner.scan(/\s*=>\s*/)
|
588
|
-
return unless value = scanner.scan(LITERAL_VALUE_REGEX)
|
594
|
+
return unless (value = scanner.scan(LITERAL_VALUE_REGEX))
|
589
595
|
return unless scanner.scan(/\s*(?:,|$)\s*/)
|
590
596
|
attributes[eval(key).to_s] = eval(value).to_s
|
591
597
|
end
|
@@ -649,13 +655,18 @@ module Haml
|
|
649
655
|
# @return [String] rest
|
650
656
|
# @return [Integer] last_line
|
651
657
|
def parse_old_attributes(text)
|
652
|
-
text = text.dup
|
653
658
|
last_line = @line.index + 1
|
654
659
|
|
655
660
|
begin
|
656
|
-
|
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, ?{)
|
657
668
|
rescue SyntaxError => e
|
658
|
-
if
|
669
|
+
if e.message == Error.message(:unbalanced_brackets) && !@template.empty?
|
659
670
|
text << "\n#{@next_line.text}"
|
660
671
|
last_line += 1
|
661
672
|
next_line
|
@@ -698,7 +709,7 @@ module Haml
|
|
698
709
|
end
|
699
710
|
|
700
711
|
static_attributes = {}
|
701
|
-
dynamic_attributes = "{"
|
712
|
+
dynamic_attributes = "{".dup
|
702
713
|
attributes.each do |name, (type, val)|
|
703
714
|
if type == :static
|
704
715
|
static_attributes[name] = val
|
@@ -713,7 +724,7 @@ module Haml
|
|
713
724
|
end
|
714
725
|
|
715
726
|
def parse_new_attribute(scanner)
|
716
|
-
unless name = scanner.scan(/[-:\w]+/)
|
727
|
+
unless (name = scanner.scan(/[-:\w]+/))
|
717
728
|
return if scanner.scan(/\)/)
|
718
729
|
return false
|
719
730
|
end
|
@@ -722,8 +733,8 @@ module Haml
|
|
722
733
|
return name, [:static, true] unless scanner.scan(/=/) #/end
|
723
734
|
|
724
735
|
scanner.scan(/\s*/)
|
725
|
-
unless quote = scanner.scan(/["']/)
|
726
|
-
return false unless var = scanner.scan(/(@@?|\$)?\w+/)
|
736
|
+
unless (quote = scanner.scan(/["']/))
|
737
|
+
return false unless (var = scanner.scan(/(@@?|\$)?\w+/))
|
727
738
|
return name, [:dynamic, var]
|
728
739
|
end
|
729
740
|
|
@@ -738,7 +749,7 @@ module Haml
|
|
738
749
|
|
739
750
|
return name, [:static, content.first[1]] if content.size == 1
|
740
751
|
return name, [:dynamic,
|
741
|
-
%!"#{content.each_with_object('') {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
752
|
+
%!"#{content.each_with_object(''.dup) {|(t, v), s| s << (t == :str ? inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
742
753
|
end
|
743
754
|
|
744
755
|
def next_line
|
@@ -809,6 +820,25 @@ module Haml
|
|
809
820
|
Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
|
810
821
|
end
|
811
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
|
839
|
+
raise SyntaxError.new(Error.message(:unbalanced_brackets))
|
840
|
+
end
|
841
|
+
|
812
842
|
def block_opened?
|
813
843
|
@next_line.tabs > @line.tabs
|
814
844
|
end
|