gql 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 40a578a7c57911bdf4d1b7c062259f3e47bfee30
4
+ data.tar.gz: 8dbc6b5ddf034a2fa5f939aa401c1ea2c031412a
5
+ SHA512:
6
+ metadata.gz: f13475e99d0efe4cac42faecb6d8128d861dbb74bd89ae971adf2613d17d47a5d8108be0bcf412eb8b00fbdfd41e1deae26a0291c5424012a1518df73929f0d2
7
+ data.tar.gz: b47a428fd3e2235fb38c81434651ffa06195f8b98c40abb0215b0e1d039a02346c7e0153a4735f2de09a5d23db6f04e0cac9793d5b69ffa42540cc9730196356
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /lib/gql/parser.output
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gql.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Martin Andert
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,160 @@
1
+ # GQL
2
+
3
+ An attempted implementation of Facebook's yet-to-be-released GraphQL specification, heavily inspired by [graphql-ruby](https://github.com/rmosolgo/graphql-ruby), but with other/more/less features/bugs.
4
+
5
+ Caution! This is pre-alpha software. Use at your own risk.
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'gql'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ ```sh
19
+ $ bundle
20
+ ```
21
+
22
+ Or install it yourself as:
23
+
24
+ ```
25
+ $ gem install gql
26
+ ```
27
+
28
+
29
+ ## Usage
30
+
31
+ TODO: Write usage instructions here
32
+
33
+
34
+ ## Example
35
+
36
+ Run `bin/console` for an interactive prompt and enter the following:
37
+
38
+ ```ruby
39
+ puts q(<<-QUERY_STRING).to_json
40
+ user(<token>) {
41
+ id,
42
+ is_admin,
43
+ full_name as name,
44
+ created_at { year, month } as created_year_and_month,
45
+ created_at.format("long") as created,
46
+ account {
47
+ bank_name,
48
+ iban,
49
+ saldo as saldo_string,
50
+ saldo {
51
+ currency,
52
+ cents /* silly block comment */
53
+ }
54
+ },
55
+ albums.first(2) {
56
+ count,
57
+ edges {
58
+ cursor,
59
+ node {
60
+ artist,
61
+ title,
62
+ songs.first(2) {
63
+ edges {
64
+ id,
65
+ title.upcase as upcased_title,
66
+ title.upcase.length as upcased_title_length
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ <token> = "ma" // a variable
75
+ QUERY_STRING
76
+ ```
77
+
78
+ This should result in the following JSON (after prettyfication):
79
+
80
+ ```json
81
+ {
82
+ "id": "ma",
83
+ "is_admin": true,
84
+ "name": "Martin Andert",
85
+ "created_year_and_month": {
86
+ "year": 2010,
87
+ "month": 3
88
+ },
89
+ "created": "March 04, 2010 14:04",
90
+ "account": {
91
+ "bank_name": "Foo Bank",
92
+ "iban": "987654321",
93
+ "saldo_string": "100000.00 EUR",
94
+ "saldo": {
95
+ "currency": "EUR",
96
+ "cents": 10000000
97
+ }
98
+ },
99
+ "albums": {
100
+ "count": 2,
101
+ "edges": [
102
+ {
103
+ "cursor": "1",
104
+ "node": {
105
+ "artist": "Metallica",
106
+ "title": "Black Album",
107
+ "songs": {
108
+ "edges": [
109
+ {
110
+ "id": 1,
111
+ "upcased_title": "ENTER SANDMAN",
112
+ "upcased_title_length": 13
113
+ }, {
114
+ "id": 2,
115
+ "upcased_title": "SAD BUT TRUE",
116
+ "upcased_title_length": 12
117
+ }
118
+ ]
119
+ }
120
+ }
121
+ }, {
122
+ "cursor": "2",
123
+ "node": {
124
+ "artist": "Nirvana",
125
+ "title": "Nevermind",
126
+ "songs": {
127
+ "edges": [
128
+ {
129
+ "id": 5,
130
+ "upcased_title": "SMELLS LIKE TEEN SPIRIT",
131
+ "upcased_title_length": 23
132
+ }, {
133
+ "id": 6,
134
+ "upcased_title": "COME AS YOU ARE",
135
+ "upcased_title_length": 15
136
+ }
137
+ ]
138
+ }
139
+ }
140
+ }
141
+ ]
142
+ }
143
+ }
144
+ ```
145
+
146
+
147
+ ## Development
148
+
149
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
150
+
151
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
152
+
153
+
154
+ ## Contributing
155
+
156
+ 1. Fork it ( https://github.com/martinandert/gql/fork )
157
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
158
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
159
+ 4. Push to the branch (`git push origin my-new-feature`)
160
+ 5. Create a new Pull Request
@@ -0,0 +1,22 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new :test => :compile do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ file 'lib/gql/tokenizer.rb' => 'lib/gql/tokenizer.rex' do |t|
9
+ sh "bundle exec rex #{t.prerequisites.first} --output-file #{t.name}"
10
+ end
11
+
12
+ file 'lib/gql/parser.rb' => 'lib/gql/parser.y' do |t|
13
+ if ENV['DEBUG']
14
+ sh "bundle exec racc --debug --verbose --output-file=#{t.name} #{t.prerequisites.first}"
15
+ else
16
+ sh "bundle exec racc --output-file=#{t.name} #{t.prerequisites.first}"
17
+ end
18
+ end
19
+
20
+ task :compile => ['lib/gql/tokenizer.rb', 'lib/gql/parser.rb']
21
+
22
+ task :default => :test
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'gql'
5
+
6
+ require_relative '../test/example'
7
+
8
+ def ctxt
9
+ @ctxt ||= { auth_token: 'ma' }
10
+ end
11
+
12
+ def q(string, context = ctxt)
13
+ GQL.execute string, context
14
+ end
15
+
16
+ def p(string)
17
+ GQL.parse string
18
+ end
19
+
20
+ def t(string, &block)
21
+ GQL.tokenize string, &block
22
+ end
23
+
24
+ require 'irb'
25
+ IRB.start
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'gql/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'gql'
9
+ spec.version = GQL::VERSION
10
+ spec.authors = ['Martin Andert']
11
+ spec.email = ['mandert@gmail.com']
12
+
13
+ spec.summary = 'An attempted Ruby implementation of Facebook\'s yet-to-be-released GraphQL specification.'
14
+ spec.homepage = 'https://github.com/martinandert/gql'
15
+ spec.license = 'MIT'
16
+
17
+ spec.required_ruby_version = '>= 2.2.0'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ spec.bindir = 'exe'
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.8'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'minitest'
26
+ spec.add_development_dependency 'rexical', '~> 1.0'
27
+ spec.add_development_dependency 'racc', '~> 1.4'
28
+
29
+ spec.add_dependency 'activesupport', '~> 4.2'
30
+ end
@@ -0,0 +1,68 @@
1
+ module GQL
2
+ autoload :Call, 'gql/call'
3
+ autoload :Connection, 'gql/connection'
4
+ autoload :Executor, 'gql/executor'
5
+ autoload :Field, 'gql/field'
6
+ autoload :Node, 'gql/node'
7
+ autoload :Parser, 'gql/parser'
8
+ autoload :Schema, 'gql/schema'
9
+ autoload :Tokenizer, 'gql/tokenizer'
10
+ autoload :VERSION, 'gql/version'
11
+
12
+ module Errors
13
+ autoload :InvalidNodeClass, 'gql/errors'
14
+ autoload :ParseError, 'gql/errors'
15
+ autoload :UndefinedCall, 'gql/errors'
16
+ autoload :UndefinedField, 'gql/errors'
17
+ autoload :UndefinedRoot, 'gql/errors'
18
+ autoload :UndefinedType, 'gql/errors'
19
+ end
20
+
21
+ module Fields
22
+ autoload :Boolean, 'gql/fields/boolean'
23
+ autoload :Connection, 'gql/fields/connection'
24
+ autoload :Float, 'gql/fields/float'
25
+ autoload :Integer, 'gql/fields/integer'
26
+ autoload :Object, 'gql/fields/object'
27
+ autoload :String, 'gql/fields/string'
28
+ end
29
+
30
+ Schema.fields.update(
31
+ boolean: Fields::Boolean,
32
+ connection: Fields::Connection,
33
+ float: Fields::Float,
34
+ integer: Fields::Integer,
35
+ object: Fields::Object,
36
+ string: Fields::String
37
+ )
38
+
39
+ extend(Module.new {
40
+ def execute(input, context = {})
41
+ ast = parse(input)
42
+
43
+ executor = Executor.new(ast)
44
+ executor.execute context
45
+ end
46
+
47
+ def parse(input)
48
+ input = input.read if input.respond_to?(:read)
49
+
50
+ tokenizer = Tokenizer.new
51
+ tokenizer.scan_setup input
52
+
53
+ parser = Parser.new(tokenizer)
54
+ parser.parse
55
+ end
56
+
57
+ def tokenize(input)
58
+ input = input.read if input.respond_to?(:read)
59
+
60
+ tokenizer = Tokenizer.new
61
+ tokenizer.scan_setup input
62
+
63
+ while token = tokenizer.next_token
64
+ yield token
65
+ end
66
+ end
67
+ })
68
+ end
@@ -0,0 +1,22 @@
1
+ module GQL
2
+ class Call
3
+ attr_reader :target, :context
4
+
5
+ def initialize(caller, ast_node, target, definition, variables, context)
6
+ @caller, @ast_node, @target = caller, ast_node, target
7
+ @definition, @variables, @context = definition, variables, context
8
+ end
9
+
10
+ def execute
11
+ args = @ast_node.arguments.map { |arg| arg.is_a?(Symbol) ? @variables[arg] : arg }
12
+ target = instance_exec(*args, &@definition[:body])
13
+
14
+ node_class = @definition[:returns] || @caller.class
15
+
16
+ raise Errors::InvalidNodeClass.new(node_class, Node) unless node_class < Node
17
+
18
+ node = node_class.new(@ast_node, target, @variables, context)
19
+ node.__value
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,32 @@
1
+ module GQL
2
+ class Connection < Node
3
+ field :edges
4
+
5
+ def initialize(node_class, *args)
6
+ super(*args)
7
+
8
+ @node_class = node_class
9
+ end
10
+
11
+ alias :items :__target
12
+
13
+ def edges_ast_node
14
+ @edges_ast_node ||= @ast_node.fields.find { |f| f.name == :edges }
15
+ end
16
+
17
+ def edges
18
+ raise Errors::InvalidNodeClass.new(@node_class, Node) unless @node_class < Node
19
+
20
+ items.map do |item|
21
+ node = @node_class.new(edges_ast_node, item, @variables, __context)
22
+ node.__value
23
+ end
24
+ end
25
+
26
+ EdgesField.class_eval do
27
+ def __value
28
+ __target
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,58 @@
1
+ require 'active_support/core_ext/array/conversions'
2
+
3
+ module GQL
4
+ class Error < StandardError
5
+ end
6
+
7
+ module Errors
8
+ class SchemaError < Error
9
+ end
10
+
11
+ class UndefinedRoot < SchemaError
12
+ def initialize
13
+ super('No root node class is known to the schema. Assign it with `GQL::Schema.root = MyRootNode`.')
14
+ end
15
+ end
16
+
17
+ class InvalidNodeClass < SchemaError
18
+ def initialize(node_class, super_class)
19
+ super("#{node_class} must be a subclass of #{super_class}.")
20
+ end
21
+ end
22
+
23
+ class UndefinedType < SchemaError
24
+ def initialize(name)
25
+ types = Schema.fields.keys.sort.map { |name| "`#{name}`" }
26
+ types = types.size > 0 ? " Available types are #{types.to_sentence}." : ''
27
+
28
+ super("The field type `#{name}` is not known to the schema. Define it with `GQL::Schema.fields[my_type] = MyFieldType`.#{types}")
29
+ end
30
+ end
31
+
32
+ class UndefinedCall < SchemaError
33
+ def initialize(name, node_class)
34
+ calls = node_class.call_definitions.keys.sort.map { |name| "`#{name}`" }
35
+ calls = calls.size > 0 ? " Available calls are #{calls.to_sentence}." : ''
36
+
37
+ super("#{node_class} has no call named `#{name}`.#{calls}")
38
+ end
39
+ end
40
+
41
+ class UndefinedField < SchemaError
42
+ def initialize(name, node_class)
43
+ fields = node_class.field_classes.keys.sort.map { |name| "`#{name}`" }
44
+ fields = fields.size > 0 ? " Available fields are #{fields.to_sentence}." : ''
45
+
46
+ super("#{node_class} has no field named `#{name}`.#{fields}")
47
+ end
48
+ end
49
+
50
+ class ParseError < Error
51
+ def initialize(value, token)
52
+ token = 'value' if token == 'error'
53
+
54
+ super("Unexpected #{token}: `#{value}`.")
55
+ end
56
+ end
57
+ end
58
+ end