graphlyte 0.2.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5e01c43639c294e8ae4121522a6b0a5e498e69fd9e4f91e9f3e0a3a04753f826
4
- data.tar.gz: 985c22c79e96547fdb5da497b52b5c2e8d03621f38253847e57afb932e18c752
3
+ metadata.gz: 97c3b9e1d17580b4d94cf3ffaa9f7291d8ff10c9441a9c2270a378daad39f977
4
+ data.tar.gz: f3586e4ea3a92e0ac462b92a17115a81a8c64e6643c296861072a23543ae2b92
5
5
  SHA512:
6
- metadata.gz: 54d9dc438641a2f9f800eff0ade575f893472bbf96faba57e9d69171efbae8a8bd8caf50bdaeb4d7e71aadc37161c61e55c2385a1da98f04d0b687b20b6d654e
7
- data.tar.gz: 671f0580825f19ce0f010926a40a4cdece1ddb408dbda8ec10120c80ad0a2274680fae80085679a1e5f688fc5049e9ad1efae52b21c595ddfa166f2c759fc509
6
+ metadata.gz: a4dd27faa45576150659090362b94f30297faad8a1a1d5758480e63359bd74e92a30960e70fad8aa446c60e1540dd384458590d5f6cc00eb49c9aa09b44d6659
7
+ data.tar.gz: 1887884979b8f92cc8c1b145c782363b7be9e907fc3541cee5e103a4e901d07261f1a6f69aa0aafcce337efda5e5de4a3950fa4803f17a074f4cc6bbd0555ace
@@ -8,19 +8,25 @@ module Graphlyte
8
8
  end
9
9
 
10
10
  def <<(buildable)
11
- raise "Must pass a Fieldset or Fragment" unless [Fragment, Fieldset].include?(buildable.class)
11
+ raise "Must pass a Fieldset or Fragment" unless [Fragment, Fieldset, InlineFragment].include?(buildable.class)
12
12
 
13
13
  @fields.concat(buildable.fields) if buildable.class.eql? Fieldset
14
14
 
15
15
  # todo: handle fragments better, it's not a field
16
- @fields << buildable if buildable.class.eql? Fragment
16
+ @fields << buildable if [InlineFragment, Fragment].include? buildable.class
17
+ end
18
+
19
+ def remove(field_symbol)
20
+ @fields.reject! do |field|
21
+ field.name == field_symbol.to_s
22
+ end
17
23
  end
18
24
 
19
25
  def method_missing(method, fieldset_or_hargs=nil, hargs={}, &block)
20
26
  # todo: camel case method
21
27
 
22
28
  # hack for ruby bug in lower versions
23
- if [Fieldset, Fragment].include?(fieldset_or_hargs.class)
29
+ if [Fieldset, Fragment, InlineFragment].include?(fieldset_or_hargs.class)
24
30
  field = Field.new(method, fieldset_or_hargs, hargs)
25
31
  else
26
32
  field = Field.new(method, Fieldset.empty, fieldset_or_hargs)
@@ -0,0 +1,21 @@
1
+ require_relative 'arguments/set'
2
+
3
+ module Graphlyte
4
+ class Directive
5
+ attr_reader :name, :inputs
6
+
7
+ def initialize(name, **hargs)
8
+ @name = name
9
+ @inputs = Arguments::Set.new(hargs)
10
+ end
11
+
12
+ def inflate(indent, string, field: nil)
13
+ # add directive after fieldname?
14
+ string += ' ' * indent
15
+ string += "#{field} " if field
16
+ string += "@#{name}"
17
+ string += @inputs.to_s unless @inputs.to_s.empty?
18
+ string
19
+ end
20
+ end
21
+ end
@@ -1,25 +1,39 @@
1
1
  require_relative "./arguments/set"
2
+ require_relative 'directive'
2
3
  require_relative "./refinements/string_refinement"
3
4
  module Graphlyte
4
5
  class Field
5
6
  using Refinements::StringRefinement
6
7
 
7
- attr_reader :name, :fieldset, :inputs, :alias
8
+ attr_reader :name, :fieldset, :inputs, :directive
8
9
 
9
- def initialize(name, fieldset, hargs, inputs: Arguments::Set.new(hargs))
10
+ def initialize(name, fieldset, hargs, directive: nil, inputs: Arguments::Set.new(hargs))
10
11
  @name = name.to_s.to_camel_case
11
12
  @fieldset = fieldset
12
13
  @inputs = inputs
13
14
  @alias = nil
15
+ @directive = directive
14
16
  end
15
17
 
16
18
  def atomic?
17
19
  fieldset.empty?
18
- end
20
+ end
19
21
 
20
22
  def alias(name, &block)
21
23
  @alias = name
