lkml 0.1.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.
data/lib/lkml/tree.rb ADDED
@@ -0,0 +1,319 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Node and token classes that make up the parse tree.
4
+
5
+ module Lkml
6
+ class SyntaxToken
7
+ attr_reader :value, :line_number, :prefix, :suffix
8
+
9
+ def initialize(value, line_number = nil, prefix: '', suffix: '')
10
+ @value = value
11
+ @line_number = line_number
12
+ @prefix = prefix
13
+ @suffix = suffix
14
+ end
15
+
16
+ def format_value
17
+ @value
18
+ end
19
+
20
+ def accept(visitor)
21
+ visitor.visit_token(self)
22
+ end
23
+
24
+ def ==(other)
25
+ if other.is_a?(self.class)
26
+ instance_variables.all? { |var| instance_variable_get(var) == other.instance_variable_get(var) }
27
+ elsif other.is_a?(String)
28
+ @value == other
29
+ else
30
+ false
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ [@prefix, format_value, @suffix].join
36
+ end
37
+ end
38
+
39
+ class LeftCurlyBrace < SyntaxToken
40
+ def initialize(*, **)
41
+ super('{', *, **)
42
+ end
43
+ end
44
+
45
+ class RightCurlyBrace < SyntaxToken
46
+ def initialize(*, **)
47
+ super('}', *, **)
48
+ end
49
+ end
50
+
51
+ class Colon < SyntaxToken
52
+ def initialize(*, **)
53
+ super(':', *, **)
54
+ end
55
+ end
56
+
57
+ class LeftBracket < SyntaxToken
58
+ def initialize(*, **)
59
+ super('[', *, **)
60
+ end
61
+ end
62
+
63
+ class RightBracket < SyntaxToken
64
+ def initialize(*, **)
65
+ super(']', *, **)
66
+ end
67
+ end
68
+
69
+ class DoubleSemicolon < SyntaxToken
70
+ def initialize(*, **)
71
+ super(';;', *, **)
72
+ end
73
+ end
74
+
75
+ class Comma < SyntaxToken
76
+ def initialize(*, **)
77
+ super(',', *, **)
78
+ end
79
+ end
80
+
81
+ class QuotedSyntaxToken < SyntaxToken
82
+ def format_value
83
+ "\"#{@value.gsub('\"', '"').gsub('"', '\"')}\""
84
+ end
85
+ end
86
+
87
+ class ExpressionSyntaxToken < SyntaxToken
88
+ attr_reader :expr_suffix
89
+
90
+ def initialize(value, line_number = nil, prefix: ' ', expr_suffix: ' ', suffix: '')
91
+ super(value, line_number, prefix: prefix, suffix: suffix)
92
+ @expr_suffix = expr_suffix
93
+ end
94
+
95
+ def to_s
96
+ [@prefix, format_value, @expr_suffix, ';;', @suffix].join
97
+ end
98
+ end
99
+
100
+ class SyntaxNode
101
+ def children
102
+ raise NotImplementedError, 'Subclasses must implement the children method'
103
+ end
104
+
105
+ def line_number
106
+ raise NotImplementedError, 'Subclasses must implement the line_number method'
107
+ end
108
+
109
+ def accept(visitor)
110
+ raise NotImplementedError, 'Subclasses must implement the accept method'
111
+ end
112
+
113
+ def ==(other)
114
+ if other.is_a?(self.class)
115
+ instance_variables.all? { |var| instance_variable_get(var) == other.instance_variable_get(var) }
116
+ elsif other.is_a?(String)
117
+ @value == other
118
+ else
119
+ false
120
+ end
121
+ end
122
+ end
123
+
124
+ class PairNode < SyntaxNode
125
+ attr_reader :type, :value, :colon
126
+
127
+ def initialize(type, value, colon: Colon.new(suffix: ' '))
128
+ super()
129
+ @type = type
130
+ @value = value
131
+ @colon = colon
132
+ end
133
+
134
+ def to_s
135
+ [@type, @colon, @value].join
136
+ end
137
+
138
+ def children
139
+ nil
140
+ end
141
+
142
+ def line_number
143
+ @type.line_number
144
+ end
145
+
146
+ def accept(visitor)
147
+ visitor.visit_pair(self)
148
+ end
149
+ end
150
+
151
+ class ListNode < SyntaxNode
152
+ attr_reader :type, :items, :left_bracket, :right_bracket, :colon, :leading_comma, :trailing_comma
153
+
154
+ def initialize(type, items:, left_bracket:, right_bracket:, colon: Colon.new(suffix: ' '), leading_comma: nil, trailing_comma: nil) # rubocop:disable Metrics/ParameterLists,Layout/LineLength
155
+ super()
156
+ @type = type
157
+ @items = items
158
+ @left_bracket = left_bracket
159
+ @right_bracket = right_bracket
160
+ @colon = colon
161
+ @leading_comma = leading_comma
162
+ @trailing_comma = trailing_comma
163
+ end
164
+
165
+ def to_s
166
+ [
167
+ @type,
168
+ @colon,
169
+ @left_bracket,
170
+ @leading_comma && @items.any? ? @leading_comma : '',
171
+ @items.map(&:to_s).join(','),
172
+ @trailing_comma && @items.any? ? @trailing_comma : '',
173
+ @right_bracket
174
+ ].join
175
+ end
176
+
177
+ def children
178
+ if @items.any? && @items.first.is_a?(PairNode)
179
+ @items
180
+ else
181
+ []
182
+ end
183
+ end
184
+
185
+ def line_number
186
+ @type.line_number
187
+ end
188
+
189
+ def accept(visitor)
190
+ visitor.visit_list(self)
191
+ end
192
+ end
193
+
194
+ class ContainerNode < SyntaxNode
195
+ attr_reader :items, :top_level
196
+
197
+ def initialize(items, top_level: false)
198
+ super()
199
+ @items = items
200
+ @top_level = top_level
201
+ validate_keys
202
+ end
203
+
204
+ def validate_keys
205
+ counter = @items.each_with_object(Hash.new(0)) { |item, hash| hash[item.type.value] += 1 }
206
+ counter.each do |key, count|
207
+ if !@top_level && count > 1 && !PLURAL_KEYS.include?(key)
208
+ raise KeyError, "Key \"#{key}\" already exists in tree and would overwrite the existing value."
209
+ end
210
+ end
211
+ end
212
+
213
+ def children
214
+ @items
215
+ end
216
+
217
+ def line_number
218
+ @items.first&.line_number
219
+ end
220
+
221
+ def accept(visitor)
222
+ visitor.visit_container(self)
223
+ end
224
+
225
+ def to_s
226
+ @items.map(&:to_s).join
227
+ end
228
+ end
229
+
230
+ class BlockNode < SyntaxNode
231
+ attr_reader :type, :left_brace, :right_brace, :colon, :name, :container
232
+
233
+ def initialize(type, left_brace: LeftCurlyBrace.new(suffix: "\n"), right_brace: RightCurlyBrace.new(prefix: "\n"), colon: Colon.new(suffix: ' '), name: nil, container: ContainerNode.new([])) # rubocop:disable Metrics/ParameterLists,Layout/LineLength
234
+ super()
235
+ @type = type
236
+ @left_brace = left_brace
237
+ @right_brace = right_brace
238
+ @colon = colon
239
+ @name = name
240
+ @container = container
241
+ end
242
+
243
+ def to_s
244
+ [
245
+ @type,
246
+ @colon,
247
+ @name || '',
248
+ @left_brace,
249
+ @container || '',
250
+ @right_brace
251
+ ].join
252
+ end
253
+
254
+ def children
255
+ @container.children
256
+ end
257
+
258
+ def line_number
259
+ @type.line_number
260
+ end
261
+
262
+ def accept(visitor)
263
+ visitor.visit_block(self)
264
+ end
265
+ end
266
+
267
+ class DocumentNode < SyntaxNode
268
+ attr_reader :container, :prefix, :suffix
269
+
270
+ def initialize(container, prefix: '', suffix: '')
271
+ super()
272
+ @container = container
273
+ @prefix = prefix
274
+ @suffix = suffix
275
+ end
276
+
277
+ def children
278
+ [@container]
279
+ end
280
+
281
+ def line_number
282
+ 1
283
+ end
284
+
285
+ def accept(visitor)
286
+ visitor.visit(self)
287
+ end
288
+
289
+ def to_s
290
+ [@prefix, @container, @suffix].join
291
+ end
292
+ end
293
+
294
+ class Visitor
295
+ def visit(document)
296
+ raise NotImplementedError, 'Subclasses must implement the visit method'
297
+ end
298
+
299
+ def visit_container(node)
300
+ raise NotImplementedError, 'Subclasses must implement the visit_container method'
301
+ end
302
+
303
+ def visit_block(node)
304
+ raise NotImplementedError, 'Subclasses must implement the visit_block method'
305
+ end
306
+
307
+ def visit_list(node)
308
+ raise NotImplementedError, 'Subclasses must implement the visit_list method'
309
+ end
310
+
311
+ def visit_pair(node)
312
+ raise NotImplementedError, 'Subclasses must implement the visit_pair method'
313
+ end
314
+
315
+ def visit_token(token)
316
+ raise NotImplementedError, 'Subclasses must implement the visit_token method'
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lkml
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ require_relative 'tree'
6
+
7
+ module Lkml
8
+ class BasicVisitor < Visitor
9
+ def _visit(node)
10
+ if node.is_a?(SyntaxToken)
11
+ nil
12
+ elsif node.respond_to?(:children) && node.children
13
+ node.children.each { |child| child.accept(self) }
14
+ end
15
+ end
16
+
17
+ def visit(document)
18
+ _visit(document)
19
+ end
20
+
21
+ def visit_container(node)
22
+ _visit(node)
23
+ end
24
+
25
+ def visit_block(node)
26
+ _visit(node)
27
+ end
28
+
29
+ def visit_list(node)
30
+ _visit(node)
31
+ end
32
+
33
+ def visit_pair(node)
34
+ _visit(node)
35
+ end
36
+
37
+ def visit_token(token)
38
+ _visit(token)
39
+ end
40
+ end
41
+
42
+ class LookMlVisitor < BasicVisitor
43
+ def _visit(node)
44
+ node.to_s
45
+ end
46
+ end
47
+
48
+ class BasicTransformer < Visitor
49
+ def _visit_items(node)
50
+ if node.respond_to?(:children) && node.children
51
+ new_children = node.children.map { |child| child.accept(self) }
52
+ node.class.new(items: new_children)
53
+ else
54
+ node
55
+ end
56
+ end
57
+
58
+ def _visit_container(node)
59
+ if node.respond_to?(:container) && node.container
60
+ new_child = node.container.accept(self)
61
+ node.class.new(container: new_child)
62
+ else
63
+ node
64
+ end
65
+ end
66
+
67
+ def visit(node)
68
+ _visit_container(node)
69
+ end
70
+
71
+ def visit_container(node)
72
+ _visit_items(node)
73
+ end
74
+
75
+ def visit_list(node)
76
+ _visit_items(node)
77
+ end
78
+
79
+ def visit_block(node)
80
+ _visit_container(node)
81
+ end
82
+
83
+ def visit_pair(node)
84
+ node
85
+ end
86
+
87
+ def visit_token(token)
88
+ token
89
+ end
90
+ end
91
+ end
data/lib/lkml.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lkml/keys'
4
+ require_relative 'lkml/lexer'
5
+ require_relative 'lkml/parser'
6
+ require_relative 'lkml/simple'
7
+ require_relative 'lkml/tokens'
8
+ require_relative 'lkml/tree'
9
+ require_relative 'lkml/visitors'
10
+
11
+ module Lkml
12
+ def self.parse(text)
13
+ lexer = Lexer.new(text)
14
+ tokens = lexer.scan
15
+ Parser.new(tokens).parse
16
+ end
17
+
18
+ def self.dump(obj, io = nil)
19
+ parser = DictParser.new
20
+ result = parser.parse(obj).to_s
21
+ return result unless io
22
+
23
+ io.write(result)
24
+ nil
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lkml
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sylvain Utard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-01-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: ''
14
+ email: sylvain.utard@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE.md
20
+ - README.md
21
+ - lib/lkml.rb
22
+ - lib/lkml/keys.rb
23
+ - lib/lkml/lexer.rb
24
+ - lib/lkml/parser.rb
25
+ - lib/lkml/simple.rb
26
+ - lib/lkml/tokens.rb
27
+ - lib/lkml/tree.rb
28
+ - lib/lkml/version.rb
29
+ - lib/lkml/visitors.rb
30
+ homepage: ''
31
+ licenses:
32
+ - MIT
33
+ metadata:
34
+ rubygems_mfa_required: 'true'
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '3.2'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubygems_version: 3.5.11
51
+ signing_key:
52
+ specification_version: 4
53
+ summary: LookML Ruby Parser
54
+ test_files: []