ejx 1.0 → 1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2dc481f36010e82885f2b6ea60a4f13bb4e6c9c4b823ee6899fd02ce12650942
4
- data.tar.gz: 7262002c9a7cb0b3ce87def1d77911e45d6a3e85dde2ae4bfe3fa41aff0f7741
3
+ metadata.gz: 2cb0b12e342368966d44f7738cff85dd83ce6af48f691885cd231d77090127df
4
+ data.tar.gz: 271e81bf0e111b4f0846dff9dfea6cf03422e223b712b1f14459f2f0539081e9
5
5
  SHA512:
6
- metadata.gz: 58f1139b43dcf0205a43619b0f167460d27bd5a75b1708cb4b9497887b8696c7aa5489df61ac89da4e17b3df22a460d3b9fa4ca2f8ce2e5ed6b5fa6c30ca41a4
7
- data.tar.gz: 8cd338246eb063e0156504252c83216fc003f507af94655b3f383984e98831ff5e01ddcc669d6c80e85fa84b8a47888170c97e1ccf11e28b0a80be6f23606142
6
+ metadata.gz: 8361e0271745451e9daa3c9638e019ba75c46eebd9be260be58286233d1340eb32fab7e6a73f79591d067a13d85545fe86ec6298246f315174ffab6e8b811fdb
7
+ data.tar.gz: b2fe93fd63d2d2096f5c2c9b4d482f03fef1bd89b525c5b68d3f127b6762f9beb4735d0d26ad10b51e489a27a5133a3f022c8bd802ca09c763518d4deb343dd5
@@ -1,47 +1,77 @@
1
+ function insertBefore(el, items) {
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
+ });
13
+ }
14
+
1
15
  function repalceejx(el, withItems) {
2
- if ( Array.isArray(withItems) ) {
3
- withItems.forEach((i) => {
4
- if (i instanceof Node) {
5
- el.insertAdjacentElement('beforebegin', i)
6
- } else {
7
- // el.insertAdjacentHTML()
8
- el.insertAdjacentText('beforebegin', i)
9
- }
10
- })
11
- el.remove();
12
- } else {
13
- el.replaceWith(withItems);
14
- }
16
+ if ( Array.isArray(withItems) ) {
17
+ insertBefore(el, withItems);
18
+ el.remove();
19
+ } else {
20
+ el.replaceWith(withItems);
21
+ }
15
22
  }
16
23
 
17
- export function append(items, to, escape, promises, promiseResults) {
18
- if ( Array.isArray(items) ) {
19
- items.forEach((i) => append(i, to, escape, promises));
20
- } else {
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
+
21
60
  let method = Array.isArray(to) ? 'push' : 'append';
22
- if (items instanceof Promise) {
23
- let holder = document.createElement( "div");
24
- to[method](holder);
25
- promises.push( items.then((resolvedItems) => {
26
- if(holder.parentElement) {
27
- repalceejx(holder, resolvedItems || promiseResults.flat())
28
- } else if (Array.isArray(resolvedItems)) {
29
- to.splice(to.indexOf(holder), 1, ...resolvedItems)
30
- } else {
31
- to.splice(to.indexOf(holder), 1, resolvedItems)
32
- }
33
- }));
34
- } else if (typeof items === 'string') {
35
- if (escape) {
36
- to[method](items);
37
- } else {
38
- var container = document.createElement(Array.isArray(to) ? 'div' : to.tagName);
39
- container.innerHTML = items;
40
- to[method](...container.childNodes);
41
- }
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
+ }
42
74
  } else {
43
- to[method](items);
75
+ to[method](items);
44
76
  }
45
-
46
- }
47
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
@@ -1,13 +1,12 @@
1
- class EJX::Template::Base
1
+ class EJX::Template::Base < EJX::Template::Node
2
2
 
3
- attr_accessor :children, :imports
4
-
5
- def initialize(escape: nil)
6
- @children = []
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
- output << "\n await Promise.all(__promises);\n"
36
- output << " return __output;\n"
37
- output << "}"
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, :children, :namespace
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}, false, __promises);\n"
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}, false, __promises);\n"
51
+ js << "#{' '*indentation}__ejx_append(#{output_var}, #{append}, 'unescape', #{promises});\n"
52
52
  js
53
53
  end
54
54
 
@@ -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
- "#{' '*indentation}__ejx_append(#{output}, #{append}, true, __promises);\n"
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}, false, __promises);\n"
17
- elsif !@modifiers.include? :comment
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
@@ -0,0 +1,17 @@
1
+ class EJX::Template::Node
2
+
3
+ attr_accessor :children
4
+
5
+ def initialize(escape: nil)
6
+ @children = []
7
+ @escape = escape
8
+ end
9
+
10
+ def push(*values)
11
+ @children.push(*values)
12
+ self
13
+ end
14
+
15
+ alias_method :<<, :push
16
+
17
+ end
@@ -1,34 +1,175 @@
1
- class EJX::Template::Subtemplate
1
+ class EJX::Template::Subtemplate < EJX::Template::Node
2
2
 
3
- attr_reader :children
3
+ attr_reader :modifiers
4
4
 
5
- def initialize(opening, modifiers)
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
17
+
18
+ def initialize(opening, modifiers, append: true)
6
19
  @children = [opening]
7
20
  @modifiers = modifiers
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
8
35
  end
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
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')
63
+ output = ''
9
64
 
10
- def to_js(indentation: 4, var_generator: nil, append: "__output")
11
- global_output_var = var_generator.next
65
+ global_output_var = var_generator.next if !assigned_to_variable?
66
+ sub_global_output_var = var_generator.next
12
67
  output_var = var_generator.next
