antlers 0.8.0 → 0.8.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: b0d88855d8351d4ce9b4ebf2bfeaf0fc05faef8a33a882ffd206fa1c8241ec9e
4
- data.tar.gz: e72f72db347e9f0f442e0a15cb4dce3f2c7405bf1bd7da6b33ab919a7003785b
3
+ metadata.gz: 8d3bcf6e14ade37294793b87b3ab3ab152e519f8c081a130644b0d28d489b63b
4
+ data.tar.gz: d9886a3044fb8654b37f111a9536ba7dfef4945c26fb08112e400ab14e76d8e7
5
5
  SHA512:
6
- metadata.gz: 6f1e6d6f8f512d327ee25068a3286d8f1e557407bf99f4ec274cd8c8e7336ef9c2d9db9980e66d13ce58b8055d07f6908108afcb8cd91d2da65224ac567946d4
7
- data.tar.gz: 27520e044188ae13cde780d7438286e510dae3874cd2218debf2901a1b3c7e9909847c44ae38ce4353e8b0482afa2516fa47857836070a58e8683934865a58c7
6
+ metadata.gz: a4179e613ca96ea49fdbc7d991a627a45640182ae5aaa5f01dc935396586ba8b583df469113c26c7a139308956bc18980cdbd04877c73a9b7cd14617682dd2d3
7
+ data.tar.gz: 698769c0d6bfd0e9872c5022f72555290acff3888615f44061ed10847af8f97af58477c35059678c2e02a2bdafc16d3f06ff514ab80aa539abc2de7cbfeda6bd
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Antlers
4
+ module Lexeme
5
+ KEYWORDS = []
6
+
7
+ def match?(name: nil, keywords: {}) = false
8
+
9
+ def lexeme(name:, props:, keywords:)
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+ end
@@ -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/lexer.rb CHANGED
@@ -1,5 +1,11 @@
1
1
  # frozen_string_literal: true
2
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'
3
9
  require_relative 'support/queries'
4
10
 
5
11
  module Antlers
@@ -7,14 +13,20 @@ module Antlers
7
13
 
8
14
  class LexerParseError < StandardError; end
9
15
 
10
- class Lexer
11
- IF_KEYWORDS = ['if:', ':if'].freeze
12
- FOR_KEYWORDS = ['for:', 'in:', ':for'].freeze
13
- FORM_KEYWORDS = ['form:', ':form'].freeze
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
+ ]
14
25
 
26
+ class Lexer
15
27
  def initialize
16
28
  @delimiters = ['<{', '}>', '{', '}']
17
- @keywords = [*IF_KEYWORDS, *FORM_KEYWORDS, *FOR_KEYWORDS, 'slot:', ':slot']
29
+ @keywords = LEXEMES.flat_map { |lexeme| lexeme.const_get(:KEYWORDS) }
18
30
  @cursor = 0
19
31
  end
20
32
 
@@ -55,12 +67,8 @@ module Antlers
55
67
 
56
68
  name, props, keywords = parse_segment(antlers_segment:)
57
69
 
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:)
70
+ lexeme = LEXEMES.find { it.match?(name:, keywords:) }
71
+ return lexeme.lexeme(name:, props:, keywords:) if lexeme
64
72
  return var(antlers_segment:, raw: true) if deerheads?(segments:)
65
73
 
66
74
  raise LexerParseError, "Unrecognised syntax: '#{antlers_segment}'"
@@ -100,30 +108,6 @@ module Antlers
100
108
  first == '<{' && last == '}>'
101
109
  end
102
110
 
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
111
  def var(antlers_segment:, raw: false)
128
112
  # String is already interpolated or not depending on user input on the template layer, now we store it without those template quotes.
129
113
  antlers_segment = antlers_segment[1..-2] if Queries.user_defined_string?(antlers_segment)
@@ -132,73 +116,5 @@ module Antlers
132
116
 
133
117
  { var: antlers_segment }
134
118
  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
119
  end
204
120
  end
@@ -23,8 +23,8 @@ 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:)
@@ -32,9 +32,10 @@ module Antlers
32
32
 
33
33
  private
34
34
 
35
- def create_instance(class_proxy:, klass:, event:)
35
+ def create_instance(class_proxy:, klass:, event:, props:)
36
36
  initialize_params = class_proxy.instance_methods[:initialize]&.tagged_params(:keyword)&.map(&:name) || []
37
37
  return klass.new(event:, **props) if initialize_params.include?(:event) && initialize_params.count > 1
38
+ return klass.new(**props) if initialize_params.count > 1
38
39
  return klass.new(event:) if initialize_params.include?(:event)
39
40
 
40
41
  klass.new
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Antlers
4
- VERSION = '0.8.0'
4
+ VERSION = '0.8.1'
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.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - maedi
@@ -64,6 +64,13 @@ files:
64
64
  - lib/interfaces/antler_node.rb
65
65
  - lib/interfaces/branch_node.rb
66
66
  - lib/interfaces/leaf_node.rb
67
+ - lib/interfaces/lexeme.rb
68
+ - lib/lexemes/for_lexeme.rb
69
+ - lib/lexemes/form_lexeme.rb
70
+ - lib/lexemes/if_lexeme.rb
71
+ - lib/lexemes/prop_lexeme.rb
72
+ - lib/lexemes/slot_lexeme.rb
73
+ - lib/lexemes/yield_lexeme.rb
67
74
  - lib/lexer.rb
68
75
  - lib/modules/namespace.rb
69
76
  - lib/modules/props.rb