22
- fieldset.builder.>.instance_eval(&block) if block
24
+ if block
25
+ fieldset.builder.>.instance_eval(&block)
26
+ else
27
+ self
28
+ end
29
+ end
30
+
31
+ def include(**hargs, &block)
32
+ make_directive('include', **hargs, &block)
33
+ end
34
+
35
+ def skip(**hargs, &block)
36
+ make_directive('skip', **hargs, &block)
23
37
  end
24
38
 
25
39
  def to_s(indent=0)
@@ -27,12 +41,25 @@ module Graphlyte
27
41
  actual_indent = ("\s" * indent) * 2
28
42
  if @alias
29
43
  str += "#{actual_indent}#{@alias}: #{name}"
30
- str += inputs.to_s.empty? ? "()" : inputs.to_s
44
+ str += inputs.to_s.empty? ? "()" : inputs.to_s
45
+ elsif @directive
46
+ str = @directive.inflate(indent * 2, str, field: name)
31
47
  else
32
48
  str += "#{actual_indent}#{name}#{inputs.to_s}"
33
49
  end
34
50
  str += " {\n#{fieldset.to_s(indent + 1)}\n#{actual_indent}}" unless atomic?
35
51
  str
36
52
  end
53
+
54
+ private
55
+
56
+ def method_missing(symbol, **hargs, &block)
57
+ make_directive(symbol.to_s, **hargs, &block)
58
+ end
59
+
60
+ def make_directive(name, **hargs, &block)
61
+ @directive = Directive.new(name, **hargs)
62
+ fieldset.builder.>.instance_eval(&block) if block
63
+ end
37
64
  end
38
65
  end
@@ -0,0 +1,29 @@
1
+ require_relative "./fieldset"
2
+
3
+ module Graphlyte
4
+ class InlineFragment < Fieldset
5
+ attr_reader :directive
6
+
7
+ def self.from_directive(directive, **hargs)
8
+ new(nil, directive: directive, **hargs)
9
+ end
10
+
11
+ def initialize(model = nil, directive: nil, **hargs)
12
+ @directive = directive
13
+ super(model, **hargs)
14
+ end
15
+
16
+ def to_s(indent=0)
17
+ actual_indent = ("\s" * indent) * 2
18
+ string = '... '
19
+ string += "on #{model_name}" if model_name
20
+ inflate_indent = model_name ? 1 : 0
21
+ string = directive.inflate(inflate_indent, string) if directive
22
+ string += " {\n"
23
+ string += super(indent + 1)
24
+ string += "\n#{actual_indent}}"
25
+
26
+ "#{actual_indent}#{string}"
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,35 @@
1
1
  require_relative "./refinements/string_refinement"
2
2
  require "json"
3
3
  module Graphlyte
4
+ class Selector
5
+ def initialize(selector)
6
+ @selector_tokens = selector.split('.')
7
+ end
8
+
9
+ def modify(fields, selector_tokens = @selector_tokens, &block)
10
+ token = selector_tokens.shift
11
+
12
+ if token == '*'
13
+ fields.each do |field|
14
+ modify(field.fieldset.fields, [token], &block)
15
+ field.fieldset.builder.instance_eval(&block) unless field.fieldset.fields.empty?
16
+ end
17
+ else
18
+ needle = fields.find do |field|
19
+ field.name == token
20
+ end
21
+
22
+ raise "#{token} not found in query" unless needle
23
+
24
+ if selector_tokens.size.zero?
25
+ needle.fieldset.builder.instance_eval(&block)
26
+ else
27
+ modify(needle.fieldset.fields, selector_tokens, &block)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
4
33
  class Query < Fieldset
5
34
  using Refinements::StringRefinement
6
35
  attr_reader :name, :type
@@ -11,6 +40,10 @@ module Graphlyte
11
40
  super(**hargs)
12
41
  end
13
42
 
43
+ def at(selector, &block)
44
+ Selector.new(selector).modify(fields, &block)
45
+ end
46
+
14
47
  def placeholders
15
48
  flatten_variables(builder.>>).map do |value|
16
49
  unless value.formal?
@@ -83,8 +116,9 @@ module Graphlyte
83
116
 
84
117
  def flatten_variables(fields, variables=[])
85
118
  fields.each do |field|
86
- variables.concat field.inputs.extract_variables unless field.class.eql?(Fragment)
87
- if field.class.eql?(Fragment)
119
+ variables.concat field.inputs.extract_variables unless [InlineFragment, Fragment].include? field.class
120
+ variables.concat field.directive.inputs.extract_variables if field.class == InlineFragment && field.directive
121
+ if [InlineFragment, Fragment].include? field.class
88
122
  flatten_variables(field.fields, variables)
89
123
  else
90
124
  flatten_variables(field.fieldset.fields, variables)
@@ -95,6 +129,7 @@ module Graphlyte
95
129
 
96
130
  def flatten(fields, new_fields = {})
97
131
  fields.each do |field|
132
+ next if field.class == InlineFragment
98
133
  if field.class.eql?(Fragment)
99
134
  new_fields[field.fragment] = field
