haml 7.3.0 → 7.5.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/CHANGELOG.md +25 -0
- data/REFERENCE.md +1 -1
- data/lib/haml/attribute_builder.rb +17 -6
- data/lib/haml/attribute_compiler.rb +22 -8
- data/lib/haml/attribute_parser.rb +19 -7
- data/lib/haml/compiler/children_compiler.rb +1 -3
- data/lib/haml/compiler/script_compiler.rb +6 -9
- data/lib/haml/dynamic_merger.rb +1 -1
- data/lib/haml/engine.rb +1 -1
- data/lib/haml/helpers.rb +1 -1
- data/lib/haml/parser.rb +13 -6
- data/lib/haml/preserver.rb +52 -0
- data/lib/haml/rails_helpers.rb +4 -12
- data/lib/haml/string_splitter.rb +14 -3
- data/lib/haml/template.rb +1 -1
- data/lib/haml/temple_line_counter.rb +15 -1
- data/lib/haml/util.rb +11 -8
- data/lib/haml/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8cf8d7cf95a5096090897276b03dd8073e73705fa1b880bba96af5dceb3b2c55
|
|
4
|
+
data.tar.gz: 4d1e1ff1bd53361d0a31c78533a838e60e52d10c5646ff0d8bbd87a552ccc658
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3bbfe67377d06702fa3240048fd25410f885f05a503d4e8e7793f0ccbba9ffb99e601d46068150d2170c11a3807660571fe26f337becfc155758f05cc470bc40
|
|
7
|
+
data.tar.gz: '01159a17fda467397309cfa38de5ad7b1e8820d506447b87221a8cdc087f02e8546cb1d2ad9a358d4ad0fa0c0b29c60b0a03bcf692e2fa551d748111701d8adc'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Haml Changelog
|
|
2
2
|
|
|
3
|
+
## 7.5.1
|
|
4
|
+
|
|
5
|
+
* Improve frozen strings management https://github.com/haml/haml/pull/1205
|
|
6
|
+
|
|
7
|
+
## 7.5.0
|
|
8
|
+
|
|
9
|
+
* Compile a multi-line attribute hash statically https://github.com/haml/haml/pull/1222
|
|
10
|
+
* Errors from multi-line attributes will be reported on the tag's line instead of the expression that raised the error.
|
|
11
|
+
|
|
12
|
+
## 7.4.1
|
|
13
|
+
|
|
14
|
+
* Build the preserve regex once per tag list instead of per call https://github.com/haml/haml/pull/1220
|
|
15
|
+
|
|
16
|
+
## 7.4.0
|
|
17
|
+
|
|
18
|
+
* `Haml::BOOLEAN_ATTRIBUTES` is changed from Array to Set https://github.com/haml/haml/pull/1216
|
|
19
|
+
* A guard on `respond_to?(:html_safe?)` is removed from `Haml::Util.escape_html_safe` https://github.com/haml/haml/pull/1217
|
|
20
|
+
* Keep interpolated text in the source encoding, fixing the `Encoding::CompatibilityError` remaining on an ASCII-8BIT
|
|
21
|
+
template source that mixes plain text and interpolation with non-ASCII characters https://github.com/haml/haml/issues/1218
|
|
22
|
+
|
|
23
|
+
## 7.3.1
|
|
24
|
+
|
|
25
|
+
* Keep Prism-derived fragments in the source encoding, fixing `Encoding::CompatibilityError` on an ASCII-8BIT
|
|
26
|
+
template source with non-ASCII characters (regression in 7.3.0) https://github.com/haml/haml/issues/1218
|
|
27
|
+
|
|
3
28
|
## 7.3.0
|
|
4
29
|
|
|
5
30
|
* Replace Ripper with Prism https://github.com/haml/haml/pull/1214
|
data/REFERENCE.md
CHANGED
|
@@ -320,7 +320,7 @@ or using `true` and `false`:
|
|
|
320
320
|
%input(selected=true)
|
|
321
321
|
|
|
322
322
|
This feature works only for attributes that are included in
|
|
323
|
-
[`Haml::BOOLEAN_ATTRIBUTES`](https://github.com/haml/haml/blob/main/lib/haml/attribute_compiler.rb#
|
|
323
|
+
[`Haml::BOOLEAN_ATTRIBUTES`](https://github.com/haml/haml/blob/main/lib/haml/attribute_compiler.rb#L9),
|
|
324
324
|
as well as `data-` and `aria-` attributes.
|
|
325
325
|
|
|
326
326
|
%input{'data-hidden' => false}
|
|
@@ -19,10 +19,12 @@ module Haml::AttributeBuilder
|
|
|
19
19
|
buf << build_data(escape_attrs, quote, format, *hash[key])
|
|
20
20
|
when 'aria'
|
|
21
21
|
buf << build_aria(escape_attrs, quote, format, *hash[key])
|
|
22
|
-
when *Haml::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
|
|
23
|
-
build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
|
|
24
22
|
else
|
|
25
|
-
|
|
23
|
+
if boolean_attribute?(key)
|
|
24
|
+
build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
|
|
25
|
+
else
|
|
26
|
+
buf << " #{key}=#{quote}#{escape_html(escape_attrs, hash[key].to_s)}#{quote}"
|
|
27
|
+
end
|
|
26
28
|
end
|
|
27
29
|
end
|
|
28
30
|
buf.join
|
|
@@ -72,6 +74,10 @@ module Haml::AttributeBuilder
|
|
|
72
74
|
|
|
73
75
|
private
|
|
74
76
|
|
|
77
|
+
def boolean_attribute?(key)
|
|
78
|
+
Haml::BOOLEAN_ATTRIBUTES.include?(key) || key.start_with?('data-', 'aria-')
|
|
79
|
+
end
|
|
80
|
+
|
|
75
81
|
def build_data_attribute(key, escape_attrs, quote, format, *hashes)
|
|
76
82
|
attrs = []
|
|
77
83
|
if hashes.size > 1 && hashes.all? { |h| h.is_a?(Hash) }
|
|
@@ -98,14 +104,18 @@ module Haml::AttributeBuilder
|
|
|
98
104
|
flattened = {}
|
|
99
105
|
|
|
100
106
|
attributes.each do |key, value|
|
|
107
|
+
# Recursing into a hash which contains itself would never end.
|
|
108
|
+
next if value.equal?(attributes)
|
|
109
|
+
|
|
101
110
|
case value
|
|
102
|
-
when attributes
|
|
103
111
|
when Hash
|
|
104
112
|
flatten_attributes(value).each do |k, v|
|
|
105
113
|
if k.nil?
|
|
106
114
|
flattened[key] = v
|
|
107
115
|
else
|
|
108
|
-
|
|
116
|
+
k = k.is_a?(Symbol) ? k.name : k.to_s
|
|
117
|
+
k = k.tr('_', '-') if k.include?('_')
|
|
118
|
+
flattened["#{key}-#{k}"] = v
|
|
109
119
|
end
|
|
110
120
|
end
|
|
111
121
|
else
|
|
@@ -122,7 +132,8 @@ module Haml::AttributeBuilder
|
|
|
122
132
|
raise ArgumentError, "Non-hash object is given to attributes!"
|
|
123
133
|
end
|
|
124
134
|
hash.each do |key, value|
|
|
125
|
-
|
|
135
|
+
# Symbol#name hands back the interned String; to_s allocates one per render.
|
|
136
|
+
key = key.is_a?(Symbol) ? key.name : key.to_s
|
|
126
137
|
case key
|
|
127
138
|
when 'id', 'class', 'data', 'aria'
|
|
128
139
|
merged[key] ||= []
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require 'set'
|
|
2
3
|
require 'haml/attribute_builder'
|
|
3
4
|
require 'haml/attribute_parser'
|
|
4
5
|
require 'haml/ruby_expression'
|
|
6
|
+
require 'haml/temple_line_counter'
|
|
5
7
|
|
|
6
8
|
module Haml
|
|
7
|
-
# The
|
|
8
|
-
BOOLEAN_ATTRIBUTES = %w[disabled readonly multiple checked autobuffer
|
|
9
|
+
# The set of boolean attributes. You may add custom attributes to this constant.
|
|
10
|
+
BOOLEAN_ATTRIBUTES = Set.new(%w[disabled readonly multiple checked autobuffer
|
|
9
11
|
autoplay controls loop selected hidden scoped async
|
|
10
12
|
defer reversed ismap seamless muted required
|
|
11
13
|
autofocus novalidate formnovalidate open pubdate
|
|
12
14
|
itemscope allowfullscreen default inert sortable
|
|
13
|
-
truespeed typemustmatch download]
|
|
15
|
+
truespeed typemustmatch download])
|
|
14
16
|
|
|
15
17
|
class AttributeCompiler
|
|
16
18
|
def initialize(identity, options)
|
|
@@ -23,12 +25,15 @@ module Haml
|
|
|
23
25
|
def compile(node)
|
|
24
26
|
hashes = []
|
|
25
27
|
return runtime_compile(node) if node.value[:object_ref] != :nil
|
|
26
|
-
|
|
28
|
+
dynamic_attributes = node.value[:dynamic_attributes]
|
|
29
|
+
[dynamic_attributes.new, dynamic_attributes.old].compact.each do |attribute_str|
|
|
27
30
|
hash = AttributeParser.parse(attribute_str)
|
|
28
31
|
return runtime_compile(node) if hash.nil? || hash.any? { |_key, value| value.empty? }
|
|
29
32
|
hashes << hash
|
|
30
33
|
end
|
|
31
|
-
static_compile(node.value[:attributes], hashes)
|
|
34
|
+
temple = static_compile(node.value[:attributes], hashes)
|
|
35
|
+
restore_newlines!(temple, dynamic_attributes)
|
|
36
|
+
temple
|
|
32
37
|
end
|
|
33
38
|
|
|
34
39
|
private
|
|
@@ -55,15 +60,24 @@ module Haml
|
|
|
55
60
|
compile_class!(temple, key, values)
|
|
56
61
|
when 'data', 'aria'
|
|
57
62
|
compile_data!(temple, key, values)
|
|
58
|
-
when *BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
|
|
59
|
-
compile_boolean!(temple, key, values)
|
|
60
63
|
else
|
|
61
|
-
|
|
64
|
+
if BOOLEAN_ATTRIBUTES.include?(key) || key.start_with?('data-', 'aria-')
|
|
65
|
+
compile_boolean!(temple, key, values)
|
|
66
|
+
else
|
|
67
|
+
compile_common!(temple, key, values)
|
|
68
|
+
end
|
|
62
69
|
end
|
|
63
70
|
end
|
|
64
71
|
temple
|
|
65
72
|
end
|
|
66
73
|
|
|
74
|
+
# ChildrenCompiler counts on the tag spanning as many lines as its attribute source does.
|
|
75
|
+
def restore_newlines!(temple, dynamic_attributes)
|
|
76
|
+
missing = dynamic_attributes.newline_count
|
|
77
|
+
missing -= TempleLineCounter.count_lines(temple) if missing > 0
|
|
78
|
+
missing.times { temple << [:newline] }
|
|
79
|
+
end
|
|
80
|
+
|
|
67
81
|
def compile_id!(temple, key, values)
|
|
68
82
|
build_code = attribute_builder(:id, values)
|
|
69
83
|
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
|
@@ -15,15 +15,11 @@ module Haml
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
# @return [Hash,nil] - keys and values are the attribute source as written, or nil if
|
|
18
|
-
# the text is not a Hash literal whose keys are all static.
|
|
18
|
+
# the text is not a Hash literal whose keys are all static, or if it holds a heredoc.
|
|
19
19
|
def parse(text)
|
|
20
20
|
exp = wrap_bracket(text)
|
|
21
|
-
# A multi-line hash is left to the runtime, which keeps the [:newline] bookkeeping of
|
|
22
|
-
# the compiled code correct. Compiling it statically is a separate optimization.
|
|
23
|
-
return if exp.include?("\n")
|
|
24
|
-
|
|
25
21
|
node = hash_node(exp)
|
|
26
|
-
return if node.nil?
|
|
22
|
+
return if node.nil? || contains_heredoc?(exp, node)
|
|
27
23
|
|
|
28
24
|
hash = {}
|
|
29
25
|
node.elements.each do |element|
|
|
@@ -32,7 +28,7 @@ module Haml
|
|
|
32
28
|
key = static_key(element.key)
|
|
33
29
|
return if key.nil?
|
|
34
30
|
|
|
35
|
-
hash[key] = value_source(element.value)
|
|
31
|
+
hash[in_source_encoding(key, exp)] = in_source_encoding(value_source(element.value), exp)
|
|
36
32
|
end
|
|
37
33
|
hash
|
|
38
34
|
end
|
|
@@ -58,6 +54,12 @@ module Haml
|
|
|
58
54
|
node if node.is_a?(Prism::HashNode)
|
|
59
55
|
end
|
|
60
56
|
|
|
57
|
+
# A heredoc's body lies outside its node, so the value's slice would not be the value.
|
|
58
|
+
# Its opener always spells `<<`, which spares the tree walk for nearly every hash.
|
|
59
|
+
def contains_heredoc?(exp, node)
|
|
60
|
+
exp.include?('<<') && !node.breadth_first_search { |n| n.respond_to?(:heredoc?) && n.heredoc? }.nil?
|
|
61
|
+
end
|
|
62
|
+
|
|
61
63
|
# The key as written between its delimiters, not unescaped: an escape has to reach the
|
|
62
64
|
# attribute name as the source spelled it, like the `\0` of `{ "a\0b" => 1 }`.
|
|
63
65
|
def static_key(key)
|
|
@@ -74,5 +76,15 @@ module Haml
|
|
|
74
76
|
|
|
75
77
|
value.slice
|
|
76
78
|
end
|
|
79
|
+
|
|
80
|
+
# Prism tags its slices with the encoding it parsed the source under (UTF-8 for a
|
|
81
|
+
# BINARY source), while the rest of the compiled template stays in the source
|
|
82
|
+
# encoding. Mixing the two raises Encoding::CompatibilityError once both sides hold
|
|
83
|
+
# non-ASCII bytes, so bring everything back to the source encoding.
|
|
84
|
+
def in_source_encoding(string, source)
|
|
85
|
+
return string if string.encoding == source.encoding
|
|
86
|
+
|
|
87
|
+
string.dup.force_encoding(source.encoding)
|
|
88
|
+
end
|
|
77
89
|
end
|
|
78
90
|
end
|
|
@@ -40,9 +40,7 @@ module Haml
|
|
|
40
40
|
when :script, :silent_script
|
|
41
41
|
@lineno += 1
|
|
42
42
|
when :tag
|
|
43
|
-
|
|
44
|
-
@lineno += attribute_hash.count("\n")
|
|
45
|
-
end
|
|
43
|
+
@lineno += node.value[:dynamic_attributes].newline_count
|
|
46
44
|
@lineno += 1 if node.children.empty? && node.value[:parse]
|
|
47
45
|
end
|
|
48
46
|
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
require 'temple/static_analyzer'
|
|
3
|
+
require 'haml/helpers'
|
|
4
|
+
require 'haml/preserver'
|
|
3
5
|
require 'haml/ruby_expression'
|
|
4
6
|
require 'haml/string_splitter'
|
|
5
7
|
|
|
6
8
|
module Haml
|
|
7
9
|
class Compiler
|
|
8
10
|
class ScriptCompiler
|
|
9
|
-
def self.find_and_preserve(input, tags)
|
|
10
|
-
|
|
11
|
-
re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
|
|
12
|
-
input.to_s.gsub(re) do |s|
|
|
13
|
-
s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
|
|
14
|
-
"<#{$1}#{$2}>#{Haml::Helpers.preserve($3)}</#{$1}>"
|
|
15
|
-
end
|
|
11
|
+
def self.find_and_preserve(input, tags = ::Haml::Preserver::DEFAULT_TAGS)
|
|
12
|
+
::Haml::Preserver.find_and_preserve(input, tags) { |content| ::Haml::Helpers.preserve(content) }
|
|
16
13
|
end
|
|
17
14
|
|
|
18
15
|
def initialize(identity, options)
|
|
@@ -69,7 +66,7 @@ module Haml
|
|
|
69
66
|
if node.value[:escape_html]
|
|
70
67
|
str = Haml::Util.escape_html(str)
|
|
71
68
|
elsif node.value[:preserve]
|
|
72
|
-
str = ScriptCompiler.find_and_preserve(str
|
|
69
|
+
str = ScriptCompiler.find_and_preserve(str)
|
|
73
70
|
end
|
|
74
71
|
[:multi, [:static, str], [:newline]]
|
|
75
72
|
end
|
|
@@ -104,7 +101,7 @@ module Haml
|
|
|
104
101
|
end
|
|
105
102
|
|
|
106
103
|
def find_and_preserve(code)
|
|
107
|
-
%Q[::Haml::Compiler::ScriptCompiler.find_and_preserve(#{code}
|
|
104
|
+
%Q[::Haml::Compiler::ScriptCompiler.find_and_preserve(#{code})]
|
|
108
105
|
end
|
|
109
106
|
|
|
110
107
|
def escape_html(temple)
|
data/lib/haml/dynamic_merger.rb
CHANGED
data/lib/haml/engine.rb
CHANGED
data/lib/haml/helpers.rb
CHANGED
data/lib/haml/parser.rb
CHANGED
|
@@ -227,7 +227,7 @@ module Haml
|
|
|
227
227
|
end
|
|
228
228
|
|
|
229
229
|
def inspect
|
|
230
|
-
%Q[(#{type} #{value.inspect}#{children.each_with_object(''
|
|
230
|
+
%Q[(#{type} #{value.inspect}#{children.each_with_object(+'') {|c, s| s << "\n#{c.inspect.gsub!(/^/, ' ')}"}})]
|
|
231
231
|
end
|
|
232
232
|
end
|
|
233
233
|
|
|
@@ -247,6 +247,12 @@ module Haml
|
|
|
247
247
|
[new, stripped_old].compact.join(', ')
|
|
248
248
|
end
|
|
249
249
|
|
|
250
|
+
# The lines a tag spans beyond its first because of these attributes, which is what
|
|
251
|
+
# the compiled tag has to span too for the line numbers after it to hold.
|
|
252
|
+
def newline_count
|
|
253
|
+
(new ? new.count("\n") : 0) + (old ? old.count("\n") : 0)
|
|
254
|
+
end
|
|
255
|
+
|
|
250
256
|
private
|
|
251
257
|
|
|
252
258
|
# For `%foo{ { foo: 1 }, bar: 2 }`, :old is "{ { foo: 1 }, bar: 2 }" and this method returns " { foo: 1 }, bar: 2 " for last argument.
|
|
@@ -397,7 +403,7 @@ module Haml
|
|
|
397
403
|
def haml_comment(text)
|
|
398
404
|
if filter_opened?
|
|
399
405
|
@flat = true
|
|
400
|
-
@filter_buffer =
|
|
406
|
+
@filter_buffer = +''
|
|
401
407
|
@filter_buffer << "#{text}\n" unless text.empty?
|
|
402
408
|
text = @filter_buffer
|
|
403
409
|
# If we don't know the indentation by now, it'll be set in Line#tabs
|
|
@@ -444,7 +450,8 @@ module Haml
|
|
|
444
450
|
end
|
|
445
451
|
else
|
|
446
452
|
if Util.contains_interpolation?(value)
|
|
447
|
-
value = Util.unescape_interpolation(value
|
|
453
|
+
value = Util.unescape_interpolation(value)
|
|
454
|
+
escape_interpolation = true if escape_html
|
|
448
455
|
parse = true
|
|
449
456
|
escape_html = false
|
|
450
457
|
end
|
|
@@ -532,7 +539,7 @@ module Haml
|
|
|
532
539
|
|
|
533
540
|
if filter_opened?
|
|
534
541
|
@flat = true
|
|
535
|
-
@filter_buffer =
|
|
542
|
+
@filter_buffer = +''
|
|
536
543
|
# If we don't know the indentation by now, it'll be set in Line#tabs
|
|
537
544
|
@flat_spaces = @indentation * (@template_tabs+1) if @indentation
|
|
538
545
|
end
|
|
@@ -742,7 +749,7 @@ module Haml
|
|
|
742
749
|
end
|
|
743
750
|
|
|
744
751
|
static_attributes = {}
|
|
745
|
-
dynamic_attributes = "{"
|
|
752
|
+
dynamic_attributes = +"{"
|
|
746
753
|
attributes.each do |name, (type, val)|
|
|
747
754
|
if type == :static
|
|
748
755
|
static_attributes[name] = val
|
|
@@ -782,7 +789,7 @@ module Haml
|
|
|
782
789
|
|
|
783
790
|
return name, [:static, content.first[1]] if content.size == 1
|
|
784
791
|
return name, [:dynamic,
|
|
785
|
-
%!"#{content.each_with_object(''
|
|
792
|
+
%!"#{content.each_with_object(+'') {|(t, v), s| s << (t == :str ? Util.inspect_obj(v)[1...-1] : "\#{#{v}}")}}"!]
|
|
786
793
|
end
|
|
787
794
|
|
|
788
795
|
def next_line
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Haml
|
|
3
|
+
# find_and_preserve, shared by compiled templates and RailsHelpers.
|
|
4
|
+
#
|
|
5
|
+
# @api private
|
|
6
|
+
module Preserver
|
|
7
|
+
DEFAULT_TAGS = %w[textarea pre code].freeze
|
|
8
|
+
|
|
9
|
+
def self.build_regex(tags)
|
|
10
|
+
# An empty entry would add an alternative matching `<>`.
|
|
11
|
+
pattern = tags.reject(&:empty?).map { |tag| Regexp.escape(tag) }.join('|')
|
|
12
|
+
/<(#{pattern})([^>]*)>(.*?)(<\/\1>)/im
|
|
13
|
+
end
|
|
14
|
+
private_class_method :build_regex
|
|
15
|
+
|
|
16
|
+
DEFAULT_REGEX = build_regex(DEFAULT_TAGS)
|
|
17
|
+
private_constant :DEFAULT_REGEX
|
|
18
|
+
|
|
19
|
+
# Past this many lists the merge below turns quadratic, so the cache starts over.
|
|
20
|
+
MAX_REGEXES = 64
|
|
21
|
+
private_constant :MAX_REGEXES
|
|
22
|
+
|
|
23
|
+
INITIAL_REGEXES = { DEFAULT_TAGS => DEFAULT_REGEX }.freeze
|
|
24
|
+
private_constant :INITIAL_REGEXES
|
|
25
|
+
|
|
26
|
+
@regexes = INITIAL_REGEXES
|
|
27
|
+
|
|
28
|
+
# The cache is replaced, never mutated, so a racing writer at worst rebuilds a regex.
|
|
29
|
+
def self.regex(tags)
|
|
30
|
+
return DEFAULT_REGEX if tags.equal?(DEFAULT_TAGS)
|
|
31
|
+
|
|
32
|
+
cache = @regexes
|
|
33
|
+
cache[tags] || begin
|
|
34
|
+
regex = build_regex(tags)
|
|
35
|
+
# Copied so a caller mutating its own list, or a String in it, cannot strand the entry.
|
|
36
|
+
key = tags.map { |tag| tag.dup.freeze }.freeze
|
|
37
|
+
cache = INITIAL_REGEXES if cache.size >= MAX_REGEXES
|
|
38
|
+
@regexes = cache.merge(key => regex).freeze
|
|
39
|
+
regex
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.find_and_preserve(input, tags)
|
|
44
|
+
re = regex(tags)
|
|
45
|
+
input.to_s.gsub(re) do |s|
|
|
46
|
+
s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
|
|
47
|
+
name, attributes, content = $1, $2, $3
|
|
48
|
+
"<#{name}#{attributes}>#{yield(content)}</#{name}>"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/haml/rails_helpers.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
# frozen_string_literal:
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
require 'haml/helpers'
|
|
3
|
+
require 'haml/preserver'
|
|
3
4
|
|
|
4
5
|
# There are only helpers that depend on ActionView internals.
|
|
5
6
|
module Haml
|
|
@@ -7,21 +8,12 @@ module Haml
|
|
|
7
8
|
include Helpers
|
|
8
9
|
extend self
|
|
9
10
|
|
|
10
|
-
DEFAULT_PRESERVE_TAGS =
|
|
11
|
+
DEFAULT_PRESERVE_TAGS = Preserver::DEFAULT_TAGS
|
|
11
12
|
|
|
12
13
|
def find_and_preserve(input = nil, tags = DEFAULT_PRESERVE_TAGS, &block)
|
|
13
14
|
return find_and_preserve(capture_haml(&block), input || tags) if block
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
s << '|' unless s.empty?
|
|
17
|
-
s << Regexp.escape(t)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
re = /<(#{tags})([^>]*)>(.*?)(<\/\1>)/im
|
|
21
|
-
input.to_s.gsub(re) do |s|
|
|
22
|
-
s =~ re # Can't rely on $1, etc. existing since Rails' SafeBuffer#gsub is incompatible
|
|
23
|
-
"<#{$1}#{$2}>#{preserve($3)}</#{$1}>"
|
|
24
|
-
end
|
|
16
|
+
Preserver.find_and_preserve(input, tags) { |content| preserve(content) }
|
|
25
17
|
end
|
|
26
18
|
|
|
27
19
|
def preserve(input = nil, &block)
|
data/lib/haml/string_splitter.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Haml
|
|
|
10
10
|
def compile(code, node: RubyExpression.string_literal_node(code))
|
|
11
11
|
case node
|
|
12
12
|
when Prism::StringNode
|
|
13
|
-
node.unescaped.empty? ? [] : [[:static, node.unescaped]]
|
|
13
|
+
node.unescaped.empty? ? [] : [[:static, in_source_encoding(node.unescaped, code)]]
|
|
14
14
|
when Prism::InterpolatedStringNode
|
|
15
15
|
compile_parts(node.parts, code)
|
|
16
16
|
else
|
|
@@ -33,12 +33,12 @@ module Haml
|
|
|
33
33
|
case part
|
|
34
34
|
when Prism::StringNode
|
|
35
35
|
content = part.unescaped
|
|
36
|
-
exps << [:static, content] unless content.empty?
|
|
36
|
+
exps << [:static, in_source_encoding(content, code)] unless content.empty?
|
|
37
37
|
when Prism::EmbeddedStatementsNode
|
|
38
38
|
embedded = embedded_source(part, code)
|
|
39
39
|
exps << [:dynamic, embedded] unless embedded.empty?
|
|
40
40
|
when Prism::EmbeddedVariableNode
|
|
41
|
-
exps << [:dynamic, part.variable.slice]
|
|
41
|
+
exps << [:dynamic, in_source_encoding(part.variable.slice, code)]
|
|
42
42
|
else
|
|
43
43
|
# Prism allows more part types than a string literal can currently hold. Dropping
|
|
44
44
|
# one would silently lose content, so fail instead of rendering something wrong.
|
|
@@ -54,6 +54,17 @@ module Haml
|
|
|
54
54
|
from = part.opening_loc.end_offset
|
|
55
55
|
code.byteslice(from, part.closing_loc.start_offset - from)
|
|
56
56
|
end
|
|
57
|
+
|
|
58
|
+
# Prism tags what it returns with the encoding it parsed `code` under (UTF-8 for
|
|
59
|
+
# a BINARY source), while the fragments Haml slices out of the source itself keep
|
|
60
|
+
# the source encoding. The compiled template mixes both kinds, so anything taken
|
|
61
|
+
# from Prism has to come back to the source encoding, or joining the fragments
|
|
62
|
+
# raises Encoding::CompatibilityError once both sides hold non-ASCII bytes.
|
|
63
|
+
def in_source_encoding(string, code)
|
|
64
|
+
return string if string.encoding == code.encoding
|
|
65
|
+
|
|
66
|
+
string.dup.force_encoding(code.encoding)
|
|
67
|
+
end
|
|
57
68
|
end
|
|
58
69
|
|
|
59
70
|
def on_dynamic(code)
|
data/lib/haml/template.rb
CHANGED
|
@@ -19,13 +19,27 @@ module Haml
|
|
|
19
19
|
arg.count("\n") + cases.map do |cond, e|
|
|
20
20
|
(cond == :else ? 0 : cond.count("\n")) + count_lines(e)
|
|
21
21
|
end.reduce(:+)
|
|
22
|
-
when :escape
|
|
22
|
+
when :escape, :fescape
|
|
23
23
|
count_lines(args[1])
|
|
24
|
+
when :html
|
|
25
|
+
count_html_lines(args)
|
|
24
26
|
when :newline
|
|
25
27
|
1
|
|
26
28
|
else
|
|
27
29
|
raise UnexpectedExpression.new("[HAML BUG] Unexpected Temple expression '#{type}' is given!")
|
|
28
30
|
end
|
|
29
31
|
end
|
|
32
|
+
|
|
33
|
+
def self.count_html_lines(args)
|
|
34
|
+
case args[0]
|
|
35
|
+
when :attrs
|
|
36
|
+
args.drop(1).sum { |a| count_lines(a) }
|
|
37
|
+
when :attr
|
|
38
|
+
count_lines(args[2])
|
|
39
|
+
else
|
|
40
|
+
raise UnexpectedExpression.new("[HAML BUG] Unexpected Temple expression 'html #{args[0]}' is given!")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
private_class_method :count_html_lines
|
|
30
44
|
end
|
|
31
45
|
end
|
data/lib/haml/util.rb
CHANGED
|
@@ -5,7 +5,6 @@ begin
|
|
|
5
5
|
rescue LoadError
|
|
6
6
|
require 'erb'
|
|
7
7
|
end
|
|
8
|
-
require 'set'
|
|
9
8
|
require 'stringio'
|
|
10
9
|
require 'strscan'
|
|
11
10
|
|
|
@@ -26,10 +25,9 @@ module Haml
|
|
|
26
25
|
end
|
|
27
26
|
end
|
|
28
27
|
|
|
29
|
-
# TODO: Remove unescape_interpolation's workaround and get rid of `respond_to?`.
|
|
30
28
|
def self.escape_html_safe(html)
|
|
31
29
|
html = html.to_s
|
|
32
|
-
|
|
30
|
+
html.html_safe? ? html : escape_html(html)
|
|
33
31
|
end
|
|
34
32
|
|
|
35
33
|
# Silence all output to STDERR within a block.
|
|
@@ -170,7 +168,7 @@ MSG
|
|
|
170
168
|
# and the rest of the string.
|
|
171
169
|
# `["Foo (Bar (Baz bang) bop)", " (Bang (bop bip))"]` in the example above.
|
|
172
170
|
def balance(scanner, start, finish, count = 0)
|
|
173
|
-
str = ''
|
|
171
|
+
str = +''
|
|
174
172
|
scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
|
|
175
173
|
regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
|
|
176
174
|
while scanner.scan(regexp)
|
|
@@ -202,8 +200,8 @@ MSG
|
|
|
202
200
|
/#[\{$@]/ === str
|
|
203
201
|
end
|
|
204
202
|
|
|
205
|
-
def unescape_interpolation(str
|
|
206
|
-
res = ''
|
|
203
|
+
def unescape_interpolation(str)
|
|
204
|
+
res = +''
|
|
207
205
|
rest = Haml::Util.handle_interpolation str.dump do |scan|
|
|
208
206
|
escapes = (scan[2].size - 1) / 2
|
|
209
207
|
char = scan[3] # '{', '@' or '$'
|
|
@@ -218,12 +216,17 @@ MSG
|
|
|
218
216
|
end
|
|
219
217
|
content = eval("\"#{interpolated}\"")
|
|
220
218
|
content = "#{char}#{content}" if char == '@' || char == '$'
|
|
221
|
-
content = "Haml::Util.escape_html_safe((#{content}).to_s)" if escape_html
|
|
222
219
|
|
|
223
220
|
res << "\#{#{content}}"
|
|
224
221
|
end
|
|
225
222
|
end
|
|
226
|
-
|
|
223
|
+
# The result is rebuilt from `str.dump` fragments and eval'd pieces, so it can
|
|
224
|
+
# come out tagged with an encoding that differs from `str`'s (e.g. UTF-8 for a
|
|
225
|
+
# BINARY source). The compiled template joins this string literal's fragments
|
|
226
|
+
# with fragments sliced out of the source itself, and mixed encodings raise
|
|
227
|
+
# Encoding::CompatibilityError, so bring it back to the source encoding.
|
|
228
|
+
# See haml/haml#1218.
|
|
229
|
+
(res + rest).force_encoding(str.encoding)
|
|
227
230
|
end
|
|
228
231
|
|
|
229
232
|
private
|
data/lib/haml/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Natalie Weizenbaum
|
|
@@ -307,6 +307,7 @@ files:
|
|
|
307
307
|
- lib/haml/identity.rb
|
|
308
308
|
- lib/haml/object_ref.rb
|
|
309
309
|
- lib/haml/parser.rb
|
|
310
|
+
- lib/haml/preserver.rb
|
|
310
311
|
- lib/haml/rails_helpers.rb
|
|
311
312
|
- lib/haml/rails_template.rb
|
|
312
313
|
- lib/haml/railtie.rb
|