antlers 0.5.0 → 0.8.0
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 +4 -4
- data/lib/antlers.rb +5 -5
- data/lib/factories/node_factory.rb +40 -17
- data/lib/interfaces/branch_node.rb +2 -2
- data/lib/lexer.rb +67 -15
- data/lib/modules/namespace.rb +19 -0
- data/lib/modules/props.rb +0 -10
- data/lib/modules/variables.rb +4 -2
- data/lib/nodes/for_node.rb +2 -4
- data/lib/nodes/form_node.rb +31 -0
- data/lib/nodes/if_node.rb +31 -0
- data/lib/nodes/prop_node.rb +35 -10
- data/lib/nodes/slot_node.rb +9 -4
- data/lib/nodes/var_node.rb +8 -3
- data/lib/nodes/yield_node.rb +2 -2
- data/lib/parser.rb +45 -33
- data/lib/version.rb +1 -1
- metadata +25 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b0d88855d8351d4ce9b4ebf2bfeaf0fc05faef8a33a882ffd206fa1c8241ec9e
|
|
4
|
+
data.tar.gz: e72f72db347e9f0f442e0a15cb4dce3f2c7405bf1bd7da6b33ab919a7003785b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f1e6d6f8f512d327ee25068a3286d8f1e557407bf99f4ec274cd8c8e7336ef9c2d9db9980e66d13ce58b8055d07f6908108afcb8cd91d2da65224ac567946d4
|
|
7
|
+
data.tar.gz: 27520e044188ae13cde780d7438286e510dae3874cd2218debf2901a1b3c7e9909847c44ae38ce4353e8b0482afa2516fa47857836070a58e8683934865a58c7
|
data/lib/antlers.rb
CHANGED
|
@@ -5,15 +5,15 @@ require_relative 'parser'
|
|
|
5
5
|
|
|
6
6
|
module Antlers
|
|
7
7
|
class << self
|
|
8
|
-
def ast(template)
|
|
8
|
+
def ast(template:, namespace: nil)
|
|
9
9
|
return template unless template.include?('<{') || template.include?('{')
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
Parser.parse(
|
|
11
|
+
sequence = Lexer.new.parse(template)
|
|
12
|
+
Parser.new(namespace:).parse(sequence:)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def render(ast:, current_binding:, parent_binding: nil, slot_node: nil
|
|
16
|
-
ast.render(current_binding:, parent_binding:, slot_node
|
|
15
|
+
def render(ast:, current_binding:, parent_binding: nil, slot_node: nil)
|
|
16
|
+
ast.render(current_binding:, parent_binding:, slot_node:)
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../nodes/if_node'
|
|
3
4
|
require_relative '../nodes/for_node'
|
|
5
|
+
require_relative '../nodes/form_node'
|
|
4
6
|
require_relative '../nodes/prop_node'
|
|
5
7
|
require_relative '../nodes/slot_node'
|
|
6
8
|
require_relative '../nodes/var_node'
|
|
@@ -8,27 +10,48 @@ require_relative '../nodes/yield_node'
|
|
|
8
10
|
|
|
9
11
|
module Antlers
|
|
10
12
|
class NodeFactory
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
def initialize(namespace:)
|
|
14
|
+
@namespace = namespace
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def if_node(segment:)
|
|
18
|
+
IfNode.new(name: segment[:if_def], value: segment[:if_def])
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def for_node(segment:)
|
|
22
|
+
value, key, items = segment.values_at(:for_def, :key, :in)
|
|
23
|
+
ForNode.new(name: value, key:, value:, items:)
|
|
24
|
+
end
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
def form_node(segment:)
|
|
27
|
+
action, method = segment.values_at(:form_def, :method)
|
|
28
|
+
return FormNode.new(name: action, action:, method:) if method
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
end
|
|
30
|
+
FormNode.new(name: action, action:)
|
|
31
|
+
end
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
def prop_node(segment:)
|
|
34
|
+
PropNode.new(name: segment[:prop], props: segment[:props], namespace:)
|
|
35
|
+
end
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
37
|
+
def slot_node(segment:)
|
|
38
|
+
SlotNode.new(name: segment[:slot_def], props: segment[:props], namespace:)
|
|
32
39
|
end
|
|
40
|
+
|
|
41
|
+
def var_node(segment:)
|
|
42
|
+
VarNode.new(value: segment[:var])
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def raw_var_node(segment:)
|
|
46
|
+
VarNode.new(value: segment[:raw_var], raw: true)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def yield_node(segment:)
|
|
50
|
+
YieldNode.new(name: segment[:slot])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
attr_reader :namespace
|
|
33
56
|
end
|
|
34
57
|
end
|
|
@@ -12,12 +12,12 @@ module Antlers
|
|
|
12
12
|
@children = children
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def render(current_binding: nil, parent_binding: nil, slot_node: nil
|
|
15
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil)
|
|
16
16
|
output = ''
|
|
17
17
|
|
|
18
18
|
@children.each do |child|
|
|
19
19
|
# Antlers nodes respond to "render", whereas HTML is stored as a string and output as is.
|
|
20
|
-
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node
|
|
20
|
+
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:) : child) || ''
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
output
|
data/lib/lexer.rb
CHANGED
|
@@ -8,9 +8,13 @@ module Antlers
|
|
|
8
8
|
class LexerParseError < StandardError; end
|
|
9
9
|
|
|
10
10
|
class Lexer
|
|
11
|
+
IF_KEYWORDS = ['if:', ':if'].freeze
|
|
12
|
+
FOR_KEYWORDS = ['for:', 'in:', ':for'].freeze
|
|
13
|
+
FORM_KEYWORDS = ['form:', ':form'].freeze
|
|
14
|
+
|
|
11
15
|
def initialize
|
|
12
16
|
@delimiters = ['<{', '}>', '{', '}']
|
|
13
|
-
@keywords = [
|
|
17
|
+
@keywords = [*IF_KEYWORDS, *FORM_KEYWORDS, *FOR_KEYWORDS, 'slot:', ':slot']
|
|
14
18
|
@cursor = 0
|
|
15
19
|
end
|
|
16
20
|
|
|
@@ -41,20 +45,23 @@ module Antlers
|
|
|
41
45
|
|
|
42
46
|
def antlers_segment(segments:)
|
|
43
47
|
next_segment = segments[@cursor + 1]
|
|
44
|
-
return nil unless next_segment && (segments[@cursor] == '<{' ||
|
|
48
|
+
return nil unless next_segment && (segments[@cursor] == '<{' || brackets?(segments:))
|
|
45
49
|
|
|
46
50
|
next_segment
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
def antlers_lexeme(antlers_segment:, segments:)
|
|
50
|
-
return var(antlers_segment:) if
|
|
54
|
+
return var(antlers_segment:) if brackets?(segments:)
|
|
51
55
|
|
|
52
56
|
name, props, keywords = parse_segment(antlers_segment:)
|
|
53
57
|
|
|
54
|
-
return slot_yield if slot_yield?(keywords)
|
|
58
|
+
return slot_yield if slot_yield?(keywords:)
|
|
55
59
|
return slot(name:, props:) if slot?(name)
|
|
56
60
|
return prop(name:, props:) if prop?(name)
|
|
61
|
+
return if_block(keywords:) if if_block?(keywords:)
|
|
57
62
|
return for_loop(keywords:) if for_loop?(keywords:)
|
|
63
|
+
return form(keywords:) if form?(keywords:)
|
|
64
|
+
return var(antlers_segment:, raw: true) if deerheads?(segments:)
|
|
58
65
|
|
|
59
66
|
raise LexerParseError, "Unrecognised syntax: '#{antlers_segment}'"
|
|
60
67
|
end
|
|
@@ -62,44 +69,80 @@ module Antlers
|
|
|
62
69
|
def parse_segment(antlers_segment:)
|
|
63
70
|
name_and_props, *keywords = antlers_segment.split(/(#{Regexp.union(@keywords)})/)
|
|
64
71
|
name, *props = name_and_props.split(' ')
|
|
72
|
+
[name, props, parse_keywords(keywords:)]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def parse_keywords(keywords:)
|
|
76
|
+
key_values = {}
|
|
77
|
+
|
|
78
|
+
while (keyword = keywords.shift)
|
|
79
|
+
keyword.strip!
|
|
80
|
+
value = keyword.end_with?(':') && value?(keywords.first.strip) ? keywords.shift.strip : nil
|
|
81
|
+
key_values[keyword] = value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
key_values
|
|
85
|
+
end
|
|
65
86
|
|
|
66
|
-
|
|
87
|
+
def value?(string)
|
|
88
|
+
!(string.start_with?(':') || string.end_with?(':'))
|
|
67
89
|
end
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
# TODO: Refactor every constant, match and result method into its own class. Loop through every class and return the first match.
|
|
92
|
+
|
|
93
|
+
def brackets?(segments:)
|
|
70
94
|
first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
|
|
71
95
|
first == '{' && last == '}'
|
|
72
96
|
end
|
|
73
97
|
|
|
98
|
+
def deerheads?(segments:)
|
|
99
|
+
first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
|
|
100
|
+
first == '<{' && last == '}>'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def if_block?(keywords:)
|
|
104
|
+
IF_KEYWORDS.include?(keywords.keys.first)
|
|
105
|
+
end
|
|
106
|
+
|
|
74
107
|
def for_loop?(keywords:)
|
|
75
|
-
|
|
108
|
+
FOR_KEYWORDS.include?(keywords.keys.first)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def form?(keywords:)
|
|
112
|
+
FORM_KEYWORDS.include?(keywords.keys.first)
|
|
76
113
|
end
|
|
77
114
|
|
|
78
115
|
def slot?(name)
|
|
79
116
|
name && (name.start_with?(':') || name.end_with?(':'))
|
|
80
117
|
end
|
|
81
118
|
|
|
82
|
-
def slot_yield?(keywords)
|
|
83
|
-
keywords.include?(':slot')
|
|
119
|
+
def slot_yield?(keywords:)
|
|
120
|
+
keywords.keys.include?(':slot')
|
|
84
121
|
end
|
|
85
122
|
|
|
86
123
|
def prop?(name)
|
|
87
124
|
name && [*'A'..'Z'].include?(name[0])
|
|
88
125
|
end
|
|
89
126
|
|
|
90
|
-
def var(antlers_segment:)
|
|
127
|
+
def var(antlers_segment:, raw: false)
|
|
91
128
|
# String is already interpolated or not depending on user input on the template layer, now we store it without those template quotes.
|
|
92
129
|
antlers_segment = antlers_segment[1..-2] if Queries.user_defined_string?(antlers_segment)
|
|
93
130
|
|
|
131
|
+
return { raw_var: antlers_segment } if raw
|
|
132
|
+
|
|
94
133
|
{ var: antlers_segment }
|
|
95
134
|
end
|
|
96
135
|
|
|
97
|
-
def
|
|
98
|
-
|
|
136
|
+
def if_block(keywords:)
|
|
137
|
+
return { if_def: keywords['if:'] } if keywords.key?('if:')
|
|
138
|
+
|
|
139
|
+
{ if_end: 'level_1' }
|
|
140
|
+
end
|
|
99
141
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
142
|
+
def for_loop(keywords:)
|
|
143
|
+
if keywords['for:']
|
|
144
|
+
*key, value = keywords['for:'].split(',').map(&:strip)
|
|
145
|
+
for_def = { for_def: value, in: keywords['in:'] }
|
|
103
146
|
for_def[:key] = key.first unless key.empty?
|
|
104
147
|
return for_def
|
|
105
148
|
end
|
|
@@ -108,6 +151,15 @@ module Antlers
|
|
|
108
151
|
{ for_end: 'level_1' }
|
|
109
152
|
end
|
|
110
153
|
|
|
154
|
+
def form(keywords:)
|
|
155
|
+
if keywords.key?('form:')
|
|
156
|
+
action = keywords['form:'] ? keywords['form:'][1...-1] : nil
|
|
157
|
+
return { form_def: action }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
{ form_end: 'level_1' }
|
|
161
|
+
end
|
|
162
|
+
|
|
111
163
|
def slot(name:, props:)
|
|
112
164
|
if name.end_with?(':')
|
|
113
165
|
slot_def = { slot_def: name.delete_suffix(':') }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Antlers
|
|
4
|
+
module Namespace
|
|
5
|
+
def class_constant(namespace:, name:)
|
|
6
|
+
return Object.const_get(name) if name.start_with?('::')
|
|
7
|
+
|
|
8
|
+
class_from_namespace(namespace:, name:)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def class_from_namespace(namespace:, name:)
|
|
12
|
+
namespace_with_name = [namespace, name].join('::')
|
|
13
|
+
return Object.const_get(namespace_with_name) if Object.const_defined?(namespace_with_name)
|
|
14
|
+
|
|
15
|
+
namespace.pop
|
|
16
|
+
class_from_namespace(namespace:, name:)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/modules/props.rb
CHANGED
|
@@ -32,15 +32,5 @@ module Antlers
|
|
|
32
32
|
|
|
33
33
|
evaluated_props
|
|
34
34
|
end
|
|
35
|
-
|
|
36
|
-
def class_from_namespace(namespace:, name:)
|
|
37
|
-
return Object.const_get(name) if Object.const_defined?(name) || name.start_with?('::') || namespace.empty?
|
|
38
|
-
|
|
39
|
-
namespace_with_name = [namespace, name].join('::')
|
|
40
|
-
return Object.const_get(namespace_with_name) if Object.const_defined?(namespace_with_name)
|
|
41
|
-
|
|
42
|
-
namespace.pop
|
|
43
|
-
class_from_namespace(namespace:, name:)
|
|
44
|
-
end
|
|
45
35
|
end
|
|
46
36
|
end
|
data/lib/modules/variables.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative '../support/queries'
|
|
2
4
|
|
|
3
5
|
module Antlers
|
|
@@ -15,7 +17,7 @@ module Antlers
|
|
|
15
17
|
name, *chain = name.split('.')
|
|
16
18
|
|
|
17
19
|
result = method_var(name:, current_binding:)
|
|
18
|
-
return method_chain(result:, chain
|
|
20
|
+
return method_chain(result:, chain:) if chain.count > 0
|
|
19
21
|
return result if result
|
|
20
22
|
|
|
21
23
|
nil
|
|
@@ -39,7 +41,7 @@ module Antlers
|
|
|
39
41
|
nil
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
def method_chain(result:, chain
|
|
44
|
+
def method_chain(result:, chain:)
|
|
43
45
|
chain.reduce(result) do |result, method_call|
|
|
44
46
|
result.send(method_call.to_sym)
|
|
45
47
|
end
|
data/lib/nodes/for_node.rb
CHANGED
|
@@ -9,8 +9,6 @@ module Antlers
|
|
|
9
9
|
include Props
|
|
10
10
|
include Variables
|
|
11
11
|
|
|
12
|
-
attr_accessor :children
|
|
13
|
-
|
|
14
12
|
def initialize(name:, items:, value:, key: nil, props: [], children: [])
|
|
15
13
|
super(name:, props:, children:)
|
|
16
14
|
|
|
@@ -19,7 +17,7 @@ module Antlers
|
|
|
19
17
|
@key = key
|
|
20
18
|
end
|
|
21
19
|
|
|
22
|
-
def render(current_binding: nil, parent_binding: nil, slot_node: nil
|
|
20
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil)
|
|
23
21
|
output = ''
|
|
24
22
|
|
|
25
23
|
evaluate(name: @items, current_binding:).each do |value|
|
|
@@ -31,7 +29,7 @@ module Antlers
|
|
|
31
29
|
|
|
32
30
|
@children.each do |child|
|
|
33
31
|
# Antlers nodes respond to "render", whereas HTML is stored as a string and output as is.
|
|
34
|
-
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node
|
|
32
|
+
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:) : child) || ''
|
|
35
33
|
end
|
|
36
34
|
end
|
|
37
35
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/branch_node'
|
|
4
|
+
require_relative '../modules/props'
|
|
5
|
+
require_relative '../modules/variables'
|
|
6
|
+
|
|
7
|
+
module Antlers
|
|
8
|
+
class FormNode < BranchNode
|
|
9
|
+
include Props
|
|
10
|
+
include Variables
|
|
11
|
+
|
|
12
|
+
def initialize(name:, action: nil, method: 'POST', props: [], children: [])
|
|
13
|
+
super(name:, props:, children:)
|
|
14
|
+
|
|
15
|
+
@action = action
|
|
16
|
+
@method = method
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil)
|
|
20
|
+
output = "<form action='#{@action}' method='#{@method}'>"
|
|
21
|
+
|
|
22
|
+
@children.each do |child|
|
|
23
|
+
# Antlers nodes respond to "render", whereas HTML is stored as a string and output as is.
|
|
24
|
+
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:) : child) || ''
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
output += '</form>'
|
|
28
|
+
output
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/branch_node'
|
|
4
|
+
require_relative '../modules/props'
|
|
5
|
+
require_relative '../modules/variables'
|
|
6
|
+
|
|
7
|
+
module Antlers
|
|
8
|
+
class IfNode < BranchNode
|
|
9
|
+
include Props
|
|
10
|
+
include Variables
|
|
11
|
+
|
|
12
|
+
def initialize(name:, value:, props: [], children: [])
|
|
13
|
+
super(name:, props:, children:)
|
|
14
|
+
|
|
15
|
+
@value = value
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil)
|
|
19
|
+
output = ''.dup
|
|
20
|
+
|
|
21
|
+
if evaluate(name: @value, current_binding:)
|
|
22
|
+
@children.each do |child|
|
|
23
|
+
# Antlers nodes respond to "render", whereas HTML is stored as a string and output as is.
|
|
24
|
+
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:) : child) || ''
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
output
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/nodes/prop_node.rb
CHANGED
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../interfaces/leaf_node'
|
|
4
|
+
require_relative '../modules/namespace'
|
|
4
5
|
require_relative '../modules/props'
|
|
5
6
|
|
|
6
7
|
module Antlers
|
|
7
8
|
class PropNode < LeafNode
|
|
8
|
-
include
|
|
9
|
+
include Namespace
|
|
10
|
+
include Props # Immediate parent ancestor that super() refers to.
|
|
9
11
|
|
|
10
|
-
def
|
|
12
|
+
def initialize(name:, props: {}, namespace: nil, **)
|
|
13
|
+
super(name:, props:)
|
|
14
|
+
|
|
15
|
+
@namespace = namespace
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Classes referenced via "<{ MyNode }>" must implement class/instance and render/render_template methods (See LowNode).
|
|
19
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil)
|
|
11
20
|
props = evaluate_props(props: @props, current_binding:)
|
|
12
21
|
event = create_render_event(props:)
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
# TODO: Get LowLoad to load constants defined in "<{ MyNode }>" syntax so that we can resolve namespace/params on class load.
|
|
24
|
+
klass = class_constant(namespace: @namespace&.split('::') || [], name: @name)
|
|
25
|
+
class_proxy = Lowkey[klass.to_s].first[klass.to_s]
|
|
26
|
+
instance = create_instance(class_proxy:, klass:, event:)
|
|
27
|
+
|
|
28
|
+
return instance.render_template(event:, parent_binding:, props:) if klass.template
|
|
29
|
+
|
|
30
|
+
render_args(class_proxy:, instance:, event:, props:)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def create_instance(class_proxy:, klass:, event:)
|
|
36
|
+
initialize_params = class_proxy.instance_methods[:initialize]&.tagged_params(:keyword)&.map(&:name) || []
|
|
37
|
+
return klass.new(event:, **props) if initialize_params.include?(:event) && initialize_params.count > 1
|
|
38
|
+
return klass.new(event:) if initialize_params.include?(:event)
|
|
39
|
+
|
|
40
|
+
klass.new
|
|
41
|
+
end
|
|
19
42
|
|
|
20
|
-
|
|
21
|
-
|
|
43
|
+
def render_args(class_proxy:, instance:, event:, props:)
|
|
44
|
+
render_params = class_proxy.instance_methods[:render]&.tagged_params(:keyword)&.map(&:name) || []
|
|
45
|
+
return instance.render(event:, **props) if render_params.include?(:event) && render_params.count > 1
|
|
46
|
+
return instance.render(event:) if render_params.include?(:event)
|
|
22
47
|
|
|
23
|
-
|
|
48
|
+
instance.render
|
|
24
49
|
end
|
|
25
50
|
end
|
|
26
51
|
end
|
data/lib/nodes/slot_node.rb
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../interfaces/branch_node'
|
|
4
|
+
require_relative '../modules/namespace'
|
|
4
5
|
require_relative '../modules/props'
|
|
5
6
|
|
|
6
7
|
module Antlers
|
|
7
8
|
class SlotNode < BranchNode
|
|
8
|
-
include
|
|
9
|
+
include Namespace
|
|
10
|
+
include Props # Immediate parent ancestor which props are passed to.
|
|
9
11
|
|
|
10
12
|
attr_accessor :children
|
|
11
13
|
|
|
12
|
-
def initialize(name:, props: [], children: [])
|
|
14
|
+
def initialize(name:, namespace:, props: [], children: [], **)
|
|
13
15
|
super(name:, props:, children:)
|
|
16
|
+
|
|
17
|
+
@namespace = namespace
|
|
14
18
|
end
|
|
15
19
|
|
|
16
|
-
def render(current_binding: nil, parent_binding: nil, slot_node: nil
|
|
20
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil)
|
|
17
21
|
props = evaluate_props(props: @props, current_binding:)
|
|
18
22
|
event = create_render_event(props:)
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
# TODO: Get LowLoad to load constants defined in "<{ MyNode }>" syntax so that we can resolve namespace/params on class load.
|
|
25
|
+
klass = class_constant(namespace: @namespace&.split('::') || [], name:)
|
|
21
26
|
instance = klass.new(event:)
|
|
22
27
|
|
|
23
28
|
# Classes referenced via "<{ ChildNode }>" must implement class/instance render/render_template methods (See LowNode).
|
data/lib/nodes/var_node.rb
CHANGED
|
@@ -11,14 +11,19 @@ module Antlers
|
|
|
11
11
|
|
|
12
12
|
attr_reader :value
|
|
13
13
|
|
|
14
|
-
def initialize(value:, name: :var)
|
|
14
|
+
def initialize(value:, name: :var, raw: false)
|
|
15
15
|
super(name:)
|
|
16
16
|
|
|
17
17
|
@value = value
|
|
18
|
+
@raw = raw
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
def render(current_binding: nil, parent_binding: nil, slot_node: nil
|
|
21
|
-
|
|
21
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil)
|
|
22
|
+
result = evaluate(name: @value, current_binding:) || fallback(@value)
|
|
23
|
+
|
|
24
|
+
return result if @raw
|
|
25
|
+
|
|
26
|
+
ERB::Util.html_escape(result)
|
|
22
27
|
end
|
|
23
28
|
end
|
|
24
29
|
end
|
data/lib/nodes/yield_node.rb
CHANGED
|
@@ -9,13 +9,13 @@ module Antlers
|
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
# Renders the children of the parent node in the binding of the parent.
|
|
12
|
-
def render(current_binding: nil, parent_binding: nil, slot_node: nil
|
|
12
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil)
|
|
13
13
|
output = ''
|
|
14
14
|
|
|
15
15
|
slot_node.children.each do |child|
|
|
16
16
|
# Antlers nodes respond to "render", whereas HTML is stored as a string and output as is.
|
|
17
17
|
if child.respond_to?(:render) # rubocop:disable Style/ConditionalAssignment
|
|
18
|
-
output += child.render(current_binding: parent_binding, parent_binding: nil, slot_node: nil
|
|
18
|
+
output += child.render(current_binding: parent_binding, parent_binding: nil, slot_node: nil)
|
|
19
19
|
else
|
|
20
20
|
output += child || ''
|
|
21
21
|
end
|
data/lib/parser.rb
CHANGED
|
@@ -4,44 +4,56 @@ require_relative 'factories/node_factory'
|
|
|
4
4
|
require_relative 'nodes/root_node'
|
|
5
5
|
|
|
6
6
|
module Antlers
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
end
|
|
7
|
+
class Parser
|
|
8
|
+
def initialize(namespace: nil)
|
|
9
|
+
@factory = NodeFactory.new(namespace:)
|
|
10
|
+
end
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
node.children << NodeFactory.var_node(segment:)
|
|
21
|
-
elsif segment[:prop]
|
|
22
|
-
node.children << NodeFactory.prop_node(segment:)
|
|
23
|
-
elsif segment[:slot]
|
|
24
|
-
node.children << NodeFactory.yield_node(segment:)
|
|
25
|
-
elsif segment[:slot_def]
|
|
26
|
-
slot_node = NodeFactory.slot_node(segment:)
|
|
27
|
-
node.children << slot_node
|
|
28
|
-
sub_branch(node: slot_node, sequence:, end_key: :slot_end, end_name: slot_node.name)
|
|
29
|
-
elsif segment[:for_def]
|
|
30
|
-
for_node = NodeFactory.for_node(segment:)
|
|
31
|
-
node.children << for_node
|
|
32
|
-
sub_branch(node: for_node, sequence:, end_key: :for_end, end_name: 'level_1')
|
|
33
|
-
end
|
|
34
|
-
end
|
|
12
|
+
def parse(sequence:, id: :root_node)
|
|
13
|
+
branch(node: RootNode.new(name: id), sequence:)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def branch(node:, sequence:) # rubocop:disable Metrics/AbcSize
|
|
17
|
+
until sequence.empty?
|
|
18
|
+
segment = sequence.shift
|
|
35
19
|
|
|
36
|
-
|
|
20
|
+
if segment.is_a?(String)
|
|
21
|
+
node.children << segment
|
|
22
|
+
elsif segment[:var]
|
|
23
|
+
node.children << @factory.var_node(segment:)
|
|
24
|
+
elsif segment[:raw_var]
|
|
25
|
+
node.children << @factory.raw_var_node(segment:)
|
|
26
|
+
elsif segment[:prop]
|
|
27
|
+
node.children << @factory.prop_node(segment:)
|
|
28
|
+
elsif segment[:slot]
|
|
29
|
+
node.children << @factory.yield_node(segment:)
|
|
30
|
+
elsif segment[:slot_def]
|
|
31
|
+
slot_node = @factory.slot_node(segment:)
|
|
32
|
+
node.children << slot_node
|
|
33
|
+
sub_branch(node: slot_node, sequence:, end_key: :slot_end, end_name: slot_node.name)
|
|
34
|
+
elsif segment[:if_def]
|
|
35
|
+
if_node = @factory.if_node(segment:)
|
|
36
|
+
node.children << if_node
|
|
37
|
+
sub_branch(node: if_node, sequence:, end_key: :if_end, end_name: 'level_1')
|
|
38
|
+
elsif segment[:for_def]
|
|
39
|
+
for_node = @factory.for_node(segment:)
|
|
40
|
+
node.children << for_node
|
|
41
|
+
sub_branch(node: for_node, sequence:, end_key: :for_end, end_name: 'level_1')
|
|
42
|
+
elsif segment[:form_def]
|
|
43
|
+
form_node = @factory.form_node(segment:)
|
|
44
|
+
node.children << form_node
|
|
45
|
+
sub_branch(node: form_node, sequence:, end_key: :form_end, end_name: 'level_1')
|
|
46
|
+
end
|
|
37
47
|
end
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
node
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def sub_branch(node:, sequence:, end_key:, end_name: nil)
|
|
53
|
+
sub_sequence = []
|
|
54
|
+
sub_sequence << sequence.shift until sequence.first.is_a?(Hash) && sequence.first[end_key] == end_name
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
end
|
|
56
|
+
branch(node:, sequence: sub_sequence)
|
|
45
57
|
end
|
|
46
58
|
end
|
|
47
59
|
end
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: antlers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -37,7 +37,22 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0'
|
|
40
|
-
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: lowkey
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
description: A templating language embedded within HTML that is itself embedded within
|
|
55
|
+
Ruby/RBX
|
|
41
56
|
email:
|
|
42
57
|
- maediprichard@gmail.com
|
|
43
58
|
executables: []
|
|
@@ -50,9 +65,12 @@ files:
|
|
|
50
65
|
- lib/interfaces/branch_node.rb
|
|
51
66
|
- lib/interfaces/leaf_node.rb
|
|
52
67
|
- lib/lexer.rb
|
|
68
|
+
- lib/modules/namespace.rb
|
|
53
69
|
- lib/modules/props.rb
|
|
54
70
|
- lib/modules/variables.rb
|
|
55
71
|
- lib/nodes/for_node.rb
|
|
72
|
+
- lib/nodes/form_node.rb
|
|
73
|
+
- lib/nodes/if_node.rb
|
|
56
74
|
- lib/nodes/prop_node.rb
|
|
57
75
|
- lib/nodes/root_node.rb
|
|
58
76
|
- lib/nodes/slot_node.rb
|
|
@@ -61,11 +79,11 @@ files:
|
|
|
61
79
|
- lib/parser.rb
|
|
62
80
|
- lib/support/queries.rb
|
|
63
81
|
- lib/version.rb
|
|
64
|
-
homepage: https://
|
|
82
|
+
homepage: https://github.com/raindeer-rb/antlers
|
|
65
83
|
licenses: []
|
|
66
84
|
metadata:
|
|
67
|
-
homepage_uri: https://
|
|
68
|
-
source_code_uri: https://
|
|
85
|
+
homepage_uri: https://github.com/raindeer-rb/antlers
|
|
86
|
+
source_code_uri: https://github.com/raindeer-rb/antlers/src/branch/main
|
|
69
87
|
rdoc_options: []
|
|
70
88
|
require_paths:
|
|
71
89
|
- lib
|
|
@@ -80,7 +98,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
80
98
|
- !ruby/object:Gem::Version
|
|
81
99
|
version: '0'
|
|
82
100
|
requirements: []
|
|
83
|
-
rubygems_version: 4.0.
|
|
101
|
+
rubygems_version: 4.0.6
|
|
84
102
|
specification_version: 4
|
|
85
|
-
summary:
|
|
103
|
+
summary: Templating language inside HTML inside Ruby
|
|
86
104
|
test_files: []
|