100
135
  unless field.empty?
@@ -10,11 +10,11 @@ module Graphlyte
10
10
  def to_camel_case
11
11
  start_of_string = match(/(^_+)/)&.[](0)
12
12
  end_of_string = match(/(_+$)/)&.[](0)
13
-
13
+
14
14
  middle = split("_").reject(&:empty?).inject([]) do |memo, str|
15
15
  memo << (memo.empty? ? str : str.capitalize)
16
16
  end.join("")
17
-
17
+
18
18
  "#{start_of_string}#{middle}#{end_of_string}"
19
19
  end
20
20
  end
@@ -13,18 +13,31 @@ module Graphlyte
13
13
  fields
14
14
  end
15
15
 
16
+ def skip_fieldset
17
+ expect(:FIELDSET)
18
+ parse_fields
19
+ need(:END_FIELDSET)
20
+ end
21
+
16
22
  def parse_field
17
23
  alias_field = expect(:ALIAS)
18
24
  if token = expect(:FRAGMENT_REF)
19
25
  raise "Can't find fragment #{token[0][1]}" unless fragments_dictionary[token[0][1]]
20
26
  fragments_dictionary[token[0][1]]
21
- elsif field = expect(:FIELD_NAME)
27
+ elsif expect(:INLINE_FRAGMENT)
28
+ field = parse_inline_fragment
29
+ elsif expect(:FIELDSET)
30
+
31
+ elsif (field = expect(:FIELD_NAME))
22
32
  args = parse_args
23
- if fieldset = parse_fieldset
24
- need(:END_FIELD)
25
- field = Field.new(field[0][1], fieldset, args)
33
+ directive = parse_directive
34
+
35
+ if builder = parse_fieldset_into_builder
36
+ need(:END_FIELDSET)
37
+ fieldset = Fieldset.new(builder: builder)
38
+ field = Field.new(field[0][1], fieldset, args, directive: directive)
26
39
  else
27
- field = Field.new(field[0][1], Fieldset.empty, args)
40
+ field = Field.new(field[0][1], Fieldset.empty, args, directive: directive)
28
41
  end
29
42
 
30
43
  if alias_field
@@ -35,10 +48,29 @@ module Graphlyte
35
48
  end
36
49
  end
37
50
 
38
- def parse_fieldset
39
- if expect(:START_FIELD)
51
+ def parse_inline_fragment
52
+ model_name = expect(:MODEL_NAME)&.dig(0, 1)
53
+ directive = parse_directive
54
+ inputs = directive ? (parse_args || {}) : {}
55
+ fields = expect(:FIELDSET) ? parse_fields : []
56
+ need(:END_FIELDSET)
57
+
58
+ InlineFragment.new(model_name, directive: directive, builder: Builder.new(fields), **inputs)
59
+ end
60
+
61
+ def parse_directive
62
+ if token = expect(:DIRECTIVE)
63
+ inputs = parse_args || {}
64
+
65
+ Directive.new(token[0][1], **inputs)
66
+ end
67
+ end
68
+
69
+ def parse_fieldset_into_builder
70
+ fields = []
71
+ if expect(:FIELDSET)
40
72
  fields = parse_fields
41
- Fieldset.new(builder: Builder.new(fields))
73
+ Builder.new(fields)
42
74
  end
43
75
  end
44
76
 
@@ -51,7 +83,7 @@ module Graphlyte
51
83
  end
52
84
 
53
85
  def parse_default
54
- if expect(:START_DEFAULT_VALUE)
86
+ if expect(:DEFAULT_VALUE)
55
87
  value = parse_value
56
88
  need(:END_DEFAULT_VALUE)
57
89
  value
@@ -89,9 +121,9 @@ module Graphlyte
89
121
  @special_args[ref]
90
122
  elsif token = expect(:SPECIAL_ARG_VAL)
91
123
  token[0][1]
92
- elsif token = expect(:ARG_HASH_START)
124
+ elsif token = expect(:ARG_HASH)
93
125
  parse_arg_hash
94
- elsif expect(:ARG_ARRAY_START)
126
+ elsif expect(:ARG_ARRAY)
95
127
  parse_arg_array
96
128
  end
97
129
  end
@@ -134,6 +166,10 @@ module Graphlyte
134
166
  end
135
167
  end
136
168
 
169
+ def tokens?
170
+ !tokens[position].nil?
171
+ end
172
+
137
173
  def need(*required_tokens)
138
174
  upcoming = tokens[position, required_tokens.size]
139
175
  expect(*required_tokens) or raise "Unexpected tokens. Expected #{required_tokens.inspect} but got #{upcoming.inspect}"
@@ -154,7 +190,7 @@ module Graphlyte
154
190
  if current_ref
155
191
  exists = sorted.any? do |frags|
156
192
  frags.find do |el|
157
- el[0] == :START_FRAGMENT && el[1] == current_ref[1]
193
+ el[0] == :FRAGMENT && el[1] == current_ref[1]
158
194
  end
