graphlyte 0.1.3 → 0.2.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: 82edd3a321164acd153524e98420c75175deb60d1fd56d28f72f52917e35dac4
4
- data.tar.gz: '049a43943a6ba45edff3a73347c903be52f0d4e4d7e9f76a9be50e0e45ed11a6'
3
+ metadata.gz: 1fb8e6d42096e7e5c6e90a08a6c41e3cf3e149ecea8be199b13b22e7d23f5ba9
4
+ data.tar.gz: 14f1a49e6137b351abad7905f6a404cfb7d8185e8e9737eb461f7585493f29fd
5
5
  SHA512:
6
- metadata.gz: a1a81b6258e70829db7010ce3c2ca3cdd818dadd0cdc21878c6e10eaefd7baa54d816fd3810c92f330699d905ed5ad00f7865c169f0290e96f4290dbf5cf8160
7
- data.tar.gz: b3bc5db253642508d0f9e93c7882525706fe7c9e1ec687e4b7f506a6096a9ba8ecb2a6e6cc1653318467911b70c01243a4bf9f55d7cd22ccf2618cfff912bfbd
6
+ metadata.gz: 25307883854658ec75267848491b2e76fa1ded6af1f0b8fee7b6315627c9adc7183ebe1d863efd1759c67f1d4241cc2677cfef23219c2add4526e9af28d282e5
7
+ data.tar.gz: 87bc35ce7157b1f53f59b23f389c7be03db2fec9c883e6db05c0a8f8e5799f00dc6b048174ef1f8f7ff7e480615d0cc0a171d8e2b339ab3da55a3774874641f2
@@ -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)
@@ -26,14 +26,22 @@ module Graphlyte
26
26
  field = Field.new(method, Fieldset.empty, fieldset_or_hargs)
27
27
  end
28
28
 
29
- block.call(field.fieldset.builder) if block
29
+ field.fieldset.builder.>.instance_eval(&block) if block
30
30
  @fields << field
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
37
41
  end
42
+
43
+ def >
44
+ self
45
+ end
38
46
  end
39
47
  end
@@ -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
@@ -17,7 +19,7 @@ module Graphlyte
17
19
 
18
20
  def alias(name, &block)
19
21
  @alias = name
20
- block.call(fieldset.builder) if block
22
+ fieldset.builder.>.instance_eval(&block) if block
21
23
  end
22
24
 
23
25
  def to_s(indent=0)
@@ -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[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)
@@ -20,7 +35,7 @@ module Graphlyte
20
35
 
21
36
  def self.build(&block)
22
37
  builder = Builder.new
23
- block.call(builder) if block
38
+ builder.>.instance_eval(&block)
24
39
  builder
25
40
  end
26
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.1.3
4
+ version: 0.2.0
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.22
81
+ signing_key:
77
82
  specification_version: 4
78
83
  summary: craft graphql queries with ruby
79
84
  test_files: []