antlers 1.0.0 → 1.1.1

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: 7e2b59df9502f3e66f123ec45e707de957f8e96656c44ddd6b8a026cf96f73ef
4
- data.tar.gz: f8455c5e3de15fc2fdfd8ab1c41dc6c2f6ab377e7d811522f7dc4aa36c40e223
3
+ metadata.gz: 3c1e20787d49349b0f3df0f6df128650c7214ec53cd697ec66840d6f3884d9e8
4
+ data.tar.gz: 3048610d15b4fe61ff67123aba2b6f81a5ef9019e4ac03dea657995c30501b17
5
5
  SHA512:
6
- metadata.gz: '045843a0d001f3f6cc846cdca65935f30a1ecb7aea81e4d5a6fb4bce3627ffb670b458f2457c65b582d048f37747e061bf62754beefcde8e4f327eb17da9565a'
7
- data.tar.gz: 7995630d52ba13a9756ee08bdb394ce7afeef4c96397eb7da0822952cf6f8516cdd54c7df519d8fdc9826a792495b9ad2b5a6aab42b683d9c07e70d2a713efaa
6
+ metadata.gz: bc0ff003117d8d08b22146b15a32f32288aef44191ba401080db525ed3125e6a7cc38fd278b64eb96eda1659d7b608cbf41f4b9a1879c8641eecf0f3fa4fc83d
7
+ data.tar.gz: e0a5d0d1c71721a4727b3f6134a1eafb40b75b34cff799cc5f2d94677cbf84b6e41dad3b6d8ad5bee5293ca1b1de10969d8e6af1d8cc19c6889b0dc0dbaca71b
data/lib/antlers/api.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'elements'
4
4
  require_relative 'lexer'
5
+ require_relative 'parser'
5
6
  require_relative '../interfaces/lexeme'
6
7
  require_relative '../interfaces/branch_node'
7
8
  require_relative '../interfaces/leaf_node'
data/lib/antlers/lexer.rb CHANGED
@@ -73,7 +73,7 @@ module Antlers
73
73
 
74
74
  while (keyword = keywords.shift)
75
75
  keyword.strip!
76
- value = keyword.end_with?(':') && value?(keywords.first.strip) ? keywords.shift.strip : nil
76
+ value = keyword.end_with?(':') && value?(keywords.first) ? keywords.shift.strip : nil
77
77
  key_values[keyword] = value
78
78
  end
79
79
 
@@ -81,7 +81,9 @@ module Antlers
81
81
  end
82
82
 
83
83
  def value?(string)
84
- !(string.start_with?(':') || string.end_with?(':'))
84
+ # Support keywords without a value like "<{ form: }>" whose value will become an empty string.
85
+ string = string.to_s.strip
86
+ !string.empty? && !(string.start_with?(':') || string.end_with?(':'))
85
87
  end
86
88
 
87
89
  def brackets?(segments:)
@@ -36,7 +36,13 @@ module Antlers
36
36
 
37
37
  def sub_branch(node:, sequence:, end_key:, end_name: nil)
38
38
  sub_sequence = []
39
- sub_sequence << sequence.shift until sequence.first.is_a?(Hash) && sequence.first[end_key] == end_name
39
+
40
+ until sequence.first.is_a?(Hash) && sequence.first[end_key] == end_name
41
+ raise ParserError, "Missing end tag for #{node.class}" if sequence.empty?
42
+
43
+ sub_sequence << sequence.shift
44
+ end
45
+
40
46
  # Remove the end tag we just stopped before.
41
47
  sequence.shift
42
48
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Antlers
4
- VERSION = '1.0.0'
4
+ VERSION = '1.1.1'
5
5
  end
data/lib/antlers.rb CHANGED
@@ -4,21 +4,17 @@ require_relative 'antlers/elements'
4
4
  require_relative 'antlers/lexer'
5
5
  require_relative 'antlers/parser'
6
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'
10
-
11
7
  module Antlers
