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