gh 0.3.0 → 0.4.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.
- 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
|