graphlyte 0.1.4 → 0.2.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: ae8c18b5676a04d0c309c37bccf668f6bdc8f61553cfda7e94f17491bbb5c995
4
- data.tar.gz: d46d31fe92d944c0e5c56d94894bf2ca8c4f5bfce33d4815c99969e3269546ac
3
+ metadata.gz: a541bce9d787c84cbb85652dfaebced77765a79ef1256f016a723d574e389313
4
+ data.tar.gz: 2b1e62293e73d388bb539fcec90f7f6f1269d843db8cd0410176ff4d90426a82
5
5
  SHA512:
6
- metadata.gz: cb092554a67b5e1805e112b9b2bf09448ed054dbae3b047cd722b84faf9b963dcd46b8bfd71ee5090f966309bfa4f006fbd7e9e7aa08dd81a1234fa400e78674
7
- data.tar.gz: 7044177b3c66375410a1d52eed7e089bf3ba68b5cf49c0702b6cfd5d88314670383eddc1aa501a567cf166ae275fad165e6356fb3506842359a64bc2c172090f
6
+ metadata.gz: 2dc601050babce04c958e6bb6f3bfcf84d55962f8164ffbb1452e69105795961e570c247f8af303baee982173c77eebd049027381a86573780408516a059e1ee
7
+ data.tar.gz: 05d5f0acce7bb921cd721125eb63728c347f136eaf6a552385d53eb537578a341707d454be924e82a9b35d13acf9af77874d5c9731ad0215159401f6407f3873
@@ -1,7 +1,9 @@
1
1
  require_relative "./value"
2
+ require_relative "./../refinements/string_refinement"
2
3
  module Graphlyte
3
4
  module Arguments
4
5
  class Set
6
+ using Refinements::StringRefinement
5
7
 
6
8
  attr_reader :values
7
9
 
@@ -9,16 +11,44 @@ module Graphlyte
9
11
  raise ArgumentError, "input #{data} must be a hash" unless data.nil? || data.is_a?(Hash)
10
12
  @values = expand_arguments(data) unless data.nil?
11
13
  end
14
+
15
+ def extract_variables(values=@values, variables=[])
16
+ values&.each do |key, value|
17
+ if value.is_a?(Set)
18
+ variables.concat extract_variables(value.values)
19
+ elsif value.is_a?(Array)
20
+ elsif value.symbol?
21
+ variables << value
22
+ elsif value.formal?
23
+ variables << value
24
+ end
25
+ end
26
+ variables
27
+ end
28
+
29
+ def to_h(inner = false)
30
+ return {} unless values && !values.empty?
31
+ values.inject({}) do |memo, (k, v)|
32
+ if v.is_a?(Array)
33
+ memo[k.to_s.to_camel_case] = v.map(&:to_s)
34
+ elsif v.is_a?(Set)
35
+ memo[k.to_s.to_camel_case] = v.to_h
36
+ else
37
+ memo[k.to_s.to_camel_case] = v.to_s
38
+ end
39
+ memo
40
+ end
41
+ end
12
42
 
13
43
  def to_s(inner = false)
14
44
  return "" unless values && !values.empty?
15
45
  arr = values.map do |k,v|
16
46
  if v.is_a?(Array)
17
- "#{k}: [#{v.map(&:to_s).join(", ")}]"
47
+ "#{k.to_s.to_camel_case}: [#{v.map(&:to_s).join(", ")}]"
18
48
  elsif v.is_a?(Set)
19
- "#{k}: { #{v.to_s(true)} }"
49
+ "#{k.to_s.to_camel_case}: { #{v.to_s(true)} }"
20
50
  else
21
- "#{k}: #{v.to_s}"
51
+ "#{k.to_s.to_camel_case}: #{v.to_s}"
22
52
  end
23
53
  end
24
54
  return arr.join(", ") if inner
@@ -31,12 +61,20 @@ module Graphlyte
31
61
  data.inject({}) do |memo, (k, v)|
32
62
  if v.is_a?(Array)
33
63
  memo[k] = v.map do |item|
34
- Value.new(item)
64
+ if item.is_a?(Value)
65
+ item
66
+ else
67
+ Value.new(item)
68
+ end
35
69
  end
36
70
  elsif v.is_a?(Hash)
37
71
  memo[k] = Set.new(v)
38
72
  else
39
- memo[k] = Value.new(v)
73
+ if v.is_a?(Value)
74
+ memo[k] = v
75
+ else
76
+ memo[k] = Value.new(v)
77
+ end
40
78
  end