159
195
  end
160
196
  if exists
@@ -172,11 +208,11 @@ module Graphlyte
172
208
 
173
209
  def take_fragments
174
210
  aggregate = @tokens.inject({taking: false, idx: 0, fragments: []}) do |memo, token_arr|
175
- if token_arr[0] == :END_FRAGMENT
211
+ if token_arr[0] == :END_FIELDSET
176
212
  memo[:fragments][memo[:idx]] << token_arr
177
213
  memo[:taking] = false
178
214
  memo[:idx] += 1
179
- elsif token_arr[0] === :START_FRAGMENT
215
+ elsif token_arr[0] === :FRAGMENT
180
216
  memo[:fragments][memo[:idx]] = [token_arr]
181
217
  memo[:taking] = true
182
218
  elsif memo[:taking]
@@ -186,6 +222,35 @@ module Graphlyte
186
222
  end
187
223
  aggregate[:fragments]
188
224
  end
225
+
226
+ def fetch_fragments(tokens = @tokens.dup, fragment_tokens = [], memo = { active: false, starts: 0, ends: 0, idx: 0 })
227
+ token_arr = tokens.shift
228
+ return fragment_tokens if token_arr.nil?
229
+
230
+
231
+ if memo[:active] == true
232
+ fragment_tokens[memo[:idx]] << token_arr
233
+ end
234
+
235
+ if token_arr[0] == :END_FIELDSET && memo[:active] == true
236
+ memo[:ends] += 1
237
+ fragment_tokens[memo[:idx]] << token_arr if memo[:starts] == memo[:ends]
238
+
239
+ memo[:active] = false
240
+ memo[:ends] = 0
241
+ memo[:starts] = 0
242
+ memo[:idx] += 1
243
+ elsif token_arr[0] == :FRAGMENT
244
+ memo[:active] = true
245
+ memo[:starts] += 1
246
+ fragment_tokens[memo[:idx]] = [token_arr]
247
+ elsif token_arr[0] == :FIELDSET && memo[:active] == true
248
+ memo[:starts] += 1
249
+ end
250
+
251
+
252
+ fetch_fragments(tokens, fragment_tokens, memo)
253
+ end
189
254
  end
190
255
 
191
256
  class FragmentParser
@@ -205,12 +270,16 @@ module Graphlyte
205
270
  end
206
271
 
207
272
  def parse_fragment
208
- if token = expect(:START_FRAGMENT)
273
+ if token = expect(:FRAGMENT)
209
274
  parse_args
210
- builder = Builder.new parse_fields
211
- fragment = Fragment.new(token[0][1], token[0][2], builder: builder)
275
+ if builder = parse_fieldset_into_builder
276
+ fragment = Fragment.new(token[0][1], token[0][2], builder: builder)
277
+ need(:END_FIELDSET) if tokens?
278
+ elsif fields = parse_fields
279
+ builder = Builder.new(fields)
280
+ fragment = Fragment.new(token[0][1], token[0][2], builder: builder)
281
+ end
212
282
  @fragments_dictionary[token[0][1]] = fragment
213
- need(:END_FRAGMENT)
214
283
  end
215
284
  end
216
285
  end
@@ -227,41 +296,42 @@ module Graphlyte
227
296
 
228
297
  def initialize(tokens)
229
298
  @tokens = tokens
230
- @fragment_tokens = sort_fragments([], take_fragments)
299
+
300
+ @fragment_tokens = sort_fragments([], fetch_fragments)
231
301
  @fragments_dictionary = {}
232
302
  @fragments_dictionary = @fragment_tokens.any? ? FragmentParser.new(@fragment_tokens).parse_fragments : {}
233
303
  @position = 0
234
304
  end
235
305
 
236
306
  def parse
237
- if token = expect(:START_QUERY)
238
- parse_query(token[0][1])
239
- elsif token = expect(:START_MUTATION)
240
- parse_mutation(token[0][1])
307
+ if token = expect(:EXPRESSION)
308
+ parse_expression(token[0][1], token[0][2])
309
+ elsif expect(:FRAGMENT)
310
+ skip_fragments
311
+ parse
241
312
  else
242
313
  raise "INVALID"
243
314
  end
244
315
  end
245
316
 
246
- def parse_query(name)
247
- parse_args
248
- builder = Builder.new parse_fields
249
- query = Query.new(name, :query, builder: builder)
250
- need(:END_QUERY)
251
- query
317
+ def skip_fragments
318
+ skip_fieldset
252
319
  end
253
320
 
254
- def parse_mutation(name)
255
- builder = Builder.new parse_fields
256
- mutation = Query.new(name, :mutation, builder: builder)
257
- need(:END_MUTATION)
258
- mutation
321
+ def parse_expression(type, name)
322
+ parse_args
323
+ fields = []
324
+ builder = parse_fieldset_into_builder
325
+ need(:END_FIELDSET)
326
+ query = Query.new(name, type.to_sym, builder: builder)
327
+ query
259
328
  end
