hamlit 2.9.4-java → 2.12.0-java
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 +12 -10
- data/CHANGELOG.md +45 -0
- data/Gemfile +2 -8
- data/REFERENCE.md +13 -4
- data/benchmark/dynamic_merger/benchmark.rb +25 -0
- data/benchmark/dynamic_merger/hello.haml +50 -0
- data/benchmark/dynamic_merger/hello.string +50 -0
- data/bin/bench +3 -3
- data/ext/hamlit/hamlit.c +0 -1
- data/hamlit.gemspec +3 -1
- data/lib/hamlit/attribute_builder.rb +2 -2
- data/lib/hamlit/attribute_compiler.rb +3 -3
- data/lib/hamlit/compiler/children_compiler.rb +18 -4
- data/lib/hamlit/compiler/comment_compiler.rb +7 -5
- data/lib/hamlit/compiler/tag_compiler.rb +0 -4
- data/lib/hamlit/dynamic_merger.rb +67 -0
- data/lib/hamlit/engine.rb +5 -6
- data/lib/hamlit/filters/plain.rb +0 -3
- data/lib/hamlit/html.rb +8 -0
- data/lib/hamlit/parser/haml_attribute_builder.rb +164 -0
- data/lib/hamlit/parser/haml_helpers.rb +6 -0
- data/lib/hamlit/parser/haml_parser.rb +32 -9
- data/lib/hamlit/parser/haml_xss_mods.rb +6 -3
- data/lib/hamlit/string_splitter.rb +9 -78
- data/lib/hamlit/template.rb +1 -1
- data/lib/hamlit/temple_line_counter.rb +31 -0
- data/lib/hamlit/version.rb +1 -1
- metadata +53 -21
- data/lib/hamlit/hamlit.su +0 -0
@@ -25,11 +25,13 @@ module Hamlit
|
|
25
25
|
condition = $1
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
content =
|
29
|
+
if node.children.empty?
|
30
|
+
[:static, " #{node.value[:text]} "]
|
31
|
+
else
|
32
|
+
yield(node)
|
33
|
+
end
|
34
|
+
[:html, :condcomment, condition, content, node.value[:revealed]]
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
@@ -55,10 +55,6 @@ module Hamlit
|
|
55
55
|
|
56
56
|
# We should handle interpolation here to escape only interpolated values.
|
57
57
|
def compile_interpolated_plain(node)
|
58
|
-
unless Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
|
59
|
-
return [:multi, [:escape, node.value[:escape_interpolation], [:dynamic, "%Q[#{node.value[:value]}]"]], [:newline]]
|
60
|
-
end
|
61
|
-
|
62
58
|
temple = [:multi]
|
63
59
|
StringSplitter.compile(node.value[:value]).each do |type, value|
|
64
60
|
case type
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Hamlit
|
3
|
+
# Compile [:multi, [:static, 'foo'], [:dynamic, 'bar']] to [:dynamic, '"foo#{bar}"']
|
4
|
+
class DynamicMerger < Temple::Filter
|
5
|
+
def on_multi(*exps)
|
6
|
+
exps = exps.dup
|
7
|
+
result = [:multi]
|
8
|
+
buffer = []
|
9
|
+
|
10
|
+
until exps.empty?
|
11
|
+
type, arg = exps.first
|
12
|
+
if type == :dynamic && arg.count("\n") == 0
|
13
|
+
buffer << exps.shift
|
14
|
+
elsif type == :static && exps.size > (count = arg.count("\n")) &&
|
15
|
+
exps[1, count].all? { |e| e == [:newline] }
|
16
|
+
(1 + count).times { buffer << exps.shift }
|
17
|
+
elsif type == :newline && exps.size > (count = count_newline(exps)) &&
|
18
|
+
exps[count].first == :static && count == exps[count].last.count("\n")
|
19
|
+
(count + 1).times { buffer << exps.shift }
|
20
|
+
else
|
21
|
+
result.concat(merge_dynamic(buffer))
|
22
|
+
buffer = []
|
23
|
+
result << compile(exps.shift)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
result.concat(merge_dynamic(buffer))
|
27
|
+
|
28
|
+
result.size == 2 ? result[1] : result
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def merge_dynamic(exps)
|
34
|
+
# Merge exps only when they have both :static and :dynamic
|
35
|
+
unless exps.any? { |type,| type == :static } && exps.any? { |type,| type == :dynamic }
|
36
|
+
return exps
|
37
|
+
end
|
38
|
+
|
39
|
+
strlit_body = String.new
|
40
|
+
exps.each do |type, arg|
|
41
|
+
case type
|
42
|
+
when :static
|
43
|
+
strlit_body << arg.dump.sub!(/\A"/, '').sub!(/"\z/, '').gsub('\n', "\n")
|
44
|
+
when :dynamic
|
45
|
+
strlit_body << "\#{#{arg}}"
|
46
|
+
when :newline
|
47
|
+
# newline is added by `gsub('\n', "\n")`
|
48
|
+
else
|
49
|
+
raise "unexpected type #{type.inspect} is given to #merge_dynamic"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
[[:dynamic, "%Q\0#{strlit_body}\0"]]
|
53
|
+
end
|
54
|
+
|
55
|
+
def count_newline(exps)
|
56
|
+
count = 0
|
57
|
+
exps.each do |exp|
|
58
|
+
if exp == [:newline]
|
59
|
+
count += 1
|
60
|
+
else
|
61
|
+
return count
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return count
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/hamlit/engine.rb
CHANGED
@@ -2,10 +2,10 @@
|
|
2
2
|
require 'temple'
|
3
3
|
require 'hamlit/parser'
|
4
4
|
require 'hamlit/compiler'
|
5
|
+
require 'hamlit/html'
|
5
6
|
require 'hamlit/escapable'
|
6
7
|
require 'hamlit/force_escapable'
|
7
|
-
require 'hamlit/
|
8
|
-
require 'hamlit/string_splitter'
|
8
|
+
require 'hamlit/dynamic_merger'
|
9
9
|
|
10
10
|
module Hamlit
|
11
11
|
class Engine < Temple::Engine
|
@@ -25,15 +25,14 @@ module Hamlit
|
|
25
25
|
use Parser
|
26
26
|
use Compiler
|
27
27
|
use HTML
|
28
|
-
|
29
|
-
|
30
|
-
filter :StaticAnalyzer
|
31
|
-
end
|
28
|
+
filter :StringSplitter
|
29
|
+
filter :StaticAnalyzer
|
32
30
|
use Escapable
|
33
31
|
use ForceEscapable
|
34
32
|
filter :ControlFlow
|
35
33
|
filter :MultiFlattener
|
36
34
|
filter :StaticMerger
|
35
|
+
use DynamicMerger
|
37
36
|
use :Generator, -> { options[:generator] }
|
38
37
|
end
|
39
38
|
end
|
data/lib/hamlit/filters/plain.rb
CHANGED
@@ -5,9 +5,6 @@ module Hamlit
|
|
5
5
|
class Filters
|
6
6
|
class Plain < Base
|
7
7
|
def compile(node)
|
8
|
-
unless Ripper.respond_to?(:lex)
|
9
|
-
raise NotImplementedError.new('This platform does not have Ripper.lex required for :plain filter')
|
10
|
-
end
|
11
8
|
text = node.value[:text]
|
12
9
|
text = text.rstrip unless ::Hamlit::HamlUtil.contains_interpolation?(text) # for compatibility
|
13
10
|
[:multi, *compile_plain(text)]
|
data/lib/hamlit/html.rb
CHANGED
@@ -10,5 +10,13 @@ module Hamlit
|
|
10
10
|
end
|
11
11
|
super(opts)
|
12
12
|
end
|
13
|
+
|
14
|
+
# This dispatcher supports Haml's "revealed" conditional comment.
|
15
|
+
def on_html_condcomment(condition, content, revealed = false)
|
16
|
+
on_html_comment [:multi,
|
17
|
+
[:static, "[#{condition}]>#{'<!-->' if revealed}"],
|
18
|
+
content,
|
19
|
+
[:static, "#{'<!--' if revealed}<![endif]"]]
|
20
|
+
end
|
13
21
|
end
|
14
22
|
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hamlit
|
4
|
+
module HamlAttributeBuilder
|
5
|
+
# https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
|
6
|
+
INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
|
10
|
+
# @TODO this is an absolutely ridiculous amount of arguments. At least
|
11
|
+
# some of this needs to be moved into an instance method.
|
12
|
+
join_char = hyphenate_data_attrs ? '-' : '_'
|
13
|
+
|
14
|
+
attributes.each do |key, value|
|
15
|
+
if value.is_a?(Hash)
|
16
|
+
data_attributes = attributes.delete(key)
|
17
|
+
data_attributes = flatten_data_attributes(data_attributes, '', join_char)
|
18
|
+
data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
|
19
|
+
verify_attribute_names!(data_attributes.keys)
|
20
|
+
attributes = data_attributes.merge(attributes)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
result = attributes.collect do |attr, value|
|
25
|
+
next if value.nil?
|
26
|
+
|
27
|
+
value = filter_and_join(value, ' ') if attr == 'class'
|
28
|
+
value = filter_and_join(value, '_') if attr == 'id'
|
29
|
+
|
30
|
+
if value == true
|
31
|
+
next " #{attr}" if is_html
|
32
|
+
next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
|
33
|
+
elsif value == false
|
34
|
+
next
|
35
|
+
end
|
36
|
+
|
37
|
+
value =
|
38
|
+
if escape_attrs == :once
|
39
|
+
Hamlit::HamlHelpers.escape_once_without_haml_xss(value.to_s)
|
40
|
+
elsif escape_attrs
|
41
|
+
Hamlit::HamlHelpers.html_escape_without_haml_xss(value.to_s)
|
42
|
+
else
|
43
|
+
value.to_s
|
44
|
+
end
|
45
|
+
" #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
|
46
|
+
end
|
47
|
+
result.compact!
|
48
|
+
result.sort!
|
49
|
+
result.join
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String, nil]
|
53
|
+
def filter_and_join(value, separator)
|
54
|
+
return '' if (value.respond_to?(:empty?) && value.empty?)
|
55
|
+
|
56
|
+
if value.is_a?(Array)
|
57
|
+
value = value.flatten
|
58
|
+
value.map! {|item| item ? item.to_s : nil}
|
59
|
+
value.compact!
|
60
|
+
value = value.join(separator)
|
61
|
+
else
|
62
|
+
value = value ? value.to_s : nil
|
63
|
+
end
|
64
|
+
!value.nil? && !value.empty? && value
|
65
|
+
end
|
66
|
+
|
67
|
+
# Merges two attribute hashes.
|
68
|
+
# This is the same as `to.merge!(from)`,
|
69
|
+
# except that it merges id, class, and data attributes.
|
70
|
+
#
|
71
|
+
# ids are concatenated with `"_"`,
|
72
|
+
# and classes are concatenated with `" "`.
|
73
|
+
# data hashes are simply merged.
|
74
|
+
#
|
75
|
+
# Destructively modifies `to`.
|
76
|
+
#
|
77
|
+
# @param to [{String => String,Hash}] The attribute hash to merge into
|
78
|
+
# @param from [{String => Object}] The attribute hash to merge from
|
79
|
+
# @return [{String => String,Hash}] `to`, after being merged
|
80
|
+
def merge_attributes!(to, from)
|
81
|
+
from.keys.each do |key|
|
82
|
+
to[key] = merge_value(key, to[key], from[key])
|
83
|
+
end
|
84
|
+
to
|
85
|
+
end
|
86
|
+
|
87
|
+
# Merge multiple values to one attribute value. No destructive operation.
|
88
|
+
#
|
89
|
+
# @param key [String]
|
90
|
+
# @param values [Array<Object>]
|
91
|
+
# @return [String,Hash]
|
92
|
+
def merge_values(key, *values)
|
93
|
+
values.inject(nil) do |to, from|
|
94
|
+
merge_value(key, to, from)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def verify_attribute_names!(attribute_names)
|
99
|
+
attribute_names.each do |attribute_name|
|
100
|
+
if attribute_name =~ INVALID_ATTRIBUTE_NAME_REGEX
|
101
|
+
raise InvalidAttributeNameError.new("Invalid attribute name '#{attribute_name}' was rendered")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Merge a couple of values to one attribute value. No destructive operation.
|
109
|
+
#
|
110
|
+
# @param to [String,Hash,nil]
|
111
|
+
# @param from [Object]
|
112
|
+
# @return [String,Hash]
|
113
|
+
def merge_value(key, to, from)
|
114
|
+
if from.kind_of?(Hash) || to.kind_of?(Hash)
|
115
|
+
from = { nil => from } if !from.is_a?(Hash)
|
116
|
+
to = { nil => to } if !to.is_a?(Hash)
|
117
|
+
to.merge(from)
|
118
|
+
elsif key == 'id'
|
119
|
+
merged_id = filter_and_join(from, '_')
|
120
|
+
if to && merged_id
|
121
|
+
merged_id = "#{to}_#{merged_id}"
|
122
|
+
elsif to || merged_id
|
123
|
+
merged_id ||= to
|
124
|
+
end
|
125
|
+
merged_id
|
126
|
+
elsif key == 'class'
|
127
|
+
merged_class = filter_and_join(from, ' ')
|
128
|
+
if to && merged_class
|
129
|
+
merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
|
130
|
+
elsif to || merged_class
|
131
|
+
merged_class ||= to
|
132
|
+
end
|
133
|
+
merged_class
|
134
|
+
else
|
135
|
+
from
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def build_data_keys(data_hash, hyphenate, attr_name="data")
|
140
|
+
Hash[data_hash.map do |name, value|
|
141
|
+
if name == nil
|
142
|
+
[attr_name, value]
|
143
|
+
elsif hyphenate
|
144
|
+
["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
|
145
|
+
else
|
146
|
+
["#{attr_name}-#{name}", value]
|
147
|
+
end
|
148
|
+
end]
|
149
|
+
end
|
150
|
+
|
151
|
+
def flatten_data_attributes(data, key, join_char, seen = [])
|
152
|
+
return {key => data} unless data.is_a?(Hash)
|
153
|
+
|
154
|
+
return {key => nil} if seen.include? data.object_id
|
155
|
+
seen << data.object_id
|
156
|
+
|
157
|
+
data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
|
158
|
+
joined = key == '' ? k : [key, k].join(join_char)
|
159
|
+
hash.merge! flatten_data_attributes(v, joined, join_char, seen)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -617,6 +617,9 @@ MESSAGE
|
|
617
617
|
text.gsub(HTML_ESCAPE_REGEX, HTML_ESCAPE)
|
618
618
|
end
|
619
619
|
|
620
|
+
# Always escape text regardless of html_safe?
|
621
|
+
alias_method :html_escape_without_haml_xss, :html_escape
|
622
|
+
|
620
623
|
HTML_ESCAPE_ONCE_REGEX = /[\"><]|&(?!(?:[a-zA-Z]+|#(?:\d+|[xX][0-9a-fA-F]+));)/
|
621
624
|
|
622
625
|
# Escapes HTML entities in `text`, but without escaping an ampersand
|
@@ -629,6 +632,9 @@ MESSAGE
|
|
629
632
|
text.gsub(HTML_ESCAPE_ONCE_REGEX, HTML_ESCAPE)
|
630
633
|
end
|
631
634
|
|
635
|
+
# Always escape text once regardless of html_safe?
|
636
|
+
alias_method :escape_once_without_haml_xss, :escape_once
|
637
|
+
|
632
638
|
# Returns whether or not the current template is a Haml template.
|
633
639
|
#
|
634
640
|
# This function, unlike other {Haml::Helpers} functions,
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'strscan'
|
2
2
|
require 'hamlit/parser/haml_util'
|
3
3
|
require 'hamlit/parser/haml_error'
|
4
|
+
require 'hamlit/parser/haml_attribute_builder'
|
4
5
|
|
5
6
|
module Hamlit
|
6
7
|
class HamlParser
|
@@ -206,6 +207,31 @@ module Hamlit
|
|
206
207
|
end
|
207
208
|
end
|
208
209
|
|
210
|
+
# @param [String] new - Hash literal including dynamic values.
|
211
|
+
# @param [String] old - Hash literal including dynamic values or Ruby literal of multiple Hashes which MUST be interpreted as method's last arguments.
|
212
|
+
DynamicAttributes = Struct.new(:new, :old) do
|
213
|
+
undef :old=
|
214
|
+
def old=(value)
|
215
|
+
unless value =~ /\A{.*}\z/m
|
216
|
+
raise ArgumentError.new('Old attributes must start with "{" and end with "}"')
|
217
|
+
end
|
218
|
+
self[:old] = value
|
219
|
+
end
|
220
|
+
|
221
|
+
# This will be a literal for Haml::Buffer#attributes's last argument, `attributes_hashes`.
|
222
|
+
def to_literal
|
223
|
+
[new, stripped_old].compact.join(', ')
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
|
228
|
+
# For `%foo{ { foo: 1 }, bar: 2 }`, :old is "{ { foo: 1 }, bar: 2 }" and this method returns " { foo: 1 }, bar: 2 " for last argument.
|
229
|
+
def stripped_old
|
230
|
+
return nil if old.nil?
|
231
|
+
old.sub!(/\A{/, '').sub!(/}\z/m, '')
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
209
235
|
# Processes and deals with lowering indentation.
|
210
236
|
def process_indent(line)
|
211
237
|
return unless line.tabs <= @template_tabs && @template_tabs > 0
|
@@ -403,22 +429,20 @@ module Hamlit
|
|
403
429
|
end
|
404
430
|
|
405
431
|
attributes = ::Hamlit::HamlParser.parse_class_and_id(attributes)
|
406
|
-
|
432
|
+
dynamic_attributes = DynamicAttributes.new
|
407
433
|
|
408
434
|
if attributes_hashes[:new]
|
409
435
|
static_attributes, attributes_hash = attributes_hashes[:new]
|
410
|
-
|
411
|
-
|
436
|
+
HamlAttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
|
437
|
+
dynamic_attributes.new = attributes_hash
|
412
438
|
end
|
413
439
|
|
414
440
|
if attributes_hashes[:old]
|
415
441
|
static_attributes = parse_static_hash(attributes_hashes[:old])
|
416
|
-
|
417
|
-
|
442
|
+
HamlAttributeBuilder.merge_attributes!(attributes, static_attributes) if static_attributes
|
443
|
+
dynamic_attributes.old = attributes_hashes[:old] unless static_attributes || @options.suppress_eval
|
418
444
|
end
|
419
445
|
|
420
|
-
attributes_list.compact!
|
421
|
-
|
422
446
|
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:illegal_nesting_self_closing), @next_line.index) if block_opened? && self_closing
|
423
447
|
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:no_ruby_code, action), last_line - 1) if parse && value.empty?
|
424
448
|
raise ::Hamlit::HamlSyntaxError.new(::Hamlit::HamlError.message(:self_closing_content), last_line - 1) if self_closing && !value.empty?
|
@@ -433,7 +457,7 @@ module Hamlit
|
|
433
457
|
line = handle_ruby_multiline(line) if parse
|
434
458
|
|
435
459
|
ParseNode.new(:tag, line.index + 1, :name => tag_name, :attributes => attributes,
|
436
|
-
:
|
460
|
+
:dynamic_attributes => dynamic_attributes, :self_closing => self_closing,
|
437
461
|
:nuke_inner_whitespace => nuke_inner_whitespace,
|
438
462
|
:nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref,
|
439
463
|
:escape_html => escape_html, :preserve_tag => preserve_tag,
|
@@ -641,7 +665,6 @@ module Hamlit
|
|
641
665
|
raise e
|
642
666
|
end
|
643
667
|
|
644
|
-
attributes_hash = attributes_hash[1...-1] if attributes_hash
|
645
668
|
return attributes_hash, rest, last_line
|
646
669
|
end
|
647
670
|
|
@@ -6,12 +6,15 @@ module Hamlit
|
|
6
6
|
# to work with Rails' XSS protection methods.
|
7
7
|
module XssMods
|
8
8
|
def self.included(base)
|
9
|
-
%w[
|
10
|
-
precede succeed capture_haml haml_concat haml_internal_concat haml_indent
|
11
|
-
escape_once].each do |name|
|
9
|
+
%w[find_and_preserve preserve list_of surround
|
10
|
+
precede succeed capture_haml haml_concat haml_internal_concat haml_indent].each do |name|
|
12
11
|
base.send(:alias_method, "#{name}_without_haml_xss", name)
|
13
12
|
base.send(:alias_method, name, "#{name}_with_haml_xss")
|
14
13
|
end
|
14
|
+
# Those two always have _without_haml_xss
|
15
|
+
%w[html_escape escape_once].each do |name|
|
16
|
+
base.send(:alias_method, name, "#{name}_with_haml_xss")
|
17
|
+
end
|
15
18
|
end
|
16
19
|
|
17
20
|
# Don't escape text that's already safe,
|
@@ -2,87 +2,18 @@ require 'ripper'
|
|
2
2
|
require 'hamlit/ruby_expression'
|
3
3
|
|
4
4
|
module Hamlit
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
[]
|
10
|
-
tokens = Ripper.lex(code.strip)
|
11
|
-
tokens.pop while tokens.last && %i[on_comment on_sp].include?(tokens.last[1])
|
12
|
-
|
13
|
-
if tokens.size < 2
|
14
|
-
raise Hamlit::InternalError.new("Expected token size >= 2 but got: #{tokens.size}")
|
15
|
-
end
|
16
|
-
compile_tokens!(exps, tokens)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def strip_quotes!(tokens)
|
23
|
-
_, type, beg_str = tokens.shift
|
24
|
-
if type != :on_tstring_beg
|
25
|
-
raise Hamlit::InternalError.new("Expected :on_tstring_beg but got: #{type}")
|
26
|
-
end
|
27
|
-
|
28
|
-
_, type, end_str = tokens.pop
|
29
|
-
if type != :on_tstring_end
|
30
|
-
raise Hamlit::InternalError.new("Expected :on_tstring_end but got: #{type}")
|
31
|
-
end
|
32
|
-
|
33
|
-
[beg_str, end_str]
|
34
|
-
end
|
35
|
-
|
36
|
-
def compile_tokens!(exps, tokens)
|
37
|
-
beg_str, end_str = strip_quotes!(tokens)
|
38
|
-
|
39
|
-
until tokens.empty?
|
40
|
-
_, type, str = tokens.shift
|
41
|
-
|
42
|
-
case type
|
43
|
-
when :on_tstring_content
|
44
|
-
exps << [:static, eval("#{beg_str}#{str}#{end_str}")]
|
45
|
-
when :on_embexpr_beg
|
46
|
-
embedded = shift_balanced_embexpr(tokens)
|
47
|
-
exps << [:dynamic, embedded] unless embedded.empty?
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def shift_balanced_embexpr(tokens)
|
53
|
-
String.new.tap do |embedded|
|
54
|
-
embexpr_open = 1
|
55
|
-
|
56
|
-
until tokens.empty?
|
57
|
-
_, type, str = tokens.shift
|
58
|
-
case type
|
59
|
-
when :on_embexpr_beg
|
60
|
-
embexpr_open += 1
|
61
|
-
when :on_embexpr_end
|
62
|
-
embexpr_open -= 1
|
63
|
-
break if embexpr_open == 0
|
64
|
-
end
|
65
|
-
|
66
|
-
embedded << str
|
67
|
-
end
|
68
|
-
end
|
5
|
+
module StringSplitter
|
6
|
+
# `code` param must be valid string literal
|
7
|
+
def self.compile(code)
|
8
|
+
unless Ripper.respond_to?(:lex) # truffleruby doesn't have Ripper.lex
|
9
|
+
return [[:dynamic, code]]
|
69
10
|
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def on_dynamic(code)
|
73
|
-
return [:dynamic, code] unless RubyExpression.string_literal?(code)
|
74
|
-
return [:dynamic, code] if code.include?("\n")
|
75
11
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
temple << [:static, content]
|
81
|
-
when :dynamic
|
82
|
-
temple << on_dynamic(content)
|
83
|
-
end
|
12
|
+
begin
|
13
|
+
Temple::Filters::StringSplitter.compile(code)
|
14
|
+
rescue Temple::FilterError => e
|
15
|
+
raise Hamlit::InternalError.new(e.message)
|
84
16
|
end
|
85
|
-
temple
|
86
17
|
end
|
87
18
|
end
|
88
19
|
end
|