antlers 1.1.0 → 1.2.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: 375ebfb2e841c81843bb17b1025d8cd43dbe851f803f4845afb73f6d24f8ef30
4
- data.tar.gz: a7db1372364cb6633b6ce6ae2492966ecf1c6d2a20f3d7af214ce14e18feda21
3
+ metadata.gz: 6ce3c9ccc84415933ff474d8187c420e28618fa7c15c56bffe0060e2364750d2
4
+ data.tar.gz: cc69421e1365a4c3f38975aadd7eee7390797ba48c2aff399ad425f2bb05160e
5
5
  SHA512:
6
- metadata.gz: c0b9b231ebe71d01f50443bebcec9210c3f97fe238138d172974ef5c298d5c543af1371a3806f45e54cc8e5a6a8cb32d6e51e17547465ed96682a633393c0ca4
7
- data.tar.gz: d7c42c19f7c1aa9a5bcb27d30fe2fe0118054897bb5e2388f266b7127bf6c89bdc35b7da4fa20e39f969a20f6a7a2ce9c6e8325ac32e0a9f78720b85a06bb21c
6
+ metadata.gz: da91462599377c23cc6cfc92fe4fa74e917a216a6378f1ef353ac3fcd6c53a947644dfc9cb3c2bb3937adaf33c5644d28a1f9698ea570c31de36479a8964301e
7
+ data.tar.gz: 6c46c7a4aa39005d09ba125a7b2f4a1129383ade80691fffbe0757c3a19683144ecbbe12392ab22814b5894c83a3d489701ccdaa1da116e927881816e2cc4ad9
data/lib/antlers/lexer.rb CHANGED
@@ -22,7 +22,8 @@ module Antlers
22
22
  sequence = []
23
23
 
24
24
  # Split on delimiters and retain capture groups.
25
- segments = template.split(/(#{Regexp.union(@delimiters)})/).map(&:strip)
25
+ # Whitespace inside the template is kept, but not around it, like the indentation of a template in a method body.
26
+ segments = template.strip.split(/(#{Regexp.union(@delimiters)})/)
26
27
 
27
28
  until segments[@cursor].nil?
28
29
  if (segment = antlers_segment(segments:))
@@ -50,6 +51,7 @@ module Antlers
50
51
  end
51
52
 
52
53
  def antlers_lexeme(segment:, segments:)
54
+ segment = segment.strip
53
55
  name, props, keywords = parse_antlers_segment(segment:)
54
56
 
55
57
  return @var_lexeme.lexeme(segment:, raw: false) if @var_lexeme && brackets?(segments:)
@@ -73,7 +75,7 @@ module Antlers
73
75
 
74
76
  while (keyword = keywords.shift)
75
77
  keyword.strip!
76
- value = keyword.end_with?(':') && value?(keywords.first.strip) ? keywords.shift.strip : nil
78
+ value = keyword.end_with?(':') && value?(keywords.first) ? keywords.shift.strip : nil
77
79
  key_values[keyword] = value
78
80
  end
79
81
 
@@ -81,7 +83,9 @@ module Antlers
81
83
  end
82
84
 
83
85
  def value?(string)
84
- !(string.start_with?(':') || string.end_with?(':'))
86
+ # Support keywords without a value like "<{ form: }>" whose value will become an empty string.
87
+ string = string.to_s.strip
88
+ !string.empty? && !(string.start_with?(':') || string.end_with?(':'))
85
89
  end
86
90
 
87
91
  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.1.0'
4
+ VERSION = '1.2.0'
5
5
  end
data/lib/antlers.rb CHANGED
@@ -7,7 +7,7 @@ require_relative 'antlers/parser'
7
7
  module Antlers
8
8
  module_function
9
9
 
10
- DEFAULT_ELEMENTS = %i[root html form for if prop slot yield var]
10
+ DEFAULT_ELEMENTS = %i[root html form for if prop slot yield var].freeze
11
11
 
12
12
  def ast(template:, namespace: nil, elements: Elements[*DEFAULT_ELEMENTS])
13
13
  sequence = Lexer.new(lexeme_types: elements[:lexeme]).parse(template:)
@@ -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
@@ -22,7 +22,7 @@ module Antlers
22
22
  evaluated_props = {}
23
23
 
24
24
  props.each do |name, value|
25
- evaluated_props[name] = evaluate(name: value, current_binding:) || fallback(value)
25
+ evaluated_props[name] = evaluate(value:, current_binding:)
26
26
  end
27
27
 
28
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
@@ -23,7 +23,7 @@ module Antlers
23
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.
@@ -20,7 +20,7 @@ module Antlers
20
20
  end
21
21
 
22
22
  def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
23
- output = "<form action='#{@action}' method='#{@method}'>"
23
+ output = "<form action='#{action(current_binding:)}' method='#{@method}'>"
24
24
 
25
25
  @children.each do |child|
26
26
  output += child.render(current_binding:, parent_binding:, slot_node:, metadata:) || ''
@@ -30,9 +30,17 @@ module Antlers
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
@@ -21,7 +21,7 @@ module Antlers
21
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
26
  output += child.render(current_binding:, parent_binding:, slot_node:, metadata:) || ''
27
27
  end
@@ -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
 
@@ -19,11 +17,7 @@ module Antlers
19
17
  end
20
18
 
21
19
  def render(current_binding: nil, parent_binding: nil, slot_node: nil, metadata: {})
22
- result = evaluate(name: @value, current_binding:) || fallback(@value)
23
-
24
- return result if @raw
25
-
26
- ERB::Util.html_escape(result)
20
+ evaluate(value: @value, current_binding:, escape: !@raw).to_s
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
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: antlers
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2026-10-13 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: erb
@@ -108,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
110
  requirements: []
111
- rubygems_version: 4.0.6
111
+ rubygems_version: 3.6.2
112
112
  specification_version: 4
113
113
  summary: Templating language inside HTML inside Ruby
114
114
  test_files: []