41
79
  memo
42
80
  end
@@ -1,17 +1,36 @@
1
+ require_relative "./../refinements/string_refinement"
1
2
  module Graphlyte
2
3
  module Arguments
3
4
  class Value
4
- attr_reader :value
5
+ using Refinements::StringRefinement
5
6
 
6
- def initialize(value)
7
+ attr_reader :value, :default
8
+
9
+ def initialize(value, default = nil)
7
10
  raise ArgumentError, "Hash not allowed in this context" if value.is_a? Hash
8
- @value = value
11
+ if value.is_a?(Value)
12
+ @value = value.value
13
+ @default = value.default
14
+ else
15
+ @value = value
16
+ @default = default
17
+ end
18
+ end
19
+
20
+ def symbol?
21
+ value.is_a? Symbol
22
+ end
23
+
24
+ def formal?
25
+ value.is_a? Schema::Types::Base
9
26
  end
10
27
 
11
28
  def to_s
29
+ return "$#{value.to_s.to_camel_case}" if value.is_a? Symbol
12
30
  return value if value.is_a? Numeric
13
31
  return "\"#{value}\"" if value.is_a? String
14
32
  return "null" if value.nil?
33
+ return "$#{value.placeholder.to_camel_case}" if value.is_a? Schema::Types::Base
15
34
  value.to_s
16
35
  end
17
36
  end
@@ -3,8 +3,8 @@ require_relative "./fieldset"
3
3
 
4
4
  module Graphlyte
5
5
  class Builder
6
- def initialize
7
- @fields = []
6
+ def initialize(fields = [])
7
+ @fields = fields
8
8
  end
9
9
 
10
10
  def <<(buildable)
@@ -17,7 +17,7 @@ module Graphlyte
17
17
  end
18
18
 
19
19
  def method_missing(method, fieldset_or_hargs=nil, hargs={}, &block)
20
- # todo: camel case method
20
+ # todo: camel case method
21
21
 
22
22
  # hack for ruby bug in lower versions
23
23
  if [Fieldset, Fragment].include?(fieldset_or_hargs.class)
@@ -31,6 +31,10 @@ module Graphlyte
31
31
  field
32
32
  end
33
33
 
34
+ def respond_to_missing
35
+ true
36
+ end
37
+
34
38
  # for internal use only
35
39
  def >>
36
40
  @fields
@@ -1,11 +1,13 @@
1
1
  require_relative "./arguments/set"
2
-
2
+ require_relative "./refinements/string_refinement"
3
3
  module Graphlyte
4
4
  class Field
5
+ using Refinements::StringRefinement
6
+
5
7
  attr_reader :name, :fieldset, :inputs, :alias
6
8
 
7
9
  def initialize(name, fieldset, hargs, inputs: Arguments::Set.new(hargs))
8
- @name = to_camel_case(name.to_s)
10
+ @name = name.to_s.to_camel_case
9
11
  @fieldset = fieldset
10
12
  @inputs = inputs
11
13
  @alias = nil
@@ -32,16 +34,5 @@ module Graphlyte
32
34
  str += " {\n#{fieldset.to_s(indent + 1)}\n#{actual_indent}}" unless atomic?
33
35
  str
34
36
  end
35
-
36
- def to_camel_case(string)
37
- start_of_string = string.match(/(^_+)/)&.[](0)
38
- end_of_string = string.match(/(_+$)/)&.[](0)
39
-
40
- middle = string.split("_").reject(&:empty?).inject([]) do |memo, str|
41
- memo << (memo.empty? ? str : str.capitalize)
42
- end.join("")
43
-
44
- "#{start_of_string}#{middle}#{end_of_string}"
45
- end
46
37
  end
47
38
  end
@@ -1,21 +1,70 @@
1
+ require_relative "./refinements/string_refinement"
2
+ require "json"
1
3
  module Graphlyte
2
4
  class Query < Fieldset
5
+ using Refinements::StringRefinement
6
+ attr_reader :name, :type
3
7
 
4
- attr_reader :name
5
-
6
- def initialize(query_name=nil, **hargs)
7
- @name = query_name
8
+ def initialize(query_name=nil, type=:query, **hargs)
9
+ @name = query_name || "anonymousQuery"
10
+ @type = type
8
11
  super(**hargs)
9
12
  end
10
13
 
