haml 5.1.1 → 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 +4 -4
- data/.github/workflows/test.yml +36 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +32 -1
- data/Gemfile +1 -4
- data/README.md +21 -16
- data/REFERENCE.md +39 -10
- data/Rakefile +2 -13
- data/haml.gemspec +2 -1
- data/lib/haml/attribute_builder.rb +58 -3
- data/lib/haml/attribute_compiler.rb +45 -32
- data/lib/haml/attribute_parser.rb +1 -1
- data/lib/haml/buffer.rb +0 -56
- data/lib/haml/compiler.rb +19 -21
- data/lib/haml/error.rb +24 -24
- data/lib/haml/escapable.rb +38 -11
- data/lib/haml/exec.rb +1 -1
- data/lib/haml/helpers.rb +8 -2
- data/lib/haml/helpers/xss_mods.rb +6 -3
- data/lib/haml/options.rb +27 -35
- data/lib/haml/parser.rb +36 -8
- data/lib/haml/plugin.rb +18 -1
- data/lib/haml/railtie.rb +5 -0
- data/lib/haml/temple_engine.rb +2 -1
- data/lib/haml/util.rb +1 -1
- data/lib/haml/version.rb +1 -1
- metadata +22 -8
- data/.travis.yml +0 -99
data/lib/haml/buffer.rb
CHANGED
@@ -130,18 +130,6 @@ module Haml
|
|
130
130
|
@real_tabs += tab_change
|
131
131
|
end
|
132
132
|
|
133
|
-
def attributes(class_id, obj_ref, *attributes_hashes)
|
134
|
-
attributes = class_id
|
135
|
-
attributes_hashes.each do |old|
|
136
|
-
result = {}
|
137
|
-
old.each { |k, v| result[k.to_s] = v }
|
138
|
-
AttributeBuilder.merge_attributes!(attributes, result)
|
139
|
-
end
|
140
|
-
AttributeBuilder.merge_attributes!(attributes, parse_object_ref(obj_ref)) if obj_ref
|
141
|
-
AttributeBuilder.build_attributes(
|
142
|
-
html?, @options[:attr_wrapper], @options[:escape_attrs], @options[:hyphenate_data_attrs], attributes)
|
143
|
-
end
|
144
|
-
|
145
133
|
# Remove the whitespace from the right side of the buffer string.
|
146
134
|
# Doesn't do anything if we're at the beginning of a capture_haml block.
|
147
135
|
def rstrip!
|
@@ -190,49 +178,5 @@ module Haml
|
|
190
178
|
tabs = [count + @tabulation, 0].max
|
191
179
|
@@tab_cache[tabs] ||= ' ' * tabs
|
192
180
|
end
|
193
|
-
|
194
|
-
# Takes an array of objects and uses the class and id of the first
|
195
|
-
# one to create an attributes hash.
|
196
|
-
# The second object, if present, is used as a prefix,
|
197
|
-
# just like you can do with `dom_id()` and `dom_class()` in Rails
|
198
|
-
def parse_object_ref(ref)
|
199
|
-
prefix = ref[1]
|
200
|
-
ref = ref[0]
|
201
|
-
# Let's make sure the value isn't nil. If it is, return the default Hash.
|
202
|
-
return {} if ref.nil?
|
203
|
-
class_name =
|
204
|
-
if ref.respond_to?(:haml_object_ref)
|
205
|
-
ref.haml_object_ref
|
206
|
-
else
|
207
|
-
underscore(ref.class)
|
208
|
-
end
|
209
|
-
ref_id =
|
210
|
-
if ref.respond_to?(:to_key)
|
211
|
-
key = ref.to_key
|
212
|
-
key.join('_') unless key.nil?
|
213
|
-
else
|
214
|
-
ref.id
|
215
|
-
end
|
216
|
-
id = "#{class_name}_#{ref_id || 'new'}"
|
217
|
-
if prefix
|
218
|
-
class_name = "#{ prefix }_#{ class_name}"
|
219
|
-
id = "#{ prefix }_#{ id }"
|
220
|
-
end
|
221
|
-
|
222
|
-
{ 'id'.freeze => id, 'class'.freeze => class_name }
|
223
|
-
end
|
224
|
-
|
225
|
-
# Changes a word from camel case to underscores.
|
226
|
-
# Based on the method of the same name in Rails' Inflector,
|
227
|
-
# but copied here so it'll run properly without Rails.
|
228
|
-
def underscore(camel_cased_word)
|
229
|
-
word = camel_cased_word.to_s.dup
|
230
|
-
word.gsub!(/::/, '_')
|
231
|
-
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
232
|
-
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
233
|
-
word.tr!('-', '_')
|
234
|
-
word.downcase!
|
235
|
-
word
|
236
|
-
end
|
237
181
|
end
|
238
182
|
end
|
data/lib/haml/compiler.rb
CHANGED
@@ -188,30 +188,28 @@ module Haml
|
|
188
188
|
|
189
189
|
if @options.html5?
|
190
190
|
'<!DOCTYPE html>'
|
191
|
-
|
192
|
-
if @
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
else
|
198
|
-
case @node.value[:type]
|
199
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
200
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
201
|
-
when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
202
|
-
when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
|
203
|
-
when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
204
|
-
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
elsif @options.html4?
|
191
|
+
elsif @options.xhtml?
|
192
|
+
if @node.value[:version] == "1.1"
|
193
|
+
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
|
194
|
+
elsif @node.value[:version] == "5"
|
195
|
+
'<!DOCTYPE html>'
|
196
|
+
else
|
209
197
|
case @node.value[:type]
|
210
|
-
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD
|
211
|
-
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD
|
212
|
-
|
198
|
+
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
|
199
|
+
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
|
200
|
+
when "mobile"; '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
|
201
|
+
when "rdfa"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">'
|
202
|
+
when "basic"; '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
|
203
|
+
else '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
|
213
204
|
end
|
214
205
|
end
|
206
|
+
|
207
|
+
elsif @options.html4?
|
208
|
+
case @node.value[:type]
|
209
|
+
when "strict"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">'
|
210
|
+
when "frameset"; '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">'
|
211
|
+
else '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">'
|
212
|
+
end
|
215
213
|
end
|
216
214
|
end
|
217
215
|
|
data/lib/haml/error.rb
CHANGED
@@ -5,29 +5,29 @@ module Haml
|
|
5
5
|
class Error < StandardError
|
6
6
|
|
7
7
|
MESSAGES = {
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
26
|
-
:
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
8
|
+
bad_script_indent: '"%s" is indented at wrong level: expected %d, but was at %d.',
|
9
|
+
cant_run_filter: 'Can\'t run "%s" filter; you must require its dependencies first',
|
10
|
+
cant_use_tabs_and_spaces: "Indentation can't use both tabs and spaces.",
|
11
|
+
deeper_indenting: "The line was indented %d levels deeper than the previous line.",
|
12
|
+
filter_not_defined: 'Filter "%s" is not defined.',
|
13
|
+
gem_install_filter_deps: '"%s" filter\'s %s dependency missing: try installing it or adding it to your Gemfile',
|
14
|
+
illegal_element: "Illegal element: classes and ids must have values.",
|
15
|
+
illegal_nesting_content: "Illegal nesting: nesting within a tag that already has content is illegal.",
|
16
|
+
illegal_nesting_header: "Illegal nesting: nesting within a header command is illegal.",
|
17
|
+
illegal_nesting_line: "Illegal nesting: content can't be both given on the same line as %%%s and nested within it.",
|
18
|
+
illegal_nesting_plain: "Illegal nesting: nesting within plain text is illegal.",
|
19
|
+
illegal_nesting_self_closing: "Illegal nesting: nesting within a self-closing tag is illegal.",
|
20
|
+
inconsistent_indentation: "Inconsistent indentation: %s used for indentation, but the rest of the document was indented using %s.",
|
21
|
+
indenting_at_start: "Indenting at the beginning of the document is illegal.",
|
22
|
+
install_haml_contrib: 'To use the "%s" filter, please install the haml-contrib gem.',
|
23
|
+
invalid_attribute_list: 'Invalid attribute list: %s.',
|
24
|
+
invalid_filter_name: 'Invalid filter name ":%s".',
|
25
|
+
invalid_tag: 'Invalid tag: "%s".',
|
26
|
+
missing_if: 'Got "%s" with no preceding "if"',
|
27
|
+
no_ruby_code: "There's no Ruby code for %s to evaluate.",
|
28
|
+
self_closing_content: "Self-closing tags can't have content.",
|
29
|
+
unbalanced_brackets: 'Unbalanced brackets.',
|
30
|
+
no_end: <<-END
|
31
31
|
You don't need to use "- end" in Haml. Un-indent to close a block:
|
32
32
|
- if foo?
|
33
33
|
%strong Foo!
|
@@ -35,7 +35,7 @@ You don't need to use "- end" in Haml. Un-indent to close a block:
|
|
35
35
|
Not foo.
|
36
36
|
%p This line is un-indented, so it isn't part of the "if" block
|
37
37
|
END
|
38
|
-
}
|
38
|
+
}.freeze
|
39
39
|
|
40
40
|
def self.message(key, *args)
|
41
41
|
string = MESSAGES[key] or raise "[HAML BUG] No error messages for #{key}"
|
data/lib/haml/escapable.rb
CHANGED
@@ -4,30 +4,31 @@ module Haml
|
|
4
4
|
# Like Temple::Filters::Escapable, but with support for escaping by
|
5
5
|
# Haml::Herlpers.html_escape and Haml::Herlpers.escape_once.
|
6
6
|
class Escapable < Temple::Filter
|
7
|
+
# Special value of `flag` to ignore html_safe?
|
8
|
+
EscapeSafeBuffer = Struct.new(:value)
|
9
|
+
|
7
10
|
def initialize(*)
|
8
11
|
super
|
9
|
-
@escape_code = "::Haml::Helpers.html_escape((%s))"
|
10
|
-
@escaper = eval("proc {|v| #{@escape_code % 'v'} }")
|
11
|
-
@once_escape_code = "::Haml::Helpers.escape_once((%s))"
|
12
|
-
@once_escaper = eval("proc {|v| #{@once_escape_code % 'v'} }")
|
13
12
|
@escape = false
|
13
|
+
@escape_safe_buffer = false
|
14
14
|
end
|
15
15
|
|
16
16
|
def on_escape(flag, exp)
|
17
|
-
|
18
|
-
@
|
17
|
+
old_escape, old_escape_safe_buffer = @escape, @escape_safe_buffer
|
18
|
+
@escape_safe_buffer = flag.is_a?(EscapeSafeBuffer)
|
19
|
+
@escape = @escape_safe_buffer ? flag.value : flag
|
19
20
|
compile(exp)
|
20
21
|
ensure
|
21
|
-
@escape =
|
22
|
+
@escape, @escape_safe_buffer = old_escape, old_escape_safe_buffer
|
22
23
|
end
|
23
24
|
|
24
25
|
# The same as Haml::AttributeBuilder.build_attributes
|
25
26
|
def on_static(value)
|
26
27
|
[:static,
|
27
28
|
if @escape == :once
|
28
|
-
|
29
|
+
escape_once(value)
|
29
30
|
elsif @escape
|
30
|
-
|
31
|
+
escape(value)
|
31
32
|
else
|
32
33
|
value
|
33
34
|
end
|
@@ -38,13 +39,39 @@ module Haml
|
|
38
39
|
def on_dynamic(value)
|
39
40
|
[:dynamic,
|
40
41
|
if @escape == :once
|
41
|
-
|
42
|
+
escape_once_code(value)
|
42
43
|
elsif @escape
|
43
|
-
|
44
|
+
escape_code(value)
|
44
45
|
else
|
45
46
|
"(#{value}).to_s"
|
46
47
|
end
|
47
48
|
]
|
48
49
|
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def escape_once(value)
|
54
|
+
if @escape_safe_buffer
|
55
|
+
::Haml::Helpers.escape_once_without_haml_xss(value)
|
56
|
+
else
|
57
|
+
::Haml::Helpers.escape_once(value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def escape(value)
|
62
|
+
if @escape_safe_buffer
|
63
|
+
::Haml::Helpers.html_escape_without_haml_xss(value)
|
64
|
+
else
|
65
|
+
::Haml::Helpers.html_escape(value)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def escape_once_code(value)
|
70
|
+
"::Haml::Helpers.escape_once#{('_without_haml_xss' if @escape_safe_buffer)}((#{value}))"
|
71
|
+
end
|
72
|
+
|
73
|
+
def escape_code(value)
|
74
|
+
"::Haml::Helpers.html_escape#{('_without_haml_xss' if @escape_safe_buffer)}((#{value}))"
|
75
|
+
end
|
49
76
|
end
|
50
77
|
end
|
data/lib/haml/exec.rb
CHANGED
@@ -121,7 +121,7 @@ module Haml
|
|
121
121
|
@options[:input], @options[:output] = input, output
|
122
122
|
end
|
123
123
|
|
124
|
-
COLORS = {
|
124
|
+
COLORS = {red: 31, green: 32, yellow: 33}.freeze
|
125
125
|
|
126
126
|
# Prints a status message about performing the given action,
|
127
127
|
# colored using the given color (via terminal escapes) if possible.
|
data/lib/haml/helpers.rb
CHANGED
@@ -593,7 +593,7 @@ MESSAGE
|
|
593
593
|
end
|
594
594
|
|
595
595
|
# Characters that need to be escaped to HTML entities from user input
|
596
|
-
HTML_ESCAPE = {
|
596
|
+
HTML_ESCAPE = {'&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => '''}.freeze
|
597
597
|
|
598
598
|
HTML_ESCAPE_REGEX = /['"><&]/
|
599
599
|
|
@@ -607,9 +607,12 @@ MESSAGE
|
|
607
607
|
# @param text [String] The string to sanitize
|
608
608
|
# @return [String] The sanitized string
|
609
609
|
def html_escape(text)
|
610
|
-
|
610
|
+
CGI.escapeHTML(text.to_s)
|
611
611
|
end
|
612
612
|
|
613
|
+
# Always escape text regardless of html_safe?
|
614
|
+
alias_method :html_escape_without_haml_xss, :html_escape
|
615
|
+
|
613
616
|
HTML_ESCAPE_ONCE_REGEX = /['"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
|
614
617
|
|
615
618
|
# Escapes HTML entities in `text`, but without escaping an ampersand
|
@@ -622,6 +625,9 @@ MESSAGE
|
|
622
625
|
text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
|
623
626
|
end
|
624
627
|
|
628
|
+
# Always escape text once regardless of html_safe?
|
629
|
+
alias_method :escape_once_without_haml_xss, :escape_once
|
630
|
+
|
625
631
|
# Returns whether or not the current template is a Haml template.
|
626
632
|
#
|
627
633
|
# This function, unlike other {Haml::Helpers} functions,
|
@@ -8,12 +8,15 @@ module Haml
|
|
8
8
|
# to work with Rails' XSS protection methods.
|
9
9
|
module XssMods
|
10
10
|
def self.included(base)
|
11
|
-
%w[
|
12
|
-
precede succeed capture_haml haml_concat haml_internal_concat haml_indent
|
13
|
-
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|
|
14
13
|
base.send(:alias_method, "#{name}_without_haml_xss", name)
|
15
14
|
base.send(:alias_method, name, "#{name}_with_haml_xss")
|
16
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
|
17
20
|
end
|
18
21
|
|
19
22
|
# Don't escape text that's already safe,
|
data/lib/haml/options.rb
CHANGED
@@ -5,44 +5,40 @@ module Haml
|
|
5
5
|
# understands. Please see the {file:REFERENCE.md#options Haml Reference} to
|
6
6
|
# learn how to set the options.
|
7
7
|
class Options
|
8
|
-
|
9
8
|
@valid_formats = [:html4, :html5, :xhtml]
|
10
|
-
|
11
9
|
@buffer_option_keys = [:autoclose, :preserve, :attr_wrapper, :format,
|
12
10
|
:encoding, :escape_html, :escape_filter_interpolations, :escape_attrs, :hyphenate_data_attrs, :cdata]
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@valid_formats
|
24
|
-
end
|
19
|
+
# An array of valid values for the `:format` option.
|
20
|
+
# @return Array
|
21
|
+
attr_reader :valid_formats
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@buffer_option_keys
|
31
|
-
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
|
32
27
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
38
34
|
end
|
39
|
-
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
def wrap(options)
|
37
|
+
if options.is_a?(Options)
|
38
|
+
options
|
39
|
+
else
|
40
|
+
Options.new(options)
|
41
|
+
end
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
@@ -139,7 +135,7 @@ module Haml
|
|
139
135
|
# formatting errors.
|
140
136
|
#
|
141
137
|
# Defaults to `false`.
|
142
|
-
|
138
|
+
attr_accessor :remove_whitespace
|
143
139
|
|
144
140
|
# Whether or not attribute hashes and Ruby scripts designated by `=` or `~`
|
145
141
|
# should be evaluated. If this is `true`, said scripts are rendered as empty
|
@@ -175,7 +171,7 @@ module Haml
|
|
175
171
|
# Key is filter name in String and value is Class to use. Defaults to {}.
|
176
172
|
attr_accessor :filters
|
177
173
|
|
178
|
-
def initialize(values = {}
|
174
|
+
def initialize(values = {})
|
179
175
|
defaults.each {|k, v| instance_variable_set :"@#{k}", v}
|
180
176
|
values.each {|k, v| send("#{k}=", v) if defaults.has_key?(k) && !v.nil?}
|
181
177
|
yield if block_given?
|
@@ -245,10 +241,6 @@ module Haml
|
|
245
241
|
xhtml? || @cdata
|
246
242
|
end
|
247
243
|
|
248
|
-
def remove_whitespace=(value)
|
249
|
-
@remove_whitespace = value
|
250
|
-
end
|
251
|
-
|
252
244
|
def encoding=(value)
|
253
245
|
return unless value
|
254
246
|
@encoding = value.is_a?(Encoding) ? value.name : value.to_s
|
data/lib/haml/parser.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'ripper'
|
3
4
|
require 'strscan'
|
4
5
|
|
5
6
|
module Haml
|
@@ -61,7 +62,7 @@ module Haml
|
|
61
62
|
SILENT_SCRIPT,
|
62
63
|
ESCAPE,
|
63
64
|
FILTER
|
64
|
-
]
|
65
|
+
].freeze
|
65
66
|
|
66
67
|
# The value of the character that designates that a line is part
|
67
68
|
# of a multiline string.
|
@@ -75,8 +76,8 @@ module Haml
|
|
75
76
|
#
|
76
77
|
BLOCK_WITH_SPACES = /do\s*\|\s*[^\|]*\s+\|\z/
|
77
78
|
|
78
|
-
MID_BLOCK_KEYWORDS = %w[else elsif rescue ensure end when]
|
79
|
-
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
|
80
81
|
# Try to parse assignments to block starters as best as possible
|
81
82
|
START_BLOCK_KEYWORD_REGEX = /(?:\w+(?:,\s*\w+)*\s*=\s*)?(#{START_BLOCK_KEYWORDS.join('|')})/
|
82
83
|
BLOCK_KEYWORD_REGEX = /^-?\s*(?:(#{MID_BLOCK_KEYWORDS.join('|')})|#{START_BLOCK_KEYWORD_REGEX.source})\b/
|
@@ -90,6 +91,9 @@ module Haml
|
|
90
91
|
ID_KEY = 'id'.freeze
|
91
92
|
CLASS_KEY = 'class'.freeze
|
92
93
|
|
94
|
+
# Used for scanning old attributes, substituting the first '{'
|
95
|
+
METHOD_CALL_PREFIX = 'a('
|
96
|
+
|
93
97
|
def initialize(options)
|
94
98
|
@options = Options.wrap(options)
|
95
99
|
# Record the indent levels of "if" statements to validate the subsequent
|
@@ -202,7 +206,7 @@ module Haml
|
|
202
206
|
end
|
203
207
|
|
204
208
|
def inspect
|
205
|
-
%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
|
206
210
|
end
|
207
211
|
end
|
208
212
|
|
@@ -307,7 +311,7 @@ module Haml
|
|
307
311
|
return ParseNode.new(:plain, line.index + 1, :text => line.text)
|
308
312
|
end
|
309
313
|
|
310
|
-
escape_html = @options.escape_html if escape_html.nil?
|
314
|
+
escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
|
311
315
|
line.text = unescape_interpolation(line.text, escape_html)
|
312
316
|
script(line, false)
|
313
317
|
end
|
@@ -651,13 +655,18 @@ module Haml
|
|
651
655
|
# @return [String] rest
|
652
656
|
# @return [Integer] last_line
|
653
657
|
def parse_old_attributes(text)
|
654
|
-
text = text.dup
|
655
658
|
last_line = @line.index + 1
|
656
659
|
|
657
660
|
begin
|
658
|
-
|
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, ?{)
|
659
668
|
rescue SyntaxError => e
|
660
|
-
if
|
669
|
+
if e.message == Error.message(:unbalanced_brackets) && !@template.empty?
|
661
670
|
text << "\n#{@next_line.text}"
|
662
671
|
last_line += 1
|
663
672
|
next_line
|
@@ -811,6 +820,25 @@ module Haml
|
|
811
820
|
Haml::Util.balance(*args) or raise(SyntaxError.new(Error.message(:unbalanced_brackets)))
|
812
821
|
end
|
813
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
|
+
|
814
842
|
def block_opened?
|
815
843
|
@next_line.tabs > @line.tabs
|
816
844
|
end
|