graphlyte 0.3.2 → 1.0.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphlyte/data.rb +68 -0
  3. data/lib/graphlyte/document.rb +131 -0
  4. data/lib/graphlyte/dsl.rb +86 -0
  5. data/lib/graphlyte/editor.rb +288 -0
  6. data/lib/graphlyte/editors/annotate_types.rb +75 -0
  7. data/lib/graphlyte/editors/canonicalize.rb +26 -0
  8. data/lib/graphlyte/editors/collect_variable_references.rb +36 -0
  9. data/lib/graphlyte/editors/infer_signature.rb +36 -0
  10. data/lib/graphlyte/editors/inline_fragments.rb +37 -0
  11. data/lib/graphlyte/editors/remove_unneeded_spreads.rb +64 -0
  12. data/lib/graphlyte/editors/select_operation.rb +116 -0
  13. data/lib/graphlyte/editors/with_variables.rb +106 -0
  14. data/lib/graphlyte/errors.rb +33 -0
  15. data/lib/graphlyte/lexer.rb +392 -0
  16. data/lib/graphlyte/lexing/location.rb +43 -0
  17. data/lib/graphlyte/lexing/token.rb +31 -0
  18. data/lib/graphlyte/parser.rb +269 -0
  19. data/lib/graphlyte/parsing/backtracking_parser.rb +160 -0
  20. data/lib/graphlyte/refinements/string_refinement.rb +14 -8
  21. data/lib/graphlyte/refinements/syntax_refinements.rb +62 -0
  22. data/lib/graphlyte/schema.rb +165 -0
  23. data/lib/graphlyte/schema_query.rb +82 -65
  24. data/lib/graphlyte/selection_builder.rb +189 -0
  25. data/lib/graphlyte/selector.rb +75 -0
  26. data/lib/graphlyte/serializer.rb +223 -0
  27. data/lib/graphlyte/syntax.rb +369 -0
  28. data/lib/graphlyte.rb +24 -42
  29. metadata +88 -19
  30. data/lib/graphlyte/arguments/set.rb +0 -94
  31. data/lib/graphlyte/arguments/value.rb +0 -42
  32. data/lib/graphlyte/arguments/value_literal.rb +0 -17
  33. data/lib/graphlyte/builder.rb +0 -59
  34. data/lib/graphlyte/directive.rb +0 -25
  35. data/lib/graphlyte/field.rb +0 -65
  36. data/lib/graphlyte/fieldset.rb +0 -36
  37. data/lib/graphlyte/fragment.rb +0 -17
  38. data/lib/graphlyte/inline_fragment.rb +0 -29
  39. data/lib/graphlyte/query.rb +0 -150
  40. data/lib/graphlyte/schema/parser.rb +0 -687
  41. data/lib/graphlyte/schema/types/base.rb +0 -54
  42. data/lib/graphlyte/types.rb +0 -9
