antlers 0.2.0 → 0.4.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 +1 -3
- data/lib/factories/node_factory.rb +6 -0
- data/lib/lexer.rb +24 -7
- data/lib/modules/props.rb +7 -8
- data/lib/modules/variables.rb +38 -0
- data/lib/nodes/for_node.rb +41 -0
- data/lib/nodes/prop_node.rb +5 -5
- data/lib/nodes/slot_node.rb +1 -1
- data/lib/nodes/var_node.rb +5 -21
- data/lib/nodes/yield_node.rb +5 -1
- data/lib/parser.rb +20 -13
- data/lib/queries.rb +1 -1
- data/lib/version.rb +1 -1
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 581b3e4990e2c5279c26989e05e545f61a80a3fb8590c8a4bfd058878f2684f8
|
|
4
|
+
data.tar.gz: 5135d9c10a19fa9bb80c79694f998287c1dad614514a6d22c915cfc371d54b71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4e85dd8fc504c21fe4c71f692203dc695fd60834e2f2f33185c1daf6edbf3e64c4c8eccf29710d899f5beab1c348cac1c9fe6a8350778b7563cd4d879cef78e2
|
|
7
|
+
data.tar.gz: 5b4853ba5e0d0749d4305c57923d6488ddcd5b05892aa2e274dd3683519ae85fcbd9a84b117e9c453a9b1ab256c814cd6f6d35b71db76935e30f0c86d17852ba
|
data/lib/antlers.rb
CHANGED
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
require_relative 'lexer'
|
|
4
4
|
require_relative 'parser'
|
|
5
5
|
|
|
6
|
-
require 'low_event'
|
|
7
|
-
|
|
8
6
|
module Antlers
|
|
9
7
|
class << self
|
|
10
|
-
def
|
|
8
|
+
def ast(template)
|
|
11
9
|
return template unless template.include?('<{') || template.include?('{')
|
|
12
10
|
|
|
13
11
|
lexemes = Lexer.new.parse(template)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative '../nodes/for_node'
|
|
3
4
|
require_relative '../nodes/prop_node'
|
|
4
5
|
require_relative '../nodes/slot_node'
|
|
5
6
|
require_relative '../nodes/var_node'
|
|
@@ -8,6 +9,11 @@ require_relative '../nodes/yield_node'
|
|
|
8
9
|
module Antlers
|
|
9
10
|
class NodeFactory
|
|
10
11
|
class << self
|
|
12
|
+
def for_node(segment:)
|
|
13
|
+
value, key, items = segment.values_at(:for_def, :key, :in)
|
|
14
|
+
ForNode.new(name: value, key:, value:, items:)
|
|
15
|
+
end
|
|
16
|
+
|
|
11
17
|
def prop_node(segment:)
|
|
12
18
|
PropNode.new(name: segment[:prop], props: segment[:props])
|
|
13
19
|
end
|
data/lib/lexer.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Antlers
|
|
|
10
10
|
class Lexer
|
|
11
11
|
def initialize
|
|
12
12
|
@delimiters = ['<{', '}>', '{', '}']
|
|
13
|
-
@keywords = ['if:', 'for:', 'in:', 'slot:', ':slot']
|
|
13
|
+
@keywords = ['if:', 'for:', 'in:', ':for', 'slot:', ':slot']
|
|
14
14
|
@cursor = 0
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -54,22 +54,27 @@ module Antlers
|
|
|
54
54
|
return slot_yield if slot_yield?(keywords)
|
|
55
55
|
return slot(name:, props:) if slot?(name)
|
|
56
56
|
return prop(name:, props:) if prop?(name)
|
|
57
|
+
return for_loop(keywords:) if for_loop?(keywords:)
|
|
57
58
|
|
|
58
|
-
raise LexerParseError, "
|
|
59
|
+
raise LexerParseError, "Unrecognised syntax: '#{antlers_segment}'"
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
def parse_segment(antlers_segment:)
|
|
62
63
|
name_and_props, *keywords = antlers_segment.split(/(#{Regexp.union(@keywords)})/)
|
|
63
64
|
name, *props = name_and_props.split(' ')
|
|
64
65
|
|
|
65
|
-
[name, props, keywords]
|
|
66
|
+
[name, props, keywords.map(&:strip)]
|
|
66
67
|
end
|
|
67
68
|
|
|
68
69
|
def var?(segments:)
|
|
69
|
-
first,
|
|
70
|
+
first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
|
|
70
71
|
first == '{' && last == '}'
|
|
71
72
|
end
|
|
72
73
|
|
|
74
|
+
def for_loop?(keywords:)
|
|
75
|
+
['for:', ':for'].include?(keywords.first)
|
|
76
|
+
end
|
|
77
|
+
|
|
73
78
|
def slot?(name)
|
|
74
79
|
name && (name.start_with?(':') || name.end_with?(':'))
|
|
75
80
|
end
|
|
@@ -84,13 +89,25 @@ module Antlers
|
|
|
84
89
|
|
|
85
90
|
def var(antlers_segment:)
|
|
86
91
|
# String is already interpolated or not depending on user input on the template layer, now we store it without those template quotes.
|
|
87
|
-
if Queries.user_defined_string?(antlers_segment)
|
|
88
|
-
antlers_segment = antlers_segment[1..-2]
|
|
89
|
-
end
|
|
92
|
+
antlers_segment = antlers_segment[1..-2] if Queries.user_defined_string?(antlers_segment)
|
|
90
93
|
|
|
91
94
|
{ var: antlers_segment }
|
|
92
95
|
end
|
|
93
96
|
|
|
97
|
+
def for_loop(keywords:)
|
|
98
|
+
key_values = keywords.count.even? ? keywords.each_slice(2).to_h : {}
|
|
99
|
+
|
|
100
|
+
if key_values['for:']
|
|
101
|
+
*key, value = key_values['for:'].split(',').map(&:strip)
|
|
102
|
+
for_def = { for_def: value, in: key_values['in:'] }
|
|
103
|
+
for_def[:key] = key.first unless key.empty?
|
|
104
|
+
return for_def
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# TODO: Keep track of which for loop we're in to allow nested for loops.
|
|
108
|
+
{ for_end: 'level_1' }
|
|
109
|
+
end
|
|
110
|
+
|
|
94
111
|
def slot(name:, props:)
|
|
95
112
|
if name.end_with?(':')
|
|
96
113
|
slot_def = { slot_def: name.delete_suffix(':') }
|
data/lib/modules/props.rb
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'low_event'
|
|
4
|
+
require_relative 'variables'
|
|
5
|
+
|
|
3
6
|
module Antlers
|
|
4
7
|
module Props
|
|
8
|
+
include Variables
|
|
9
|
+
|
|
5
10
|
attr_accessor :props
|
|
6
11
|
|
|
7
12
|
def initialize(name:, props: {}, **)
|
|
@@ -12,7 +17,7 @@ module Antlers
|
|
|
12
17
|
|
|
13
18
|
private
|
|
14
19
|
|
|
15
|
-
def
|
|
20
|
+
def create_render_event(props:)
|
|
16
21
|
Low::Events::RenderEvent.new(action: :render, props:)
|
|
17
22
|
end
|
|
18
23
|
|
|
@@ -22,13 +27,7 @@ module Antlers
|
|
|
22
27
|
evaluated_props = {}
|
|
23
28
|
|
|
24
29
|
props.each do |name, value|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if receiver.respond_to?(value.to_sym)
|
|
28
|
-
evaluated_props[name] = receiver.send(value.to_sym)
|
|
29
|
-
elsif value.start_with?('@')
|
|
30
|
-
evaluated_props[name] = receiver.instance_variable_get(value)
|
|
31
|
-
end
|
|
30
|
+
evaluated_props[name] = evaluate(name: value, current_binding:)
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
evaluated_props
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Antlers
|
|
2
|
+
module Variables
|
|
3
|
+
# Evaluation limited to the following for security and to prevent a templating language becoming a programming language.
|
|
4
|
+
# 1. An instance variable
|
|
5
|
+
# 2. A method call/local variable
|
|
6
|
+
# 3. A method chain
|
|
7
|
+
# 4. A static string
|
|
8
|
+
def evaluate(name:, current_binding:)
|
|
9
|
+
return @value.to_s unless current_binding
|
|
10
|
+
|
|
11
|
+
name, *chain = name.split('.')
|
|
12
|
+
|
|
13
|
+
result = method_var(name:, current_binding:)
|
|
14
|
+
return method_chain(result:, chain:, current_binding:) if chain.count > 0
|
|
15
|
+
return result if result
|
|
16
|
+
|
|
17
|
+
@value.to_s
|
|
18
|
+
rescue NameError
|
|
19
|
+
@value.to_s
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def method_var(name:, current_binding:)
|
|
25
|
+
return current_binding.receiver.instance_variable_get(name) if name.start_with?('@')
|
|
26
|
+
return current_binding.local_variable_get(name) if current_binding.local_variable_defined?(name)
|
|
27
|
+
return current_binding.receiver.send(name.to_sym) if current_binding.receiver.respond_to?(name.to_sym)
|
|
28
|
+
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def method_chain(result:, chain:, current_binding:)
|
|
33
|
+
chain.reduce(result) do |result, method_call|
|
|
34
|
+
result.send(method_call.to_sym)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
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 ForNode < BranchNode
|
|
9
|
+
include Props
|
|
10
|
+
include Variables
|
|
11
|
+
|
|
12
|
+
attr_accessor :children
|
|
13
|
+
|
|
14
|
+
def initialize(name:, items:, value:, key: nil, props: [], children: [])
|
|
15
|
+
super(name:, props:, children:)
|
|
16
|
+
|
|
17
|
+
@items = items
|
|
18
|
+
@value = value
|
|
19
|
+
@key = key
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def render(current_binding: nil, parent_binding: nil, slot_node: nil, namespace: nil)
|
|
23
|
+
output = ''
|
|
24
|
+
|
|
25
|
+
evaluate(name: @items, current_binding:).each do |value|
|
|
26
|
+
key, value = value if @key
|
|
27
|
+
|
|
28
|
+
# TODO: Parallelize by creating new bindings and ensuring children have any args they need via RenderEvent.
|
|
29
|
+
current_binding.local_variable_set(@value, value)
|
|
30
|
+
current_binding.local_variable_set(@key, key) if @key
|
|
31
|
+
|
|
32
|
+
@children.each do |child|
|
|
33
|
+
# 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:, namespace:) : child) || ''
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
output
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/nodes/prop_node.rb
CHANGED
|
@@ -9,15 +9,15 @@ module Antlers
|
|
|
9
9
|
|
|
10
10
|
def render(current_binding: nil, parent_binding: nil, slot_node: nil, namespace: nil)
|
|
11
11
|
props = evaluate_props(props: @props, current_binding:)
|
|
12
|
-
event =
|
|
12
|
+
event = create_render_event(props:)
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
renderable_klass = class_from_namespace(namespace: namespace&.split('::') || [], name: @name)
|
|
15
|
+
renderable_instance = renderable_klass.new(event:)
|
|
16
16
|
|
|
17
17
|
# Classes referenced via "<{ ChildNode }>" must implement class/instance render/render_template methods (See LowNode).
|
|
18
|
-
return
|
|
18
|
+
return renderable_instance.render_template(event:, parent_binding:, props:) if renderable_klass.template
|
|
19
19
|
|
|
20
|
-
props.empty? ?
|
|
20
|
+
props.empty? ? renderable_instance.render(event:) : renderable_instance.render(event:, **props)
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
end
|
data/lib/nodes/slot_node.rb
CHANGED
|
@@ -15,7 +15,7 @@ module Antlers
|
|
|
15
15
|
|
|
16
16
|
def render(current_binding: nil, parent_binding: nil, slot_node: nil, namespace: nil)
|
|
17
17
|
props = evaluate_props(props: @props, current_binding:)
|
|
18
|
-
event =
|
|
18
|
+
event = create_render_event(props:)
|
|
19
19
|
|
|
20
20
|
klass = class_from_namespace(namespace: namespace&.split('::') || [], name: @name)
|
|
21
21
|
instance = klass.new(event:)
|
data/lib/nodes/var_node.rb
CHANGED
|
@@ -3,38 +3,22 @@
|
|
|
3
3
|
require 'erb'
|
|
4
4
|
|
|
5
5
|
require_relative '../interfaces/leaf_node'
|
|
6
|
+
require_relative '../modules/variables'
|
|
6
7
|
|
|
7
8
|
module Antlers
|
|
8
9
|
class VarNode < LeafNode
|
|
10
|
+
include Variables
|
|
11
|
+
|
|
9
12
|
attr_reader :value
|
|
10
13
|
|
|
11
|
-
def initialize(name: :var
|
|
14
|
+
def initialize(value:, name: :var)
|
|
12
15
|
super(name:)
|
|
13
16
|
|
|
14
17
|
@value = value
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
def render(current_binding: nil, parent_binding: nil, slot_node: nil, namespace: nil)
|
|
18
|
-
ERB::Util.html_escape(
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
# A variable is deliberately limited in what it can represent.
|
|
24
|
-
# 1. An instance/local variable
|
|
25
|
-
# 2. A method call
|
|
26
|
-
# 3. A static string
|
|
27
|
-
def evaluate_value(current_binding)
|
|
28
|
-
if current_binding
|
|
29
|
-
return current_binding.receiver.instance_variable_get(@value) if @value.start_with?('@')
|
|
30
|
-
return current_binding.local_variable_get(@value) if current_binding.local_variable_defined?(@value)
|
|
31
|
-
return current_binding.receiver.send(@value.to_sym) if current_binding.receiver.respond_to?(@value.to_sym)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
@value
|
|
35
|
-
rescue NameError => e
|
|
36
|
-
# TODO: Must be a better way to handle variables input as literal strings.
|
|
37
|
-
@value
|
|
21
|
+
ERB::Util.html_escape(evaluate(name: @value, current_binding:) || @value)
|
|
38
22
|
end
|
|
39
23
|
end
|
|
40
24
|
end
|
data/lib/nodes/yield_node.rb
CHANGED
|
@@ -14,7 +14,11 @@ module Antlers
|
|
|
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, namespace:)
|
|
19
|
+
else
|
|
20
|
+
output += child || ''
|
|
21
|
+
end
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
output
|
data/lib/parser.rb
CHANGED
|
@@ -7,33 +7,40 @@ module Antlers
|
|
|
7
7
|
module Parser
|
|
8
8
|
class << self
|
|
9
9
|
def parse(sequence, id: :root_node)
|
|
10
|
-
branch(
|
|
10
|
+
branch(node: RootNode.new(name: id), sequence:)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def branch(
|
|
13
|
+
def branch(node:, sequence:) # rubocop:disable Metrics/AbcSize
|
|
14
14
|
until sequence.empty?
|
|
15
15
|
segment = sequence.shift
|
|
16
16
|
|
|
17
17
|
if segment.is_a?(String)
|
|
18
|
-
|
|
18
|
+
node.children << segment
|
|
19
19
|
elsif segment[:var]
|
|
20
|
-
|
|
20
|
+
node.children << NodeFactory.var_node(segment:)
|
|
21
21
|
elsif segment[:prop]
|
|
22
|
-
|
|
22
|
+
node.children << NodeFactory.prop_node(segment:)
|
|
23
23
|
elsif segment[:slot]
|
|
24
|
-
|
|
24
|
+
node.children << NodeFactory.yield_node(segment:)
|
|
25
25
|
elsif segment[:slot_def]
|
|
26
26
|
slot_node = NodeFactory.slot_node(segment:)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
node
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def sub_branch(node:, sequence:, end_key:, end_name: nil)
|
|
40
|
+
sub_sequence = []
|
|
41
|
+
sub_sequence << sequence.shift until sequence.first.is_a?(Hash) && sequence.first[end_key] == end_name
|
|
42
|
+
|
|
43
|
+
branch(node:, sequence: sub_sequence)
|
|
37
44
|
end
|
|
38
45
|
end
|
|
39
46
|
end
|
data/lib/queries.rb
CHANGED
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.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -10,7 +10,7 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: erb
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
@@ -24,7 +24,7 @@ dependencies:
|
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: low_event
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - ">="
|
|
@@ -51,6 +51,8 @@ files:
|
|
|
51
51
|
- lib/interfaces/leaf_node.rb
|
|
52
52
|
- lib/lexer.rb
|
|
53
53
|
- lib/modules/props.rb
|
|
54
|
+
- lib/modules/variables.rb
|
|
55
|
+
- lib/nodes/for_node.rb
|
|
54
56
|
- lib/nodes/prop_node.rb
|
|
55
57
|
- lib/nodes/root_node.rb
|
|
56
58
|
- lib/nodes/slot_node.rb
|
|
@@ -78,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
80
|
- !ruby/object:Gem::Version
|
|
79
81
|
version: '0'
|
|
80
82
|
requirements: []
|
|
81
|
-
rubygems_version:
|
|
83
|
+
rubygems_version: 4.0.10
|
|
82
84
|
specification_version: 4
|
|
83
85
|
summary: Deer to be different
|
|
84
86
|
test_files: []
|