ejx 1.1 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ejx/assets/ejx.js +61 -40
- data/lib/ejx/template/balance_scanner.rb +32 -0
- data/lib/ejx/template/base.rb +12 -10
- data/lib/ejx/template/html_comment.rb +19 -0
- data/lib/ejx/template/html_tag.rb +9 -9
- data/lib/ejx/template/js.rb +10 -4
- data/lib/ejx/template/multitemplate.rb +37 -0
- data/lib/ejx/template/node.rb +17 -0
- data/lib/ejx/template/subtemplate.rb +149 -19
- data/lib/ejx/template.rb +82 -49
- data/lib/ejx/version.rb +1 -1
- metadata +21 -4
- data/lib/ejx/template/parse_helpers.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cb0b12e342368966d44f7738cff85dd83ce6af48f691885cd231d77090127df
|
4
|
+
data.tar.gz: 271e81bf0e111b4f0846dff9dfea6cf03422e223b712b1f14459f2f0539081e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8361e0271745451e9daa3c9638e019ba75c46eebd9be260be58286233d1340eb32fab7e6a73f79591d067a13d85545fe86ec6298246f315174ffab6e8b811fdb
|
7
|
+
data.tar.gz: b2fe93fd63d2d2096f5c2c9b4d482f03fef1bd89b525c5b68d3f127b6762f9beb4735d0d26ad10b51e489a27a5133a3f022c8bd802ca09c763518d4deb343dd5
|
data/lib/ejx/assets/ejx.js
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
function insertBefore(el, items) {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
}
|
14
|
-
});
|
2
|
+
items.forEach((i) => {
|
3
|
+
if ( Array.isArray(i) ) {
|
4
|
+
insertBefore(el, i);
|
5
|
+
} else if (i instanceof Element) {
|
6
|
+
el.insertAdjacentElement('beforebegin', i);
|
7
|
+
} else if (i instanceof Node) {
|
8
|
+
el.parentNode.insertBefore(i, el)
|
9
|
+
} else {
|
10
|
+
el.insertAdjacentText('beforebegin', i);
|
11
|
+
}
|
12
|
+
});
|
15
13
|
}
|
16
14
|
|
17
15
|
function repalceejx(el, withItems) {
|
@@ -23,34 +21,57 @@ function repalceejx(el, withItems) {
|
|
23
21
|
}
|
24
22
|
}
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
function placehold(promise, to, promises) {
|
25
|
+
const placeholder = document.createElement( "div");
|
26
|
+
to[Array.isArray(to) ? 'push' : 'append'](placeholder);
|
27
|
+
|
28
|
+
var newPromise = promise.then((resolvedItems) => {
|
29
|
+
if (resolvedItems !== undefined) {
|
30
|
+
if (placeholder.parentElement) {
|
31
|
+
repalceejx(placeholder, resolvedItems || "");
|
32
|
+
} else if (Array.isArray(resolvedItems)) {
|
33
|
+
to.splice(to.indexOf(placeholder), 1, ...resolvedItems);
|
34
|
+
} else {
|
35
|
+
to.splice(to.indexOf(placeholder), 1, resolvedItems);
|
36
|
+
}
|
37
|
+
} else {
|
38
|
+
if (placeholder.parentElement) {
|
39
|
+
placeholder.remove();
|
40
|
+
} else {
|
41
|
+
to.splice(to.indexOf(placeholder), 1);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
return resolvedItems;
|
45
|
+
});
|
46
|
+
promises.push(newPromise);
|
47
|
+
return newPromise;
|
48
|
+
}
|
49
|
+
|
50
|
+
export function append(items, to, appendMode, promises, awaiter, marker) {
|
51
|
+
if (awaiter instanceof Promise) {
|
52
|
+
return placehold(awaiter, to, promises);
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
if ( Array.isArray(items) ) {
|
57
|
+
return items.map((i) => append(i, to, appendMode, promises));
|
58
|
+
}
|
59
|
+
|
30
60
|
let method = Array.isArray(to) ? 'push' : 'append';
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
to
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
if (escape) {
|
45
|
-
to[method](items);
|
46
|
-
} else {
|
47
|
-
var container = document.createElement(Array.isArray(to) ? 'div' : to.tagName);
|
48
|
-
container.innerHTML = items;
|
49
|
-
to[method](...container.childNodes);
|
50
|
-
}
|
61
|
+
|
62
|
+
if (items instanceof Promise || (items && items.then)) {
|
63
|
+
return placehold(items, to, promises);
|
64
|
+
}
|
65
|
+
|
66
|
+
if (typeof items === 'string') {
|
67
|
+
if (appendMode === 'escape') {
|
68
|
+
to[method](items);
|
69
|
+
} else {
|
70
|
+
var container = document.createElement(Array.isArray(to) ? 'div' : to.tagName);
|
71
|
+
container.innerHTML = items;
|
72
|
+
to[method](...container.childNodes);
|
73
|
+
}
|
51
74
|
} else {
|
52
|
-
|
75
|
+
to[method](items);
|
53
76
|
}
|
54
|
-
|
55
|
-
}
|
56
77
|
}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class EJX::Template::BalanceScanner
|
2
|
+
include StreamParser
|
3
|
+
|
4
|
+
BALANCE = {
|
5
|
+
"}" => "{",
|
6
|
+
")" => "(",
|
7
|
+
"{" => "}",
|
8
|
+
"(" => ")",
|
9
|
+
}
|
10
|
+
|
11
|
+
def parse(stack = [])
|
12
|
+
while !eos?
|
13
|
+
if match = scan_until(/[\(\)\{\}\"\'\`]/)
|
14
|
+
case match[0]
|
15
|
+
when "}", ")"
|
16
|
+
if stack.last == BALANCE[match[0]]
|
17
|
+
stack.pop
|
18
|
+
else
|
19
|
+
stack << match[0]
|
20
|
+
end
|
21
|
+
when "\"", "'", "`"
|
22
|
+
quoted_value(match[0])
|
23
|
+
else
|
24
|
+
stack << match[0]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
stack
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
data/lib/ejx/template/base.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
class EJX::Template::Base
|
1
|
+
class EJX::Template::Base < EJX::Template::Node
|
2
2
|
|
3
|
-
attr_accessor :
|
4
|
-
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
@escape = escape
|
3
|
+
attr_accessor :imports
|
4
|
+
|
5
|
+
def initialize(**options)
|
6
|
+
super
|
8
7
|
@imports = []
|
9
8
|
end
|
10
|
-
|
9
|
+
|
11
10
|
def to_module
|
12
11
|
var_generator = EJX::Template::VarGenerator.new
|
13
12
|
|
@@ -32,9 +31,12 @@ class EJX::Template::Base
|
|
32
31
|
child.to_js(var_generator: var_generator)
|
33
32
|
end
|
34
33
|
end
|
35
|
-
|
36
|
-
output << "
|
37
|
-
|
34
|
+
|
35
|
+
output << "\n await Promise.all(__promises);"
|
36
|
+
|
37
|
+
output << "\n return __output;"
|
38
|
+
output << "\n}"
|
39
|
+
|
38
40
|
output
|
39
41
|
end
|
40
42
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class EJX::Template::HTMLComment
|
2
|
+
|
3
|
+
def initialize(comment)
|
4
|
+
@comment = comment
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
@comment
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
"#<EJX::HTMLComment:#{self.object_id} @comment=#{@comment}>"
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_js(append: "__output", var_generator:, indentation: 4, namespace: nil)
|
16
|
+
"#{' '*indentation}#{append}.push(document.createComment(#{JSON.generate(@comment)}));\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -1,12 +1,12 @@
|
|
1
|
-
class EJX::Template::HTMLTag
|
1
|
+
class EJX::Template::HTMLTag < EJX::Template::Node
|
2
2
|
|
3
3
|
autoload :AttributeValue, File.expand_path('../html_tag/attribute_value', __FILE__)
|
4
4
|
|
5
|
-
attr_accessor :tag_name, :attrs, :
|
5
|
+
attr_accessor :tag_name, :attrs, :namespace
|
6
6
|
|
7
7
|
def initialize
|
8
|
+
super
|
8
9
|
@attrs = []
|
9
|
-
@children = []
|
10
10
|
end
|
11
11
|
|
12
12
|
def to_s
|
@@ -16,8 +16,8 @@ class EJX::Template::HTMLTag
|
|
16
16
|
def inspect
|
17
17
|
"#<EJX::HTMLTag:#{self.object_id} @tag_name=#{tag_name}>"
|
18
18
|
end
|
19
|
-
|
20
|
-
def to_js(append: "__output", var_generator:, indentation: 4, namespace: nil)
|
19
|
+
|
20
|
+
def to_js(append: "__output", var_generator:, indentation: 4, namespace: nil, promises: '__promises')
|
21
21
|
namespace ||= self.namespace
|
22
22
|
|
23
23
|
output_var = var_generator.next
|
@@ -40,15 +40,15 @@ class EJX::Template::HTMLTag
|
|
40
40
|
|
41
41
|
@children.each do |child|
|
42
42
|
js << if child.is_a?(EJX::Template::String)
|
43
|
-
"#{' '*indentation}__ejx_append(#{child.to_js}, #{output_var},
|
43
|
+
"#{' '*indentation}__ejx_append(#{child.to_js}, #{output_var}, 'unescape', #{promises});\n"
|
44
44
|
elsif child.is_a?(EJX::Template::HTMLTag)
|
45
|
-
child.to_js(var_generator: var_generator, indentation: indentation, append: output_var, namespace: namespace)
|
45
|
+
child.to_js(var_generator: var_generator, indentation: indentation, append: output_var, namespace: namespace, promises: promises)
|
46
46
|
else
|
47
|
-
child.to_js(var_generator: var_generator, indentation: indentation, append: output_var)
|
47
|
+
child.to_js(var_generator: var_generator, indentation: indentation, append: output_var, promises: promises)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
js << "#{' '*indentation}__ejx_append(#{output_var}, #{append},
|
51
|
+
js << "#{' '*indentation}__ejx_append(#{output_var}, #{append}, 'unescape', #{promises});\n"
|
52
52
|
js
|
53
53
|
end
|
54
54
|
|
data/lib/ejx/template/js.rb
CHANGED
@@ -7,14 +7,20 @@ class EJX::Template::JS
|
|
7
7
|
@value = value
|
8
8
|
end
|
9
9
|
|
10
|
-
def to_js(indentation: 4, var_generator: nil, append: "__output")
|
10
|
+
def to_js(indentation: 4, var_generator: nil, append: "__output", promises: '__promises')
|
11
11
|
output = @value
|
12
12
|
|
13
13
|
if @modifiers.include? :escape
|
14
|
-
|
14
|
+
if output =~ /\A\s*(var|const|let)\s+(\S+)/
|
15
|
+
"#{' '*indentation}#{output}#{output.strip.end_with?(';') ? '' : ';'}\n#{' '*indentation}__ejx_append(#{$2}, #{append}, 'escape', #{promises});\n"
|
16
|
+
else
|
17
|
+
"#{' '*indentation}__ejx_append(#{output.gsub(/;\s*\Z/, '')}, #{append}, 'escape', #{promises});\n"
|
18
|
+
end
|
15
19
|
elsif @modifiers.include? :unescape
|
16
|
-
"#{' '*indentation}__ejx_append(#{output}, #{append},
|
17
|
-
elsif
|
20
|
+
"#{' '*indentation}__ejx_append(#{output.gsub(/;\s*\Z/, '')}, #{append}, 'unescape', #{promises});\n"
|
21
|
+
elsif @modifiers.include? :comment
|
22
|
+
"#{' '*indentation}#{output.index("\n").nil? ? "// #{output}" : "/* #{output.gsub(/\n\s+/, "\n"+(' '*indentation)+" ")} */"}\n"
|
23
|
+
else
|
18
24
|
"#{' '*indentation}#{output}\n"
|
19
25
|
end
|
20
26
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class EJX::Template::Multitemplate < EJX::Template::Subtemplate
|
2
|
+
|
3
|
+
def to_js(indentation: 4, var_generator: nil, append: "__output", promises: '__promises')
|
4
|
+
already_assigned = @children.first =~ /\A\s*(var|const|let)\s+(\S+)/
|
5
|
+
output_var = $2
|
6
|
+
output = ""
|
7
|
+
if already_assigned# || !@append
|
8
|
+
output << "#{' '*indentation}#{@children.first}\n"
|
9
|
+
else
|
10
|
+
output << "#{' '*indentation}__ejx_append("
|
11
|
+
output << @children.first << "\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
@children[1..-2].each do |child|
|
15
|
+
output << case child
|
16
|
+
when EJX::Template::String
|
17
|
+
"#{' '*(indentation+4)}__ejx_append(#{child.to_js}, #{append}, 'unescape', __promises);\n"
|
18
|
+
when String
|
19
|
+
"#{' '*(indentation)}#{child}\n"
|
20
|
+
when EJX::Template::Subtemplate
|
21
|
+
child.to_sub_js(indentation: indentation + 4, var_generator: var_generator, promises: promises)
|
22
|
+
else
|
23
|
+
child.to_js(indentation: indentation + 4, var_generator: var_generator, append: append)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
output << ' '*indentation << @children.last.strip.delete_suffix(';')
|
28
|
+
if already_assigned
|
29
|
+
output << ";\n#{' '*indentation}__ejx_append(#{output_var}, #{append}, 'escape', __promises);\n"
|
30
|
+
else
|
31
|
+
output << ", #{append}, 'escape', __promises);\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
output
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -1,45 +1,175 @@
|
|
1
|
-
class EJX::Template::Subtemplate
|
1
|
+
class EJX::Template::Subtemplate < EJX::Template::Node
|
2
2
|
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :modifiers
|
4
|
+
|
5
|
+
[:assigned_to_variable, :async, :append].each do |fn_name|
|
6
|
+
attr_reader fn_name
|
7
|
+
define_method(:"#{fn_name}?", instance_method(fn_name))
|
8
|
+
end
|
9
|
+
|
10
|
+
def function?
|
11
|
+
!!@function_type
|
12
|
+
end
|
13
|
+
|
14
|
+
def arrow_function?
|
15
|
+
@function_type == :arrow
|
16
|
+
end
|
4
17
|
|
5
18
|
def initialize(opening, modifiers, append: true)
|
6
19
|
@children = [opening]
|
7
20
|
@modifiers = modifiers
|
8
21
|
@append = append
|
22
|
+
@balance_stack = opening ? EJX::Template::BalanceScanner.parse(opening) : []
|
23
|
+
|
24
|
+
@assigned_to_variable = @children.first&.match(/\A\s*(var|const|let)\s+(\S+)/)&.send(:[], 2)
|
25
|
+
@async = false
|
26
|
+
|
27
|
+
if match = @children.first&.match(/(?:async\s+)?function(:?\s+\w+)?\s*\([^\)]*\)\s*\{\s*\Z/m)
|
28
|
+
@function_type = :regular
|
29
|
+
@async = match[0].start_with?('async')
|
30
|
+
@assigned_to_variable = true if !match[1].nil?
|
31
|
+
elsif match = @children.first&.match(/(?:async)?(?:\s*\([^\)\(]*\)|(?:(?<=\()|\s+)[^\(\s]+)?\s*=>\s*\{\s*\Z/m)
|
32
|
+
@function_type = :arrow
|
33
|
+
@async = match[0].start_with?('async')
|
34
|
+
end
|
9
35
|
end
|
10
|
-
|
11
|
-
def
|
12
|
-
|
13
|
-
|
36
|
+
|
37
|
+
def push(*values)
|
38
|
+
values.each do |value|
|
39
|
+
case value
|
40
|
+
when EJX::Template::JS
|
41
|
+
EJX::Template::BalanceScanner.parse(value.value, @balance_stack)
|
42
|
+
when String
|
43
|
+
EJX::Template::BalanceScanner.parse(value, @balance_stack)
|
44
|
+
end
|
45
|
+
end
|
14
46
|
|
47
|
+
super
|
48
|
+
end
|
49
|
+
|
50
|
+
def balanced?
|
51
|
+
@balance_stack.empty?
|
52
|
+
end
|
53
|
+
|
54
|
+
def balance
|
55
|
+
@balance_stack
|
56
|
+
end
|
57
|
+
|
58
|
+
def ending_balance
|
59
|
+
@balance_stack.reverse.map { |i| EJX::Template::BalanceScanner::BALANCE[i] }
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_js(indentation: 4, var_generator: nil, append: "__output", promises: '__promises')
|
15
63
|
output = ''
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
64
|
+
|
65
|
+
global_output_var = var_generator.next if !assigned_to_variable?
|
66
|
+
sub_global_output_var = var_generator.next
|
67
|
+
output_var = var_generator.next
|
68
|
+
|
69
|
+
if assigned_to_variable?
|
21
70
|
output << "#{' '*indentation}#{@children.first}\n"
|
71
|
+
output << "#{' '*(indentation+4)}var #{sub_global_output_var}_promises = [];\n"
|
72
|
+
elsif !(@modifiers & [:escape, :unescape]).empty?
|
73
|
+
output << "#{' '*indentation}var #{global_output_var}_result = #{@children.first}\n"
|
74
|
+
output << "#{' '*(indentation+4)}var #{sub_global_output_var}_promises = [];\n"
|
75
|
+
else
|
76
|
+
output << <<~JS.gsub(/\n+\Z/, '')
|
77
|
+
#{' '*(indentation)}var #{global_output_var}_results = [];
|
78
|
+
#{' '*(indentation)}var #{global_output_var}_promises = [];
|
79
|
+
#{' '*(indentation)}var #{global_output_var}_result =
|
80
|
+
JS
|
81
|
+
|
82
|
+
indentation += 4
|
83
|
+
output << if arrow_function?
|
84
|
+
@children.first.sub(/((?:async)?(?:\s*\([^\)\(]*\)|(?:(?<=\()|\s+)[^\(\s]+)?\s*=>\s*\{\s*)\Z/m, <<~JS)
|
85
|
+
(...__args) => {
|
86
|
+
#{' '*(indentation)}var #{sub_global_output_var}_results = [];
|
87
|
+
#{' '*(indentation)}var #{sub_global_output_var}_promises = [];
|
88
|
+
#{' '*(indentation)}var #{sub_global_output_var}_result = (\\1
|
89
|
+
JS
|
90
|
+
else
|
91
|
+
@children.first.sub(/((?:async\s+)?\s*function\s*\([^\)\(]*\)\s*\{\s*)\Z/m, <<~JS)
|
92
|
+
(...__args) => {
|
93
|
+
#{' '*(indentation)}var #{sub_global_output_var}_results = [];
|
94
|
+
#{' '*(indentation)}var #{sub_global_output_var}_promises = [];
|
95
|
+
#{' '*(indentation)}var #{sub_global_output_var}_result = (\\1
|
96
|
+
JS
|
97
|
+
end
|
22
98
|
end
|
23
99
|
|
24
100
|
output << "#{' '*(indentation+4)}var #{output_var} = [];\n"
|
25
101
|
|
26
102
|
@children[1..-2].each do |child|
|
103
|
+
promise_var = "#{sub_global_output_var}_promises"
|
27
104
|
output << case child
|
28
105
|
when EJX::Template::String
|
29
|
-
"#{' '*(indentation+4)}__ejx_append(#{child.to_js}, #{output_var},
|
106
|
+
"#{' '*(indentation+4)}__ejx_append(#{child.to_js}, #{output_var}, 'unescape', #{promise_var});\n"
|
30
107
|
else
|
31
|
-
child.to_js(indentation: indentation + 4, var_generator: var_generator, append: output_var)
|
108
|
+
child.to_js(indentation: indentation + 4, var_generator: var_generator, append: output_var, promises: promise_var)
|
32
109
|
end
|
33
110
|
end
|
111
|
+
|
112
|
+
if !assigned_to_variable? && (@modifiers & [:escape, :unescape]).empty?
|
113
|
+
output << ' '*(indentation+4) << "#{sub_global_output_var}_results.push(#{output_var});\n"
|
114
|
+
end
|
115
|
+
|
116
|
+
if async?
|
117
|
+
output << ' '*(indentation+4) << "await Promise.all(#{sub_global_output_var}_promises);\n"
|
118
|
+
output << ' '*(indentation+4) << "return #{output_var};\n";
|
119
|
+
elsif assigned_to_variable?
|
120
|
+
output << ' '*(indentation+4)
|
121
|
+
output << "return #{sub_global_output_var}_promises.length === 0 ? #{output_var} : Promise.all(#{sub_global_output_var}_promises).then(() => #{output_var});\n"
|
122
|
+
else
|
123
|
+
output << ' '*(indentation+4) << "return Promise.all(#{sub_global_output_var}_promises).then(() => #{output_var});\n"
|
124
|
+
end
|
125
|
+
|
126
|
+
output << ' '*((@modifiers & [:escape, :unescape]).empty? ? indentation : indentation-4)
|
127
|
+
|
128
|
+
split = @children.last.strip.delete_suffix(';').split(/\}/, 2)
|
129
|
+
output << split[0]
|
130
|
+
|
131
|
+
if !assigned_to_variable? && (@modifiers & [:escape, :unescape]).empty?
|
132
|
+
output << "})(...__args);\n"
|
133
|
+
output << "#{' '*indentation}__ejx_append(#{sub_global_output_var}_results, #{global_output_var}_results, 'escape', #{global_output_var}_promises, #{sub_global_output_var}_result);\n"
|
134
|
+
output << ' '*(indentation) << "return #{sub_global_output_var}_result;\n"
|
135
|
+
output << ' '*(indentation-4) << "}" << split[1]
|
136
|
+
else
|
137
|
+
output << ' '*(indentation-4) << "}" << split[1]
|
138
|
+
end
|
139
|
+
indentation = indentation - 4
|
34
140
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
141
|
+
if assigned_to_variable?
|
142
|
+
output << ";\n"
|
143
|
+
if !(@modifiers & [:escape, :unescape]).empty?
|
144
|
+
output << "#{' '*indentation}__ejx_append(#{@assigned_to_variable}, #{append}, 'escape', #{promises});\n"
|
145
|
+
end
|
40
146
|
else
|
41
|
-
";\n"
|
147
|
+
output << ";\n"
|
148
|
+
if !(@modifiers & [:escape, :unescape]).empty?
|
149
|
+
output << "#{' '*(indentation+4)}__ejx_append(#{global_output_var}_result, #{append}, 'escape', #{promises});\n"
|
150
|
+
else
|
151
|
+
output << "#{' '*indentation}__ejx_append(#{global_output_var}_results.flat(1), #{append}, 'escape', #{promises}, "
|
152
|
+
output << "(#{global_output_var}_result instanceof Promise) ? #{global_output_var}_result.then(() => Promise.all(#{global_output_var}_promises).then(r => r.flat(1))) : Promise.all(#{global_output_var}_promises).then(r => r.flat(1)));\n"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
output
|
157
|
+
end
|
158
|
+
|
159
|
+
def to_sub_js(indentation: 4, var_generator: nil, promises: '__promises')
|
160
|
+
output_var = var_generator.next
|
161
|
+
|
162
|
+
output = "#{' '*(indentation)}var #{output_var} = [];\n"
|
163
|
+
|
164
|
+
@children[1..-1].each do |child|
|
165
|
+
output << case child
|
166
|
+
when EJX::Template::String
|
167
|
+
"#{' '*(indentation)}__ejx_append(#{child.to_js}, #{output_var}, 'unescape', #{promises});\n"
|
168
|
+
else
|
169
|
+
child.to_js(indentation: indentation, var_generator: var_generator, append: output_var)
|
170
|
+
end
|
42
171
|
end
|
172
|
+
output << ' '*(indentation) << "return #{output_var};\n";
|
43
173
|
|
44
174
|
output
|
45
175
|
end
|
data/lib/ejx/template.rb
CHANGED
@@ -1,19 +1,22 @@
|
|
1
|
+
require 'stream_parser'
|
2
|
+
|
1
3
|
class EJX::Template
|
2
4
|
|
3
5
|
autoload :JS, File.expand_path('../template/js', __FILE__)
|
4
6
|
autoload :Base, File.expand_path('../template/base', __FILE__)
|
5
7
|
autoload :String, File.expand_path('../template/string', __FILE__)
|
6
8
|
autoload :HTMLTag, File.expand_path('../template/html_tag', __FILE__)
|
7
|
-
autoload :
|
9
|
+
autoload :HTMLComment, File.expand_path('../template/html_comment', __FILE__)
|
8
10
|
autoload :Subtemplate, File.expand_path('../template/subtemplate', __FILE__)
|
11
|
+
autoload :Multitemplate, File.expand_path('../template/multitemplate', __FILE__)
|
9
12
|
autoload :VarGenerator, File.expand_path('../template/var_generator', __FILE__)
|
13
|
+
autoload :Node, File.expand_path('../template/node', __FILE__)
|
14
|
+
autoload :BalanceScanner, File.expand_path('../template/balance_scanner', __FILE__)
|
10
15
|
|
11
|
-
include
|
12
|
-
|
13
|
-
attr_accessor :source
|
16
|
+
include StreamParser
|
14
17
|
|
15
18
|
def initialize(source, options={})
|
16
|
-
|
19
|
+
super(source.strip)
|
17
20
|
|
18
21
|
@js_start_tags = [options[:open_tag] || EJX.settings[:open_tag]]
|
19
22
|
@html_start_tags = ['<']
|
@@ -27,11 +30,10 @@ class EJX::Template
|
|
27
30
|
@close_tag_modifiers = EJX.settings[:close_tag_modifiers].merge(options[:close_tag_modifiers] || {})
|
28
31
|
|
29
32
|
@escape = options[:escape]
|
30
|
-
|
33
|
+
parse
|
31
34
|
end
|
32
35
|
|
33
|
-
def
|
34
|
-
seek(0)
|
36
|
+
def parse
|
35
37
|
@tree = [EJX::Template::Base.new(escape: @escape)]
|
36
38
|
@stack = [:str]
|
37
39
|
|
@@ -40,13 +42,17 @@ class EJX::Template
|
|
40
42
|
when :str
|
41
43
|
scan_until(Regexp.new("(#{@start_tags.map{|s| Regexp.escape(s) }.join('|')}|\\z)"))
|
42
44
|
if !pre_match.strip.empty?
|
43
|
-
@tree.last
|
45
|
+
@tree.last << EJX::Template::String.new(pre_match)
|
44
46
|
end
|
45
47
|
|
46
|
-
if !
|
47
|
-
if
|
48
|
+
if !match.nil?
|
49
|
+
if peek(3) == '!--'
|
50
|
+
scan_until('!--')
|
51
|
+
@stack << :html_comment
|
52
|
+
elsif @js_start_tags.include?(match)
|
53
|
+
# @stack.pop
|
48
54
|
@stack << :js
|
49
|
-
elsif @html_start_tags.include?(
|
55
|
+
elsif @html_start_tags.include?(match)
|
50
56
|
@stack << :html_tag
|
51
57
|
end
|
52
58
|
end
|
@@ -55,7 +61,7 @@ class EJX::Template
|
|
55
61
|
scan_until(Regexp.new("(#{@js_close_tags.map{|s| Regexp.escape(s) }.join('|')})"))
|
56
62
|
pm = pre_match
|
57
63
|
open_modifier = @open_tag_modifiers.find { |k,v| pm.start_with?(v) }&.first
|
58
|
-
close_modifier = @close_tag_modifiers.find { |k,v|
|
64
|
+
close_modifier = @close_tag_modifiers.find { |k,v| match.end_with?(v) }&.first
|
59
65
|
pm.slice!(0, open_modifier[1].size) if open_modifier
|
60
66
|
pm.slice!(pm.size - close_modifier[1].size, close_modifier[1].size) if close_modifier
|
61
67
|
|
@@ -64,17 +70,39 @@ class EJX::Template
|
|
64
70
|
import += ';' if !import.end_with?(';')
|
65
71
|
@tree.first.imports << import
|
66
72
|
@stack.pop
|
67
|
-
elsif @tree.last.is_a?(EJX::Template::Subtemplate) &&
|
73
|
+
elsif @tree.last.is_a?(EJX::Template::Subtemplate) && EJX::Template::BalanceScanner.parse(pm) == @tree.last.ending_balance
|
74
|
+
#&& pm.match(/\A\s*\}/m) && !pm.match(/\{\s*\Z/m)
|
75
|
+
|
68
76
|
subtemplate = @tree.pop
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
77
|
+
if @tree.last.is_a?(EJX::Template::Multitemplate)
|
78
|
+
multitemplate = @tree.pop
|
79
|
+
multitemplate << subtemplate << pm.strip
|
80
|
+
@tree.last << multitemplate
|
81
|
+
else
|
82
|
+
subtemplate << pm.strip
|
83
|
+
@tree.last << subtemplate
|
84
|
+
end
|
85
|
+
@stack.pop# if subtemplate.balanced?
|
86
|
+
elsif pm.match(/function\s*(:?\w+)?\s*\([^\)]*\)\s*\{\s*\Z/m) || pm.match(/=>\s*\{\s*\Z/m)
|
87
|
+
if @tree.last.is_a?(EJX::Template::Subtemplate) && pm.match(/\A\s*\}/m)
|
88
|
+
template = @tree.pop
|
89
|
+
multitemplate = EJX::Template::Multitemplate.new(template.children.shift, template.modifiers, append: template.append)
|
90
|
+
@tree << multitemplate
|
91
|
+
subtemplate = EJX::Template::Subtemplate.new(nil, [open_modifier, close_modifier].compact, append: false)
|
92
|
+
subtemplate.push(*template.children)
|
93
|
+
@tree.last << subtemplate << pm.strip
|
94
|
+
@tree << EJX::Template::Subtemplate.new(nil, [open_modifier, close_modifier].compact, append: false)
|
95
|
+
@tree.last.instance_variable_set(:@balance_stack, multitemplate.balance)
|
96
|
+
elsif @tree.last.is_a?(EJX::Template::Multitemplate) && pm.match(/\A\s*\}/m)
|
97
|
+
@tree.last << pm.strip
|
98
|
+
@tree.last << EJX::Template::Subtemplate.new(nil, [open_modifier, close_modifier].compact, append: false)
|
99
|
+
else
|
100
|
+
@tree << EJX::Template::Subtemplate.new(pm.strip, [open_modifier, close_modifier].compact, append: [:escape, :unescape].include?(open_modifier) || !pm.match?(/\A\s*(var|const|let)?\s*[^(]+\s*=/))
|
101
|
+
end
|
74
102
|
@stack.pop
|
75
103
|
else
|
76
104
|
if open_modifier != :comment && !pre_js.empty? && @tree.last.children.last.is_a?(EJX::Template::JS)
|
77
|
-
@tree.last
|
105
|
+
@tree.last << EJX::Template::String.new(' ')
|
78
106
|
end
|
79
107
|
value = EJX::Template::JS.new(pm.strip, [open_modifier, close_modifier].compact)
|
80
108
|
|
@@ -89,60 +117,61 @@ class EJX::Template
|
|
89
117
|
@tree.last.attrs << {@stack_info.last => value}
|
90
118
|
@stack.pop
|
91
119
|
else
|
92
|
-
@tree.last
|
120
|
+
@tree.last << value
|
93
121
|
end
|
94
122
|
end
|
95
123
|
when :html_tag
|
96
124
|
if @tree.last.children.last.is_a?(EJX::Template::JS)
|
97
|
-
@tree.last
|
125
|
+
@tree.last << EJX::Template::String.new(' ')
|
98
126
|
end
|
99
127
|
|
100
128
|
scan_until(Regexp.new("(#{@js_start_tags.map{|s| Regexp.escape(s) }.join('|')}|\\/|[^\\s>]+)"))
|
101
|
-
if @js_start_tags.include?(
|
129
|
+
if @js_start_tags.include?(match)
|
102
130
|
@tree << EJX::Template::HTMLTag.new
|
103
131
|
@stack << :js
|
104
|
-
elsif
|
132
|
+
elsif match == '/'
|
105
133
|
@stack.pop
|
106
134
|
@stack << :html_close_tag
|
107
135
|
else
|
108
136
|
@tree << EJX::Template::HTMLTag.new
|
109
|
-
@tree.last.tag_name =
|
137
|
+
@tree.last.tag_name = match
|
110
138
|
@stack << :html_tag_attr_key
|
111
139
|
end
|
112
140
|
when :html_close_tag
|
113
141
|
scan_until(Regexp.new("(#{@js_start_tags.map{|s| Regexp.escape(s) }.join('|')}|[^\\s>]+)"))
|
114
142
|
|
115
|
-
if @js_start_tags.include?(
|
143
|
+
if @js_start_tags.include?(match)
|
116
144
|
@stack << :js
|
117
145
|
else
|
118
146
|
el = @tree.pop
|
119
|
-
if el.tag_name !=
|
120
|
-
raise EJX::TemplateError.new("Expected to close #{el.tag_name} tag, instead closed #{
|
147
|
+
if el.tag_name != match
|
148
|
+
raise EJX::TemplateError.new("Expected to close #{el.tag_name} tag, instead closed #{match}\n#{cursor}")
|
121
149
|
end
|
122
|
-
@tree.last
|
150
|
+
@tree.last << el
|
123
151
|
scan_until(Regexp.new("(#{@html_close_tags.map{|s| Regexp.escape(s) }.join('|')})"))
|
124
152
|
@stack.pop
|
125
153
|
end
|
126
154
|
when :html_tag_attr_key
|
127
155
|
scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|[^\\s=>]+)"))
|
128
|
-
if @js_start_tags.include?(
|
156
|
+
if @js_start_tags.include?(match)
|
129
157
|
@stack << :js
|
130
|
-
elsif @html_close_tags.include?(
|
131
|
-
if
|
158
|
+
elsif @html_close_tags.include?(match)
|
159
|
+
if match == '/>' || EJX::VOID_ELEMENTS.include?(@tree.last.tag_name)
|
132
160
|
el = @tree.pop
|
133
|
-
@tree.last
|
161
|
+
@tree.last << el
|
134
162
|
@stack.pop
|
135
163
|
@stack.pop
|
136
164
|
else
|
165
|
+
@stack.pop
|
137
166
|
@stack << :str
|
138
167
|
end
|
139
168
|
else
|
140
|
-
key = if
|
141
|
-
|
142
|
-
elsif
|
143
|
-
|
169
|
+
key = if match.start_with?('"') && match.end_with?('"')
|
170
|
+
match[1..-2]
|
171
|
+
elsif match.start_with?('"') && match.end_with?('"')
|
172
|
+
match[1..-2]
|
144
173
|
else
|
145
|
-
|
174
|
+
match
|
146
175
|
end
|
147
176
|
@tree.last.attrs << key
|
148
177
|
@stack << :html_tag_attr_value_tx
|
@@ -150,19 +179,19 @@ class EJX::Template
|
|
150
179
|
when :html_tag_attr_value_tx
|
151
180
|
scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|=|\\S)"))
|
152
181
|
tag_key = @tree.last.attrs.pop
|
153
|
-
if @js_start_tags.include?(
|
182
|
+
if @js_start_tags.include?(match)
|
154
183
|
@stack << :js
|
155
|
-
elsif @html_close_tags.include?(
|
184
|
+
elsif @html_close_tags.include?(match)
|
156
185
|
el = @tree.last
|
157
186
|
el.attrs << tag_key
|
158
187
|
if EJX::VOID_ELEMENTS.include?(el.tag_name)
|
159
188
|
@tree.pop
|
160
|
-
@tree.last
|
189
|
+
@tree.last << el
|
161
190
|
end
|
162
191
|
@stack.pop
|
163
192
|
@stack.pop
|
164
193
|
@stack.pop
|
165
|
-
elsif
|
194
|
+
elsif match == '='
|
166
195
|
@stack.pop
|
167
196
|
@tree.last.attrs << tag_key
|
168
197
|
@stack << :html_tag_attr_value
|
@@ -175,24 +204,24 @@ class EJX::Template
|
|
175
204
|
when :html_tag_attr_value
|
176
205
|
scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|'|\"|\\S+)"))
|
177
206
|
|
178
|
-
if @js_start_tags.include?(
|
207
|
+
if @js_start_tags.include?(match)
|
179
208
|
push(:js)
|
180
|
-
elsif
|
209
|
+
elsif match == '"'
|
181
210
|
@stack.pop
|
182
211
|
@stack << :html_tag_attr_value_double_quoted
|
183
|
-
elsif
|
212
|
+
elsif match == "'"
|
184
213
|
@stack.pop
|
185
214
|
@stack << :html_tag_attr_value_single_quoted
|
186
215
|
else
|
187
216
|
@stack.pop
|
188
217
|
key = @tree.last.attrs.pop
|
189
|
-
@tree.last.namespace =
|
190
|
-
@tree.last.attrs << { key =>
|
218
|
+
@tree.last.namespace = match if key == 'xmlns'
|
219
|
+
@tree.last.attrs << { key => match }
|
191
220
|
end
|
192
221
|
when :html_tag_attr_value_double_quoted
|
193
222
|
quoted_value = []
|
194
223
|
scan_until(/("|\[\[=)/)
|
195
|
-
while
|
224
|
+
while match == '[[='
|
196
225
|
quoted_value << pre_match if !pre_match.strip.empty?
|
197
226
|
scan_until(/\]\]/)
|
198
227
|
quoted_value << EJX::Template::JS.new(pre_match.strip)
|
@@ -211,7 +240,7 @@ class EJX::Template
|
|
211
240
|
when :html_tag_attr_value_single_quoted
|
212
241
|
quoted_value = []
|
213
242
|
scan_until(/('|\[\[=)/)
|
214
|
-
while
|
243
|
+
while match == '[[='
|
215
244
|
quoted_value << pre_match if !pre_match.strip.empty?
|
216
245
|
scan_until(/\]\]/)
|
217
246
|
quoted_value << EJX::Template::JS.new(pre_match.strip)
|
@@ -227,6 +256,10 @@ class EJX::Template
|
|
227
256
|
@tree.last.attrs << { key => quoted_value }
|
228
257
|
scan_until(/\'/)
|
229
258
|
@stack.pop
|
259
|
+
when :html_comment
|
260
|
+
scan_until('-->')
|
261
|
+
@tree.last << EJX::Template::HTMLComment.new(pre_match)
|
262
|
+
@stack.pop
|
230
263
|
end
|
231
264
|
end
|
232
265
|
end
|
data/lib/ejx/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ejx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '1.
|
4
|
+
version: '1.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Bracy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: stream_parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rake
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,11 +109,14 @@ files:
|
|
95
109
|
- lib/ejx.rb
|
96
110
|
- lib/ejx/assets/ejx.js
|
97
111
|
- lib/ejx/template.rb
|
112
|
+
- lib/ejx/template/balance_scanner.rb
|
98
113
|
- lib/ejx/template/base.rb
|
114
|
+
- lib/ejx/template/html_comment.rb
|
99
115
|
- lib/ejx/template/html_tag.rb
|
100
116
|
- lib/ejx/template/html_tag/attribute_value.rb
|
101
117
|
- lib/ejx/template/js.rb
|
102
|
-
- lib/ejx/template/
|
118
|
+
- lib/ejx/template/multitemplate.rb
|
119
|
+
- lib/ejx/template/node.rb
|
103
120
|
- lib/ejx/template/string.rb
|
104
121
|
- lib/ejx/template/subtemplate.rb
|
105
122
|
- lib/ejx/template/var_generator.rb
|
@@ -123,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
140
|
- !ruby/object:Gem::Version
|
124
141
|
version: '0'
|
125
142
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
143
|
+
rubygems_version: 3.4.13
|
127
144
|
signing_key:
|
128
145
|
specification_version: 4
|
129
146
|
summary: EJX Template Compiler
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module EJX::Template::ParseHelpers
|
2
|
-
|
3
|
-
attr_accessor :matched
|
4
|
-
|
5
|
-
def eos?
|
6
|
-
@index >= @source.size
|
7
|
-
end
|
8
|
-
|
9
|
-
def scan_until(r)
|
10
|
-
index = @source.index(r, @index)
|
11
|
-
match = @source.match(r, @index)
|
12
|
-
@matched = match.to_s
|
13
|
-
@old_index = @index
|
14
|
-
@index = index + @matched.size
|
15
|
-
match
|
16
|
-
end
|
17
|
-
|
18
|
-
def pre_match
|
19
|
-
@source[@old_index...(@index-@matched.size)]
|
20
|
-
end
|
21
|
-
|
22
|
-
def rewind(by=1)
|
23
|
-
@index -= by
|
24
|
-
end
|
25
|
-
|
26
|
-
def seek(pos)
|
27
|
-
@old_index = nil
|
28
|
-
@matched = nil
|
29
|
-
@index = pos
|
30
|
-
end
|
31
|
-
|
32
|
-
def current_line
|
33
|
-
start = (@source.rindex("\n", @old_index) || 0) + 1
|
34
|
-
uptop = @source.index("\n", @index) || (@old_index + @matched.length)
|
35
|
-
@source[start..uptop]
|
36
|
-
end
|
37
|
-
|
38
|
-
def cursor
|
39
|
-
start = (@source.rindex("\n", @old_index) || 0) + 1
|
40
|
-
uptop = @source.index("\n", @index) || (@old_index + @matched.length)
|
41
|
-
lineno = @source[0..start].count("\n") + 1
|
42
|
-
"#{lineno.to_s.rjust(4)}: " + @source[start..uptop] + "\n #{'-'* (@old_index-start)}#{'^'*(@matched.length)}"
|
43
|
-
end
|
44
|
-
end
|