@@ -1,42 +0,0 @@
1
- require_relative "./../refinements/string_refinement"
2
- module Graphlyte
3
- module Arguments
4
- class Value
5
- using Refinements::StringRefinement
6
-
7
- attr_reader :value
8
-
9
- def initialize(value)
10
- raise ArgumentError, "Hash not allowed in this context" if value.is_a? Hash
11
- @value = value
12
- end
13
-
14
- def self.from(value)
15
- return value if value.is_a? self
16
-
17
- new(value)
18
- end
19
-
20
- def symbol?
21
- value.is_a? Symbol
22
- end
23
-
24
- def formal?
25
- value.is_a? Schema::Types::Base
26
- end
27
-
28
- def refresh(args)
29
- @value = value.call(args) if value.is_a? Proc
30
- end
31
-
32
- def to_s(raw = false)
33
- return "$#{value.to_s.to_camel_case}" if value.is_a? Symbol
34
- return value if value.is_a? Numeric
35
- return "\"#{value}\"" if value.is_a?(String) && !raw
36
- return "null" if value.nil?
37
- return "$#{value.placeholder.to_camel_case}" if value.is_a? Schema::Types::Base
38
- value.to_s
39
- end
40
- end
41
- end
42
- end
@@ -1,17 +0,0 @@
1
- module Graphlyte
2
- module Arguments
3
- class ValueLiteral
4
- attr_reader :value
5
-
6
- def initialize(string)
7
- raise 'Value must be a string' unless string.class == String
8
-
9
- @value = string
10
- end
11
-
12
- def to_s
13
- @value
14
- end
15
- end
16
- end
17
- end
@@ -1,59 +0,0 @@
1
- require_relative "./field"
2
- require_relative "./fieldset"
3
-
4
- module Graphlyte
5
- class Builder
6
- def initialize(fields = [])
7
- @fields = fields
8
- end
9
-
10
- def <<(buildable)
11
- raise "Must pass a Fieldset or Fragment" unless [Fragment, Fieldset, InlineFragment].include?(buildable.class)
12
-
13
- @fields.concat(buildable.fields) if buildable.class.eql? Fieldset
14
-
15
- # todo: handle fragments better, it's not a field
16
- @fields << buildable if [InlineFragment, Fragment].include? buildable.class
17
- end
18
-
19
- def remove(field_symbol)
20
- @fields.reject! do |field|
21
- field.class == Fragment ? false : field.name == field_symbol.to_s
22
- end
23
-
24
- @fields.select { |field| field.class == Fragment }.each do |fragment|
25
- fragment.fields.reject! do |field|
26
- field.name == field_symbol.to_s
27
- end
28
- end
29
- end
30
-
31
- def method_missing(method, fieldset_or_hargs=nil, hargs={}, &block)
32
- # todo: camel case method
33
-
34
- # hack for ruby bug in lower versions
35
- if [Fieldset, Fragment, InlineFragment].include?(fieldset_or_hargs.class)
36
- field = Field.new(method, fieldset_or_hargs, hargs)
37
- else
38
- field = Field.new(method, Fieldset.empty, fieldset_or_hargs)
39
- end
40
-
41
- field.fieldset.builder.>.instance_eval(&block) if block
42
- @fields << field
43
- field
44
- end
45
-
46
- def respond_to_missing
47
- true
48
- end
49
-
50
- # for internal use only
51
- def >>
52
- @fields
53
- end
54
-
55
- def >
56
- self
57
- end
58
- end
59
- end
@@ -1,25 +0,0 @@
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, args: nil)
13
- # add directive after fieldname?
14
- string += ' ' * indent unless indent.nil?
15
- if !args.nil? && args.to_s.empty?
16
- string += "#{field} " if field
17
- else
18
- string += "#{field}#{args.to_s} "
19
- end
20
- string += "@#{name}"
21
- string += @inputs.to_s unless @inputs.to_s.empty?
22
- string
23
- end
24
- end
25
- end
@@ -1,65 +0,0 @@
1
- require_relative "./arguments/set"
2
- require_relative 'directive'
3
- require_relative "./refinements/string_refinement"
4
- module Graphlyte
5
- class Field
6
- using Refinements::StringRefinement
7
-
8
- attr_reader :name, :fieldset, :inputs, :directive
9
-
10
- def initialize(name, fieldset, hargs, directive: nil, inputs: Arguments::Set.new(hargs))
11
- @name = name.to_s.to_camel_case
12
- @fieldset = fieldset
13
- @inputs = inputs
14
- @alias = nil
15
- @directive = directive
16
- end
17
-
18
- def atomic?
19
- fieldset.empty?
20
- end
21
-
22
- def alias(name, &block)
23
- @alias = name
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)
37
- end
38
-
39
- def to_s(indent=0)
40
- str = ""
41
- actual_indent = ("\s" * indent) * 2
42
- if @alias
43
- str += "#{actual_indent}#{@alias}: #{name}"
44
- str += inputs.to_s.empty? ? "()" : inputs.to_s
45
- elsif @directive
46
- str = @directive.inflate(indent * 2, str, field: name, args: inputs)
47
- else
48
- str += "#{actual_indent}#{name}#{inputs.to_s}"
49
- end
50
- str += " {\n#{fieldset.to_s(indent + 1)}\n#{actual_indent}}" unless atomic?
51
- str
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
64
- end
65
- end
@@ -1,36 +0,0 @@
1
- require_relative "./builder"
2
-
3
- module Graphlyte
4
- class Fieldset
5
- def self.empty
6
- new
7
- end
8
-
9
- attr_reader :model_name, :builder
10
-
11
- def initialize(model_name = nil, builder: Builder.new)
12
- @model_name = model_name
13
- @builder = builder
14
- end
15
-
16
- def fields
17
- builder.>>
18
- end
19
-
20
- def empty?
21
- fields.empty?
22
- end
23
-
24
- def to_s(indent=0)
25
- fields.map { |field| field.to_s(indent) }.join("\n")
26
- end
27
-
28
- def to_a
29
- [ to_s ]
30
- end
31
-
32
- def +(fieldset)
33
- to_s + "\n" + fieldset.to_s
34
- end
35
- end
36
- end
@@ -1,17 +0,0 @@
1
- require_relative "./fieldset"
2
-
3
- module Graphlyte
4
- class Fragment < Fieldset
5
- attr_reader :fragment
6
-
7
- def initialize(fragment_name, model_name=nil, **hargs)
8
- @fragment = fragment_name
9
- super(model_name, **hargs)
10
- end
11
-
12
- def to_s(indent=0)
13
- actual_indent = ("\s" * indent) * 2
14
- "#{actual_indent}...#{fragment}#{actual_indent}"
15
- end
16
- end
17
- end
@@ -1,29 +0,0 @@
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(0, string) if directive
22
- string += " {\n"
23
- string += super(indent + 1)
24
- string += "\n#{actual_indent}}"
25
-
26
- "#{actual_indent}#{string}"
27
- end
28
- end
29
- end
@@ -1,150 +0,0 @@
1
- require_relative "./refinements/string_refinement"
2
- require "json"
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
- next if field.class == Fragment
15
-
16
- modify(field.fieldset.fields, [token], &block)
17
- field.fieldset.builder.instance_eval(&block) unless field.fieldset.fields.empty?
18
- end
19
- else
20
- needle = fields.find do |field|
21
- field.name == token
22
- end
23
-
24
- raise "#{token} not found in query" unless needle
25
-
26
- if selector_tokens.size.zero?
27
- needle.fieldset.builder.instance_eval(&block)
28
- else
29
- modify(needle.fieldset.fields, selector_tokens, &block)
30
- end
31
- end
32
- end
33
- end
34
-
35
- class Query < Fieldset
36
- using Refinements::StringRefinement
37
- attr_reader :name, :type
38
-
39
- def initialize(query_name=nil, type=:query, **hargs)
40
- @name = query_name || "anonymousQuery"
41
- @type = type
42
- super(**hargs)
43
- end
44
-
45
- def at(selector, &block)
46
- Selector.new(selector).modify(fields, &block)
47
- end
48
-
49
- def placeholders
50
- flatten_variables(builder.>>).map do |value|
51
- unless value.formal?
52
- str = ":#{value.value.to_sym.inspect} of unknown"
53
- else
54
- str = ":#{value.value.placeholder} of #{value.value.name}"
55
- end
56
-
57
- if value.value.default
58
- str += " with default "
59
- value.value.default.merge!(str)
60
- end
61
- str
62
- end.join("\n")
63
- end
64
-
65
- def to_json(query_name=name, **hargs)
66
- variables = flatten_variables(builder.>>).uniq { |v| v.value }
67
- types = merge_variable_types(variables, hargs)
68
-
69
- str = "#{type} #{query_name}"
70
- unless types.empty?
71
- type_new = types.map do |type_arr|
72
- type_str = "$#{type_arr[0].to_camel_case}: #{type_arr[1]}"
73
- unless type_arr[2].nil?
74
- type_str << " = "
75
- type_arr[2].merge!(type_str)
76
- end
77
- type_str
78
- end
79
- str += "(#{type_new.join(", ")})"
80
- end
81
- { query: "#{str} #{to_s(1)}", variables: Arguments::Set.new(hargs).to_h(true) }.to_json
82
- end
83
-
84
- def to_s(indent=0)
85
- "{\n#{super(indent + 1)}\n}#{format_fragments}"
86
- end
87
-
88
- def merge_variable_types(variables=[], hargs)
89
- variables.inject([]) do |memo, var|
90
- unless var.formal?
91
- if hargs[var.value].is_a? String
92
- memo << [var.value.to_camel_case, "String"]
93
- elsif [TrueClass, FalseClass].include? hargs[var.value].class
94
- memo << [var.value ,"Boolean"]
95
- elsif hargs[var.value].is_a? Float
96
- memo << [var.value, "Float"]
97
- elsif hargs[var.value].is_a? Integer
98
- memo << [var.value, "Int"]
99
- elsif hargs[var.value].is_a? Array
100
- memo << "[#{merge_variable_types(var.value, hargs).first}]"
101
- end
102
- else
103
- memo << [var.value.placeholder, var.value.name, var.value.default]
104
- end
105
- memo
106
- end
107
- end
108
-
109
- def format_fragments
110
- str = "\n"
111
- flatten(builder.>>).each do |_, fragment|
112
- str += "\nfragment #{fragment.fragment}"
113
- str += " on #{fragment.model_name}" unless fragment.model_name.nil?
114
- str += " {\n#{fragment.fields.map {|f| f.to_s(1) }.join("\n")}\n}"
115
- end
116
- str
117
- end
118
-
119
- def flatten_variables(fields, variables=[])
120
- fields.each do |field|
121
- variables.concat field.inputs.extract_variables unless [InlineFragment, Fragment].include? field.class
122
- variables.concat field.directive.inputs.extract_variables if field.respond_to?(:directive) && field.directive
123
- if [InlineFragment, Fragment].include? field.class
124
- flatten_variables(field.fields, variables)
125
- else
126
- flatten_variables(field.fieldset.fields, variables)
127
- end
128
- end
129
- variables
130
- end
131
-
132
- def flatten(fields, new_fields = {})
133
- fields.each do |field|
134
- next if field.class == InlineFragment
135
- if field.class.eql?(Fragment)
136
- new_fields[field.fragment] = field
137
- unless field.empty?
138
- flatten(field.fields, new_fields)
139
- end
140
- else
141
- if field.fieldset.class.eql?(Fragment)
142
- new_fields[field.fieldset.fragment] = field.fieldset
143
- end
144
- flatten(field.fieldset.fields, new_fields) unless field.atomic?
145
- end
146
- end
147
- new_fields
148
- end
149
- end
150
- end