12
- DEFAULT_ELEMENTS = [:root, :html, :form, :for, :if, :prop, :slot, :yield, :var]
8
+ module_function
9
+
10
+ DEFAULT_ELEMENTS = %i[root html form for if prop slot yield var].freeze
13
11
 
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:)
18
- end
12
+ def ast(template:, namespace: nil, elements: Elements[*DEFAULT_ELEMENTS])
13
+ sequence = Lexer.new(lexeme_types: elements[:lexeme]).parse(template:)
14
+ Parser.new(namespace:, node_types: elements[:node]).parse(sequence:, template:)
15
+ end
19
16
 
20
- def render(ast:, current_binding:, parent_binding: nil, slot_node: nil)
21
- ast.render(current_binding:, parent_binding:, slot_node:)
22
- end
17
+ def render(ast:, current_binding:, parent_binding: nil, slot_node: nil, metadata: {})
18
+ ast.render(current_binding:, parent_binding:, slot_node:, metadata:)
23
19
  end
24
20
  end
@@ -11,7 +11,7 @@ module Antlers
11
11
  @name = name
12
12
  end
13
13
 
14
- def render(current_binding: nil, parent_binding: nil, slot_node: nil, namespace: nil)
14
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, namespace: nil, metadata: {})
15
15
  raise NotImplementedError
16
16
  end
17
17
 
@@ -19,6 +19,13 @@ module Antlers
19
19
  'level_1'
20
20
  end
21
21
 
22
+ # Support sending an event to render_template hook when lowevent available. See lownode as an example implementation.
23
+ def possible_render_event(props:)
24
+ Low::Events::RenderEvent.new(action: :render, props:) if defined?(LowEvent)
25
+
26
+ nil
27
+ end
28
+
22
29
  class << self
23
30
  def match?(segment:)
24
31
  raise NotImplementedError
@@ -12,11 +12,11 @@ module Antlers
12
12
  @children = children
13
13
  end
14
14
 
15
- def render(current_binding: nil, parent_binding: nil, slot_node: nil)
15
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
16
16
  output = ''
17
17
 
18
18
  @children.each do |child|
19
- output += child.render(current_binding:, parent_binding:, slot_node:) || ''
19
+ output += child.render(current_binding:, parent_binding:, slot_node:, metadata:) || ''
20
20
  end
21
21
 
22
22
  output
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Antlers
4
4
  module Lexeme
5
- KEYWORDS = []
5
+ KEYWORDS = [].freeze
6
6
 
7
7
  def match?(name: nil, keywords: {}) = false
8
8
 
@@ -14,10 +14,7 @@ module Antlers
14
14
  end
15
15
 
16
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
17
+ return { form_def: keywords['form:'] } if keywords.key?('form:')
21
18
 
22
19
  { form_end: 'level_1' }
23
20
  end
@@ -9,15 +9,12 @@ module Antlers
9
9
  extend Queries
10
10
  extend self
11
11
 
12
- # Variables are special and are matched by the lexer.
12
+ # Variables are special and are matched by the lexer in a hard-coded fashion.
13
13
  def match?(**)
14
14
  false
15
15
  end
16
16
 
17
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
18
  return { raw_var: segment } if raw
22
19
 
23
20
  { var: segment }
data/lib/modules/props.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'low_event'
4
3
  require_relative 'variables'
5
4
 
6
5
  module Antlers
@@ -17,17 +16,13 @@ module Antlers
17
16
 
18
17
  private
19
18
 
20
- def create_render_event(props:)
21
- Low::Events::RenderEvent.new(action: :render, props:)
22
- end
23
-
24
19
  def evaluate_props(props:, current_binding:)
25
20
  return {} if props.nil?
26
21
 
27
22
  evaluated_props = {}
28
23
 
29
24
  props.each do |name, value|
30
- evaluated_props[name] = evaluate(name: value, current_binding:) || fallback(value)
25
+ evaluated_props[name] = evaluate(value:, current_binding:)
31
26
  end
32
27
 
33
28
  evaluated_props
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'erb'
3
4
  require_relative '../support/queries'
4
5
 
5
6
  module Antlers
@@ -7,28 +8,29 @@ module Antlers
7
8
  extend Queries
8
9
 
9
10
  # Evaluation limited to the following for security and to stop a templating language becoming a programming language.