11
- def to_json
12
- { query: to_s }.to_json
14
+ def placeholders
15
+ flatten_variables(builder.>>).map do |value|
16
+ unless value.formal?
17
+ str = ":#{value.value.to_sym.inspect} of unknown"
18
+ else
19
+ str = ":#{value.value.placeholder} of #{value.value.name}"
20
+ end
21
+
22
+ if value.default
23
+ str += " with default #{value.default.to_s}"
24
+ end
25
+ str
26
+ end.join("\n")
27
+ end
28
+
29
+ def to_json(query_name=name, **hargs)
30
+ variables = flatten_variables(builder.>>).uniq { |v| v.value }
31
+ types = merge_variable_types(variables, hargs)
32
+
33
+ str = "#{type} #{query_name}"
34
+ unless types.empty?
35
+ type_new = types.map do |type_arr|
36
+ "$#{type_arr[0].to_camel_case}: #{type_arr[1]}"
37
+ end
38
+ str += "(#{type_new.join(", ")})"
39
+ end
40
+ { query: "#{str} #{to_s(1)}", variables: Arguments::Set.new(hargs).to_h }.to_json
13
41
  end
14
42
 
15
43
  def to_s(indent=0)
16
44
  "{\n#{super(indent + 1)}\n}#{format_fragments}"
17
45
  end
18
46
 
47
+ def merge_variable_types(variables=[], hargs)
48
+ variables.inject([]) do |memo, var|
49
+ unless var.formal?
50
+ if hargs[var.value].is_a? String
51
+ memo << [var.value.to_camel_case, "String"]
52
+ elsif [TrueClass, FalseClass].include? hargs[var.value].class
53
+ memo << [var.value ,"Boolean"]
54
+ elsif hargs[var.value].is_a? Float
55
+ memo << [var.value, "Float"]
56
+ elsif hargs[var.value].is_a? Integer
57
+ memo << [var.value, "Int"]
58
+ elsif hargs[var.value].is_a? Array
59
+ memo << "[#{merge_variable_types(var.value, hargs).first}]"
60
+ end
61
+ else
62
+ memo << [var.value.placeholder, var.value.name]
63
+ end
64
+ memo
65
+ end
66
+ end
67
+
19
68
  def format_fragments
20
69
  str = "\n"
21
70
  flatten(builder.>>).each do |_, fragment|
@@ -26,6 +75,18 @@ module Graphlyte
26
75
  str
27
76
  end
28
77
 
78
+ def flatten_variables(fields, variables=[])
79
+ fields.each do |field|
80
+ variables.concat field.inputs.extract_variables unless field.class.eql?(Fragment)
81
+ if field.class.eql?(Fragment)
82
+ flatten_variables(field.fields, variables)
83
+ else
84
+ flatten_variables(field.fieldset.fields, variables)
85
+ end
86
+ end
87
+ variables
88
+ end
89
+
29
90
  def flatten(fields, new_fields = {})
30
91
  fields.each do |field|
31
92
  if field.class.eql?(Fragment)
@@ -36,10 +97,8 @@ module Graphlyte
36
97
  else
37
98
  if field.fieldset.class.eql?(Fragment)
38
99
  new_fields[field.fieldset.fragment] = field.fieldset
39
- flatten(field.fieldset.fields, new_fields) unless field.atomic?
40
- else
41
- flatten(field.fieldset.fields, new_fields) unless field.atomic?
42
100
  end
101
+ flatten(field.fieldset.fields, new_fields) unless field.atomic?
43
102
  end
44
103
  end
45
104
  new_fields
