antlers 0.8.0 → 0.8.2
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/elements.rb +132 -0
- data/lib/antlers/lexer.rb +105 -0
- data/lib/antlers/parser.rb +43 -0
- data/lib/{version.rb → antlers/version.rb} +1 -1
- data/lib/antlers.rb +12 -5
- data/lib/interfaces/antler_node.rb +18 -1
- data/lib/interfaces/branch_node.rb +1 -2
- data/lib/interfaces/lexeme.rb +13 -0
- data/lib/lexemes/for_lexeme.rb +28 -0
- data/lib/lexemes/form_lexeme.rb +25 -0
- data/lib/lexemes/if_lexeme.rb +22 -0
- data/lib/lexemes/prop_lexeme.rb +43 -0
- data/lib/lexemes/slot_lexeme.rb +26 -0
- data/lib/lexemes/yield_lexeme.rb +18 -0
- data/lib/modules/variables.rb +1 -1
- data/lib/nodes/for_node.rb +15 -2
- data/lib/nodes/form_node.rb +17 -2
- data/lib/nodes/html_node.rb +27 -0
- data/lib/nodes/if_node.rb +14 -2
- data/lib/nodes/prop_node.rb +13 -2
- data/lib/nodes/root_node.rb +10 -0
- data/lib/nodes/slot_node.rb +18 -1
- data/lib/nodes/var_node.rb +10 -0
- data/lib/nodes/yield_node.rb +13 -8
- metadata +27 -5
- data/lib/factories/node_factory.rb +0 -57
- data/lib/lexer.rb +0 -204
- data/lib/parser.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0dbec268b4fe0b7966b5f7161c61b16c155695ae10ab9c16d66ea67462348f4
|
|
4
|
+
data.tar.gz: a5ef4b1479077100df98ebf33a16379cf934cecbb980184008b5ecb86c3bcbb0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8e4537ad73ba6356a5ce6a4632da46113374c21e6449ba9f744f9166e47ec93fcb619cab17d536d0b6413145c11e91f726b0a48e6b628af35c3662ec55e2970
|
|
7
|
+
data.tar.gz: 34b6b683e8ca4d3b736efa8684d049064032abf7ca081d2791370e0135b8ff7adc02e67dcf22a3c32dbe616b84302882b5346cc28c0915145e5278252cab5048
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'plugs'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
class Elements
|
|
7
|
+
include Plugs
|
|
8
|
+
|
|
9
|
+
plug(:root) do
|
|
10
|
+
plug(:node) do
|
|
11
|
+
lazy do
|
|
12
|
+
require_relative '../nodes/root_node'
|
|
13
|
+
RootNode
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
plug(:html) do
|
|
19
|
+
plug(:node) do
|
|
20
|
+
lazy do
|
|
21
|
+
require_relative '../nodes/html_node'
|
|
22
|
+
HTMLNode
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
plug(:form) do
|
|
28
|
+
plug(:lexeme) do
|
|
29
|
+
lazy do
|
|
30
|
+
require_relative '../lexemes/form_lexeme'
|
|
31
|
+
FormLexeme
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
plug(:node) do
|
|
36
|
+
lazy do
|
|
37
|
+
require_relative '../nodes/form_node'
|
|
38
|
+
FormNode
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
plug(:for) do
|
|
44
|
+
plug(:lexeme) do
|
|
45
|
+
lazy do
|
|
46
|
+
require_relative '../lexemes/for_lexeme'
|
|
47
|
+
ForLexeme
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
plug(:node) do
|
|
52
|
+
lazy do
|
|
53
|
+
require_relative '../nodes/for_node'
|
|
54
|
+
ForNode
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
plug(:if) do
|
|
60
|
+
plug(:lexeme) do
|
|
61
|
+
lazy do
|
|
62
|
+
require_relative '../lexemes/if_lexeme'
|
|
63
|
+
IfLexeme
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
plug(:node) do
|
|
68
|
+
lazy do
|
|
69
|
+
require_relative '../nodes/if_node'
|
|
70
|
+
IfNode
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
plug(:prop) do
|
|
76
|
+
plug(:lexeme) do
|
|
77
|
+
lazy do
|
|
78
|
+
require_relative '../lexemes/prop_lexeme'
|
|
79
|
+
PropLexeme
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
plug(:node) do
|
|
84
|
+
lazy do
|
|
85
|
+
require_relative '../nodes/prop_node'
|
|
86
|
+
PropNode
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
plug(:slot) do
|
|
92
|
+
plug(:lexeme) do
|
|
93
|
+
lazy do
|
|
94
|
+
require_relative '../lexemes/slot_lexeme'
|
|
95
|
+
SlotLexeme
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
plug(:node) do
|
|
100
|
+
lazy do
|
|
101
|
+
require_relative '../nodes/slot_node'
|
|
102
|
+
SlotNode
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
plug(:yield) do
|
|
108
|
+
plug(:lexeme) do
|
|
109
|
+
lazy do
|
|
110
|
+
require_relative '../lexemes/yield_lexeme'
|
|
111
|
+
YieldLexeme
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
plug(:node) do
|
|
116
|
+
lazy do
|
|
117
|
+
require_relative '../nodes/yield_node'
|
|
118
|
+
YieldNode
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
plug(:var) do
|
|
124
|
+
plug(:node) do
|
|
125
|
+
lazy do
|
|
126
|
+
require_relative '../nodes/var_node'
|
|
127
|
+
VarNode
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../support/queries'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
extend Queries
|
|
7
|
+
|
|
8
|
+
class LexerError < StandardError; end
|
|
9
|
+
|
|
10
|
+
class Lexer
|
|
11
|
+
def initialize(lexeme_types:)
|
|
12
|
+
@delimiters = ['<{', '}>', '{', '}']
|
|
13
|
+
@lexeme_types = lexeme_types
|
|
14
|
+
@keywords = @lexeme_types.flat_map { |lexeme| lexeme.const_get(:KEYWORDS) }
|
|
15
|
+
@cursor = 0
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def parse(template:)
|
|
19
|
+
@cursor = 0
|
|
20
|
+
sequence = []
|
|
21
|
+
|
|
22
|
+
# Split on delimiters and retain capture groups.
|
|
23
|
+
segments = template.split(/(#{Regexp.union(@delimiters)})/).map(&:strip)
|
|
24
|
+
|
|
25
|
+
until segments[@cursor].nil?
|
|
26
|
+
if (antlers_segment = antlers_segment(segments:))
|
|
27
|
+
sequence << antlers_lexeme(antlers_segment:, segments:)
|
|
28
|
+
# Skipping: ['{', 'expression', '}']
|
|
29
|
+
# Skipping: ['<{', 'name + props + keywords', '}>']
|
|
30
|
+
@cursor += 3
|
|
31
|
+
else
|
|
32
|
+
segment = segments[@cursor]
|
|
33
|
+
sequence << segment unless segment.empty?
|
|
34
|
+
@cursor += 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sequence
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def antlers_segment(segments:)
|
|
44
|
+
next_segment = segments[@cursor + 1]
|
|
45
|
+
return nil unless next_segment && (segments[@cursor] == '<{' || brackets?(segments:))
|
|
46
|
+
|
|
47
|
+
next_segment
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def antlers_lexeme(antlers_segment:, segments:)
|
|
51
|
+
return var(antlers_segment:) if brackets?(segments:)
|
|
52
|
+
|
|
53
|
+
name, props, keywords = parse_segment(antlers_segment:)
|
|
54
|
+
|
|
55
|
+
lexeme = @lexeme_types.find { it.match?(name:, keywords:) }
|
|
56
|
+
return lexeme.lexeme(name:, props:, keywords:) if lexeme
|
|
57
|
+
return var(antlers_segment:, raw: true) if deerheads?(segments:)
|
|
58
|
+
|
|
59
|
+
raise LexerError, "Unrecognised syntax: '#{antlers_segment}'"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def parse_segment(antlers_segment:)
|
|
63
|
+
name_and_props, *keywords = antlers_segment.split(/(#{Regexp.union(@keywords)})/)
|
|
64
|
+
name, *props = name_and_props.split(' ')
|
|
65
|
+
[name, props, parse_keywords(keywords:)]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def parse_keywords(keywords:)
|
|
69
|
+
key_values = {}
|
|
70
|
+
|
|
71
|
+
while (keyword = keywords.shift)
|
|
72
|
+
keyword.strip!
|
|
73
|
+
value = keyword.end_with?(':') && value?(keywords.first.strip) ? keywords.shift.strip : nil
|
|
74
|
+
key_values[keyword] = value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
key_values
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def value?(string)
|
|
81
|
+
!(string.start_with?(':') || string.end_with?(':'))
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# TODO: Refactor every constant, match and result method into its own class. Loop through every class and return the first match.
|
|
85
|
+
|
|
86
|
+
def brackets?(segments:)
|
|
87
|
+
first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
|
|
88
|
+
first == '{' && last == '}'
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def deerheads?(segments:)
|
|
92
|
+
first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
|
|
93
|
+
first == '<{' && last == '}>'
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def var(antlers_segment:, raw: false)
|
|
97
|
+
# String is already interpolated or not depending on user input on the template layer, now we store it without those template quotes.
|
|
98
|
+
antlers_segment = antlers_segment[1..-2] if Queries.user_defined_string?(antlers_segment)
|
|
99
|
+
|
|
100
|
+
return { raw_var: antlers_segment } if raw
|
|
101
|
+
|
|
102
|
+
{ var: antlers_segment }
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Antlers
|
|
4
|
+
class Parser
|
|
5
|
+
class ParserError < StandardError; end
|
|
6
|
+
|
|
7
|
+
def initialize(namespace: nil, node_types:)
|
|
8
|
+
@namespace = namespace
|
|
9
|
+
@node_types = node_types
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def parse(sequence:, id: :root_node)
|
|
13
|
+
branch(node: RootNode.new(name: id), sequence:)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def branch(node:, sequence:)
|
|
17
|
+
until sequence.empty?
|
|
18
|
+
segment = sequence.shift
|
|
19
|
+
|
|
20
|
+
child_class = @node_types.find { |n| n.match?(segment:) }
|
|
21
|
+
raise(ParserError, "No node matches #{segment}") unless child_class
|
|
22
|
+
|
|
23
|
+
child = child_class.build(segment:, namespace: @namespace)
|
|
24
|
+
node.children << child
|
|
25
|
+
|
|
26
|
+
if (end_key = child_class::END_KEY)
|
|
27
|
+
sub_branch(node: child, sequence:, end_key:, end_name: child.end_name)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
node
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def sub_branch(node:, sequence:, end_key:, end_name: nil)
|
|
35
|
+
sub_sequence = []
|
|
36
|
+
sub_sequence << sequence.shift until sequence.first.is_a?(Hash) && sequence.first[end_key] == end_name
|
|
37
|
+
# Remove the end tag we just stopped before.
|
|
38
|
+
sequence.shift
|
|
39
|
+
|
|
40
|
+
branch(node:, sequence: sub_sequence)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/antlers.rb
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '
|
|
4
|
-
require_relative '
|
|
3
|
+
require_relative 'antlers/elements'
|
|
4
|
+
require_relative 'antlers/lexer'
|
|
5
|
+
require_relative 'antlers/parser'
|
|
6
|
+
|
|
7
|
+
# These requires allow downstream libraries to build their own antlers nodes.
|
|
8
|
+
require_relative 'interfaces/branch_node'
|
|
9
|
+
require_relative 'interfaces/leaf_node'
|
|
5
10
|
|
|
6
11
|
module Antlers
|
|
12
|
+
DEFAULT_ELEMENTS = [:root, :html, :form, :for, :if, :prop, :slot, :yield, :var]
|
|
13
|
+
|
|
7
14
|
class << self
|
|
8
|
-
def ast(template:, namespace: nil)
|
|
15
|
+
def ast(template:, namespace: nil, elements: Elements[*DEFAULT_ELEMENTS])
|
|
9
16
|
return template unless template.include?('<{') || template.include?('{')
|
|
10
17
|
|
|
11
|
-
sequence = Lexer.new.parse(template)
|
|
12
|
-
Parser.new(namespace:).parse(sequence:)
|
|
18
|
+
sequence = Lexer.new(lexeme_types: elements[:lexeme].to_a).parse(template:)
|
|
19
|
+
Parser.new(namespace:, node_types: elements[:node].to_a).parse(sequence:)
|
|
13
20
|
end
|
|
14
21
|
|
|
15
22
|
def render(ast:, current_binding:, parent_binding: nil, slot_node: nil)
|
|
@@ -4,6 +4,9 @@ module Antlers
|
|
|
4
4
|
class AntlerNode
|
|
5
5
|
attr_reader :name
|
|
6
6
|
|
|
7
|
+
DEF_KEY = nil
|
|
8
|
+
END_KEY = nil
|
|
9
|
+
|
|
7
10
|
def initialize(name:)
|
|
8
11
|
@name = name
|
|
9
12
|
end
|
|
@@ -12,7 +15,21 @@ module Antlers
|
|
|
12
15
|
raise NotImplementedError
|
|
13
16
|
end
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
def end_name
|
|
19
|
+
'level_1'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
def match?(segment:)
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def build(segment:, namespace:)
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Compare instance as a value object.
|
|
16
33
|
def ==(other) = other.class == self.class
|
|
17
34
|
def eql?(other) = self == other
|
|
18
35
|
def hash = [self.class].hash
|
|
@@ -16,8 +16,7 @@ module Antlers
|
|
|
16
16
|
output = ''
|
|
17
17
|
|
|
18
18
|
@children.each do |child|
|
|
19
|
-
|
|
20
|
-
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:) : child) || ''
|
|
19
|
+
output += child.render(current_binding:, parent_binding:, slot_node:) || ''
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
output
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/lexeme'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
module ForLexeme
|
|
7
|
+
include Lexeme
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
KEYWORDS = ['for:', 'in:', ':for'].freeze
|
|
11
|
+
|
|
12
|
+
def match?(keywords:, **)
|
|
13
|
+
KEYWORDS.include?(keywords.keys.first)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def lexeme(keywords:, **)
|
|
17
|
+
if keywords['for:']
|
|
18
|
+
*key, value = keywords['for:'].split(',').map(&:strip)
|
|
19
|
+
for_def = { for_def: value, in: keywords['in:'] }
|
|
20
|
+
for_def[:key] = key.first unless key.empty?
|
|
21
|
+
return for_def
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# TODO: Keep track of which for loop we're in to allow nested for loops.
|
|
25
|
+
{ for_end: 'level_1' }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/lexeme'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
module FormLexeme
|
|
7
|
+
include Lexeme
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
KEYWORDS = ['form:', ':form'].freeze
|
|
11
|
+
|
|
12
|
+
def match?(keywords:, **)
|
|
13
|
+
KEYWORDS.include?(keywords.keys.first)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def lexeme(keywords:, **)
|
|
17
|
+
if keywords.key?('form:')
|
|
18
|
+
action = keywords['form:'] ? keywords['form:'][1...-1] : nil
|
|
19
|
+
return { form_def: action }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
{ form_end: 'level_1' }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/lexeme'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
module IfLexeme
|
|
7
|
+
include Lexeme
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
KEYWORDS = ['if:', ':if'].freeze
|
|
11
|
+
|
|
12
|
+
def match?(keywords:, **)
|
|
13
|
+
KEYWORDS.include?(keywords.keys.first)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def lexeme(keywords:, **)
|
|
17
|
+
return { if_def: keywords['if:'] } if keywords.key?('if:')
|
|
18
|
+
|
|
19
|
+
{ if_end: 'level_1' }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/lexeme'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
module PropLexeme
|
|
7
|
+
include Lexeme
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
def match?(name:, **)
|
|
11
|
+
name && [*'A'..'Z'].include?(name[0]) && !(name.start_with?(':') || name.end_with?(':'))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def lexeme(name:, props:, **)
|
|
15
|
+
prop = { prop: name }
|
|
16
|
+
prop[:props] = props(props) unless props.empty?
|
|
17
|
+
prop
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def props(props)
|
|
23
|
+
odd_props = props.join(' ').split(/(=)|\s/)
|
|
24
|
+
|
|
25
|
+
return {} unless odd_props.any?
|
|
26
|
+
|
|
27
|
+
props = {}
|
|
28
|
+
until odd_props.empty?
|
|
29
|
+
prop = odd_props.shift
|
|
30
|
+
value = nil
|
|
31
|
+
|
|
32
|
+
if odd_props.first == '='
|
|
33
|
+
odd_props.shift
|
|
34
|
+
value = odd_props.shift
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
props[prop.to_sym] = value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
props
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/lexeme'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
module SlotLexeme
|
|
7
|
+
include Lexeme
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
KEYWORDS = ['slot:', ':slot'].freeze
|
|
11
|
+
|
|
12
|
+
def match?(name:, **)
|
|
13
|
+
name && (name.start_with?(':') || name.end_with?(':'))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def lexeme(name:, props:, **)
|
|
17
|
+
if name.end_with?(':')
|
|
18
|
+
slot_def = { slot_def: name.delete_suffix(':') }
|
|
19
|
+
slot_def[:props] = props(props) unless props.empty?
|
|
20
|
+
return slot_def
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
{ slot_end: name.delete_prefix(':') }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/lexeme'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
module YieldLexeme
|
|
7
|
+
include Lexeme
|
|
8
|
+
extend self
|
|
9
|
+
|
|
10
|
+
def match?(keywords:, **)
|
|
11
|
+
keywords.keys.include?(':slot')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def lexeme(**)
|
|
15
|
+
{ slot: :default }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/modules/variables.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Antlers
|
|
|
6
6
|
module Variables
|
|
7
7
|
extend Queries
|
|
8
8
|
|
|
9
|
-
# Evaluation limited to the following for security and to
|
|
9
|
+
# Evaluation limited to the following for security and to stop a templating language becoming a programming language.
|
|
10
10
|
# 1. An instance variable
|
|
11
11
|
# 2. A method call/local variable
|
|
12
12
|
# 3. A method chain
|
data/lib/nodes/for_node.rb
CHANGED
|
@@ -9,6 +9,9 @@ module Antlers
|
|
|
9
9
|
include Props
|
|
10
10
|
include Variables
|
|
11
11
|
|
|
12
|
+
DEF_KEY = :for_def
|
|
13
|
+
END_KEY = :for_end
|
|
14
|
+
|
|
12
15
|
def initialize(name:, items:, value:, key: nil, props: [], children: [])
|
|
13
16
|
super(name:, props:, children:)
|
|
14
17
|
|
|
@@ -28,12 +31,22 @@ module Antlers
|
|
|
28
31
|
current_binding.local_variable_set(@key, key) if @key
|
|
29
32
|
|
|
30
33
|
@children.each do |child|
|
|
31
|
-
|
|
32
|
-
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:) : child) || ''
|
|
34
|
+
output += child.render(current_binding:, parent_binding:, slot_node:) || ''
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
output
|
|
37
39
|
end
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
def match?(segment:)
|
|
43
|
+
segment[DEF_KEY]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def build(segment:, **)
|
|
47
|
+
value, key, items = segment.values_at(DEF_KEY, :key, :in)
|
|
48
|
+
new(name: value, key:, value:, items:)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
38
51
|
end
|
|
39
52
|
end
|
data/lib/nodes/form_node.rb
CHANGED
|
@@ -9,6 +9,9 @@ module Antlers
|
|
|
9
9
|
include Props
|
|
10
10
|
include Variables
|
|
11
11
|
|
|
12
|
+
DEF_KEY = :form_def
|
|
13
|
+
END_KEY = :form_end
|
|
14
|
+
|
|
12
15
|
def initialize(name:, action: nil, method: 'POST', props: [], children: [])
|
|
13
16
|
super(name:, props:, children:)
|
|
14
17
|
|
|
@@ -20,12 +23,24 @@ module Antlers
|
|
|
20
23
|
output = "<form action='#{@action}' method='#{@method}'>"
|
|
21
24
|
|
|
22
25
|
@children.each do |child|
|
|
23
|
-
|
|
24
|
-
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:) : child) || ''
|
|
26
|
+
output += child.render(current_binding:, parent_binding:, slot_node:) || ''
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
output += '</form>'
|
|
28
30
|
output
|
|
29
31
|
end
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
def match?(segment:)
|
|
35
|
+
segment[DEF_KEY]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build(segment:, **)
|
|
39
|
+
action, method = segment.values_at(DEF_KEY, :method)
|
|
40
|
+
return new(name: action, action:, method:) if method
|
|
41
|
+
|
|
42
|
+
new(name: action, action:)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
30
45
|
end
|
|
31
46
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../interfaces/leaf_node'
|
|
4
|
+
|
|
5
|
+
module Antlers
|
|
6
|
+
class HTMLNode < LeafNode
|
|
7
|
+
def initialize(html:)
|
|
8
|
+
super(name: nil)
|
|
9
|
+
|
|
10
|
+
@html = html
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def render(**)
|
|
14
|
+
@html
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def match?(segment:)
|
|
19
|
+
segment.is_a?(String)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build(segment:, **)
|
|
23
|
+
new(html: segment)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/nodes/if_node.rb
CHANGED
|
@@ -9,6 +9,9 @@ module Antlers
|
|
|
9
9
|
include Props
|
|
10
10
|
include Variables
|
|
11
11
|
|
|
12
|
+
DEF_KEY = :if_def
|
|
13
|
+
END_KEY = :if_end
|
|
14
|
+
|
|
12
15
|
def initialize(name:, value:, props: [], children: [])
|
|
13
16
|
super(name:, props:, children:)
|
|
14
17
|
|
|
@@ -20,12 +23,21 @@ module Antlers
|
|
|
20
23
|
|
|
21
24
|
if evaluate(name: @value, current_binding:)
|
|
22
25
|
@children.each do |child|
|
|
23
|
-
|
|
24
|
-
output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:) : child) || ''
|
|
26
|
+
output += child.render(current_binding:, parent_binding:, slot_node:) || ''
|
|
25
27
|
end
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
output
|
|
29
31
|
end
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
def match?(segment:)
|
|
35
|
+
segment[DEF_KEY]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build(segment:, **)
|
|
39
|
+
new(name: segment[DEF_KEY], value: segment[DEF_KEY])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
30
42
|
end
|
|
31
43
|
end
|
data/lib/nodes/prop_node.rb
CHANGED
|
@@ -23,18 +23,29 @@ module Antlers
|
|
|
23
23
|
# TODO: Get LowLoad to load constants defined in "<{ MyNode }>" syntax so that we can resolve namespace/params on class load.
|
|
24
24
|
klass = class_constant(namespace: @namespace&.split('::') || [], name: @name)
|
|
25
25
|
class_proxy = Lowkey[klass.to_s].first[klass.to_s]
|
|
26
|
-
instance = create_instance(class_proxy:, klass:, event:)
|
|
27
26
|
|
|
27
|
+
instance = create_instance(class_proxy:, klass:, event:, props:)
|
|
28
28
|
return instance.render_template(event:, parent_binding:, props:) if klass.template
|
|
29
29
|
|
|
30
30
|
render_args(class_proxy:, instance:, event:, props:)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
class << self
|
|
34
|
+
def match?(segment:)
|
|
35
|
+
segment[:prop]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build(segment:, namespace:)
|
|
39
|
+
new(name: segment[:prop], props: segment[:props], namespace:)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
33
43
|
private
|
|
34
44
|
|
|
35
|
-
def create_instance(class_proxy:, klass:, event:)
|
|
45
|
+
def create_instance(class_proxy:, klass:, event:, props:)
|
|
36
46
|
initialize_params = class_proxy.instance_methods[:initialize]&.tagged_params(:keyword)&.map(&:name) || []
|
|
37
47
|
return klass.new(event:, **props) if initialize_params.include?(:event) && initialize_params.count > 1
|
|
48
|
+
return klass.new(**props) if initialize_params.count > 1
|
|
38
49
|
return klass.new(event:) if initialize_params.include?(:event)
|
|
39
50
|
|
|
40
51
|
klass.new
|
data/lib/nodes/root_node.rb
CHANGED
data/lib/nodes/slot_node.rb
CHANGED
|
@@ -7,7 +7,10 @@ require_relative '../modules/props'
|
|
|
7
7
|
module Antlers
|
|
8
8
|
class SlotNode < BranchNode
|
|
9
9
|
include Namespace
|
|
10
|
-
include Props #
|
|
10
|
+
include Props # The immediate ancestor which props are passed to.
|
|
11
|
+
|
|
12
|
+
DEF_KEY = :slot_def
|
|
13
|
+
END_KEY = :slot_end
|
|
11
14
|
|
|
12
15
|
attr_accessor :children
|
|
13
16
|
|
|
@@ -30,5 +33,19 @@ module Antlers
|
|
|
30
33
|
|
|
31
34
|
props.empty? ? instance.render(event:) : instance.render(event:, **props)
|
|
32
35
|
end
|
|
36
|
+
|
|
37
|
+
def end_name
|
|
38
|
+
@name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class << self
|
|
42
|
+
def match?(segment:)
|
|
43
|
+
segment[DEF_KEY]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def build(segment:, namespace:)
|
|
47
|
+
new(name: segment[DEF_KEY], props: segment[:props], namespace:)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
33
50
|
end
|
|
34
51
|
end
|
data/lib/nodes/var_node.rb
CHANGED
|
@@ -25,5 +25,15 @@ module Antlers
|
|
|
25
25
|
|
|
26
26
|
ERB::Util.html_escape(result)
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
def match?(segment:)
|
|
31
|
+
segment[:var] || segment[:raw_var]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build(segment:, **)
|
|
35
|
+
new(value: segment[:var] || segment[:raw_var], raw: !!segment[:raw_var])
|
|
36
|
+
end
|
|
37
|
+
end
|
|
28
38
|
end
|
|
29
39
|
end
|
data/lib/nodes/yield_node.rb
CHANGED
|
@@ -8,20 +8,25 @@ module Antlers
|
|
|
8
8
|
super(name:)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
def render(current_binding: nil, parent_binding
|
|
11
|
+
# Render the children in the binding of the parent.
|
|
12
|
+
def render(current_binding: nil, parent_binding:, slot_node:)
|
|
13
13
|
output = ''
|
|
14
14
|
|
|
15
15
|
slot_node.children.each do |child|
|
|
16
|
-
|
|
17
|
-
if child.respond_to?(:render) # rubocop:disable Style/ConditionalAssignment
|
|
18
|
-
output += child.render(current_binding: parent_binding, parent_binding: nil, slot_node: nil)
|
|
19
|
-
else
|
|
20
|
-
output += child || ''
|
|
21
|
-
end
|
|
16
|
+
output += child.render(current_binding: parent_binding, parent_binding:, slot_node:) || ''
|
|
22
17
|
end
|
|
23
18
|
|
|
24
19
|
output
|
|
25
20
|
end
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
def match?(segment:)
|
|
24
|
+
segment[:slot]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def build(segment:, **)
|
|
28
|
+
new(name: segment[:slot])
|
|
29
|
+
end
|
|
30
|
+
end
|
|
26
31
|
end
|
|
27
32
|
end
|
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.8.
|
|
4
|
+
version: 0.8.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: plugs
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
54
68
|
description: A templating language embedded within HTML that is itself embedded within
|
|
55
69
|
Ruby/RBX
|
|
56
70
|
email:
|
|
@@ -60,25 +74,33 @@ extensions: []
|
|
|
60
74
|
extra_rdoc_files: []
|
|
61
75
|
files:
|
|
62
76
|
- lib/antlers.rb
|
|
63
|
-
- lib/
|
|
77
|
+
- lib/antlers/elements.rb
|
|
78
|
+
- lib/antlers/lexer.rb
|
|
79
|
+
- lib/antlers/parser.rb
|
|
80
|
+
- lib/antlers/version.rb
|
|
64
81
|
- lib/interfaces/antler_node.rb
|
|
65
82
|
- lib/interfaces/branch_node.rb
|
|
66
83
|
- lib/interfaces/leaf_node.rb
|
|
67
|
-
- lib/
|
|
84
|
+
- lib/interfaces/lexeme.rb
|
|
85
|
+
- lib/lexemes/for_lexeme.rb
|
|
86
|
+
- lib/lexemes/form_lexeme.rb
|
|
87
|
+
- lib/lexemes/if_lexeme.rb
|
|
88
|
+
- lib/lexemes/prop_lexeme.rb
|
|
89
|
+
- lib/lexemes/slot_lexeme.rb
|
|
90
|
+
- lib/lexemes/yield_lexeme.rb
|
|
68
91
|
- lib/modules/namespace.rb
|
|
69
92
|
- lib/modules/props.rb
|
|
70
93
|
- lib/modules/variables.rb
|
|
71
94
|
- lib/nodes/for_node.rb
|
|
72
95
|
- lib/nodes/form_node.rb
|
|
96
|
+
- lib/nodes/html_node.rb
|
|
73
97
|
- lib/nodes/if_node.rb
|
|
74
98
|
- lib/nodes/prop_node.rb
|
|
75
99
|
- lib/nodes/root_node.rb
|
|
76
100
|
- lib/nodes/slot_node.rb
|
|
77
101
|
- lib/nodes/var_node.rb
|
|
78
102
|
- lib/nodes/yield_node.rb
|
|
79
|
-
- lib/parser.rb
|
|
80
103
|
- lib/support/queries.rb
|
|
81
|
-
- lib/version.rb
|
|
82
104
|
homepage: https://github.com/raindeer-rb/antlers
|
|
83
105
|
licenses: []
|
|
84
106
|
metadata:
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative '../nodes/if_node'
|
|
4
|
-
require_relative '../nodes/for_node'
|
|
5
|
-
require_relative '../nodes/form_node'
|
|
6
|
-
require_relative '../nodes/prop_node'
|
|
7
|
-
require_relative '../nodes/slot_node'
|
|
8
|
-
require_relative '../nodes/var_node'
|
|
9
|
-
require_relative '../nodes/yield_node'
|
|
10
|
-
|
|
11
|
-
module Antlers
|
|
12
|
-
class NodeFactory
|
|
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
|
|
25
|
-
|
|
26
|
-
def form_node(segment:)
|
|
27
|
-
action, method = segment.values_at(:form_def, :method)
|
|
28
|
-
return FormNode.new(name: action, action:, method:) if method
|
|
29
|
-
|
|
30
|
-
FormNode.new(name: action, action:)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def prop_node(segment:)
|
|
34
|
-
PropNode.new(name: segment[:prop], props: segment[:props], namespace:)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def slot_node(segment:)
|
|
38
|
-
SlotNode.new(name: segment[:slot_def], props: segment[:props], namespace:)
|
|
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
|
|
56
|
-
end
|
|
57
|
-
end
|
data/lib/lexer.rb
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'support/queries'
|
|
4
|
-
|
|
5
|
-
module Antlers
|
|
6
|
-
extend Queries
|
|
7
|
-
|
|
8
|
-
class LexerParseError < StandardError; end
|
|
9
|
-
|
|
10
|
-
class Lexer
|
|
11
|
-
IF_KEYWORDS = ['if:', ':if'].freeze
|
|
12
|
-
FOR_KEYWORDS = ['for:', 'in:', ':for'].freeze
|
|
13
|
-
FORM_KEYWORDS = ['form:', ':form'].freeze
|
|
14
|
-
|
|
15
|
-
def initialize
|
|
16
|
-
@delimiters = ['<{', '}>', '{', '}']
|
|
17
|
-
@keywords = [*IF_KEYWORDS, *FORM_KEYWORDS, *FOR_KEYWORDS, 'slot:', ':slot']
|
|
18
|
-
@cursor = 0
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def parse(template)
|
|
22
|
-
@cursor = 0
|
|
23
|
-
sequence = []
|
|
24
|
-
|
|
25
|
-
# Split on delimiters and retain capture groups.
|
|
26
|
-
segments = template.split(/(#{Regexp.union(@delimiters)})/).map(&:strip)
|
|
27
|
-
|
|
28
|
-
until segments[@cursor].nil?
|
|
29
|
-
if (antlers_segment = antlers_segment(segments:))
|
|
30
|
-
sequence << antlers_lexeme(antlers_segment:, segments:)
|
|
31
|
-
# Skipping: ['{', 'expression', '}']
|
|
32
|
-
# Skipping: ['<{', 'name + props + keywords', '}>']
|
|
33
|
-
@cursor += 3
|
|
34
|
-
else
|
|
35
|
-
segment = segments[@cursor]
|
|
36
|
-
sequence << segment unless segment.empty?
|
|
37
|
-
@cursor += 1
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
sequence
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
private
|
|
45
|
-
|
|
46
|
-
def antlers_segment(segments:)
|
|
47
|
-
next_segment = segments[@cursor + 1]
|
|
48
|
-
return nil unless next_segment && (segments[@cursor] == '<{' || brackets?(segments:))
|
|
49
|
-
|
|
50
|
-
next_segment
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def antlers_lexeme(antlers_segment:, segments:)
|
|
54
|
-
return var(antlers_segment:) if brackets?(segments:)
|
|
55
|
-
|
|
56
|
-
name, props, keywords = parse_segment(antlers_segment:)
|
|
57
|
-
|
|
58
|
-
return slot_yield if slot_yield?(keywords:)
|
|
59
|
-
return slot(name:, props:) if slot?(name)
|
|
60
|
-
return prop(name:, props:) if prop?(name)
|
|
61
|
-
return if_block(keywords:) if if_block?(keywords:)
|
|
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:)
|
|
65
|
-
|
|
66
|
-
raise LexerParseError, "Unrecognised syntax: '#{antlers_segment}'"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def parse_segment(antlers_segment:)
|
|
70
|
-
name_and_props, *keywords = antlers_segment.split(/(#{Regexp.union(@keywords)})/)
|
|
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
|
|
86
|
-
|
|
87
|
-
def value?(string)
|
|
88
|
-
!(string.start_with?(':') || string.end_with?(':'))
|
|
89
|
-
end
|
|
90
|
-
|
|
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:)
|
|
94
|
-
first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
|
|
95
|
-
first == '{' && last == '}'
|
|
96
|
-
end
|
|
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
|
-
|
|
107
|
-
def for_loop?(keywords:)
|
|
108
|
-
FOR_KEYWORDS.include?(keywords.keys.first)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def form?(keywords:)
|
|
112
|
-
FORM_KEYWORDS.include?(keywords.keys.first)
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def slot?(name)
|
|
116
|
-
name && (name.start_with?(':') || name.end_with?(':'))
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def slot_yield?(keywords:)
|
|
120
|
-
keywords.keys.include?(':slot')
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
def prop?(name)
|
|
124
|
-
name && [*'A'..'Z'].include?(name[0])
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def var(antlers_segment:, raw: false)
|
|
128
|
-
# String is already interpolated or not depending on user input on the template layer, now we store it without those template quotes.
|
|
129
|
-
antlers_segment = antlers_segment[1..-2] if Queries.user_defined_string?(antlers_segment)
|
|
130
|
-
|
|
131
|
-
return { raw_var: antlers_segment } if raw
|
|
132
|
-
|
|
133
|
-
{ var: antlers_segment }
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
def if_block(keywords:)
|
|
137
|
-
return { if_def: keywords['if:'] } if keywords.key?('if:')
|
|
138
|
-
|
|
139
|
-
{ if_end: 'level_1' }
|
|
140
|
-
end
|
|
141
|
-
|
|
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:'] }
|
|
146
|
-
for_def[:key] = key.first unless key.empty?
|
|
147
|
-
return for_def
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# TODO: Keep track of which for loop we're in to allow nested for loops.
|
|
151
|
-
{ for_end: 'level_1' }
|
|
152
|
-
end
|
|
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
|
-
|
|
163
|
-
def slot(name:, props:)
|
|
164
|
-
if name.end_with?(':')
|
|
165
|
-
slot_def = { slot_def: name.delete_suffix(':') }
|
|
166
|
-
slot_def[:props] = props(props) unless props.empty?
|
|
167
|
-
return slot_def
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
{ slot_end: name.delete_prefix(':') }
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
def slot_yield
|
|
174
|
-
{ slot: :default }
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
def prop(name:, props:)
|
|
178
|
-
prop = { prop: name }
|
|
179
|
-
prop[:props] = props(props) unless props.empty?
|
|
180
|
-
prop
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def props(props)
|
|
184
|
-
odd_props = props.join(' ').split(/(=)|\s/)
|
|
185
|
-
|
|
186
|
-
return {} unless odd_props.any?
|
|
187
|
-
|
|
188
|
-
props = {}
|
|
189
|
-
until odd_props.empty?
|
|
190
|
-
prop = odd_props.shift
|
|
191
|
-
value = nil
|
|
192
|
-
|
|
193
|
-
if odd_props.first == '='
|
|
194
|
-
odd_props.shift
|
|
195
|
-
value = odd_props.shift
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
props[prop.to_sym] = value
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
props
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
end
|
data/lib/parser.rb
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'factories/node_factory'
|
|
4
|
-
require_relative 'nodes/root_node'
|
|
5
|
-
|
|
6
|
-
module Antlers
|
|
7
|
-
class Parser
|
|
8
|
-
def initialize(namespace: nil)
|
|
9
|
-
@factory = NodeFactory.new(namespace:)
|
|
10
|
-
end
|
|
11
|
-
|
|
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
|
|
19
|
-
|
|
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
|
|
47
|
-
end
|
|
48
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
branch(node:, sequence: sub_sequence)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|