graphlyte 0.2.4 → 0.3.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.
- checksums.yaml +4 -4
- data/lib/graphlyte/builder.rb +9 -3
- data/lib/graphlyte/directive.rb +21 -0
- data/lib/graphlyte/field.rb +32 -5
- data/lib/graphlyte/inline_fragment.rb +29 -0
- data/lib/graphlyte/query.rb +37 -2
- data/lib/graphlyte/refinements/string_refinement.rb +2 -2
- data/lib/graphlyte/schema/parser.rb +352 -199
- data/lib/graphlyte.rb +14 -1
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 97c3b9e1d17580b4d94cf3ffaa9f7291d8ff10c9441a9c2270a378daad39f977
|
|
4
|
+
data.tar.gz: f3586e4ea3a92e0ac462b92a17115a81a8c64e6643c296861072a23543ae2b92
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a4dd27faa45576150659090362b94f30297faad8a1a1d5758480e63359bd74e92a30960e70fad8aa446c60e1540dd384458590d5f6cc00eb49c9aa09b44d6659
|
|
7
|
+
data.tar.gz: 1887884979b8f92cc8c1b145c782363b7be9e907fc3541cee5e103a4e901d07261f1a6f69aa0aafcce337efda5e5de4a3950fa4803f17a074f4cc6bbd0555ace
|
data/lib/graphlyte/builder.rb
CHANGED
|
@@ -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
|
|
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
|
data/lib/graphlyte/field.rb
CHANGED
|
@@ -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, :
|
|
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
|
-
|
|
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
|
data/lib/graphlyte/query.rb
CHANGED
|
@@ -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
|
|
87
|
-
if field.class.
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
39
|
-
|
|
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
|
-
|
|
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(:
|
|
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(:
|
|
124
|
+
elsif token = expect(:ARG_HASH)
|
|
93
125
|
parse_arg_hash
|
|
94
|
-
elsif expect(:
|
|
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] == :
|
|
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] == :
|
|
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] === :
|
|
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(:
|
|
273
|
+
if token = expect(:FRAGMENT)
|
|
209
274
|
parse_args
|
|
210
|
-
builder =
|
|
211
|
-
|
|
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
|
-
|
|
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(:
|
|
238
|
-
|
|
239
|
-
elsif
|
|
240
|
-
|
|
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
|
|
247
|
-
|
|
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
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
361
|
+
scanner.scan /\(/
|
|
362
|
+
@tokens << [:START_ARGS]
|
|
363
|
+
push_state :arguments
|
|
358
364
|
end
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
376
|
+
scanner.scan /\(/
|
|
377
|
+
@tokens << [:START_ARGS]
|
|
378
|
+
push_state :arguments
|
|
374
379
|
end
|
|
375
|
-
|
|
376
|
-
if
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
385
|
-
|
|
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
|
-
|
|
406
|
+
def check_for_last(regex = /\s*\}/)
|
|
407
|
+
scanner.check regex
|
|
408
|
+
end
|
|
392
409
|
|
|
393
|
-
def
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
elsif scanner.scan
|
|
407
|
-
@tokens << [:
|
|
408
|
-
|
|
409
|
-
|
|
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
|
|
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
|
|
428
|
-
@tokens << [:
|
|
535
|
+
elsif scanner.scan %r[{]
|
|
536
|
+
@tokens << [:ARG_HASH]
|
|
537
|
+
|
|
429
538
|
push_state :hash_arguments
|
|
430
|
-
elsif scanner.scan /\
|
|
431
|
-
@tokens << [:
|
|
539
|
+
elsif scanner.scan /\[/
|
|
540
|
+
@tokens << [:ARG_ARRAY]
|
|
541
|
+
|
|
432
542
|
push_state :array_arguments
|
|
433
|
-
elsif scanner.scan
|
|
543
|
+
elsif scanner.scan %r{"(.*?)"}
|
|
434
544
|
@tokens << [:ARG_STRING_VALUE, scanner[1]]
|
|
435
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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
|
-
|
|
574
|
+
advance
|
|
459
575
|
end
|
|
460
576
|
end
|
|
461
577
|
|
|
462
|
-
def
|
|
463
|
-
if scanner.
|
|
464
|
-
|
|
465
|
-
|
|
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
|
-
|
|
615
|
+
advance
|
|
468
616
|
end
|
|
469
617
|
end
|
|
470
618
|
|
|
471
|
-
def
|
|
472
|
-
if scanner.scan
|
|
473
|
-
@tokens << [:
|
|
474
|
-
|
|
619
|
+
def tokenize_fieldset
|
|
620
|
+
if scanner.scan %r[\s*{]
|
|
621
|
+
@tokens << [:FIELDSET]
|
|
622
|
+
|
|
623
|
+
push_state :fieldset
|
|
475
624
|
else
|
|
476
|
-
|
|
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.
|
|
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-
|
|
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
|