ejx 1.0 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
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