ejx 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +80 -0
- data/lib/condenser/transformers/ejx.rb +23 -0
- data/lib/ejx.rb +66 -0
- data/lib/ejx/assets/ejx.js +34 -0
- data/lib/ejx/template.rb +206 -0
- data/lib/ejx/template/base.rb +41 -0
- data/lib/ejx/template/html_tag.rb +53 -0
- data/lib/ejx/template/js.rb +19 -0
- data/lib/ejx/template/parse_helpers.rb +44 -0
- data/lib/ejx/template/string.rb +13 -0
- data/lib/ejx/template/subtemplate.rb +33 -0
- data/lib/ejx/template/var_generator.rb +10 -0
- data/lib/ejx/version.rb +3 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7d977622dbdaa5af0afbc8c9c024439f104337c3b4133dbf2c7866656256f373
|
4
|
+
data.tar.gz: e02233b080ee6d8dce9e94135993771464e33497a9102419d75ad8cf058f8984
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e5dd9308fe7904ad51c24475de414887f92cdc62c76111f2c98ff03342d4d2874e17d44cbea939ae9dc3285670ebce7b027afb278630931b96898a40657babd1
|
7
|
+
data.tar.gz: f53c0b6ca412d595ae449f8db65f6038a152ba411f12debc9f57226d9679134c2a471f5e69a14572f8728c97bd0b797e9243322cf47e3af12532ce8719e4b6f4
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Jon Bracy
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
EJX (Embedded JavaScript) template compiler for Ruby
|
2
|
+
====================================================
|
3
|
+
|
4
|
+
EJX templates embed JavaScript code inside `<% ... %>` tags, much like ERB. This
|
5
|
+
library is inspired by [Underscore.js](https://underscorejs.org)'s
|
6
|
+
[`_.template` function](https://underscorejs.org/#template) and
|
7
|
+
[JSX](https://reactjs.org/docs/jsx-in-depth.html), but without the virtual DOM.
|
8
|
+
|
9
|
+
The EJX tag syntax is as follows:
|
10
|
+
|
11
|
+
* `<% ... %>` silently evaluates the statement inside the tags.
|
12
|
+
* `<%= ... %>` evaluates the expression inside the tags, escapes and inserts it
|
13
|
+
into the template output.
|
14
|
+
* `<%- ... %>` behaves like `<%= ... %>` but does not escape it's output.
|
15
|
+
|
16
|
+
The functions compiled with EJX will return an array containing `Node` objects
|
17
|
+
and/or `DOMString` which can be appended to a Node via `Node.append(...)`
|
18
|
+
|
19
|
+
Examples
|
20
|
+
--------
|
21
|
+
|
22
|
+
To compile an EJX template into a Javascript module pass the template to `EJX.compile`:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
EJX.compile("Hello <span><%= name %></span>")
|
26
|
+
# => import {append as __ejx_append} from 'ejx';
|
27
|
+
# =>
|
28
|
+
# => export default async function (locals) {
|
29
|
+
# => var __output = [], __promises = [];
|
30
|
+
# =>
|
31
|
+
# => __output.push("Hello ");
|
32
|
+
# => var __a = document.createElement("span");
|
33
|
+
# => __ejx_append(name, __a, true, __promises);
|
34
|
+
# => __ejx_append(__a, __output, false, __promises);
|
35
|
+
# =>
|
36
|
+
# => await Promise.all(__promises);
|
37
|
+
# => return __output;
|
38
|
+
# => }
|
39
|
+
JS
|
40
|
+
```
|
41
|
+
|
42
|
+
If a evalation tag (`<%=` or `<%-`) ends with an opening of a function, the
|
43
|
+
function returns a compiled template. For example:
|
44
|
+
|
45
|
+
```erb
|
46
|
+
<% formTag = function(template) {
|
47
|
+
var a = document.createElement("form");
|
48
|
+
a.append.apply(a, template());
|
49
|
+
return a;
|
50
|
+
} %>
|
51
|
+
|
52
|
+
<%= formTag(function () { %>
|
53
|
+
<input type="submit" />
|
54
|
+
<% }) %>
|
55
|
+
```
|
56
|
+
|
57
|
+
generates:
|
58
|
+
|
59
|
+
```js
|
60
|
+
import {append as __ejx_append} from 'ejx';
|
61
|
+
|
62
|
+
export default async function (locals) {
|
63
|
+
var __output = [], __promises = [];
|
64
|
+
|
65
|
+
formTag = function(template) {
|
66
|
+
var a = document.createElement("form");
|
67
|
+
a.append.apply(a, template());
|
68
|
+
return a;
|
69
|
+
}
|
70
|
+
var __a = [];
|
71
|
+
__ejx_append(formTag(function () {
|
72
|
+
var __b = document.createElement("input");
|
73
|
+
__b.setAttribute("type", "submit");
|
74
|
+
__ejx_append(__b, __a, false, __promises);
|
75
|
+
}), __output, true, __promises, __a);
|
76
|
+
|
77
|
+
await Promise.all(__promises);
|
78
|
+
return __output;
|
79
|
+
}
|
80
|
+
```
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Condenser::EjxTransformer < Condenser::NodeProcessor
|
2
|
+
|
3
|
+
def initialize(options = {})
|
4
|
+
@options = options
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.setup(environment)
|
8
|
+
require 'ejx' unless defined?(::EJX)
|
9
|
+
|
10
|
+
if !environment.path.include?(EJX::ASSET_DIR)
|
11
|
+
environment.append_path(EJX::ASSET_DIR)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.call(environment, input)
|
16
|
+
new.call(environment, input)
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(environment, input)
|
20
|
+
input[:source] = EJX::Template.new(input[:source], @options).to_module
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/lib/ejx.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module EJX
|
2
|
+
|
3
|
+
class TemplateError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
autoload :Template, File.expand_path('../ejx/template', __FILE__)
|
7
|
+
|
8
|
+
@@settings = {
|
9
|
+
open_tag: '<%',
|
10
|
+
close_tag: '%>',
|
11
|
+
|
12
|
+
open_tag_modifiers: {
|
13
|
+
escape: '=',
|
14
|
+
unescape: '-',
|
15
|
+
comment: '#',
|
16
|
+
literal: '%'
|
17
|
+
},
|
18
|
+
|
19
|
+
close_tag_modifiers: {
|
20
|
+
trim: '-',
|
21
|
+
literal: '%'
|
22
|
+
},
|
23
|
+
|
24
|
+
escape: nil
|
25
|
+
}
|
26
|
+
|
27
|
+
ASSET_DIR = File.join(__dir__, 'ejx', 'assets')
|
28
|
+
|
29
|
+
VOID_ELEMENTS = [
|
30
|
+
'area',
|
31
|
+
'base',
|
32
|
+
'br',
|
33
|
+
'col',
|
34
|
+
'embed',
|
35
|
+
'hr',
|
36
|
+
'img',
|
37
|
+
'input',
|
38
|
+
'link',
|
39
|
+
'meta',
|
40
|
+
'param',
|
41
|
+
'source',
|
42
|
+
'track',
|
43
|
+
'wbr'
|
44
|
+
]
|
45
|
+
|
46
|
+
def self.compile(source, options = {})
|
47
|
+
EJX::Template.new(source, options).to_module
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.settings
|
51
|
+
@@settings
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.settings=(value)
|
55
|
+
@@settings = value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
if defined?(Condenser)
|
61
|
+
Condenser.configure do
|
62
|
+
autoload :EjxTransformer, 'condenser/transformers/ejx'
|
63
|
+
register_mime_type 'application/ejx', extensions: '.ejx', charset: :unicode
|
64
|
+
register_template 'application/ejx', Condenser::EjxTransformer
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
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
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
export function append(items, to, escape, promises, promise_results) {
|
18
|
+
if ( Array.isArray(items) ) {
|
19
|
+
items.forEach((i) => append(i, to, escape, promises));
|
20
|
+
} else {
|
21
|
+
let method = Array.isArray(to) ? 'push' : 'append';
|
22
|
+
|
23
|
+
if (items instanceof Promise) {
|
24
|
+
let holder = document.createElement( "div");
|
25
|
+
to[method](holder);
|
26
|
+
promises.push( items.then((resolvedItems) => {
|
27
|
+
repalceejx(holder, promise_results || resolvedItems)
|
28
|
+
}));
|
29
|
+
} else {
|
30
|
+
to[method](items);
|
31
|
+
}
|
32
|
+
|
33
|
+
}
|
34
|
+
}
|
data/lib/ejx/template.rb
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
class EJX::Template
|
2
|
+
|
3
|
+
autoload :JS, File.expand_path('../template/js', __FILE__)
|
4
|
+
autoload :Base, File.expand_path('../template/base', __FILE__)
|
5
|
+
autoload :String, File.expand_path('../template/string', __FILE__)
|
6
|
+
autoload :HTMLTag, File.expand_path('../template/html_tag', __FILE__)
|
7
|
+
autoload :ParseHelpers, File.expand_path('../template/parse_helpers', __FILE__)
|
8
|
+
autoload :Subtemplate, File.expand_path('../template/subtemplate', __FILE__)
|
9
|
+
autoload :VarGenerator, File.expand_path('../template/var_generator', __FILE__)
|
10
|
+
|
11
|
+
include EJX::Template::ParseHelpers
|
12
|
+
|
13
|
+
attr_accessor :source
|
14
|
+
|
15
|
+
def initialize(source, options={})
|
16
|
+
@source = source.strip
|
17
|
+
|
18
|
+
@js_start_tags = [options[:open_tag] || EJX.settings[:open_tag]]
|
19
|
+
@html_start_tags = ['<']
|
20
|
+
@start_tags = @js_start_tags + @html_start_tags
|
21
|
+
|
22
|
+
@js_close_tags = [options[:close_tag] || EJX.settings[:close_tag]]
|
23
|
+
@html_close_tags = ['/>', '>']
|
24
|
+
@close_tags = @js_close_tags + @html_close_tags
|
25
|
+
|
26
|
+
@open_tag_modifiers = EJX.settings[:open_tag_modifiers].merge(options[:open_tag_modifiers] || {})
|
27
|
+
@close_tag_modifiers = EJX.settings[:close_tag_modifiers].merge(options[:close_tag_modifiers] || {})
|
28
|
+
|
29
|
+
@escape = options[:escape]
|
30
|
+
process
|
31
|
+
end
|
32
|
+
|
33
|
+
def process
|
34
|
+
seek(0)
|
35
|
+
@tree = [EJX::Template::Base.new(escape: @escape)]
|
36
|
+
@stack = [:str]
|
37
|
+
|
38
|
+
while !eos?
|
39
|
+
case @stack.last
|
40
|
+
when :str
|
41
|
+
scan_until(Regexp.new("(#{@start_tags.map{|s| Regexp.escape(s) }.join('|')}|\\z)"))
|
42
|
+
if !pre_match.strip.empty?
|
43
|
+
@tree.last.children << EJX::Template::String.new(pre_match)
|
44
|
+
end
|
45
|
+
|
46
|
+
if !matched.nil?
|
47
|
+
if @js_start_tags.include?(matched)
|
48
|
+
@stack << :js
|
49
|
+
elsif @html_start_tags.include?(matched)
|
50
|
+
@stack << :html_tag
|
51
|
+
end
|
52
|
+
end
|
53
|
+
when :js
|
54
|
+
scan_until(Regexp.new("(#{@js_close_tags.map{|s| Regexp.escape(s) }.join('|')})"))
|
55
|
+
pm = pre_match
|
56
|
+
open_modifier = @open_tag_modifiers.find { |k,v| pm.start_with?(v) }&.first
|
57
|
+
close_modifier = @close_tag_modifiers.find { |k,v| matched.end_with?(v) }&.first
|
58
|
+
pm.slice!(0, open_modifier[1].size) if open_modifier
|
59
|
+
pm.slice!(pm.size - close_modifier[1].size, close_modifier[1].size) if close_modifier
|
60
|
+
|
61
|
+
if pm =~ /\A\s*import/
|
62
|
+
import = pm.strip
|
63
|
+
import += ';' if !import.end_with?(';')
|
64
|
+
@tree.first.imports << import
|
65
|
+
@stack.pop
|
66
|
+
elsif @tree.last.is_a?(EJX::Template::Subtemplate) && pm.match(/\A\s*\}\s*\)/m) && !pm.match(/\A\s*\}.*\{\s*\Z/m)
|
67
|
+
subtemplate = @tree.pop
|
68
|
+
subtemplate.children << pm.strip
|
69
|
+
@tree.last.children << subtemplate
|
70
|
+
@stack.pop
|
71
|
+
elsif pm.match(/function\s*\([^\)]*\)\s*\{\s*\Z/m)
|
72
|
+
@tree << EJX::Template::Subtemplate.new(pm.strip, [open_modifier, close_modifier].compact)
|
73
|
+
@stack.pop
|
74
|
+
else
|
75
|
+
value = EJX::Template::JS.new(pm.strip, [open_modifier, close_modifier].compact)
|
76
|
+
|
77
|
+
@stack.pop
|
78
|
+
case @stack.last
|
79
|
+
when :html_tag
|
80
|
+
@tree.last.tag_name = value
|
81
|
+
push(:html_tag_attr_key)
|
82
|
+
when :html_tag_attr_key
|
83
|
+
@tree.last.attrs << value
|
84
|
+
when :html_tag_attr_value
|
85
|
+
@tree.last.attrs << {@stack_info.last => value}
|
86
|
+
@stack.pop
|
87
|
+
else
|
88
|
+
@tree.last.children << value
|
89
|
+
end
|
90
|
+
end
|
91
|
+
when :html_tag
|
92
|
+
scan_until(Regexp.new("(#{@js_start_tags.map{|s| Regexp.escape(s) }.join('|')}|\\/|[^\\s>]+)"))
|
93
|
+
if @js_start_tags.include?(matched)
|
94
|
+
@tree << EJX::Template::HTMLTag.new
|
95
|
+
@stack << :js
|
96
|
+
elsif matched == '/'
|
97
|
+
@stack.pop
|
98
|
+
@stack << :html_close_tag
|
99
|
+
else
|
100
|
+
@tree << EJX::Template::HTMLTag.new
|
101
|
+
@tree.last.tag_name = matched
|
102
|
+
@stack << :html_tag_attr_key
|
103
|
+
end
|
104
|
+
when :html_close_tag
|
105
|
+
scan_until(Regexp.new("(#{@js_start_tags.map{|s| Regexp.escape(s) }.join('|')}|[^\\s>]+)"))
|
106
|
+
|
107
|
+
if @js_start_tags.include?(matched)
|
108
|
+
@stack << :js
|
109
|
+
else
|
110
|
+
el = @tree.pop
|
111
|
+
if el.tag_name != matched
|
112
|
+
raise EJX::TemplateError.new("Expected to close #{el.tag_name} tag, instead closed #{matched}\n#{cursor}")
|
113
|
+
end
|
114
|
+
@tree.last.children << el
|
115
|
+
scan_until(Regexp.new("(#{@html_close_tags.map{|s| Regexp.escape(s) }.join('|')})"))
|
116
|
+
@stack.pop
|
117
|
+
end
|
118
|
+
when :html_tag_attr_key
|
119
|
+
scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|[^\\s=>]+)"))
|
120
|
+
if @js_start_tags.include?(matched)
|
121
|
+
@stack << :js
|
122
|
+
elsif @html_close_tags.include?(matched)
|
123
|
+
if matched == '/>' || EJX::VOID_ELEMENTS.include?(@tree.last.tag_name)
|
124
|
+
el = @tree.pop
|
125
|
+
@tree.last.children << el
|
126
|
+
@stack.pop
|
127
|
+
@stack.pop
|
128
|
+
else
|
129
|
+
@stack << :str
|
130
|
+
end
|
131
|
+
else
|
132
|
+
key = if matched.start_with?('"') && matched.end_with?('"')
|
133
|
+
matched[1..-2]
|
134
|
+
elsif matched.start_with?('"') && matched.end_with?('"')
|
135
|
+
matched[1..-2]
|
136
|
+
else
|
137
|
+
matched
|
138
|
+
end
|
139
|
+
@tree.last.attrs << key
|
140
|
+
@stack << :html_tag_attr_value_tx
|
141
|
+
end
|
142
|
+
when :html_tag_attr_value_tx
|
143
|
+
scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|=|\\S)"))
|
144
|
+
tag_key = @tree.last.attrs.pop
|
145
|
+
if @js_start_tags.include?(matched)
|
146
|
+
@stack << :js
|
147
|
+
elsif @html_close_tags.include?(matched)
|
148
|
+
el = @tree.last
|
149
|
+
el.attrs << tag_key
|
150
|
+
if EJX::VOID_ELEMENTS.include?(el.tag_name)
|
151
|
+
@tree.pop
|
152
|
+
@tree.last.children << el
|
153
|
+
end
|
154
|
+
@stack.pop
|
155
|
+
@stack.pop
|
156
|
+
@stack.pop
|
157
|
+
elsif matched == '='
|
158
|
+
@stack.pop
|
159
|
+
@tree.last.attrs << tag_key
|
160
|
+
@stack << :html_tag_attr_value
|
161
|
+
else
|
162
|
+
@stack.pop
|
163
|
+
@tree.last.attrs << tag_key
|
164
|
+
rewind(1)
|
165
|
+
end
|
166
|
+
|
167
|
+
when :html_tag_attr_value
|
168
|
+
scan_until(Regexp.new("(#{(@js_start_tags+@html_close_tags).map{|s| Regexp.escape(s) }.join('|')}|'|\"|\\S+)"))
|
169
|
+
|
170
|
+
if @js_start_tags.include?(matched)
|
171
|
+
push(:js)
|
172
|
+
elsif matched == '"'
|
173
|
+
@stack.pop
|
174
|
+
@stack << :html_tag_attr_value_double_quoted
|
175
|
+
elsif matched == "'"
|
176
|
+
@stack.pop
|
177
|
+
@stack << :html_tag_attr_value_single_quoted
|
178
|
+
else
|
179
|
+
@stack.pop
|
180
|
+
key = @tree.last.attrs.pop
|
181
|
+
@tree.last.namespace = matched if key == 'xmlns'
|
182
|
+
@tree.last.attrs << { key => matched }
|
183
|
+
end
|
184
|
+
when :html_tag_attr_value_double_quoted
|
185
|
+
scan_until(/[^\"]*/)
|
186
|
+
key = @tree.last.attrs.pop
|
187
|
+
@tree.last.namespace = matched if key == 'xmlns'
|
188
|
+
@tree.last.attrs << { key => matched }
|
189
|
+
scan_until(/\"/)
|
190
|
+
@stack.pop
|
191
|
+
when :html_tag_attr_value_single_quoted
|
192
|
+
scan_until(/[^']*/)
|
193
|
+
key = @tree.last.attrs.pop
|
194
|
+
@tree.last.namespace = matched if key == 'xmlns'
|
195
|
+
@tree.last.attrs << { key => matched }
|
196
|
+
scan_until(/'/)
|
197
|
+
@stack.pop
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_module
|
203
|
+
@tree.first.to_module
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class EJX::Template::Base
|
2
|
+
|
3
|
+
attr_accessor :children, :imports
|
4
|
+
|
5
|
+
def initialize(escape: nil)
|
6
|
+
@children = []
|
7
|
+
@escape = escape
|
8
|
+
@imports = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_module
|
12
|
+
var_generator = EJX::Template::VarGenerator.new
|
13
|
+
|
14
|
+
output = if @escape
|
15
|
+
"import {" + @escape.split('.').reverse.join(" as escape} from '") + "';\n"
|
16
|
+
else
|
17
|
+
"import {append as __ejx_append} from 'ejx';\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
@imports.each do |import|
|
21
|
+
output << import << "\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
output << "\nexport default async function (locals) {\n"
|
25
|
+
output << " var __output = [], __promises = [];\n \n"
|
26
|
+
|
27
|
+
@children.each do |child|
|
28
|
+
output << case child
|
29
|
+
when EJX::Template::String
|
30
|
+
" __output.push(#{child.to_js});\n"
|
31
|
+
else
|
32
|
+
child.to_js(var_generator: var_generator)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
output << "\n await Promise.all(__promises);\n"
|
36
|
+
output << " return __output;\n"
|
37
|
+
output << "}"
|
38
|
+
output
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class EJX::Template::HTMLTag
|
2
|
+
|
3
|
+
attr_accessor :tag_name, :attrs, :children, :namespace
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@attrs = []
|
7
|
+
@children = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
@value
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"#<EJX::HTMLTag:#{self.object_id} @tag_name=#{tag_name}>"
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_js(append: "__output", var_generator:, indentation: 4, namespace: nil)
|
19
|
+
namespace ||= self.namespace
|
20
|
+
|
21
|
+
output_var = var_generator.next
|
22
|
+
js = "#{' '*indentation}var #{output_var} = document.createElement"
|
23
|
+
js << if namespace
|
24
|
+
"NS(#{JSON.generate(namespace)}, #{JSON.generate(tag_name)});\n"
|
25
|
+
else
|
26
|
+
"(#{JSON.generate(tag_name)});\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
@attrs.each do |attr|
|
30
|
+
if attr.is_a?(Hash)
|
31
|
+
attr.each do |k, v|
|
32
|
+
js << "#{' '*indentation}#{output_var}.setAttribute(#{JSON.generate(k)}, #{JSON.generate(v)});\n"
|
33
|
+
end
|
34
|
+
else
|
35
|
+
js << "#{' '*indentation}#{output_var}.setAttribute(#{JSON.generate(attr)}, \"\");\n"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@children.each do |child|
|
40
|
+
js << if child.is_a?(EJX::Template::String)
|
41
|
+
"#{' '*indentation}__ejx_append(#{child.to_js}, #{output_var}, false, __promises);\n"
|
42
|
+
elsif child.is_a?(EJX::Template::HTMLTag)
|
43
|
+
child.to_js(var_generator: var_generator, indentation: indentation, append: output_var, namespace: namespace)
|
44
|
+
else
|
45
|
+
child.to_js(var_generator: var_generator, indentation: indentation, append: output_var)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
js << "#{' '*indentation}__ejx_append(#{output_var}, #{append}, false, __promises);\n"
|
50
|
+
js
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class EJX::Template::JS
|
2
|
+
|
3
|
+
def initialize(value, modifiers)
|
4
|
+
@modifiers = modifiers
|
5
|
+
@value = value
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_js(indentation: 4, var_generator: nil, append: "__output")
|
9
|
+
output = @value
|
10
|
+
|
11
|
+
if @modifiers.include? :escape
|
12
|
+
"#{' '*indentation}__ejx_append(#{output}, #{append}, true, __promises);\n"
|
13
|
+
elsif @modifiers.include? :unescape
|
14
|
+
"#{' '*indentation}__ejx_append(#{output}, #{append}, false, __promises);\n"
|
15
|
+
elsif !@modifiers.include? :comment
|
16
|
+
"#{' '*indentation}#{output}\n"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,44 @@
|
|
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)
|
23
|
+
@index -= 1
|
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
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class EJX::Template::Subtemplate
|
2
|
+
|
3
|
+
attr_reader :children
|
4
|
+
|
5
|
+
def initialize(opening, modifiers)
|
6
|
+
@children = [opening]
|
7
|
+
@modifiers = modifiers
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_js(indentation: 4, var_generator: nil, append: "__output")
|
11
|
+
output_var = var_generator.next
|
12
|
+
output = "#{' '*indentation}var #{output_var} = [];\n"
|
13
|
+
output << "#{' '*indentation}__ejx_append("
|
14
|
+
output << @children.first
|
15
|
+
output << "\n"
|
16
|
+
|
17
|
+
# var_generator ||= EJX::Template::VarGenerator.new
|
18
|
+
@children[1..-2].each do |child|
|
19
|
+
output << case child
|
20
|
+
when EJX::Template::String
|
21
|
+
"#{' '*(indentation+4)}__ejx_append(#{child.to_js}, #{output_var}, false, __promises);\n"
|
22
|
+
else
|
23
|
+
child.to_js(indentation: indentation + 4, var_generator: var_generator, append: output_var)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
output << ' '*indentation
|
27
|
+
output << @children.last
|
28
|
+
output << ", #{append}, true, __promises, #{output_var});\n"
|
29
|
+
|
30
|
+
output
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/ejx/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ejx
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Bracy
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-04-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
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'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: |
|
70
|
+
Compile EJX (Embedded JavaScript) templates to Javascript
|
71
|
+
functions with Ruby.
|
72
|
+
email:
|
73
|
+
- jonbracy@gmail.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- LICENSE
|
79
|
+
- README.md
|
80
|
+
- lib/condenser/transformers/ejx.rb
|
81
|
+
- lib/ejx.rb
|
82
|
+
- lib/ejx/assets/ejx.js
|
83
|
+
- lib/ejx/template.rb
|
84
|
+
- lib/ejx/template/base.rb
|
85
|
+
- lib/ejx/template/html_tag.rb
|
86
|
+
- lib/ejx/template/js.rb
|
87
|
+
- lib/ejx/template/parse_helpers.rb
|
88
|
+
- lib/ejx/template/string.rb
|
89
|
+
- lib/ejx/template/subtemplate.rb
|
90
|
+
- lib/ejx/template/var_generator.rb
|
91
|
+
- lib/ejx/version.rb
|
92
|
+
homepage: https://github.com/malomalo/ejx
|
93
|
+
licenses:
|
94
|
+
- MIT
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubygems_version: 3.1.2
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: EJX Template Compiler
|
115
|
+
test_files: []
|