13
-
14
- output = "#{' '*indentation}var #{global_output_var} = [];\n"
15
- output << "#{' '*indentation}__ejx_append("
16
- output << @children.first << "\n"
68
+
69
+ if assigned_to_variable?
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
98
+ end
99
+
17
100
  output << "#{' '*(indentation+4)}var #{output_var} = [];\n"
18
101
 
19
102
  @children[1..-2].each do |child|
103
+ promise_var = "#{sub_global_output_var}_promises"
20
104
  output << case child
21
105
  when EJX::Template::String
22
- "#{' '*(indentation+4)}__ejx_append(#{child.to_js}, #{output_var}, false, __promises);\n"
106
+ "#{' '*(indentation+4)}__ejx_append(#{child.to_js}, #{output_var}, 'unescape', #{promise_var});\n"
23
107
  else
24
- 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)
25
109
  end
26
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
27
140
 
28
- output << ' '*(indentation+4) << "#{global_output_var}.push(#{output_var});\n";
29
- output << ' '*(indentation+4) << "return #{output_var};\n";
30
- output << ' '*indentation << @children.last.strip.delete_suffix(';')
31
- output << ", #{append}, true, __promises, #{global_output_var});\n"
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
146
+ else
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
171
+ end
172
+ output << ' '*(indentation) << "return #{output_var};\n";
32
173
 
33
174
  output
34
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 :ParseHelpers, File.expand_path('../template/parse_helpers', __FILE__)
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 EJX::Template::ParseHelpers
12
-
13
- attr_accessor :source
16
+ include StreamParser
14
17
 
15
18
  def initialize(source, options={})
16
- @source = source.strip
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
- process
33
+ parse
31
34
  end
32
35
 
33
- def process
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.children << EJX::Template::String.new(pre_match)
45
+ @tree.last << EJX::Template::String.new(pre_match)
44
46
  end
45
47
 
46
- if !matched.nil?
47
- if @js_start_tags.include?(matched)
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?(matched)
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| matched.end_with?(v) }&.first
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) && pm.match(/\A\s*\}.*\)/m) && !pm.match(/\A\s*\}.*\{\s*\Z/m)
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
- subtemplate.children << pm.strip
70
- @tree.last.children << subtemplate
71
- @stack.pop
72
- elsif pm.match(/function\s*\([^\)]*\)\s*\{\s*\Z/m) || pm.match(/=>\s*\{\s*\Z/m)
73
- @tree << EJX::Template::Subtemplate.new(pm.strip, [open_modifier, close_modifier].compact)
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.children << EJX::Template::String.new(' ')
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.children << value
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.children << EJX::Template::String.new(' ')
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?(matched)
129
+ if @js_start_tags.include?(match)
102
130
  @tree << EJX::Template::HTMLTag.new
103
131
  @stack << :js
104
- elsif matched == '/'
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 = matched
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?(matched)
143
+ if @js_start_tags.include?(match)
116
144
  @stack << :js
117
145
  else
118
146
  el = @tree.pop
119
- if el.tag_name != matched
120
- raise EJX::TemplateError.new("Expected to close #{el.tag_name} tag, instead closed #{matched}\n#{cursor}")
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.children << el
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?(matched)
156
+ if @js_start_tags.include?(match)
129
157
  @stack << :js
130
- elsif @html_close_tags.include?(matched)
131
- if matched == '/>' || EJX::VOID_ELEMENTS.include?(@tree.last.tag_name)
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.children << el
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 matched.start_with?('"') && matched.end_with?('"')
141
- matched[1..-2]
142
- elsif matched.start_with?('"') && matched.end_with?('"')
143
- matched[1..-2]
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
- matched
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?(matched)
182
+ if @js_start_tags.include?(match)
154
183
  @stack << :js
155
- elsif @html_close_tags.include?(matched)
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.children << el
189
+ @tree.last << el
161
190
  end
162
191
  @stack.pop
163
192
  @stack.pop
164
193
  @stack.pop
165
- elsif matched == '='
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?(matched)
207
+ if @js_start_tags.include?(match)
179
208
  push(:js)
180
- elsif matched == '"'
209
+ elsif match == '"'
181
210
  @stack.pop
182
211
  @stack << :html_tag_attr_value_double_quoted
183
- elsif matched == "'"
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 = matched if key == 'xmlns'
190
- @tree.last.attrs << { key => matched }
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 matched == '[[='
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 matched == '[[='
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
@@ -1,3 +1,3 @@
1
1
  module EJX
2
- VERSION = '1.0'
2
+ VERSION = '1.2'
3
3
  end
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.0'
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: 2021-03-26 00:00:00.000000000 Z
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
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - ">="
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: bundler
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -81,11 +109,14 @@ files:
81
109
  - lib/ejx.rb
82
110
  - lib/ejx/assets/ejx.js
83
111
  - lib/ejx/template.rb
112
+ - lib/ejx/template/balance_scanner.rb
84
113
  - lib/ejx/template/base.rb
114
+ - lib/ejx/template/html_comment.rb
85
115
  - lib/ejx/template/html_tag.rb
86
116
  - lib/ejx/template/html_tag/attribute_value.rb
87
117
  - lib/ejx/template/js.rb
88
- - lib/ejx/template/parse_helpers.rb
118
+ - lib/ejx/template/multitemplate.rb
119
+ - lib/ejx/template/node.rb
89
120
  - lib/ejx/template/string.rb
90
121
  - lib/ejx/template/subtemplate.rb
91
122
  - lib/ejx/template/var_generator.rb
@@ -109,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
140
  - !ruby/object:Gem::Version
110
141
  version: '0'
111
142
  requirements: []
112
- rubygems_version: 3.2.3
143
+ rubygems_version: 3.4.13
113
144
  signing_key:
114
145
  specification_version: 4
115
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