antlers 0.2.0 → 0.3.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: eca914b3e0a81f538aaa1e4db0b477a9b46c0934d365786962d2a391c957b3ee
4
- data.tar.gz: 7f46ddc34ff748822e99537b59e0300fe6c094abe79d2cd042ad78ad7cb9068d
3
+ metadata.gz: 57d224246ca8ba02aac3045b0f06904d8cebb0cfdb7f3d518d3c646324a1e3b6
4
+ data.tar.gz: e8b7787ccfc284dbb1d370292ba4238576e346c75bc957d5da70067220635ba7
5
5
  SHA512:
6
- metadata.gz: ca62292741c30d4a3ed0a0c0b62dcb20a895eb3018d55b179ef3512f47e078628eff04089b285490add876d1cfa5ca473a4b85b7bdf01d69d56e459b0364e1bb
7
- data.tar.gz: db84676dac56bce48b73b1019d73924c7c82c7e4c98d6b5a9c9dddbae2e8defd68336f8ec83e64154403a2f9d1fe611d441ebec99f5f2c2a899bb2fe6a7acfc4
6
+ metadata.gz: b4cb6632bbbafb3ec12cc27256b2ae9e2134e3b621e28a7edcfd269bfff828d128cd0f659d7135a734b7792d121477c0ca012c3215a1eef442d3106bb637e89d
7
+ data.tar.gz: 0a0f5f6fc83e72a7253a737956c3a4cff5961999722b23b96ad44716b3ad2b2e40ce7742145cea8610f9604467a438a12000a0609cd9408487f85472f362cb3d
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 parse(template)
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,10 @@ require_relative '../nodes/yield_node'
8
9
  module Antlers
9
10
  class NodeFactory
10
11
  class << self
12
+ def for_node(segment:)
13
+ ForNode.new(name: segment[:for_def], item: segment[:for_def], items: segment[:in])
14
+ end
15
+
11
16
  def prop_node(segment:)
12
17
  PropNode.new(name: segment[:prop], props: segment[:props])
13
18
  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,15 +54,16 @@ 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, "Couldn't parse antlers syntax: '#{antlers_segment}'"
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:)
@@ -70,6 +71,10 @@ module Antlers
70
71
  first == '{' && last == '}'
71
72
  end
72
73
 
74
+ def for_loop?(keywords:)
75
+ keywords.first == 'for:' || keywords.first == ':for'
76
+ end
77
+
73
78
  def slot?(name)
74
79
  name && (name.start_with?(':') || name.end_with?(':'))
75
80
  end
@@ -91,6 +96,19 @@ module Antlers
91
96
  { var: antlers_segment }
92
97
  end
93
98
 
99
+ def for_loop(keywords:)
100
+ key_values = keywords.count % 2 == 0 ? keywords.each_slice(2).to_h : {}
101
+
102
+ if key_values['for:']
103
+ for_def = { for_def: key_values['for:'] }
104
+ for_def[:in] = key_values['in:']
105
+ return for_def
106
+ end
107
+
108
+ # TODO: Keep track of which for loop we're in to allow nested for loops.
109
+ { for_end: 'level_1' }
110
+ end
111
+
94
112
  def slot(name:, props:)
95
113
  if name.end_with?(':')
96
114
  slot_def = { slot_def: name.delete_suffix(':') }
data/lib/modules/props.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'low_event'
4
+
3
5
  module Antlers
4
6
  module Props
5
7
  attr_accessor :props
@@ -12,7 +14,7 @@ module Antlers
12
14
 
13
15
  private
14
16
 
15
- def create_event(props:)
17
+ def create_render_event(props:)
16
18
  Low::Events::RenderEvent.new(action: :render, props:)
17
19
  end
18
20
 
@@ -0,0 +1,19 @@
1
+ module Antlers
2
+ module Variables
3
+ # A variable is deliberately limited in what it can represent.
4
+ # 1. An instance variable
5
+ # 2. A method call/local variable
6
+ # 3. A static string
7
+ def evaluate_variable(name:, current_binding:)
8
+ if current_binding
9
+ return current_binding.receiver.instance_variable_get(name) if name.start_with?('@')
10
+ return current_binding.local_variable_get(name) if current_binding.local_variable_defined?(name)
11
+ return current_binding.receiver.send(name.to_sym) if current_binding.receiver.respond_to?(name.to_sym)
12
+ end
13
+
14
+ @value.to_s
15
+ rescue NameError
16
+ @value.to_s
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,36 @@
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:, item:, items:, props: [], children: [])
15
+ super(name:, props:, children:)
16
+
17
+ @item = item
18
+ @items = items
19
+ end
20
+
21
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, namespace: nil)
22
+ output = ''
23
+
24
+ evaluate_variable(name: @items, current_binding:).each do |item|
25
+ current_binding.local_variable_set(@item, item)
26
+
27
+ @children.each do |child|
28
+ # Antlers nodes respond to "render", whereas HTML is stored as a string and output as is.
29
+ output += (child.respond_to?(:render) ? child.render(current_binding:, parent_binding:, slot_node:, namespace:) : child) || ''
30
+ end
31
+ end
32
+
33
+ output
34
+ end
35
+ end
36
+ end
@@ -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 = create_event(props:)
12
+ event = create_render_event(props:)
13
13
 
14
- klass = class_from_namespace(namespace: namespace&.split('::') || [], name: @name)
15
- instance = klass.new(event:)
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 instance.render_template(event:, parent_binding:, props:) if klass.template
18
+ return renderable_instance.render_template(event:, parent_binding:, props:) if renderable_klass.template
19
19
 
20
- props.empty? ? instance.render(event:) : instance.render(event:, **props)
20
+ props.empty? ? renderable_instance.render(event:) : renderable_instance.render(event:, **props)
21
21
  end
22
22
  end
23
23
  end
@@ -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 = create_event(props:)
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:)
@@ -3,9 +3,12 @@
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
14
  def initialize(name: :var, value:)
@@ -15,26 +18,7 @@ module Antlers
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(evaluate_value(current_binding))
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_variable(name: @value, current_binding:) || @value)
38
22
  end
39
23
  end
40
24
  end
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(branch_node: RootNode.new(name: id), sequence:)
10
+ branch(node: RootNode.new(name: id), sequence:)
11
11
  end
12
12
 
13
- def branch(branch_node:, sequence:) # rubocop:disable Metrics/AbcSize
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
- branch_node.children << segment
18
+ node.children << segment
19
19
  elsif segment[:var]
20
- branch_node.children << NodeFactory.var_node(segment:)
20
+ node.children << NodeFactory.var_node(segment:)
21
21
  elsif segment[:prop]
22
- branch_node.children << NodeFactory.prop_node(segment:)
22
+ node.children << NodeFactory.prop_node(segment:)
23
23
  elsif segment[:slot]
24
- branch_node.children << NodeFactory.yield_node(segment:)
24
+ node.children << NodeFactory.yield_node(segment:)
25
25
  elsif segment[:slot_def]
26
26
  slot_node = NodeFactory.slot_node(segment:)
27
- branch_node.children << slot_node
28
-
29
- sub_sequence = []
30
- sub_sequence << sequence.shift until sequence.first.is_a?(Hash) && sequence.first[:slot_end] == slot_node.name
31
-
32
- branch(branch_node: slot_node, sequence: sub_sequence)
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
- branch_node
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/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Antlers
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -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: 3.7.2
83
+ rubygems_version: 4.0.10
82
84
  specification_version: 4
83
85
  summary: Deer to be different
84
86
  test_files: []