haml 6.4.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/.github/workflows/test.yml +1 -5
- data/CHANGELOG.md +64 -0
- data/Gemfile +6 -14
- data/README.md +1 -1
- data/REFERENCE.md +1 -1
- data/bin/console +3 -4
- data/bin/stackprof +2 -2
- data/haml.gemspec +6 -3
- data/lib/haml/attribute_builder.rb +25 -14
- data/lib/haml/attribute_compiler.rb +24 -12
- data/lib/haml/attribute_parser.rb +53 -79
- data/lib/haml/compiler/children_compiler.rb +1 -3
- data/lib/haml/compiler/script_compiler.rb +12 -14
- data/lib/haml/compiler/tag_compiler.rb +8 -5
- data/lib/haml/dynamic_merger.rb +1 -1
- data/lib/haml/engine.rb +3 -3
- data/lib/haml/filters/plain.rb +6 -1
- data/lib/haml/helpers.rb +1 -1
- data/lib/haml/parser.rb +48 -30
- data/lib/haml/preserver.rb +52 -0
- data/lib/haml/rails_helpers.rb +4 -12
- data/lib/haml/ruby_expression.rb +25 -18
- data/lib/haml/string_splitter.rb +61 -113
- data/lib/haml/template.rb +1 -1
- data/lib/haml/temple_line_counter.rb +15 -1
- data/lib/haml/util.rb +13 -10
- data/lib/haml/version.rb +1 -1
- data/lib/haml.rb +1 -5
- metadata +21 -17
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/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
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
|
+
|
|
28
|
+
## 7.3.0
|
|
29
|
+
|
|
30
|
+
* Replace Ripper with Prism https://github.com/haml/haml/pull/1214
|
|
31
|
+
* Interpolate `#@ivar`, `#$gvar` and `#@@cvar` in string literals instead of dropping them
|
|
32
|
+
* Keep an escaped delimiter of a percent literal, so `= %q{a\}b}` renders `a}b`
|
|
33
|
+
* Deprecate `Haml::AttributeParser.available?`, which is now always true and will be removed in the future
|
|
34
|
+
|
|
35
|
+
## 7.2.2
|
|
36
|
+
|
|
37
|
+
* Remove obsolete TruffleRuby compatibility skips https://github.com/haml/haml/pull/1210
|
|
38
|
+
|
|
39
|
+
## 7.2.1
|
|
40
|
+
|
|
41
|
+
* Do not rely on Ripper quirk in parsing old-style Haml attributes https://github.com/haml/haml/pull/1212
|
|
42
|
+
|
|
43
|
+
## 7.2.0
|
|
44
|
+
|
|
45
|
+
* Do not require rails when haml is required https://github.com/haml/haml/pull/1201
|
|
46
|
+
* Point `changelog_uri` to GitHub Releases https://github.com/haml/haml/pull/1202
|
|
47
|
+
|
|
48
|
+
## 7.1.0
|
|
49
|
+
|
|
50
|
+
* Support xhtml format for boolean nested data attributes https://github.com/haml/haml/pull/1200
|
|
51
|
+
|
|
52
|
+
## 7.0.2
|
|
53
|
+
|
|
54
|
+
* Replace usages of `=~` with `.match?` where possible https://github.com/haml/haml/pull/1196
|
|
55
|
+
* Add source code URI to gemspec metadata https://github.com/haml/haml/pull/1197
|
|
56
|
+
|
|
57
|
+
## 7.0.1
|
|
58
|
+
|
|
59
|
+
* Use `Regexp#match?` predicate where possible https://github.com/haml/haml/pull/1194
|
|
60
|
+
* Fix the 'Changelog' link on rubygems.org/gems/haml https://github.com/haml/haml/pull/1192
|
|
61
|
+
|
|
62
|
+
## 7.0.0
|
|
63
|
+
|
|
64
|
+
* Change the default `attr_quote` from `'` to `"` https://github.com/haml/haml/issues/1188
|
|
65
|
+
* Bump required Ruby version to 3.2 https://github.com/haml/haml/issues/1176
|
|
66
|
+
|
|
3
67
|
## 6.4.0
|
|
4
68
|
|
|
5
69
|
* Authorize characters in attribute name for vuejs https://github.com/haml/haml/pull/1172
|
data/Gemfile
CHANGED
|
@@ -10,25 +10,17 @@ gemspec
|
|
|
10
10
|
|
|
11
11
|
gem 'benchmark-ips', '2.3.0'
|
|
12
12
|
gem 'maxitest'
|
|
13
|
-
gem '
|
|
13
|
+
gem 'base64'
|
|
14
|
+
gem 'bigdecimal'
|
|
15
|
+
gem 'mutex_m'
|
|
14
16
|
|
|
15
|
-
if /java
|
|
17
|
+
if /java/.match?(RUBY_PLATFORM) # JRuby
|
|
16
18
|
gem 'pandoc-ruby'
|
|
19
|
+
gem 'rdoc', '< 8' # rdoc 8 depends on rbs, whose native extension doesn't build on JRuby
|
|
17
20
|
else
|
|
18
21
|
gem 'redcarpet'
|
|
19
22
|
|
|
20
|
-
if RUBY_PLATFORM !~ /mswin|mingw/
|
|
23
|
+
if RUBY_PLATFORM !~ /mswin|mingw/
|
|
21
24
|
gem 'stackprof'
|
|
22
25
|
end
|
|
23
26
|
end
|
|
24
|
-
|
|
25
|
-
if RUBY_VERSION < '2.6'
|
|
26
|
-
gem 'rake-compiler', '< 1.2.4'
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# Temporary workaround to ensure Ruby 2.5 and 2.6 can pass tests with Rails 6.1
|
|
30
|
-
# Reference: https://github.com/rails/rails/issues/54260
|
|
31
|
-
# TODO: Remove this workaround when dropping support for Rails versions below 7.1
|
|
32
|
-
if RUBY_VERSION < '2.7'
|
|
33
|
-
gem 'concurrent-ruby', '< 1.3.5'
|
|
34
|
-
end
|
data/README.md
CHANGED
|
@@ -157,7 +157,7 @@ on a specific area:
|
|
|
157
157
|
ruby -Itest test/helper_test.rb -n test_buffer_access
|
|
158
158
|
~~~
|
|
159
159
|
|
|
160
|
-
Haml currently supports Ruby 2.0
|
|
160
|
+
Haml currently supports Ruby 3.2.0 and higher, so please make sure your changes run on 3.2+.
|
|
161
161
|
|
|
162
162
|
## Team
|
|
163
163
|
|
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}
|
data/bin/console
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
3
|
require 'bundler/setup'
|
|
4
|
-
require '
|
|
4
|
+
require 'haml'
|
|
5
5
|
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Pry.start
|
|
9
|
+
require 'irb'
|
|
10
|
+
IRB.start
|
data/bin/stackprof
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
3
|
require 'bundler/setup'
|
|
4
|
-
require '
|
|
4
|
+
require 'haml'
|
|
5
5
|
require 'stackprof'
|
|
6
6
|
|
|
7
7
|
def open_flamegraph(report)
|
|
@@ -20,7 +20,7 @@ end
|
|
|
20
20
|
|
|
21
21
|
haml = File.read(ARGV.first)
|
|
22
22
|
StackProf.start(mode: :wall, interval: 1, raw: false)
|
|
23
|
-
|
|
23
|
+
Haml::Engine.new.call(haml)
|
|
24
24
|
StackProf.stop
|
|
25
25
|
|
|
26
26
|
report = StackProf::Report.new(StackProf.results)
|
data/haml.gemspec
CHANGED
|
@@ -21,10 +21,14 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
|
|
22
22
|
spec.metadata = { 'rubygems_mfa_required' => 'true' }
|
|
23
23
|
|
|
24
|
-
spec.metadata["
|
|
24
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/haml/haml/issues"
|
|
25
|
+
spec.metadata["changelog_uri"] = "https://github.com/haml/haml/blob/main/CHANGELOG.md"
|
|
26
|
+
spec.metadata["homepage_uri"] = "https://haml.info/"
|
|
27
|
+
spec.metadata["source_code_uri"] = "https://github.com/haml/haml"
|
|
25
28
|
|
|
26
|
-
spec.required_ruby_version = '>= 2.
|
|
29
|
+
spec.required_ruby_version = '>= 3.2.0'
|
|
27
30
|
|
|
31
|
+
spec.add_dependency 'prism', '>= 1.1.0'
|
|
28
32
|
spec.add_dependency 'temple', '>= 0.8.2'
|
|
29
33
|
spec.add_dependency 'thor'
|
|
30
34
|
spec.add_dependency 'tilt'
|
|
@@ -33,7 +37,6 @@ Gem::Specification.new do |spec|
|
|
|
33
37
|
spec.add_development_dependency 'bundler'
|
|
34
38
|
spec.add_development_dependency 'coffee-script'
|
|
35
39
|
spec.add_development_dependency 'erubi'
|
|
36
|
-
spec.add_development_dependency 'haml', '>= 5'
|
|
37
40
|
spec.add_development_dependency 'less'
|
|
38
41
|
spec.add_development_dependency 'minitest-reporters', '~> 1.1'
|
|
39
42
|
spec.add_development_dependency 'rails', '>= 4.0'
|
|
@@ -16,13 +16,15 @@ module Haml::AttributeBuilder
|
|
|
16
16
|
when 'class'
|
|
17
17
|
buf << " class=#{quote}#{build_class(escape_attrs, *hash[key])}#{quote}"
|
|
18
18
|
when 'data'
|
|
19
|
-
buf << build_data(escape_attrs, quote, *hash[key])
|
|
19
|
+
buf << build_data(escape_attrs, quote, format, *hash[key])
|
|
20
20
|
when 'aria'
|
|
21
|
-
buf << build_aria(escape_attrs, quote, *hash[key])
|
|
22
|
-
when *Haml::BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
|
|
23
|
-
build_boolean!(escape_attrs, quote, format, buf, key, hash[key])
|
|
21
|
+
buf << build_aria(escape_attrs, quote, format, *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
|
|
@@ -62,17 +64,21 @@ module Haml::AttributeBuilder
|
|
|
62
64
|
escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
|
|
63
65
|
end
|
|
64
66
|
|
|
65
|
-
def build_data(escape_attrs, quote, *hashes)
|
|
66
|
-
build_data_attribute(:data, escape_attrs, quote, *hashes)
|
|
67
|
+
def build_data(escape_attrs, quote, format, *hashes)
|
|
68
|
+
build_data_attribute(:data, escape_attrs, quote, format, *hashes)
|
|
67
69
|
end
|
|
68
70
|
|
|
69
|
-
def build_aria(escape_attrs, quote, *hashes)
|
|
70
|
-
build_data_attribute(:aria, escape_attrs, quote, *hashes)
|
|
71
|
+
def build_aria(escape_attrs, quote, format, *hashes)
|
|
72
|
+
build_data_attribute(:aria, escape_attrs, quote, format, *hashes)
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
private
|
|
74
76
|
|
|
75
|
-
def
|
|
77
|
+
def boolean_attribute?(key)
|
|
78
|
+
Haml::BOOLEAN_ATTRIBUTES.include?(key) || key.start_with?('data-', 'aria-')
|
|
79
|
+
end
|
|
80
|
+
|
|
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) }
|
|
78
84
|
data_value = merge_all_attrs(hashes)
|
|
@@ -84,7 +90,7 @@ module Haml::AttributeBuilder
|
|
|
84
90
|
hash.sort_by(&:first).each do |key, value|
|
|
85
91
|
case value
|
|
86
92
|
when true
|
|
87
|
-
|
|
93
|
+
build_boolean!(escape_attrs, quote, format, attrs, key, value)
|
|
88
94
|
when nil, false
|
|
89
95
|
# noop
|
|
90
96
|
else
|
|
@@ -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)
|
|
@@ -22,15 +24,16 @@ module Haml
|
|
|
22
24
|
|
|
23
25
|
def compile(node)
|
|
24
26
|
hashes = []
|
|
25
|
-
if node.value[:object_ref] != :nil
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
|
|
27
|
+
return runtime_compile(node) if node.value[:object_ref] != :nil
|
|
28
|
+
dynamic_attributes = node.value[:dynamic_attributes]
|
|
29
|
+
[dynamic_attributes.new, dynamic_attributes.old].compact.each do |attribute_str|
|
|
29
30
|
hash = AttributeParser.parse(attribute_str)
|
|
30
31
|
return runtime_compile(node) if hash.nil? || hash.any? { |_key, value| value.empty? }
|
|
31
32
|
hashes << hash
|
|
32
33
|
end
|
|
33
|
-
static_compile(node.value[:attributes], hashes)
|
|
34
|
+
temple = static_compile(node.value[:attributes], hashes)
|
|
35
|
+
restore_newlines!(temple, dynamic_attributes)
|
|
36
|
+
temple
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
private
|
|
@@ -57,15 +60,24 @@ module Haml
|
|
|
57
60
|
compile_class!(temple, key, values)
|
|
58
61
|
when 'data', 'aria'
|
|
59
62
|
compile_data!(temple, key, values)
|
|
60
|
-
when *BOOLEAN_ATTRIBUTES, /\Adata-/, /\Aaria-/
|
|
61
|
-
compile_boolean!(temple, key, values)
|
|
62
63
|
else
|
|
63
|
-
|
|
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
|
|
64
69
|
end
|
|
65
70
|
end
|
|
66
71
|
temple
|
|
67
72
|
end
|
|
68
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
|
+
|
|
69
81
|
def compile_id!(temple, key, values)
|
|
70
82
|
build_code = attribute_builder(:id, values)
|
|
71
83
|
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
|
@@ -85,7 +97,7 @@ module Haml
|
|
|
85
97
|
end
|
|
86
98
|
|
|
87
99
|
def compile_data!(temple, key, values)
|
|
88
|
-
args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", values.map { |v| literal_for(v) }]
|
|
100
|
+
args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect, values.map { |v| literal_for(v) }]
|
|
89
101
|
build_code = "::Haml::AttributeBuilder.build_#{key}(#{args.join(', ')})"
|
|
90
102
|
|
|
91
103
|
if values.all? { |type, exp| type == :static || Temple::StaticAnalyzer.static?(exp) }
|
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
require '
|
|
2
|
+
require 'prism'
|
|
3
3
|
|
|
4
4
|
module Haml
|
|
5
5
|
class AttributeParser
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
# @return [TrueClass, FalseClass] - return true if AttributeParser.parse can be used.
|
|
6
|
+
# @deprecated Prism is a hard dependency, so this is always true. Haml itself no longer
|
|
7
|
+
# asks, and this will be removed in the future.
|
|
8
|
+
# @return [TrueClass] - return true if AttributeParser.parse can be used.
|
|
10
9
|
def self.available?
|
|
11
|
-
|
|
12
|
-
defined?(Ripper) && Ripper.respond_to?(:lex) && Temple::StaticAnalyzer.available?
|
|
10
|
+
true
|
|
13
11
|
end
|
|
14
12
|
|
|
15
13
|
def self.parse(text)
|
|
16
14
|
self.new.parse(text)
|
|
17
15
|
end
|
|
18
16
|
|
|
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, or if it holds a heredoc.
|
|
19
19
|
def parse(text)
|
|
20
20
|
exp = wrap_bracket(text)
|
|
21
|
-
|
|
21
|
+
node = hash_node(exp)
|
|
22
|
+
return if node.nil? || contains_heredoc?(exp, node)
|
|
22
23
|
|
|
23
24
|
hash = {}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
node.elements.each do |element|
|
|
26
|
+
return unless element.is_a?(Prism::AssocNode)
|
|
27
|
+
|
|
28
|
+
key = static_key(element.key)
|
|
29
|
+
return if key.nil?
|
|
30
|
+
|
|
31
|
+
hash[in_source_encoding(key, exp)] = in_source_encoding(value_source(element.value), exp)
|
|
28
32
|
end
|
|
29
33
|
hash
|
|
30
|
-
rescue ParseSkip
|
|
31
|
-
nil
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
private
|
|
@@ -39,78 +41,50 @@ module Haml
|
|
|
39
41
|
"{#{text}}"
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
key
|
|
54
|
-
when :on_tstring_beg
|
|
55
|
-
_, _, key = tokens.shift
|
|
56
|
-
next_token = tokens.shift
|
|
57
|
-
unless next_token[1] == :on_label_end
|
|
58
|
-
assert_type!(next_token, :on_tstring_end)
|
|
59
|
-
skip_until_hash_rocket!(tokens)
|
|
60
|
-
end
|
|
61
|
-
key
|
|
62
|
-
else
|
|
63
|
-
raise ParseSkip
|
|
64
|
-
end
|
|
44
|
+
def hash_node(exp)
|
|
45
|
+
# partial_script, because an attribute may legitimately call `yield` and the like:
|
|
46
|
+
# a template is compiled into a method body.
|
|
47
|
+
result = Prism.parse(exp, partial_script: true)
|
|
48
|
+
return if result.failure?
|
|
49
|
+
|
|
50
|
+
statements = result.value.statements.body
|
|
51
|
+
return if statements.size != 1
|
|
52
|
+
|
|
53
|
+
node = statements.first
|
|
54
|
+
node if node.is_a?(Prism::HashNode)
|
|
65
55
|
end
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
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?
|
|
69
61
|
end
|
|
70
62
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
# The key as written between its delimiters, not unescaped: an escape has to reach the
|
|
64
|
+
# attribute name as the source spelled it, like the `\0` of `{ "a\0b" => 1 }`.
|
|
65
|
+
def static_key(key)
|
|
66
|
+
case key
|
|
67
|
+
when Prism::SymbolNode then key.value_loc&.slice
|
|
68
|
+
when Prism::StringNode then key.content_loc&.slice
|
|
75
69
|
end
|
|
76
70
|
end
|
|
77
71
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
open_tokens[:array] -= 1
|
|
95
|
-
when :on_lbrace
|
|
96
|
-
open_tokens[:block] += 1
|
|
97
|
-
when :on_rbrace
|
|
98
|
-
open_tokens[:block] -= 1
|
|
99
|
-
when :on_lparen
|
|
100
|
-
open_tokens[:paren] += 1
|
|
101
|
-
when :on_rparen
|
|
102
|
-
open_tokens[:paren] -= 1
|
|
103
|
-
when :on_embexpr_beg
|
|
104
|
-
open_tokens[:embexpr] += 1
|
|
105
|
-
when :on_embexpr_end
|
|
106
|
-
open_tokens[:embexpr] -= 1
|
|
107
|
-
when :on_sp
|
|
108
|
-
next if attr_tokens.empty?
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
attr_tokens << token
|
|
112
|
-
end
|
|
113
|
-
yield(attr_tokens) unless attr_tokens.empty?
|
|
72
|
+
def value_source(value)
|
|
73
|
+
# Ruby 3.1 value omission (`{ foo: }`). An empty value tells AttributeCompiler to
|
|
74
|
+
# fall back to the runtime, which is what resolves it.
|
|
75
|
+
return '' if value.is_a?(Prism::ImplicitNode)
|
|
76
|
+
|
|
77
|
+
value.slice
|
|
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)
|
|
114
88
|
end
|
|
115
89
|
end
|
|
116
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)
|
|
@@ -21,10 +18,6 @@ module Haml
|
|
|
21
18
|
end
|
|
22
19
|
|
|
23
20
|
def compile(node, &block)
|
|
24
|
-
unless Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
|
|
25
|
-
return dynamic_compile(node, &block)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
21
|
no_children = node.children.empty?
|
|
29
22
|
case
|
|
30
23
|
when no_children && node.value[:escape_interpolation]
|
|
@@ -43,8 +36,13 @@ module Haml
|
|
|
43
36
|
# String-interpolated plain text must be compiled with this method
|
|
44
37
|
# because we have to escape only interpolated values.
|
|
45
38
|
def compile_interpolated_plain(node)
|
|
39
|
+
compiled = StringSplitter.try_compile(node.value[:text])
|
|
40
|
+
# Ruby that does not parse: let the generated code report it, rather than failing
|
|
41
|
+
# the whole compilation with an error that points at Haml instead of the template.
|
|
42
|
+
return delegate_optimization(node) if compiled.nil?
|
|
43
|
+
|
|
46
44
|
temple = [:multi]
|
|
47
|
-
|
|
45
|
+
compiled.each do |type, value|
|
|
48
46
|
case type
|
|
49
47
|
when :static
|
|
50
48
|
temple << [:static, value]
|
|
@@ -68,7 +66,7 @@ module Haml
|
|
|
68
66
|
if node.value[:escape_html]
|
|
69
67
|
str = Haml::Util.escape_html(str)
|
|
70
68
|
elsif node.value[:preserve]
|
|
71
|
-
str = ScriptCompiler.find_and_preserve(str
|
|
69
|
+
str = ScriptCompiler.find_and_preserve(str)
|
|
72
70
|
end
|
|
73
71
|
[:multi, [:static, str], [:newline]]
|
|
74
72
|
end
|
|
@@ -103,7 +101,7 @@ module Haml
|
|
|
103
101
|
end
|
|
104
102
|
|
|
105
103
|
def find_and_preserve(code)
|
|
106
|
-
%Q[::Haml::Compiler::ScriptCompiler.find_and_preserve(#{code}
|
|
104
|
+
%Q[::Haml::Compiler::ScriptCompiler.find_and_preserve(#{code})]
|
|
107
105
|
end
|
|
108
106
|
|
|
109
107
|
def escape_html(temple)
|
|
@@ -28,10 +28,8 @@ module Haml
|
|
|
28
28
|
nil
|
|
29
29
|
when node.value[:parse]
|
|
30
30
|
return compile_interpolated_plain(node) if node.value[:escape_interpolation]
|
|
31
|
-
if
|
|
32
|
-
|
|
33
|
-
return delegate_optimization(node) if Temple::StaticAnalyzer.static?(node.value[:value])
|
|
34
|
-
end
|
|
31
|
+
return delegate_optimization(node) if RubyExpression.string_literal?(node.value[:value])
|
|
32
|
+
return delegate_optimization(node) if Temple::StaticAnalyzer.static?(node.value[:value])
|
|
35
33
|
|
|
36
34
|
var = @identity.generate
|
|
37
35
|
[:multi,
|
|
@@ -55,8 +53,13 @@ module Haml
|
|
|
55
53
|
|
|
56
54
|
# We should handle interpolation here to escape only interpolated values.
|
|
57
55
|
def compile_interpolated_plain(node)
|
|
56
|
+
compiled = StringSplitter.try_compile(node.value[:value])
|
|
57
|
+
# Ruby that does not parse: let the generated code report it, rather than failing
|
|
58
|
+
# the whole compilation with an error that points at Haml instead of the template.
|
|
59
|
+
return delegate_optimization(node) if compiled.nil?
|
|
60
|
+
|
|
58
61
|
temple = [:multi]
|
|
59
|
-
|
|
62
|
+
compiled.each do |type, value|
|
|
60
63
|
case type
|
|
61
64
|
when :static
|
|
62
65
|
temple << [:static, value]
|