10
- # 1. An instance variable
11
- # 2. A method call/local variable
12
- # 3. A method chain
13
- # 4. A static string
14
- def evaluate(name:, current_binding:)
15
- return name.to_s unless current_binding
11
+ # 1. A static string
12
+ # 2. An instance variable
13
+ # 3. A method call/local variable
14
+ # 4. A method chain
15
+ def evaluate(value:, current_binding:, escape: true)
16
+ result = result(value:, current_binding:)
16
17
 
17
- name, *chain = name.split('.')
18
+ return ERB::Util.html_escape(result) if escape
19
+
20
+ result
21
+ end
22
+
23
+ def result(value:, current_binding:)
24
+ return value[1..-2] if Queries.user_defined_string?(value)
25
+ return value unless current_binding
26
+
27
+ name, *chain = value.split('.')
18
28
 
19
29
  result = method_var(name:, current_binding:)
20
30
  return method_chain(result:, chain:) if chain.count > 0
21
31
  return result if result
22
32
 
23
33
  nil
24
- rescue StandardError
25
- nil
26
- end
27
-
28
- def fallback(value)
29
- return value[1..-2] if Queries.user_defined_string?(value)
30
-
31
- value
32
34
  end
33
35
 
34
36
  private
@@ -20,10 +20,10 @@ module Antlers
20
20
  @key = key
21
21
  end
22
22
 
23
- def render(current_binding: nil, parent_binding: nil, slot_node: nil)
23
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
24
24
  output = ''
25
25
 
26
- evaluate(name: @items, current_binding:).each do |value|
26
+ evaluate(value: @items, current_binding:, escape: false).each do |value|
27
27
  key, value = value if @key
28
28
 
29
29
  # TODO: Parallelize by creating new bindings and ensuring children have any args they need via RenderEvent.
@@ -31,7 +31,7 @@ module Antlers
31
31
  current_binding.local_variable_set(@key, key) if @key
32
32
 
33
33
  @children.each do |child|
34
- output += child.render(current_binding:, parent_binding:, slot_node:) || ''
34
+ output += child.render(current_binding:, parent_binding:, slot_node:, metadata:) || ''
35
35
  end
36
36
  end
37
37
 
@@ -19,20 +19,28 @@ module Antlers
19
19
  @method = method
20
20
  end
21
21
 
22
- def render(current_binding: nil, parent_binding: nil, slot_node: nil)
23
- output = "<form action='#{@action}' method='#{@method}'>"
22
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
23
+ output = "<form action='#{action(current_binding:)}' method='#{@method}'>"
24
24
 
25
25
  @children.each do |child|
26
- output += child.render(current_binding:, parent_binding:, slot_node:) || ''
26
+ output += child.render(current_binding:, parent_binding:, slot_node:, metadata:) || ''
27
27
  end
28
28
 
29
29
  output += '</form>'
30
30
  output
31
31
  end
32
32
 
33
+ private
34
+
35
+ def action(current_binding:)
36
+ return if @action.nil?
37
+
38
+ evaluate(value: @action, current_binding:)
39
+ end
40
+
33
41
  class << self
34
42
  def match?(segment:)
35
- segment[DEF_KEY]
43
+ segment.is_a?(Hash) && segment.key?(DEF_KEY)
36
44
  end
37
45
 
38
46
  def build(segment:, **)
data/lib/nodes/if_node.rb CHANGED
@@ -18,12 +18,12 @@ module Antlers
18
18
  @value = value
19
19
  end
20
20
 
21
- def render(current_binding: nil, parent_binding: nil, slot_node: nil)
21
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
22
22
  output = ''.dup
23
23
 
24
- if evaluate(name: @value, current_binding:)
24
+ if evaluate(value: @value, current_binding:, escape: false)
25
25
  @children.each do |child|
26
- output += child.render(current_binding:, parent_binding:, slot_node:) || ''
26
+ output += child.render(current_binding:, parent_binding:, slot_node:, metadata:) || ''
27
27
  end
28
28
  end
29
29
 
@@ -15,12 +15,12 @@ module Antlers
15
15
  @namespace = namespace