@@ -0,0 +1,23 @@
1
+ module Graphlyte
2
+ module Refinements
3
+ module StringRefinement
4
+ refine Symbol do
5
+ def to_camel_case
6
+ to_s.to_camel_case
7
+ end
8
+ end
9
+ refine String do
10
+ def to_camel_case
11
+ start_of_string = match(/(^_+)/)&.[](0)
12
+ end_of_string = match(/(_+$)/)&.[](0)
13
+
14
+ middle = split("_").reject(&:empty?).inject([]) do |memo, str|
15
+ memo << (memo.empty? ? str : str.capitalize)
16
+ end.join("")
17
+
18
+ "#{start_of_string}#{middle}#{end_of_string}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,517 @@
1
+ require "strscan"
2
+ require_relative "../fieldset"
3
+ require_relative "../query"
4
+ require_relative "../fragment"
5
+ require_relative "../schema_query"
6
+ require_relative "../types"
7
+
8
+ module Graphlyte
9
+ module Schema
10
+ module ParserHelpers
11
+ def parse_fields
12
+ fields = repeat(:parse_field)
13
+ fields
14
+ end
15
+
16
+ def parse_field
17
+ alias_field = expect(:ALIAS)
18
+ if token = expect(:FRAGMENT_REF)
19
+ raise "Can't find fragment #{token[0][1]}" unless fragments_dictionary[token[0][1]]
20
+ fragments_dictionary[token[0][1]]
21
+ elsif field = expect(:FIELD_NAME)
22
+ args = parse_args
23
+ if fieldset = parse_fieldset
24
+ need(:END_FIELD)
25
+ field = Field.new(field[0][1], fieldset, args)
26
+ else
27
+ field = Field.new(field[0][1], Fieldset.empty, args)
28
+ end
29
+
30
+ if alias_field
31
+ field.alias(alias_field[0][1])
32
+ end
33
+
34
+ field
35
+ end
36
+ end
37
+
38
+ def parse_fieldset
39
+ if expect(:START_FIELD)
40
+ fields = parse_fields
41
+ Fieldset.new(builder: Builder.new(fields))
42
+ end
43
+ end
44
+
45
+ def parse_args
46
+ if expect(:START_ARGS)
47
+ args = repeat(:parse_arg).inject(&:merge)
48
+ need(:END_ARGS)
49
+ args
50
+ end
51
+ end
52
+
53
+ def parse_default
54
+ if expect(:START_DEFAULT_VALUE)
55
+ value = parse_value
56
+ need(:END_DEFAULT_VALUE)
57
+ value
58
+ end
59
+ end
60
+
61
+ def parse_arg
62
+ if (token = expect(:ARG_KEY)) && (value = parse_value)
63
+ defaults = parse_default
64
+ key = token[0][1]
65
+ hash = {}
66
+ if [Array, Hash].include?(value.class)
67
+ hash[key] = value
68
+ else
69
+ hash[key] = Graphlyte::Arguments::Value.new(value, defaults)
70
+ end
71
+ hash
72
+ elsif (token = expect(:SPECIAL_ARG_KEY)) && (value = parse_value)
73
+ defaults = parse_default
74
+ @special_args ||= {}
75
+ arg = {}
76
+ if [Array, Hash].include?(value.class)
77
+ arg[token[0][1]] = value
78
+ else
79
+ arg[token[0][1]] = Graphlyte::Arguments::Value.new(value, defaults)
80
+ end
81
+ @special_args.merge!(arg)
82
+ arg
83
+ end
84
+ end
85
+
86
+ def parse_value
87
+ if token = expect(:ARG_NUM_VALUE) || expect(:ARG_STRING_VALUE) || expect(:ARG_BOOL_VALUE) || expect(:ARG_FLOAT_VALUE)
88
+ token[0][1]
89
+ elsif token = expect(:SPECIAL_ARG_REF)
90
+ ref = token[0][1]
91
+ raise "Can't find ref $#{ref}" unless @special_args[ref]
92
+ value = @special_args[ref]
93
+ Arguments::Value.new(Graphlyte::TYPES.send(value.value, ref.to_sym), value.default)
94
+ elsif token = expect(:SPECIAL_ARG_VAL)
95
+ token[0][1]
96
+ elsif token = expect(:ARG_HASH_START)
97
+ parse_arg_hash
98
+ elsif expect(:ARG_ARRAY_START)
99
+ parse_arg_array
100
+ end
101
+ end
102
+
103
+ def parse_arg_array
104
+ args = repeat(:parse_value)
105
+ need(:ARG_ARRAY_END)
106
+ args
107
+ end
108
+
109
+ def parse_arg_hash
110
+ if (key = expect(:ARG_KEY)) && (value = parse_value)
111
+ need(:ARG_HASH_END)
112
+ hash = {}
113
+ hash[key[0][1]] = value
114
+ hash
115
+ end
116
+ end
117
+
118
+ def repeat(method)
119
+ results = []
120
+
121
+ while result = send(method)
122
+ results << result
123
+ end
124
+
125
+ results
126
+ end
127
+
128
+ def expect(*expected_tokens)
129
+ upcoming = tokens[position, expected_tokens.size]
130
+ if upcoming.map(&:first) == expected_tokens
131
+ advance(expected_tokens.size)
132
+ upcoming
133
+ end
134
+ end
135
+
136
+ def need(*required_tokens)
137
+ upcoming = tokens[position, required_tokens.size]
138
+ expect(*required_tokens) or raise "Unexpected tokens. Expected #{required_tokens.inspect} but got #{upcoming.inspect}"
139
+ end
140
+
141
+ def advance(offset = 1)
142
+ @position += offset
143
+ end
144
+
145
+ def sort_fragments(sorted = [], fragments)
146
+ return sorted if !fragments || fragments.empty?
147
+ fragment_tokens = fragments.shift
148
+
149
+ current_ref = fragment_tokens.find do |token|
150
+ token[0] == :FRAGMENT_REF
151
+ end
152
+
153
+ if current_ref
154
+ exists = sorted.any? do |frags|
155
+ frags.find do |el|
156
+ el[0] == :START_FRAGMENT && el[1] == current_ref[1]
157
+ end
158
+ end
159
+ if exists
160
+ sorted << fragment_tokens
161
+ sort_fragments(sorted, fragments)
162
+ else
163
+ fragments.push fragment_tokens
164
+ sort_fragments(sorted, fragments)
165
+ end
166
+ else
167
+ sorted << fragment_tokens
168
+ sort_fragments(sorted, fragments)
169
+ end
170
+ end
171
+
172
+ def take_fragments
173
+ aggregate = @tokens.inject({taking: false, idx: 0, fragments: []}) do |memo, token_arr|
174
+ if token_arr[0] == :END_FRAGMENT
175
+ memo[:fragments][memo[:idx]] << token_arr
176
+ memo[:taking] = false
177
+ memo[:idx] += 1
178
+ elsif token_arr[0] === :START_FRAGMENT
179
+ memo[:fragments][memo[:idx]] = [token_arr]
180
+ memo[:taking] = true
181
+ elsif memo[:taking]
182
+ memo[:fragments][memo[:idx]] << token_arr
183
+ end
184
+ memo
185
+ end
186
+ aggregate[:fragments]
187
+ end
188
+ end
189
+
190
+ class FragmentParser
191
+ attr_reader :tokens, :position, :fragments_dictionary
192
+
193
+ include ParserHelpers
194
+
195
+ def initialize(tokens)
196
+ @tokens = tokens.flatten(1)
197
+ @position = 0
198
+ @fragments_dictionary = {}
199
+ end
200
+
201
+ def parse_fragments
202
+ repeat(:parse_fragment)
203
+ fragments_dictionary
204
+ end
205
+
206
+ def parse_fragment
207
+ if token = expect(:START_FRAGMENT)
208
+ builder = Builder.new parse_fields
209
+ fragment = Fragment.new(token[0][1], token[0][2], builder: builder)
210
+ @fragments_dictionary[token[0][1]] = fragment
211
+ need(:END_FRAGMENT)
212
+ end
213
+ end
214
+ end
215
+
216
+ class Parser
217
+ attr_reader :tokens, :position, :fragments_dictionary
218
+
219
+ include ParserHelpers
220
+
221
+ def self.parse(gql)
222
+ obj = new Lexer.new(gql).tokenize
223
+ obj.parse
224
+ end
225
+
226
+ def initialize(tokens)
227
+ @tokens = tokens
228
+ @fragment_tokens = sort_fragments([], take_fragments)
229
+ @fragments_dictionary = {}
230
+ @fragments_dictionary = @fragment_tokens.any? ? FragmentParser.new(@fragment_tokens).parse_fragments : {}
231
+ @position = 0
232
+ end
233
+
234
+ def parse
235
+ if token = expect(:START_QUERY)
236
+ parse_query(token[0][1])
237
+ elsif token = expect(:START_MUTATION)
238
+ parse_mutation(token[0][1])
239
+ else
240
+ raise "INVALID"
241
+ end
242
+ end
243
+
244
+ def parse_query(name)
245
+ parse_args
246
+ builder = Builder.new parse_fields
247
+ query = Query.new(name, :query, builder: builder)
248
+ need(:END_QUERY)
249
+ query
250
+ end
251
+
252
+ def parse_mutation(name)
253
+ builder = Builder.new parse_fields
254
+ mutation = Query.new(name, :mutation, builder: builder)
255
+ need(:END_MUTATION)
256
+ mutation
257
+ end
258
+ end
259
+
260
+ class Lexer
261
+ attr_reader :stack, :scanner
262
+
263
+ def initialize(gql, scanner: StringScanner.new(gql))
264
+ @original_string = gql
265
+ @scanner = scanner
266
+ @tokens = []
267
+ end
268
+
269
+ SPECIAL_ARG_REGEX = /^\s*(?:(?<![\"\{]))([\w\!\[\]]+)(?:(?![\"\}]))/
270
+ SIMPLE_EXPRESSION = /(query|mutation|fragment)\s*\w+\s*on\w*.*\{\s*\n*[.|\w\s]*\}/
271
+ START_MAP = {
272
+ 'query' => :START_QUERY,
273
+ 'mutation' => :START_MUTATION,
274
+ 'fragment' => :START_FRAGMENT
275
+ }
276
+
277
+ def tokenize
278
+ until scanner.eos?
279
+ case state
280
+ when :default
281
+ if scanner.scan /^query (\w+)/
282
+ @tokens << [:START_QUERY, scanner[1]]
283
+ push_state :query
284
+ elsif scanner.scan /^mutation (\w+)/
285
+ @tokens << [:START_MUTATION, scanner[1]]
286
+ push_state :mutation
287
+ elsif scanner.scan /\s*fragment\s*(\w+)\s*on\s*(\w+)/
288
+ @tokens << [:START_FRAGMENT, scanner[1], scanner[2]]
289
+ push_state :fragment
290
+ elsif scanner.scan /\s*{\s*/
291
+ @tokens << [:START_FIELD]
292
+ push_state :field
293
+ elsif scanner.scan /\s*}\s*/
294
+ @tokens << [:END_EXPRESSION_SHOULDNT_GET_THIS]
295
+ else
296
+ advance
297
+ end
298
+ when :fragment
299
+ if scanner.scan /\s*\}\s*/
300
+ @tokens << [:END_FRAGMENT]
301
+ pop_state
302
+ pop_context
303
+ elsif scanner.check /^\s*\{\s*$/
304
+ if get_context == :field
305
+ push_state :field
306
+ push_context :field
307
+ else
308
+ scanner.scan /^\s*\{\s*$/
309
+ push_context :field
310
+ end
311
+ else
312
+ handle_field
313
+ end
314
+ when :mutation
315
+ if scanner.scan /\}/
316
+ @tokens << [:END_MUTATION]
317
+ pop_state
318
+ pop_context
319
+ elsif scanner.check /^\s*\{\s*$/
320
+ if get_context == :field
321
+ push_state :field
322
+ else
323
+ scanner.scan /^\s*\{\s*$/
324
+ push_context :field
325
+ end
326
+ else
327
+ handle_field
328
+ end
329
+ when :query
330
+ if scanner.scan /\}/
331
+ @tokens << [:END_QUERY]
332
+ pop_state
333
+ pop_context
334
+ elsif scanner.check /^\s*\{\s*$/
335
+ if get_context == :field
336
+ push_state :field
337
+ push_context :field
338
+ else
339
+ scanner.scan /^\s*\{\s*$/
340
+ push_context :field
341
+ end
342
+ else
343
+ handle_field
344
+ end
345
+ when :field
346
+ if scanner.check /\s*\}\s*/
347
+ if get_context == :field
348
+ scanner.scan /\s*\}\s*/
349
+ @tokens << [:END_FIELD]
350
+ pop_state
351
+ else
352
+ pop_state
353
+ end
354
+ else
355
+ handle_field
356
+ end
357
+ when :hash_arguments
358
+ handle_hash_arguments
359
+ when :array_arguments
360
+ handle_array_arguments
361
+ when :arguments
362
+ if scanner.scan /\s*\)\s*/
363
+ @tokens << [:END_ARGS]
364
+ pop_state
365
+ elsif scanner.scan /\=/
366
+ @tokens << [:START_DEFAULT_VALUE]
367
+ push_state :argument_defaults
368
+ elsif scanner.scan /,/
369
+ #
370
+ else
371
+ handle_shared_arguments
372
+ end
373
+ when :argument_defaults
374
+ if @stack.reverse.take(2).eql?([:argument_defaults, :argument_defaults])
375
+ @tokens << [:END_DEFAULT_VALUE]
376
+ pop_state
377
+ pop_state
378
+ else
379
+ push_state :argument_defaults
380
+ handle_shared_arguments
381
+ end
382
+ when :special_args
383
+ handle_special_args
384
+ end
385
+ end
386
+ @tokens
387
+ end
388
+
389
+ private
390
+
391
+ def handle_field
392
+ if scanner.scan /\s*\{\s*/
393
+ @context = :field
394
+ @tokens << [:START_FIELD]
395
+ push_state :field
396
+ elsif scanner.check /\.{3}(\w+)\s*\}/
397
+ scanner.scan /\.{3}(\w+)/
398
+ @tokens << [:FRAGMENT_REF, scanner[1]]
399
+ pop_context
400
+ pop_state if scanner.check /\s*\}\s*\}/
401
+ elsif scanner.scan /\.{3}(\w+)/
402
+ @tokens << [:FRAGMENT_REF, scanner[1]]
403
+ elsif scanner.scan /\s*(\w+):\s*/
404
+ @tokens << [:ALIAS, scanner[1]]
405
+ elsif scanner.check /\s*(\w+)\s*\}/
406
+ scanner.scan /\s*(\w+)\s*/
407
+ @tokens << [:FIELD_NAME, scanner[1]]
408
+ pop_context
409
+ pop_state if scanner.check /\s*\}\s*\}/
410
+ elsif scanner.scan /\s*(\w+)\s*/
411
+ @tokens << [:FIELD_NAME, scanner[1]]
412
+ elsif scanner.scan /^\s*\(/
413
+ @tokens << [:START_ARGS]
414
+ push_state :arguments
415
+ else
416
+ advance
417
+ end
418
+ end
419
+
420
+ def handle_shared_arguments
421
+ if scanner.scan /^(\w+):/
422
+ @tokens << [:ARG_KEY, scanner[1]]
423
+ elsif scanner.scan /^\s*\{\s*?/
424
+ @tokens << [:ARG_HASH_START]
425
+ push_state :hash_arguments
426
+ elsif scanner.scan /\s*\[\s*/
427
+ @tokens << [:ARG_ARRAY_START]
428
+ push_state :array_arguments
429
+ elsif scanner.scan /\s?\"([\w\s]+)\"/
430
+ @tokens << [:ARG_STRING_VALUE, scanner[1]]
431
+ elsif scanner.scan /\s?(\d+\.\d+)/
432
+ @tokens << [:ARG_FLOAT_VALUE, scanner[1].to_f]
433
+ elsif scanner.scan /\s?(\d+)/
434
+ @tokens << [:ARG_NUM_VALUE, scanner[1].to_i]
435
+ elsif scanner.scan /\s?(true|false)\s?/
436
+ bool = scanner[1] == "true"
437
+ @tokens << [:ARG_BOOL_VALUE, bool]
438
+ elsif scanner.scan /\$(\w+):/
439
+ @tokens << [:SPECIAL_ARG_KEY, scanner[1]]
440
+ push_state :special_args
441
+ elsif scanner.scan /\$(\w+)/
442
+ @tokens << [:SPECIAL_ARG_REF, scanner[1]]
443
+ else
444
+ advance
445
+ end
446
+ end
447
+
448
+ def handle_special_args
449
+ if scanner.check SPECIAL_ARG_REGEX
450
+ scanner.scan SPECIAL_ARG_REGEX
451
+ @tokens << [:SPECIAL_ARG_VAL, scanner[1]]
452
+ pop_state
453
+ else
454
+ pop_state
455
+ end
456
+ end
457
+
458
+ def handle_hash_arguments
459
+ if scanner.scan /\}/
460
+ @tokens << [:ARG_HASH_END]
461
+ pop_state
462
+ else
463
+ handle_shared_arguments
464
+ end
465
+ end
466
+
467
+ def handle_array_arguments
468
+ if scanner.scan /\s*\]\s*/
469
+ @tokens << [:ARG_ARRAY_END]
470
+ pop_state
471
+ else
472
+ handle_shared_arguments
473
+ end
474
+ end
475
+
476
+ def env
477
+ @ctx ||= []
478
+ end
479
+
480
+ def get_context
481
+ env.last || :default
482
+ end
483
+
484
+ def push_context(context)
485
+ env << context
486
+ end
487
+
488
+ def pop_context
489
+ env.pop
490
+ end
491
+
492
+ def rewind
493
+ scanner.pos = scanner.pos - 1
494
+ end
495
+
496
+ def advance
497
+ scanner.pos = scanner.pos + 1
498
+ end
499
+
500
+ def stack
501
+ @stack ||= []
502
+ end
503
+
504
+ def state
505
+ stack.last || :default
506
+ end
507
+
508
+ def push_state(state)
509
+ stack << state
510
+ end
511
+
512
+ def pop_state
513
+ stack.pop
514
+ end
515
+ end
516
+ end
517
+ end
@@ -0,0 +1,14 @@
1
+ module Graphlyte
2
+ module Schema
3
+ module Types
4
+ class Base
5
+ attr_reader :name, :placeholder
6
+
7
+ def initialize(name, placeholder)
8
+ @name = name
9
+ @placeholder = placeholder
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,83 @@
1
+
2
+ module Graphlyte
3
+ module SchemaQuery
4
+ def schema_query
5
+ type_ref_fragment = Graphlyte.fragment('TypeRef', '__Type') do
6
+ kind
7
+ name
8
+ of_type {
9
+ kind
10
+ name
11
+ of_type {
12
+ kind
13
+ name
14
+ of_type {
15
+ kind
16
+ name
17
+ of_type {
18
+ kind
19
+ name
20
+ of_type {
21
+ kind
22
+ name
23
+ of_type {
24
+ kind
25
+ name
26
+ of_type {
27
+ kind
28
+ name
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ end
37
+
38
+ input_value_fragment = Graphlyte.fragment('InputValues', '__InputValue') do
39
+ name
40
+ description
41
+ type type_ref_fragment
42
+ default_value
43
+ end
44
+
45
+ full_type_fragment = Graphlyte.fragment('FullType', '__Type') do
46
+ kind
47
+ name
48
+ description
49
+ fields(includeDeprecated: true) do
50
+ name
51
+ description
52
+ args input_value_fragment
53
+ type type_ref_fragment
54
+ is_deprecated
55
+ deprecation_reason
56
+ end
57
+ input_fields input_value_fragment
58
+ interfaces type_ref_fragment
59
+ enum_values(includeDeprecated: true) do
60
+ name
61
+ description
62
+ is_deprecated
63
+ deprecation_reason
64
+ end
65
+ possible_types type_ref_fragment
66
+ end
67
+
68
+ Graphlyte.query do
69
+ __schema do
70
+ query_type { name }
71
+ mutation_type { name }
72
+ subscription_type { name }
73
+ types full_type_fragment
74
+ directives do
75
+ name
76
+ description
77
+ args input_value_fragment
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,9 @@
1
+ require_relative "schema/types/base"
2
+
3
+ module Graphlyte
4
+ class Types
5
+ def method_missing(method, placeholder)
6
+ Schema::Types::Base.new(method, placeholder)
7
+ end
8
+ end
9
+ end
data/lib/graphlyte.rb CHANGED
@@ -2,10 +2,25 @@ require 'json'
2
2
  require_relative "./graphlyte/fieldset"
3
3
  require_relative "./graphlyte/query"
4
4
  require_relative "./graphlyte/fragment"
5
+ require_relative "./graphlyte/schema_query"
6
+ require_relative "./graphlyte/types"
7
+ require_relative "./graphlyte/schema/parser"
5
8
 
6
9
  module Graphlyte
10
+ extend SchemaQuery
11
+
12
+ TYPES = Types.new
13
+
14
+ def self.parse(gql)
15
+ Graphlyte::Schema::Parser.parse(gql)
16
+ end
17
+
7
18
  def self.query(name = nil, &block)
8
- Query.new(name, builder: build(&block))
19
+ Query.new(name, :query, builder: build(&block))
20
+ end
21
+
22
+ def self.mutation(name = nil, &block)
23
+ Query.new(name, :mutation, builder: build(&block))
9
24
  end
10
25
 
11
26
  def self.fragment(fragment_name, model_name, &block)
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.1.4
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Gregory
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-11 00:00:00.000000000 Z
11
+ date: 2021-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -52,12 +52,17 @@ files:
52
52
  - lib/graphlyte/fieldset.rb
53
53
  - lib/graphlyte/fragment.rb
54
54
  - lib/graphlyte/query.rb
55
+ - lib/graphlyte/refinements/string_refinement.rb
56
+ - lib/graphlyte/schema/parser.rb
57
+ - lib/graphlyte/schema/types/base.rb
58
+ - lib/graphlyte/schema_query.rb
59
+ - lib/graphlyte/types.rb
55
60
  homepage: https://rubygems.org/gems/graphlyte
56
61
  licenses:
57
62
  - MIT
58
63
  metadata:
59
- source_code_uri: https://github.com/skinnyjames/graphlyte
60
- post_install_message:
64
+ source_code_uri: https://gitlab.com/seanchristophergregory/graphlyte
65
+ post_install_message:
61
66
  rdoc_options: []
62
67
  require_paths:
63
68
  - lib
@@ -72,8 +77,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
77
  - !ruby/object:Gem::Version
73
78
  version: '0'
74
79
  requirements: []
75
- rubygems_version: 3.2.3
76
- signing_key:
80
+ rubygems_version: 3.2.31
81
+ signing_key:
77
82
  specification_version: 4
78
83
  summary: craft graphql queries with ruby
79
84
  test_files: []