haml 5.0.4 → 5.2.1
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 +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
|