260
329
  end
261
330
 
331
+ class LexerError < StandardError; end
332
+
262
333
  class Lexer
263
334
  attr_reader :stack, :scanner
264
-
265
335
  def initialize(gql, scanner: StringScanner.new(gql))
266
336
  @original_string = gql
267
337
  @scanner = scanner
@@ -269,211 +339,290 @@ module Graphlyte
269
339
  end
270
340
 
271
341
  SPECIAL_ARG_REGEX = /^\s*(?:(?<![\"\{]))([\w\!\[\]]+)(?:(?![\"\}]))/
272
- SIMPLE_EXPRESSION = /(query|mutation|fragment)\s*\w+\s*on\w*.*\{\s*\n*[.|\w\s]*\}/
273
- START_MAP = {
274
- 'query' => :START_QUERY,
275
- 'mutation' => :START_MUTATION,
276
- 'fragment' => :START_FRAGMENT
277
- }
278
342
 
279
343
  def tokenize
280
344
  until scanner.eos?
281
- case state
282
- when :default
283
- if scanner.scan /^query (\w+)/
284
- @tokens << [:START_QUERY, scanner[1]]
285
- push_state :query
286
- elsif scanner.scan /^mutation (\w+)/
287
- @tokens << [:START_MUTATION, scanner[1]]
288
- push_state :mutation
289
- elsif scanner.scan /\s*fragment\s*(\w+)\s*on\s*(\w+)/
290
- @tokens << [:START_FRAGMENT, scanner[1], scanner[2]]
291
- push_state :fragment
292
- elsif scanner.scan /\s*{\s*/
293
- @tokens << [:START_FIELD]
294
- push_state :field
295
- elsif scanner.scan /\s*}\s*/
296
- @tokens << [:END_EXPRESSION_SHOULDNT_GET_THIS]
297
- else
298
- advance
299
- end
300
- when :fragment
301
- if scanner.scan /\s*\}\s*/
302
- @tokens << [:END_FRAGMENT]
303
- pop_state
304
- pop_context
305
- elsif scanner.check /^\s*\{\s*/
306
- if get_context == :field
307
- push_state :field
308
- push_context :field
309
- else
310
- scanner.scan /^\s*\{\s*/
311
- push_context :field
312
- end
313
- else
314
- handle_field
315
- end
316
- when :mutation
317
- if scanner.scan /\}/
318
- @tokens << [:END_MUTATION]
319
- pop_state
320
- pop_context
321
- elsif scanner.check /^\s*\{\s*$/
322
- if get_context == :field
323
- push_state :field
324
- else
325
- scanner.scan /^\s*\{\s*$/
326
- push_context :field
327
- end
328
- else
329
- handle_field
330
- end
331
- when :query
332
- if scanner.scan /\s*\}\s*/
333
- @tokens << [:END_QUERY]
334
- pop_state
335
- pop_context
336
- elsif scanner.check /^\s*\{\s*/
337
- if get_context == :field
338
- push_state :field
339
- push_context :field
340
- else
341
- scanner.scan /^\s*\{\s*/
342
- push_context :field
343
- end
344
- else
345
- handle_field
346
- end
347
- when :field
348
- if scanner.check /\s*\}\s*/
349
- if get_context == :field
350
- scanner.scan /\s*\}\s*/
351
- @tokens << [:END_FIELD]
352
- pop_state
353
- else
354
- pop_state
355
- end
345
+ tokenize_objects
346
+ end
347
+
348
+ @tokens
349
+ end
350
+
351
+ def tokenize_objects
352
+ case state
353
+ when :default # the stack is empty, can only process top level fragments or expressions
354
+ if scanner.scan %r{\s*fragment\s*(\w+)\s*on\s*(\w+)}
355
+ @tokens << [:FRAGMENT, scanner[1], scanner[2]]
356
+ push_context :fragments
357
+ # check for a fieldset
358
+ if scanner.check %r[\s*{]
359
+ tokenize_fieldset
356
360
  else
357
- handle_field
361
+ scanner.scan /\(/
362
+ @tokens << [:START_ARGS]
363
+ push_state :arguments
358
364
  end
359
- when :hash_arguments
360
- handle_hash_arguments
361
- when :array_arguments
362
- handle_array_arguments
363
- when :arguments
364
- if scanner.scan /\s*\)\s*/
365
- @tokens << [:END_ARGS]
366
- pop_state
367
- elsif scanner.scan /\=/
368
- @tokens << [:START_DEFAULT_VALUE]
369
- push_state :argument_defaults
370
- elsif scanner.scan /,/
371
- #
365
+ elsif scanner.check /\{/
366
+ push_context :fieldset
367
+
368
+ tokenize_fieldset
369
+ elsif scanner.scan %r{^(\w+) (\w+)}
370
+ @tokens << [:EXPRESSION, scanner[1], scanner[2]]
371
+ push_context :expression
372
+ # check for a fieldset
373
+ if scanner.check %r[\s*{]
374
+ tokenize_fieldset
372
375
  else
373
- handle_shared_arguments
376
+ scanner.scan /\(/
377
+ @tokens << [:START_ARGS]
378
+ push_state :arguments
374
379
  end
375
- when :argument_defaults
376
- if @stack.reverse.take(2).eql?([:argument_defaults, :argument_defaults])
377
- @tokens << [:END_DEFAULT_VALUE]
378
- pop_state
379
- pop_state
380
- else
381
- push_state :argument_defaults
382
- handle_shared_arguments
380
+ elsif scanner.check /\s*\}/
381
+ if get_context == :fragments
382
+ end_fragment
383
+ elsif get_context == :expression
384
+ end_expression
383
385
  end
384
- when :special_args
385
- handle_special_args
386
+ else
387
+ advance
386
388
  end
389
+ when :fieldset
390
+ tokenize_fields
391
+ when :arguments
392
+ tokenize_arguments
393
+ when :argument_defaults
394
+ tokenize_shared_arguments
395
+ when :hash_arguments
396
+ tokenize_hash_arguments
397
+ when :array_arguments
398
+ tokenize_array_arguments
399
+ when :special_args
400
+ tokenize_special_arguments
401
+ when :inline_fragment
402
+ tokenize_inline_fragment
387
403
  end
388
- @tokens
389
404
  end
390
405
 
391
- private
406
+ def check_for_last(regex = /\s*\}/)
407
+ scanner.check regex
408
+ end
392
409
 
393
- def handle_field
394
- if scanner.scan /\s*\{\s*/
395
- @context = :field
396
- @tokens << [:START_FIELD]
397
- push_state :field
398
- elsif scanner.check /\.{3}(\w+)\s*\}/
399
- scanner.scan /\.{3}(\w+)/
400
- @tokens << [:FRAGMENT_REF, scanner[1]]
401
- pop_context
402
- # we need to pop state if we are nested in a field, and not in the query context
403
- pop_state if get_context == :field
404
- elsif scanner.scan /\.{3}(\w+)/
405
- @tokens << [:FRAGMENT_REF, scanner[1]]
406
- elsif scanner.scan /\s*(\w+):\s*/
407
- @tokens << [:ALIAS, scanner[1]]
408
- elsif scanner.check /\s*(\w+)\s*\}/
409
- scanner.scan /\s*(\w+)\s*/
410
- @tokens << [:FIELD_NAME, scanner[1]]
411
- pop_context
412
- # we need to pop state if we are nested in a field, and not in the query context
413
- pop_state if get_context == :field
414
- elsif scanner.scan /\s*(\w+)\s*/
415
- @tokens << [:FIELD_NAME, scanner[1]]
416
- elsif scanner.scan /^\s*\(/
417
- @tokens << [:START_ARGS]
418
- push_state :arguments
410
+ def check_for_final
411
+ scanner.check /\s*\}(?!\s*\})/
412
+ end
413
+
414
+ def check_for_not_last
415
+ scanner.check /\s*\}(?=\s*\})/
416
+ end
417
+
418
+ def tokenize_inline_fragment
419
+ if scanner.scan /on (\w+)/
420
+ @tokens << [:MODEL_NAME, scanner[1]]
421
+
422
+ pop_state
423
+ elsif scanner.scan /@(\w+)/
424
+ @tokens << [:DIRECTIVE, scanner[1]]
425
+
426
+ pop_state
419
427
  else
428
+ # throw an error here?
420
429
  advance
421
430
  end
422
431
  end
423
432
 
424
- def handle_shared_arguments
433
+ def end_fieldset
434
+ scanner.scan /\s*\}/
435
+ @tokens << [:END_FIELDSET]
436
+ pop_state
437
+ end
438
+
439
+ def end_arguments
440
+ scanner.scan /\s*\)/
441
+ @tokens << [:END_ARGS]
442
+ pop_state
443
+ end
444
+
445
+ def end_fragment
446
+ scanner.scan /\s*\}/
447
+ @tokens << [:END_FRAGMENT]
448
+ pop_state
449
+ pop_context
450
+ end
451
+
452
+ def end_expression
453
+ scanner.scan /\s*\}/
454
+ @tokens << [:END_EXPRESSION]
455
+ pop_state
456
+ pop_context
457
+ end
458
+
459
+ # to tired to figure out why this is right now
460
+ def tokenize_argument_defaults
461
+ if scanner.scan /\)/
462
+ @tokens << [:END_DEFAULT_VALUE]
463
+ pop_state
464
+ else
465
+ tokenize_shared_arguments
466
+ end
467
+ end
468
+
469
+ def tokenize_special_arguments
470
+ if scanner.check SPECIAL_ARG_REGEX
471
+ scanner.scan SPECIAL_ARG_REGEX
472
+
473
+ @tokens << [:SPECIAL_ARG_VAL, scanner[1]]
474
+
475
+ pop_state
476
+
477
+ end_arguments if check_for_last(/\s*\)/)
478
+ else
479
+ # revisit this.. should we throw an error here?
480
+ pop_state
481
+ raise LexerError, "why can't we parse #{scanner.peek(5)}"
482
+ end
483
+ end
484
+
485
+ def tokenize_array_arguments
486
+ if scanner.scan /\]/
487
+ @tokens << [:ARG_ARRAY_END]
488
+
489
+ pop_state
490
+ # if check_for_last(')')
491
+ # pop_state
492
+ # end
493
+ else
494
+ tokenize_shared_arguments
495
+ end
496
+ end
497
+
498
+ def tokenize_hash_arguments
499
+ if scanner.scan /\}/
500
+ @tokens << [:ARG_HASH_END]
501
+
502
+ pop_state
503
+ # if this is the last argument in the list, maybe get back to the field scope?
504
+ # if check_for_last(')')
505
+ # pop_state
506
+ # end
507
+ else
508
+ tokenize_shared_arguments
509
+ end
510
+ end
511
+
512
+ def tokenize_arguments
513
+ # pop argument state if arguments are finished
514
+ if scanner.scan %r{\)}
515
+ @tokens << [:END_ARGS]
516
+
517
+ pop_state
518
+ # something(argument: $argument = true)
519
+ # ^
520
+ elsif scanner.scan %r{=}
521
+ @tokens << [:DEFAULT_VALUE]
522
+
523
+ push_state :argument_defaults
524
+ # noop, should expect this, but not important
525
+ elsif scanner.scan %r{,}
526
+ nil
527
+ else
528
+ tokenize_shared_arguments
529
+ end
530
+ end
531
+
532
+ def tokenize_shared_arguments
425
533
  if scanner.scan /^(\w+):/
