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 +4 -4
- data/lib/graphlyte/arguments/set.rb +43 -5
- data/lib/graphlyte/arguments/value.rb +22 -3
- data/lib/graphlyte/builder.rb +7 -3
- data/lib/graphlyte/field.rb +4 -13
- data/lib/graphlyte/query.rb +68 -9
- data/lib/graphlyte/refinements/string_refinement.rb +23 -0
- data/lib/graphlyte/schema/parser.rb +517 -0
- data/lib/graphlyte/schema/types/base.rb +14 -0
- data/lib/graphlyte/schema_query.rb +83 -0
- data/lib/graphlyte/types.rb +9 -0
- data/lib/graphlyte.rb +16 -1
- metadata +12 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a541bce9d787c84cbb85652dfaebced77765a79ef1256f016a723d574e389313
|
4
|
+
data.tar.gz: 2b1e62293e73d388bb539fcec90f7f6f1269d843db8cd0410176ff4d90426a82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
-
|
5
|
+
using Refinements::StringRefinement
|
5
6
|
|
6
|
-
|
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
|
-
|
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
|
data/lib/graphlyte/builder.rb
CHANGED
@@ -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
|
data/lib/graphlyte/field.rb
CHANGED
@@ -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 =
|
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
|
data/lib/graphlyte/query.rb
CHANGED
@@ -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
|
-
|
5
|
-
|
6
|
-
|
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
|
12
|
-
|
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,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
|
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
|
+
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-
|
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://
|
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.
|
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: []
|