antlers 0.8.1 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d3bcf6e14ade37294793b87b3ab3ab152e519f8c081a130644b0d28d489b63b
4
- data.tar.gz: d9886a3044fb8654b37f111a9536ba7dfef4945c26fb08112e400ab14e76d8e7
3
+ metadata.gz: f16d22b587d7dbdc07a0a51fc19032271be9347e90eb09c77541a56099f8088f
4
+ data.tar.gz: b1437b4cac8a748fc23fc6eaf06c07e5d3111d63d5100651afb9195f488dacfb
5
5
  SHA512:
6
- metadata.gz: a4179e613ca96ea49fdbc7d991a627a45640182ae5aaa5f01dc935396586ba8b583df469113c26c7a139308956bc18980cdbd04877c73a9b7cd14617682dd2d3
7
- data.tar.gz: 698769c0d6bfd0e9872c5022f72555290acff3888615f44061ed10847af8f97af58477c35059678c2e02a2bdafc16d3f06ff514ab80aa539abc2de7cbfeda6bd
6
+ metadata.gz: ecc5de204a45f9cda60f03aeefc49aee735fc9aa3da5fd20b1d12959497bfb4683ab299bd64d4ae8795b3296ab9e018c8b8eb70672f3788aeeeaf8355f03d07e
7
+ data.tar.gz: 0b14edede169971914c9958f4b3369c361271566d36c883dd3c1774ef968e8299d2d95c58a90e921ea2bbae5d70f4a2634c8ce00217e058c2ee6c71a9ec3a42f
@@ -0,0 +1,137 @@
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
+ require_relative '../nodes/root_node'
12
+ RootNode
13
+ end
14
+ end
15
+
16
+ plug(:html) do
17
+ plug(:node) do
18
+ lazy do
19
+ require_relative '../nodes/html_node'
20
+ HTMLNode
21
+ end
22
+ end
23
+ end
24
+
25
+ plug(:form) do
26
+ plug(:lexeme) do
27
+ lazy do
28
+ require_relative '../lexemes/form_lexeme'
29
+ FormLexeme
30
+ end
31
+ end
32
+
33
+ plug(:node) do
34
+ lazy do
35
+ require_relative '../nodes/form_node'
36
+ FormNode
37
+ end
38
+ end
39
+ end
40
+
41
+ plug(:for) do
42
+ plug(:lexeme) do
43
+ lazy do
44
+ require_relative '../lexemes/for_lexeme'
45
+ ForLexeme
46
+ end
47
+ end
48
+
49
+ plug(:node) do
50
+ lazy do
51
+ require_relative '../nodes/for_node'
52
+ ForNode
53
+ end
54
+ end
55
+ end
56
+
57
+ plug(:if) do
58
+ plug(:lexeme) do
59
+ lazy do
60
+ require_relative '../lexemes/if_lexeme'
61
+ IfLexeme
62
+ end
63
+ end
64
+
65
+ plug(:node) do
66
+ lazy do
67
+ require_relative '../nodes/if_node'
68
+ IfNode
69
+ end
70
+ end
71
+ end
72
+
73
+ plug(:prop) do
74
+ plug(:lexeme) do
75
+ lazy do
76
+ require_relative '../lexemes/prop_lexeme'
77
+ PropLexeme
78
+ end
79
+ end
80
+
81
+ plug(:node) do
82
+ lazy do
83
+ require_relative '../nodes/prop_node'
84
+ PropNode
85
+ end
86
+ end
87
+ end
88
+
89
+ plug(:slot) do
90
+ plug(:lexeme) do
91
+ lazy do
92
+ require_relative '../lexemes/slot_lexeme'
93
+ SlotLexeme
94
+ end
95
+ end
96
+
97
+ plug(:node) do
98
+ lazy do
99
+ require_relative '../nodes/slot_node'
100
+ SlotNode
101
+ end
102
+ end
103
+ end
104
+
105
+ plug(:yield) do
106
+ plug(:lexeme) do
107
+ lazy do
108
+ require_relative '../lexemes/yield_lexeme'
109
+ YieldLexeme
110
+ end
111
+ end
112
+
113
+ plug(:node) do
114
+ lazy do
115
+ require_relative '../nodes/yield_node'
116
+ YieldNode
117
+ end
118
+ end
119
+ end
120
+
121
+ plug(:var) do
122
+ plug(:node) do
123
+ lazy do
124
+ require_relative '../nodes/var_node'
125
+ VarNode
126
+ end
127
+ end
128
+
129
+ plug(:lexeme) do
130
+ lazy do
131
+ require_relative '../lexemes/var_lexeme'
132
+ VarLexeme
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lexemes/var_lexeme'
4
+
5
+ module Antlers
6
+ class LexerError < StandardError; end
7
+
8
+ class Lexer
9
+ def initialize(lexeme_types:)
10
+ @lexeme_types = lexeme_types.to_a
11
+ @var_lexeme = @lexeme_types.find { it == VarLexeme }
12
+
13
+ @delimiters = ['<{', '}>']
14
+ @delimiters = [*@delimiters, '{', '}'] if @var_lexeme
15
+
16
+ @keywords = @lexeme_types.flat_map { |lexeme| lexeme.const_get(:KEYWORDS) }
17
+ @cursor = 0
18
+ end
19
+
20
+ def parse(template:)
21
+ @cursor = 0
22
+ sequence = []
23
+
24
+ # Split on delimiters and retain capture groups.
25
+ segments = template.split(/(#{Regexp.union(@delimiters)})/).map(&:strip)
26
+
27
+ until segments[@cursor].nil?
28
+ if (segment = antlers_segment(segments:))
29
+ sequence << antlers_lexeme(segment:, segments:)
30
+ # Skipping: ['{', 'expression', '}']
31
+ # Skipping: ['<{', 'name + props + keywords', '}>']
32
+ @cursor += 3
33
+ else
34
+ segment = segments[@cursor]
35
+ sequence << segment unless segment.empty?
36
+ @cursor += 1
37
+ end
38
+ end
39
+
40
+ sequence
41
+ end
42
+
43
+ private
44
+
45
+ def antlers_segment(segments:)
46
+ next_segment = segments[@cursor + 1]
47
+ return nil unless next_segment && (segments[@cursor] == '<{' || brackets?(segments:))
48
+
49
+ next_segment
50
+ end
51
+
52
+ def antlers_lexeme(segment:, segments:)
53
+ name, props, keywords = parse_antlers_segment(segment:)
54
+
55
+ return @var_lexeme.lexeme(segment:, raw: false) if @var_lexeme && brackets?(segments:)
56
+
57
+ lexeme = @lexeme_types.find { it.match?(name:, keywords:) }
58
+ return lexeme.lexeme(name:, props:, keywords:) if lexeme
59
+
60
+ return @var_lexeme.lexeme(segment:, raw: true) if @var_lexeme && deerheads?(segments:)
61
+
62
+ raise LexerError, "Unrecognised syntax: '#{segment}'"
63
+ end
64
+
65
+ def parse_antlers_segment(segment:)
66
+ name_and_props, *keywords = segment.split(/(#{Regexp.union(@keywords)})/)
67
+ name, *props = name_and_props.split(' ')
68
+ [name, props, parse_keywords(keywords:)]
69
+ end
70
+
71
+ def parse_keywords(keywords:)
72
+ key_values = {}
73
+
74
+ while (keyword = keywords.shift)
75
+ keyword.strip!
76
+ value = keyword.end_with?(':') && value?(keywords.first.strip) ? keywords.shift.strip : nil
77
+ key_values[keyword] = value
78
+ end
79
+
80
+ key_values
81
+ end
82
+
83
+ def value?(string)
84
+ !(string.start_with?(':') || string.end_with?(':'))
85
+ end
86
+
87
+ def brackets?(segments:)
88
+ first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
89
+ first == '{' && last == '}'
90
+ end
91
+
92
+ def deerheads?(segments:)
93
+ first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
94
+ first == '<{' && last == '}>'
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Antlers
4
+ class Parser
5
+ class ParserError < StandardError; end
6
+
7
+ def initialize(node_types:, namespace: nil)
8
+ @node_types = node_types.to_a
9
+ @namespace = namespace
10
+ @template = nil
11
+ end
12
+
13
+ def parse(sequence:, template: nil, id: :root_node)
14
+ @template = template
15
+
16
+ branch(node: RootNode.new(name: id), sequence:)
17
+ end
18
+
19
+ def branch(node:, sequence:)
20
+ until sequence.empty?
21
+ segment = sequence.shift
22
+
23
+ child_class = @node_types.find { |n| n.match?(segment:) }
24
+ raise(ParserError, "No node matches #{segment}") unless child_class
25
+
26
+ child = child_class.build(segment:, namespace: @namespace, template: @template)
27
+ node.children << child
28
+
29
+ if (end_key = child_class::END_KEY)
30
+ sub_branch(node: child, sequence:, end_key:, end_name: child.end_name)
31
+ end
32
+ end
33
+
34
+ node
35
+ end
36
+
37
+ def sub_branch(node:, sequence:, end_key:, end_name: nil)
38
+ sub_sequence = []
39
+ sub_sequence << sequence.shift until sequence.first.is_a?(Hash) && sequence.first[end_key] == end_name
40
+ # Remove the end tag we just stopped before.
41
+ sequence.shift
42
+
43
+ branch(node:, sequence: sub_sequence)
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Antlers
4
- VERSION = '0.8.1'
4
+ VERSION = '0.9.0'
5
5
  end
data/lib/antlers.rb CHANGED
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lexer'
4
- require_relative 'parser'
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
7
- class << self
8
- def ast(template:, namespace: nil)
9
- return template unless template.include?('<{') || template.include?('{')
12
+ DEFAULT_ELEMENTS = [:root, :html, :form, :for, :if, :prop, :slot, :yield, :var]
10
13
 
11
- sequence = Lexer.new.parse(template)
12
- Parser.new(namespace:).parse(sequence:)
14
+ class << self
15
+ def ast(template:, namespace: nil, elements: Elements[*DEFAULT_ELEMENTS])
16
+ sequence = Lexer.new(lexeme_types: elements[:lexeme]).parse(template:)
17
+ Parser.new(namespace:, node_types: elements[:node]).parse(sequence:, template:)
13
18
  end
14
19
 
15
20
  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
- # Consider instance a value object on comparison.
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
- # 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:) : child) || ''
19
+ output += child.render(current_binding:, parent_binding:, slot_node:) || ''
21
20
  end
22
21
 
23
22
  output
@@ -6,8 +6,31 @@ module Antlers
6
6
 
7
7
  def match?(name: nil, keywords: {}) = false
8
8
 
9
- def lexeme(name:, props:, keywords:)
9
+ def lexeme(name:, props:, keywords:, segment: nil, raw: false)
10
10
  raise NotImplementedError
11
11
  end
12
+
13
+ private
14
+
15
+ def props(props)
16
+ odd_props = props.join(' ').split(/(=)|\s/)
17
+
18
+ return {} unless odd_props.any?
19
+
20
+ props = {}
21
+ until odd_props.empty?
22
+ prop = odd_props.shift
23
+ value = nil
24
+
25
+ if odd_props.first == '='
26
+ odd_props.shift
27
+ value = odd_props.shift
28
+ end
29
+
30
+ props[prop.to_sym] = value
31
+ end
32
+
33
+ props
34
+ end
12
35
  end
13
36
  end
@@ -16,28 +16,5 @@ module Antlers
16
16
  prop[:props] = props(props) unless props.empty?
17
17
  prop
18
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
19
  end
43
20
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../interfaces/lexeme'
4
+ require_relative '../support/queries'
5
+
6
+ module Antlers
7
+ module VarLexeme
8
+ include Lexeme
9
+ extend Queries
10
+ extend self
11
+
12
+ # Variables are special and are matched by the lexer.
13
+ def match?(**)
14
+ false
15
+ end
16
+
17
+ def lexeme(segment:, raw:, **)
18
+ # String is already interpolated or not depending on user input on the template layer, now we store it without those template quotes.
19
+ segment = segment[1..-2] if Queries.user_defined_string?(segment)
20
+
21
+ return { raw_var: segment } if raw
22
+
23
+ { var: segment }
24
+ end
25
+ end
26
+ end
@@ -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 prevent a templating language becoming a programming language.
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
@@ -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
- # Antlers nodes respond to "render", whereas HTML is stored as a string and output as is.
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
@@ -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
- # 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) || ''
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
- # 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) || ''
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
@@ -30,6 +30,16 @@ module Antlers
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
45
  def create_instance(class_proxy:, klass:, event:, props:)
@@ -7,5 +7,15 @@ module Antlers
7
7
  def initialize(name: :root_node, children: [])
8
8
  super(name:, children:)
9
9
  end
10
+
11
+ class << self
12
+ def match?(**)
13
+ false
14
+ end
15
+
16
+ def build(**)
17
+ new()
18
+ end
19
+ end
10
20
  end
11
21
  end
@@ -7,11 +7,14 @@ require_relative '../modules/props'
7
7
  module Antlers
8
8
  class SlotNode < BranchNode
9
9
  include Namespace
10
- include Props # Immediate parent ancestor which props are passed to.
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
 
14
- def initialize(name:, namespace:, props: [], children: [], **)
17
+ def initialize(name:, namespace:, props: {}, children: [], **)
15
18
  super(name:, props:, children:)
16
19
 
17
20
  @namespace = namespace
@@ -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
@@ -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
@@ -8,20 +8,25 @@ module Antlers
8
8
  super(name:)
9
9
  end
10
10
 
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)
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
- # Antlers nodes respond to "render", whereas HTML is stored as a string and output as is.
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.1
4
+ version: 0.9.0
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,7 +74,10 @@ extensions: []
60
74
  extra_rdoc_files: []
61
75
  files:
62
76
  - lib/antlers.rb
63
- - lib/factories/node_factory.rb
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
@@ -70,22 +87,21 @@ files:
70
87
  - lib/lexemes/if_lexeme.rb
71
88
  - lib/lexemes/prop_lexeme.rb
72
89
  - lib/lexemes/slot_lexeme.rb
90
+ - lib/lexemes/var_lexeme.rb
73
91
  - lib/lexemes/yield_lexeme.rb
74
- - lib/lexer.rb
75
92
  - lib/modules/namespace.rb
76
93
  - lib/modules/props.rb
77
94
  - lib/modules/variables.rb
78
95
  - lib/nodes/for_node.rb
79
96
  - lib/nodes/form_node.rb
97
+ - lib/nodes/html_node.rb
80
98
  - lib/nodes/if_node.rb
81
99
  - lib/nodes/prop_node.rb
82
100
  - lib/nodes/root_node.rb
83
101
  - lib/nodes/slot_node.rb
84
102
  - lib/nodes/var_node.rb
85
103
  - lib/nodes/yield_node.rb
86
- - lib/parser.rb
87
104
  - lib/support/queries.rb
88
- - lib/version.rb
89
105
  homepage: https://github.com/raindeer-rb/antlers
90
106
  licenses: []
91
107
  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,120 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lexemes/for_lexeme'
4
- require_relative 'lexemes/form_lexeme'
5
- require_relative 'lexemes/if_lexeme'
6
- require_relative 'lexemes/prop_lexeme'
7
- require_relative 'lexemes/slot_lexeme'
8
- require_relative 'lexemes/yield_lexeme'
9
- require_relative 'support/queries'
10
-
11
- module Antlers
12
- extend Queries
13
-
14
- class LexerParseError < StandardError; end
15
-
16
- LEXEMES = [
17
- # FormLexeme must be ordered before ForLexeme for regex to work.
18
- Antlers::FormLexeme,
19
- Antlers::ForLexeme,
20
- Antlers::IfLexeme,
21
- Antlers::PropLexeme,
22
- Antlers::SlotLexeme,
23
- Antlers::YieldLexeme,
24
- ]
25
-
26
- class Lexer
27
- def initialize
28
- @delimiters = ['<{', '}>', '{', '}']
29
- @keywords = LEXEMES.flat_map { |lexeme| lexeme.const_get(:KEYWORDS) }
30
- @cursor = 0
31
- end
32
-
33
- def parse(template)
34
- @cursor = 0
35
- sequence = []
36
-
37
- # Split on delimiters and retain capture groups.
38
- segments = template.split(/(#{Regexp.union(@delimiters)})/).map(&:strip)
39
-
40
- until segments[@cursor].nil?
41
- if (antlers_segment = antlers_segment(segments:))
42
- sequence << antlers_lexeme(antlers_segment:, segments:)
43
- # Skipping: ['{', 'expression', '}']
44
- # Skipping: ['<{', 'name + props + keywords', '}>']
45
- @cursor += 3
46
- else
47
- segment = segments[@cursor]
48
- sequence << segment unless segment.empty?
49
- @cursor += 1
50
- end
51
- end
52
-
53
- sequence
54
- end
55
-
56
- private
57
-
58
- def antlers_segment(segments:)
59
- next_segment = segments[@cursor + 1]
60
- return nil unless next_segment && (segments[@cursor] == '<{' || brackets?(segments:))
61
-
62
- next_segment
63
- end
64
-
65
- def antlers_lexeme(antlers_segment:, segments:)
66
- return var(antlers_segment:) if brackets?(segments:)
67
-
68
- name, props, keywords = parse_segment(antlers_segment:)
69
-
70
- lexeme = LEXEMES.find { it.match?(name:, keywords:) }
71
- return lexeme.lexeme(name:, props:, keywords:) if lexeme
72
- return var(antlers_segment:, raw: true) if deerheads?(segments:)
73
-
74
- raise LexerParseError, "Unrecognised syntax: '#{antlers_segment}'"
75
- end
76
-
77
- def parse_segment(antlers_segment:)
78
- name_and_props, *keywords = antlers_segment.split(/(#{Regexp.union(@keywords)})/)
79
- name, *props = name_and_props.split(' ')
80
- [name, props, parse_keywords(keywords:)]
81
- end
82
-
83
- def parse_keywords(keywords:)
84
- key_values = {}
85
-
86
- while (keyword = keywords.shift)
87
- keyword.strip!
88
- value = keyword.end_with?(':') && value?(keywords.first.strip) ? keywords.shift.strip : nil
89
- key_values[keyword] = value
90
- end
91
-
92
- key_values
93
- end
94
-
95
- def value?(string)
96
- !(string.start_with?(':') || string.end_with?(':'))
97
- end
98
-
99
- # TODO: Refactor every constant, match and result method into its own class. Loop through every class and return the first match.
100
-
101
- def brackets?(segments:)
102
- first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
103
- first == '{' && last == '}'
104
- end
105
-
106
- def deerheads?(segments:)
107
- first, _, last = segments[@cursor..@cursor + 3].map(&:strip)
108
- first == '<{' && last == '}>'
109
- end
110
-
111
- def var(antlers_segment:, raw: false)
112
- # String is already interpolated or not depending on user input on the template layer, now we store it without those template quotes.
113
- antlers_segment = antlers_segment[1..-2] if Queries.user_defined_string?(antlers_segment)
114
-
115
- return { raw_var: antlers_segment } if raw
116
-
117
- { var: antlers_segment }
118
- end
119
- end
120
- 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