gh 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +3 -5
- data/gh.gemspec +2 -1
- data/lib/gh.rb +7 -2
- data/lib/gh/error.rb +17 -0
- data/lib/gh/instrumentation.rb +36 -0
- data/lib/gh/lazy_loader.rb +4 -0
- data/lib/gh/link_follower.rb +4 -0
- data/lib/gh/merge_commit.rb +38 -12
- data/lib/gh/normalizer.rb +2 -0
- data/lib/gh/pagination.rb +31 -0
- data/lib/gh/remote.rb +27 -7
- data/lib/gh/response.rb +6 -0
- data/lib/gh/version.rb +1 -1
- data/lib/gh/wrapper.rb +14 -0
- data/spec/error_spec.rb +31 -0
- data/spec/instrumentation_spec.rb +30 -0
- data/spec/merge_commit_spec.rb +12 -0
- data/spec/normalizer_spec.rb +5 -0
- data/spec/pagination_spec.rb +10 -0
- data/spec/payloads/.yml +3 -0
- data/spec/payloads/repos/travis-repos/test-project-1/git/commits/ca3c0a44ec1d9bf8557d2653aa1b79fcc9ff5f5d.yml +23 -0
- data/spec/payloads/users/rkh/repos.yml +52 -0
- data/spec/payloads/users/rkh/repos?page=2.yml +54 -0
- data/spec/payloads/users/rkh/repos?page=3.yml +57 -0
- data/spec/payloads/users/rkh/repos?page=4.yml +56 -0
- data/spec/payloads/users/rkh/repos?page=5.yml +28 -0
- data/spec/remote_spec.rb +6 -0
- metadata +46 -14
- data/lib/gh/faraday.rb +0 -99
data/README.md
CHANGED
@@ -13,13 +13,11 @@ This will by default use all the middleware that ships with GH, in the following
|
|
13
13
|
|
14
14
|
* `GH::Remote` - sends HTTP requests to GitHub and parses the response
|
15
15
|
* `GH::Normalizer` - renames fields consistenly, adds hypermedia links if possible
|
16
|
+
* `GH::Cache` - caches the responses (will use Rails cache if in Rails, in-memory cache otherwise)
|
16
17
|
* `GH::LazyLoader` - will load missing fields when accessed (handy for dealing with incomplete data without sending to many requests)
|
17
|
-
* `GH::LinkFollower` - will add content of hypermedia links as fields (lazyly), allows you to traverse relations
|
18
18
|
* `GH::MergeCommit` - adds infos about merge commits to pull request payloads
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
* `GH::Cache` - caches the responses (will use Rails cache if in Rails, in-memory cache otherwise)
|
19
|
+
* `GH::LinkFollower` - will add content of hypermedia links as fields (lazyly), allows you to traverse relations
|
20
|
+
* `GH::Instrumentation` - let's you instrument `gh`
|
23
21
|
|
24
22
|
## Main Entry Points
|
25
23
|
|
data/gh.gemspec
CHANGED
@@ -19,7 +19,8 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.add_development_dependency 'rspec'
|
20
20
|
s.add_development_dependency 'webmock'
|
21
21
|
|
22
|
-
s.add_runtime_dependency 'faraday', '~> 0.
|
22
|
+
s.add_runtime_dependency 'faraday', '~> 0.8'
|
23
23
|
s.add_runtime_dependency 'backports', '~> 2.3'
|
24
24
|
s.add_runtime_dependency 'multi_json', '~> 1.0'
|
25
|
+
s.add_runtime_dependency 'addressable'
|
25
26
|
end
|
data/lib/gh.rb
CHANGED
@@ -5,10 +5,13 @@ require 'forwardable'
|
|
5
5
|
module GH
|
6
6
|
autoload :Cache, 'gh/cache'
|
7
7
|
autoload :Case, 'gh/case'
|
8
|
+
autoload :Error, 'gh/error'
|
9
|
+
autoload :Instrumentation, 'gh/instrumentation'
|
8
10
|
autoload :LazyLoader, 'gh/lazy_loader'
|
9
11
|
autoload :LinkFollower, 'gh/link_follower'
|
10
12
|
autoload :MergeCommit, 'gh/merge_commit'
|
11
13
|
autoload :Normalizer, 'gh/normalizer'
|
14
|
+
autoload :Pagination, 'gh/pagination'
|
12
15
|
autoload :Remote, 'gh/remote'
|
13
16
|
autoload :Response, 'gh/response'
|
14
17
|
autoload :ResponseWrapper, 'gh/response_wrapper'
|
@@ -32,13 +35,15 @@ module GH
|
|
32
35
|
end
|
33
36
|
|
34
37
|
extend SingleForwardable
|
35
|
-
def_delegators :current, :api_host, :[], :reset, :load, :post
|
38
|
+
def_delegators :current, :api_host, :[], :reset, :load, :post, :delete, :patch, :put
|
36
39
|
|
37
40
|
DefaultStack = Stack.new do
|
41
|
+
use Instrumentation
|
42
|
+
use Pagination
|
38
43
|
use LinkFollower
|
39
44
|
use MergeCommit
|
40
45
|
use LazyLoader
|
41
|
-
|
46
|
+
use Cache
|
42
47
|
use Normalizer
|
43
48
|
use Remote
|
44
49
|
end
|
data/lib/gh/error.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'gh'
|
2
|
+
|
3
|
+
module GH
|
4
|
+
class Error < Exception
|
5
|
+
attr_reader :error, :payload
|
6
|
+
|
7
|
+
def initialize(error = nil, payload = nil)
|
8
|
+
error = error.error while error.respond_to? :error
|
9
|
+
@error, @payload = error, payload
|
10
|
+
set_backtrace error.backtrace if error
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
"GH request failed (#{error.message}) with payload: #{payload.inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'gh'
|
2
|
+
|
3
|
+
module GH
|
4
|
+
# Public: This class caches responses.
|
5
|
+
class Instrumentation < Wrapper
|
6
|
+
# Public: Get/set instrumenter to use. Compatible with ActiveSupport::Notification and Travis::EventLogger.
|
7
|
+
attr_accessor :instrumenter
|
8
|
+
|
9
|
+
def setup(backend, options)
|
10
|
+
self.instrumenter ||= Travis::EventLogger.method(:notify) if defined? Travis::EventLogger
|
11
|
+
self.instrumenter ||= ActiveSupport::Notifications.method(:instrument) if defined? ActiveSupport::Notifications
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def http(verb, url, *)
|
16
|
+
instrument(:http, :verb => verb, :url => url) { super }
|
17
|
+
end
|
18
|
+
|
19
|
+
def load(data)
|
20
|
+
instrument(:load, :data => data) { super }
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
instrument(:access, :key => key) { super }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def instrument(type, payload = {})
|
30
|
+
return yield unless instrumenter
|
31
|
+
result = nil
|
32
|
+
instrumenter.call("#{type}.gh", payload.merge(:gh => frontend)) { result = yield }
|
33
|
+
return result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/gh/lazy_loader.rb
CHANGED
@@ -11,12 +11,16 @@ module GH
|
|
11
11
|
link = hash['_links'].try(:[], 'self') unless loaded
|
12
12
|
setup_lazy_loading(hash, link['href']) if link
|
13
13
|
hash
|
14
|
+
rescue Exception => error
|
15
|
+
raise Error.new(error, hash)
|
14
16
|
end
|
15
17
|
|
16
18
|
private
|
17
19
|
|
18
20
|
def lazy_load(hash, key, link)
|
19
21
|
result = modify_hash(backend[link].data, true)
|
22
|
+
rescue Exception => error
|
23
|
+
raise Error.new(error, hash)
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
data/lib/gh/link_follower.rb
CHANGED
@@ -7,6 +7,8 @@ module GH
|
|
7
7
|
hash = super
|
8
8
|
setup_lazy_loading(hash) if hash['_links']
|
9
9
|
hash
|
10
|
+
rescue Exception => error
|
11
|
+
raise Error.new(error, hash)
|
10
12
|
end
|
11
13
|
|
12
14
|
private
|
@@ -14,6 +16,8 @@ module GH
|
|
14
16
|
def lazy_load(hash, key)
|
15
17
|
link = hash['_links'][key]
|
16
18
|
{ key => self[link['href']] } if link
|
19
|
+
rescue Exception => error
|
20
|
+
raise Error.new(error, hash)
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
data/lib/gh/merge_commit.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'gh'
|
2
|
+
require 'timeout'
|
2
3
|
|
3
4
|
module GH
|
4
5
|
# Public: ...
|
@@ -13,24 +14,34 @@ module GH
|
|
13
14
|
|
14
15
|
def modify_hash(hash)
|
15
16
|
setup_lazy_loading(super)
|
17
|
+
rescue Exception => error
|
18
|
+
raise Error.new(error, hash)
|
16
19
|
end
|
17
20
|
|
18
21
|
private
|
19
22
|
|
20
23
|
def lazy_load(hash, key)
|
21
|
-
return unless key =~ /^(merge|head)_commit$/ and hash.include? 'mergeable'
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
31
|
-
end
|
24
|
+
return unless key =~ /^(merge|head|base)_commit$/ and hash.include? 'mergeable'
|
25
|
+
return unless force_merge_commit(hash)
|
26
|
+
fields = pull_request_refs(hash)
|
27
|
+
fields['base_commit'] ||= commit_for hash, hash['base']
|
28
|
+
fields['head_commit'] ||= commit_for hash, hash['head']
|
29
|
+
fields
|
30
|
+
rescue Exception => error
|
31
|
+
raise Error.new(error, hash)
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
+
def commit_for(from, hash)
|
35
|
+
{ 'sha' => hash['sha'], 'ref' => hash['ref'],
|
36
|
+
'_links' => { 'self' => { 'href' => git_url_for(from, hash['sha']) } } }
|
37
|
+
end
|
38
|
+
|
39
|
+
def git_url_for(hash, commitish)
|
40
|
+
hash['_links']['self']['href'].gsub(%r{/pulls/(\d+)$}, "/git/#{commitish}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def pull_request_refs(hash)
|
44
|
+
link = git_url_for(hash, 'refs/pull/\1')
|
34
45
|
commits = self[link].map do |data|
|
35
46
|
ref = data['ref']
|
36
47
|
name = ref.split('/').last + "_commit"
|
@@ -39,5 +50,20 @@ module GH
|
|
39
50
|
end
|
40
51
|
Hash[commits]
|
41
52
|
end
|
53
|
+
|
54
|
+
def force_merge_commit(hash)
|
55
|
+
Timeout.timeout(10) do # MAGIC NUMBERS FTW
|
56
|
+
# FIXME: Rick said "this will become part of the API"
|
57
|
+
# until then, please look the other way
|
58
|
+
while hash['mergeable'].nil?
|
59
|
+
url = hash['_links']['html']['href'] + '/mergeable'
|
60
|
+
case frontend.http(:get, url).body
|
61
|
+
when "true" then hash['mergeable'] = true
|
62
|
+
when "false" then hash['mergeable'] = false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
hash['mergeable']
|
67
|
+
end
|
42
68
|
end
|
43
69
|
end
|
data/lib/gh/normalizer.rb
CHANGED
data/lib/gh/pagination.rb
CHANGED
@@ -1,4 +1,35 @@
|
|
1
1
|
module GH
|
2
2
|
class Pagination < Wrapper
|
3
|
+
class Paginated
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize(page, url, gh)
|
7
|
+
@page, @next_url, @gh = page, url, gh
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
return enum_for(:each) unless block
|
12
|
+
@page.each(&block)
|
13
|
+
next_page.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
"[#{first.inspect}, ...]"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def next_page
|
23
|
+
@next_page ||= @gh[@next_url]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
wraps GH::Normalizer
|
28
|
+
double_dispatch
|
29
|
+
|
30
|
+
def modify_response(response)
|
31
|
+
return response unless response.headers['link'] =~ /<([^>]+)>;\s*rel=\"next\"/
|
32
|
+
Paginated.new(response, $1, self)
|
33
|
+
end
|
3
34
|
end
|
4
35
|
end
|
data/lib/gh/remote.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'gh'
|
2
|
-
require '
|
2
|
+
require 'faraday'
|
3
3
|
|
4
4
|
module GH
|
5
5
|
# Public: This class deals with HTTP requests to Github. It is the base Wrapper you always want to use.
|
@@ -43,7 +43,7 @@ module GH
|
|
43
43
|
faraday_options.merge! options[:faraday_options] if options[:faraday_options]
|
44
44
|
|
45
45
|
@connection = Faraday.new(faraday_options) do |builder|
|
46
|
-
builder.request(:
|
46
|
+
builder.request(:authorization, :token, token) if token
|
47
47
|
builder.request(:basic_auth, username, password) if username and password
|
48
48
|
builder.request(:retry)
|
49
49
|
builder.response(:raise_error)
|
@@ -66,7 +66,7 @@ module GH
|
|
66
66
|
# Raises Faraday::Error::ClientError if the resource returns a status between 400 and 599.
|
67
67
|
# Returns the Response.
|
68
68
|
def [](key)
|
69
|
-
response = http(:get, path_for(key), headers)
|
69
|
+
response = frontend.http(:get, path_for(key), headers)
|
70
70
|
modify(response.body, response.headers)
|
71
71
|
end
|
72
72
|
|
@@ -75,14 +75,34 @@ module GH
|
|
75
75
|
connection.run_request(verb, url, nil, headers, &block)
|
76
76
|
end
|
77
77
|
|
78
|
-
#
|
79
|
-
def
|
80
|
-
response = http(
|
81
|
-
req.body = Response.new({}, body).to_s
|
78
|
+
# Internal: ...
|
79
|
+
def request(verb, key, body = nil)
|
80
|
+
response = frontend.http(verb, path_for(key), headers) do |req|
|
81
|
+
req.body = Response.new({}, body).to_s if body
|
82
82
|
end
|
83
83
|
modify(response.body, response.headers)
|
84
84
|
end
|
85
85
|
|
86
|
+
# Public: ...
|
87
|
+
def post(key, body)
|
88
|
+
request(:post, key, body)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Public: ...
|
92
|
+
def delete(key)
|
93
|
+
request(:delete, key)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Public: ...
|
97
|
+
def patch(key, body)
|
98
|
+
request(:patch, key, body)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Public: ...
|
102
|
+
def put(key, body)
|
103
|
+
request(:put, key, body)
|
104
|
+
end
|
105
|
+
|
86
106
|
# Public: ...
|
87
107
|
def reset
|
88
108
|
end
|
data/lib/gh/response.rb
CHANGED
@@ -31,6 +31,7 @@ module GH
|
|
31
31
|
when respond_to(:to_str) then @body = body.to_str
|
32
32
|
when respond_to(:to_hash) then @data = body.to_hash
|
33
33
|
when respond_to(:to_ary) then @data = body.to_ary
|
34
|
+
when nil then @data = {}
|
34
35
|
else raise ArgumentError, "cannot parse #{body.inspect}"
|
35
36
|
end
|
36
37
|
|
@@ -74,6 +75,11 @@ module GH
|
|
74
75
|
self
|
75
76
|
end
|
76
77
|
|
78
|
+
# Public: ...
|
79
|
+
def ==(other)
|
80
|
+
super or @data == other
|
81
|
+
end
|
82
|
+
|
77
83
|
protected
|
78
84
|
|
79
85
|
def dup_ivars
|
data/lib/gh/version.rb
CHANGED
data/lib/gh/wrapper.rb
CHANGED
@@ -36,6 +36,15 @@ module GH
|
|
36
36
|
# Public: ...
|
37
37
|
def_delegator :backend, :post
|
38
38
|
|
39
|
+
# Public: ...
|
40
|
+
def_delegator :backend, :delete
|
41
|
+
|
42
|
+
# Public: ...
|
43
|
+
def_delegator :backend, :patch
|
44
|
+
|
45
|
+
# Public: ...
|
46
|
+
def_delegator :backendt, :put
|
47
|
+
|
39
48
|
# Public: Retrieves resources from Github.
|
40
49
|
def self.[](key)
|
41
50
|
new[key]
|
@@ -124,6 +133,8 @@ module GH
|
|
124
133
|
when respond_to(:to_int) then modify_integer(data)
|
125
134
|
else modify_unkown data
|
126
135
|
end
|
136
|
+
rescue Exception => error
|
137
|
+
raise Error.new(error, data)
|
127
138
|
end
|
128
139
|
|
129
140
|
def modify_response(response)
|
@@ -133,6 +144,8 @@ module GH
|
|
133
144
|
|
134
145
|
def modify(data, *)
|
135
146
|
data
|
147
|
+
rescue Exception => error
|
148
|
+
raise Error.new(error, data)
|
136
149
|
end
|
137
150
|
|
138
151
|
def modify_array(array)
|
@@ -182,6 +195,7 @@ module GH
|
|
182
195
|
next if loaded
|
183
196
|
fields = lazy_load(hash, key, *args)
|
184
197
|
if fields
|
198
|
+
modify_hash fields
|
185
199
|
hash.merge! fields
|
186
200
|
loaded = true
|
187
201
|
fields[key]
|
data/spec/error_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GH::Error do
|
4
|
+
class SomeWrapper < GH::Wrapper
|
5
|
+
double_dispatch
|
6
|
+
def modify_hash(*)
|
7
|
+
raise "foo"
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:exception) do
|
12
|
+
begin
|
13
|
+
SomeWrapper.new.load('foo' => 'bar')
|
14
|
+
nil
|
15
|
+
rescue Exception => error
|
16
|
+
error
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it "wraps connection" do
|
21
|
+
exception.should be_an(GH::Error)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "exposes the original exception" do
|
25
|
+
exception.error.should be_a(RuntimeError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'keeps the payload around' do
|
29
|
+
exception.payload.should be == {'foo' => 'bar'}
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GH::Instrumentation do
|
4
|
+
before do
|
5
|
+
@events = []
|
6
|
+
subject.instrumenter = proc { |*a, &b| @events << a and b[] }
|
7
|
+
stub_request(:get, "https://api.github.com/").to_return :body => "{}"
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'instruments http' do
|
11
|
+
subject.http :get, '/'
|
12
|
+
@events.size.should be == 1
|
13
|
+
@events.first.should be == ['http.gh', {:verb => :get, :url => '/', :gh => subject}]
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'instruments []' do
|
17
|
+
subject['/']
|
18
|
+
@events.size.should be == 2
|
19
|
+
@events.should be == [
|
20
|
+
['access.gh', {:key => '/', :gh => subject}],
|
21
|
+
['http.gh', {:verb => :get, :url => '/', :gh => subject}]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'instruments load' do
|
26
|
+
subject.load("[]")
|
27
|
+
@events.size.should be == 1
|
28
|
+
@events.first.should be == ['load.gh', {:data => "[]", :gh => subject}]
|
29
|
+
end
|
30
|
+
end
|