16
16
  end
17
17
 
18
- # Classes referenced via "<{ MyNode }>" must implement class/instance and render/render_template methods (See LowNode).
19
- def render(current_binding: nil, parent_binding: nil, slot_node: nil)
18
+ # Classes referenced via "<{ MyNode }>" must implement class/instance and render/render_template methods (See lownode).
19
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
20
20
  props = evaluate_props(props: @props, current_binding:)
21
- event = create_render_event(props:)
21
+ event = possible_render_event(props:)
22
22
 
23
- # TODO: Get LowLoad to load constants defined in "<{ MyNode }>" syntax so that we can resolve namespace/params on class load.
23
+ # TODO: Get dirload 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
26
 
@@ -44,6 +44,7 @@ module Antlers
44
44
 
45
45
  def create_instance(class_proxy:, klass:, event:, props:)
46
46
  initialize_params = class_proxy.instance_methods[:initialize]&.tagged_params(:keyword)&.map(&:name) || []
47
+
47
48
  return klass.new(event:, **props) if initialize_params.include?(:event) && initialize_params.count > 1
48
49
  return klass.new(**props) if initialize_params.count > 1
49
50
  return klass.new(event:) if initialize_params.include?(:event)
@@ -14,7 +14,7 @@ module Antlers
14
14
  end
15
15
 
16
16
  def build(**)
17
- new()
17
+ new
18
18
  end
19
19
  end
20
20
  end
@@ -20,9 +20,9 @@ module Antlers
20
20
  @namespace = namespace
21
21
  end
22
22
 
23
- def render(current_binding: nil, parent_binding: nil, slot_node: nil)
23
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
24
24
  props = evaluate_props(props: @props, current_binding:)
25
- event = create_render_event(props:)
25
+ event = possible_render_event(props:)
26
26
 
27
27
  # TODO: Get LowLoad to load constants defined in "<{ MyNode }>" syntax so that we can resolve namespace/params on class load.
28
28
  klass = class_constant(namespace: @namespace&.split('::') || [], name:)
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'erb'
4
-
5
3
  require_relative '../interfaces/leaf_node'
6
4
  require_relative '../modules/variables'
7
5
 
@@ -18,12 +16,8 @@ module Antlers
18
16
  @raw = raw
19
17
  end
20
18
 
21
- def render(current_binding: nil, parent_binding: nil, slot_node: nil)
22
- result = evaluate(name: @value, current_binding:) || fallback(@value)
23
-
24
- return result if @raw
25
-
26
- ERB::Util.html_escape(result)
19
+ def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
20
+ evaluate(value: @value, current_binding:, escape: !@raw)
27
21
  end
28
22
 
29
23
  class << self
@@ -32,7 +26,7 @@ module Antlers
32
26
  end
33
27
 
34
28
  def build(segment:, **)
35
- new(value: segment[:var] || segment[:raw_var], raw: !!segment[:raw_var])
29
+ new(value: segment[:var] || segment[:raw_var], raw: !segment[:raw_var].nil?)
36
30
  end
37
31
  end
38
32
  end
@@ -9,11 +9,11 @@ module Antlers
9
9
  end
10
10
 
11
11
  # Render the children in the binding of the parent.
12
- def render(current_binding: nil, parent_binding:, slot_node:)
12
+ def render(parent_binding:, slot_node:, current_binding: nil, metadata: {})
13
13
  output = ''
14
14
 
15
15
  slot_node.children.each do |child|
16
- output += child.render(current_binding: parent_binding, parent_binding:, slot_node:) || ''
16
+ output += child.render(current_binding: parent_binding, parent_binding:, slot_node:, metadata:) || ''
17
17
  end
18
18
 
19
19
  output
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: 1.0.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -23,20 +23,6 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
- - !ruby/object:Gem::Dependency
27
- name: low_event
28
- requirement: !ruby/object:Gem::Requirement
29
- requirements:
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: '0'
33
- type: :runtime
34
- prerelease: false
35
- version_requirements: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
26
  - !ruby/object:Gem::Dependency
41
27
  name: lowkey
42
28
  requirement: !ruby/object:Gem::Requirement