426
534
  @tokens << [:ARG_KEY, scanner[1]]
427
- elsif scanner.scan /^\s*\{\s*?/
428
- @tokens << [:ARG_HASH_START]
535
+ elsif scanner.scan %r[{]
536
+ @tokens << [:ARG_HASH]
537
+
429
538
  push_state :hash_arguments
430
- elsif scanner.scan /\s*\[\s*/
431
- @tokens << [:ARG_ARRAY_START]
539
+ elsif scanner.scan /\[/
540
+ @tokens << [:ARG_ARRAY]
541
+
432
542
  push_state :array_arguments
433
- elsif scanner.scan /\s?\"([\w\s]+)\"/
543
+ elsif scanner.scan %r{"(.*?)"}
434
544
  @tokens << [:ARG_STRING_VALUE, scanner[1]]
435
- elsif scanner.scan /\s?(\d+\.\d+)/
545
+
546
+ end_arguments if check_for_last(/\s*\)/)
547
+ elsif scanner.scan /(\d+\.\d+)/
436
548
  @tokens << [:ARG_FLOAT_VALUE, scanner[1].to_f]
437
- elsif scanner.scan /\s?(\d+)/
549
+
550
+ end_arguments if check_for_last(/\s*\)/)
551
+ elsif scanner.scan /(\d+)/
438
552
  @tokens << [:ARG_NUM_VALUE, scanner[1].to_i]
439
- elsif scanner.scan /\s?(true|false)\s?/
440
- bool = scanner[1] == "true"
441
- @tokens << [:ARG_BOOL_VALUE, bool]
553
+
554
+ end_arguments if check_for_last(/\s*\)/)
555
+ elsif scanner.scan /(true|false)/
556
+ @tokens << [:ARG_BOOL_VALUE, (scanner[1] == 'true')]
557
+
558
+ end_arguments if check_for_last(/\s*\)/)
442
559
  elsif scanner.scan /\$(\w+):/
443
560
  @tokens << [:SPECIAL_ARG_KEY, scanner[1]]
561
+
444
562
  push_state :special_args
445
563
  elsif scanner.scan /\$(\w+)/
446
564
  @tokens << [:SPECIAL_ARG_REF, scanner[1]]
447
- else
448
- advance
449
- end
450
- end
451
565
 
452
- def handle_special_args
453
- if scanner.check SPECIAL_ARG_REGEX
454
- scanner.scan SPECIAL_ARG_REGEX
455
- @tokens << [:SPECIAL_ARG_VAL, scanner[1]]
566
+ end_arguments if check_for_last(/\s*\)/)
567
+ elsif scanner.scan /,/
568
+ # no-op
569
+ elsif check_for_last(/\s*\)/)
570
+ @tokens << [:END_DEFAULT_VALUE] if state == :argument_defaults
571
+ end_arguments
456
572
  pop_state
457
573
  else
458
- pop_state
574
+ advance
459
575
  end
460
576
  end
461
577
 
462
- def handle_hash_arguments
463
- if scanner.scan /\}/
464
- @tokens << [:ARG_HASH_END]
465
- pop_state
578
+ def tokenize_fields
579
+ if scanner.check %r[{]
580
+ tokenize_fieldset
581
+ # ... on Model - or - ... @directive
582
+ elsif scanner.scan %r{\.{3}\s}
583
+ @tokens << [:INLINE_FRAGMENT]
584
+ push_state :inline_fragment
585
+ # @directive
586
+ elsif scanner.scan %r{@(\w+)}
587
+ @tokens << [:DIRECTIVE, scanner[1]]
588
+ # ...fragmentReference (check for last since it is a field literal)
589
+ elsif scanner.scan /\.{3}(\w+)/
590
+ @tokens << [:FRAGMENT_REF, scanner[1]]
591
+
592
+ end_fieldset while check_for_last && state == :fieldset
593
+ # alias:
594
+ elsif scanner.scan %r{(\w+):}
595
+ @tokens << [:ALIAS, scanner[1]]
596
+ # fieldLiteral
597
+ elsif scanner.scan %r{(\w+)}
598
+ @tokens << [:FIELD_NAME, scanner[1]]
599
+
600
+ end_fieldset while check_for_last && state == :fieldset
601
+ # (arguments: true)
602
+ elsif scanner.scan /^\s*\(/
603
+ @tokens << [:START_ARGS]
604
+
605
+ push_state :arguments
606
+ elsif check_for_final
607
+ if get_context == :fragments
608
+ end_fragment
609
+ elsif get_context == :expression
610
+ end_expression
611
+ else
612
+ advance
613
+ end
466
614
  else
467
- handle_shared_arguments
615
+ advance
468
616
  end
469
617
  end
470
618
 
471
- def handle_array_arguments
472
- if scanner.scan /\s*\]\s*/
473
- @tokens << [:ARG_ARRAY_END]
474
- pop_state
619
+ def tokenize_fieldset
620
+ if scanner.scan %r[\s*{]
621
+ @tokens << [:FIELDSET]
622
+
623
+ push_state :fieldset
475
624
  else
476
- handle_shared_arguments
625
+ raise LexerError, "Expecting `{` got `#{scanner.peek(3)}`"
477
626
  end
478
627
  end
479
628
 
@@ -498,6 +647,10 @@ module Graphlyte
498
647
  end
499
648
 
500
649
  def advance
650
+ unless scanner.check /\s/
651
+ raise LexerError, "Unexpected Char: '#{scanner.peek(3)}'"
652
+ end
653
+
501
654
  scanner.pos = scanner.pos + 1
502
655
  end
503
656
 
data/lib/graphlyte.rb CHANGED
@@ -2,13 +2,14 @@ require 'json'
2
2
  require_relative "./graphlyte/fieldset"
3
3
  require_relative "./graphlyte/query"
4
4
  require_relative "./graphlyte/fragment"
5
+ require_relative 'graphlyte/inline_fragment'
5
6
  require_relative "./graphlyte/schema_query"
6
7
  require_relative "./graphlyte/types"
7
8
  require_relative "./graphlyte/schema/parser"
8
9
 
9
10
  module Graphlyte
10
11
  extend SchemaQuery
11
-
12
+
12
13
  TYPES = Types.new
13
14
 
14
15
  def self.parse(gql)
@@ -23,6 +24,18 @@ module Graphlyte
23
24
  Query.new(name, :mutation, builder: build(&block))
24
25
  end
25
26
 
27
+ def self.custom(name, type, &block)
28
+ Query.new(name, type.to_sym, builder: build(&block))
29
+ end
30
+
31
+ def self.inline_fragment(model_name, &block)
32
+ InlineFragment.new(model_name, builder: build(&block))
33
+ end
34
+
35
+ def self.inline_directive(directive, **hargs, &block)
36
+ InlineFragment.from_directive(directive, **hargs, builder: build(&block) )
37
+ end
38
+
26
39
  def self.fragment(fragment_name, model_name, &block)
27
40
  Fragment.new(fragment_name, model_name, builder: build(&block))
28
41
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphlyte
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Gregory
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-24 00:00:00.000000000 Z
11
+ date: 2022-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -48,9 +48,11 @@ files:
48
48
  - lib/graphlyte/arguments/set.rb
49
49
  - lib/graphlyte/arguments/value.rb
50
50
  - lib/graphlyte/builder.rb
51
+ - lib/graphlyte/directive.rb
51
52
  - lib/graphlyte/field.rb
52
53
  - lib/graphlyte/fieldset.rb
53
54
  - lib/graphlyte/fragment.rb
55
+ - lib/graphlyte/inline_fragment.rb
54
56
  - lib/graphlyte/query.rb
55
57
  - lib/graphlyte/refinements/string_refinement.rb
56
58
  - lib/graphlyte/schema/parser.rb