travis-gh 0.21.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.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/lib/gh/cache.rb +80 -0
- data/lib/gh/case.rb +13 -0
- data/lib/gh/custom_limit.rb +29 -0
- data/lib/gh/error.rb +71 -0
- data/lib/gh/instrumentation.rb +39 -0
- data/lib/gh/lazy_loader.rb +28 -0
- data/lib/gh/link_follower.rb +25 -0
- data/lib/gh/merge_commit.rb +89 -0
- data/lib/gh/nested_resources.rb +51 -0
- data/lib/gh/normalizer.rb +121 -0
- data/lib/gh/pagination.rb +64 -0
- data/lib/gh/parallel.rb +68 -0
- data/lib/gh/remote.rb +163 -0
- data/lib/gh/response.rb +101 -0
- data/lib/gh/response_wrapper.rb +14 -0
- data/lib/gh/response_x_header_formatter.rb +13 -0
- data/lib/gh/stack.rb +64 -0
- data/lib/gh/token_check.rb +35 -0
- data/lib/gh/version.rb +6 -0
- data/lib/gh/wrapper.rb +235 -0
- data/lib/gh.rb +84 -0
- metadata +179 -0
data/lib/gh/parallel.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'gh'
|
|
4
|
+
require 'backports/basic_object' unless defined? BasicObject
|
|
5
|
+
|
|
6
|
+
module GH
|
|
7
|
+
# Public: ...
|
|
8
|
+
class Parallel < Wrapper
|
|
9
|
+
attr_accessor :parallelize
|
|
10
|
+
|
|
11
|
+
class Dummy < BasicObject
|
|
12
|
+
attr_accessor :__delegate__
|
|
13
|
+
|
|
14
|
+
def method_missing(*args)
|
|
15
|
+
::Kernel.raise ::RuntimeError, 'response not yet loaded' if __delegate__.nil?
|
|
16
|
+
__delegate__.__send__(*args)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def respond_to_missing?(method, *)
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def setup(*)
|
|
25
|
+
@parallelize = true if @parallelize.nil?
|
|
26
|
+
@in_parallel = false
|
|
27
|
+
@mutex = Mutex.new
|
|
28
|
+
@queue = []
|
|
29
|
+
super
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def generate_response(key, response)
|
|
33
|
+
return super unless in_parallel?
|
|
34
|
+
|
|
35
|
+
dummy = Dummy.new
|
|
36
|
+
@mutex.synchronize { @queue << [dummy, key, response] }
|
|
37
|
+
dummy
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def in_parallel
|
|
41
|
+
return yield if in_parallel? || !@parallelize
|
|
42
|
+
|
|
43
|
+
was = @in_parallel
|
|
44
|
+
@in_parallel = true
|
|
45
|
+
result = nil
|
|
46
|
+
connection.in_parallel { result = yield }
|
|
47
|
+
@mutex.synchronize do
|
|
48
|
+
@queue.each { |dummy, key, response| dummy.__delegate__ = backend.generate_response(key, response) }
|
|
49
|
+
@queue.clear
|
|
50
|
+
end
|
|
51
|
+
result
|
|
52
|
+
ensure
|
|
53
|
+
@in_parallel = was unless was.nil?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def in_parallel?
|
|
57
|
+
@in_parallel
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def connection
|
|
61
|
+
@connection ||= begin
|
|
62
|
+
layer = backend
|
|
63
|
+
layer = layer.backend until layer.respond_to? :connection
|
|
64
|
+
layer.connection
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/gh/remote.rb
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
require 'gh'
|
|
4
|
+
require 'faraday'
|
|
5
|
+
require 'faraday/retry'
|
|
6
|
+
require 'faraday/typhoeus'
|
|
7
|
+
require 'active_support/core_ext/string'
|
|
8
|
+
|
|
9
|
+
module GH
|
|
10
|
+
# Public: This class deals with HTTP requests to Github. It is the base Wrapper you always want to use.
|
|
11
|
+
# Note that it is usually used implicitely by other wrapper classes if not specified.
|
|
12
|
+
class Remote < Wrapper
|
|
13
|
+
attr_reader :api_host, :connection, :headers, :prefix
|
|
14
|
+
|
|
15
|
+
# Public: Generates a new Remote instance.
|
|
16
|
+
#
|
|
17
|
+
# api_host - HTTP host to send requests to, has to include schema (https or http)
|
|
18
|
+
# options - Hash with configuration options:
|
|
19
|
+
# :token - OAuth token to use (optional).
|
|
20
|
+
# :username - Github user used for login (optional).
|
|
21
|
+
# :password - Github password used for login (optional).
|
|
22
|
+
# :origin - Value of the origin request header (optional).
|
|
23
|
+
# :headers - HTTP headers to be send on every request (optional).
|
|
24
|
+
#
|
|
25
|
+
# It is highly recommended to set origin, but not to set headers.
|
|
26
|
+
# If you set the username, you should also set the password.
|
|
27
|
+
def setup(api_host, options)
|
|
28
|
+
token, username, password = options.values_at :token, :username, :password
|
|
29
|
+
|
|
30
|
+
api_host = api_host.api_host if api_host.respond_to? :api_host
|
|
31
|
+
@api_host = Addressable::URI.parse(api_host)
|
|
32
|
+
@headers = {
|
|
33
|
+
'User-Agent' => options[:user_agent] || "GH/#{GH::VERSION}",
|
|
34
|
+
'Accept' => options[:accept] || 'application/vnd.github.v3+json',
|
|
35
|
+
'Accept-Charset' => 'utf-8'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@headers.merge! options[:headers] if options[:headers]
|
|
39
|
+
@headers['Origin'] = options[:origin] if options[:origin]
|
|
40
|
+
|
|
41
|
+
@prefix = ''
|
|
42
|
+
@prefix << "#{token}@" if token
|
|
43
|
+
@prefix << "#{username}:#{password}@" if username && password
|
|
44
|
+
@prefix << @api_host.host
|
|
45
|
+
|
|
46
|
+
faraday_options = { url: api_host }
|
|
47
|
+
faraday_options[:ssl] = options[:ssl] if options[:ssl]
|
|
48
|
+
faraday_options.merge! options[:faraday_options] if options[:faraday_options]
|
|
49
|
+
|
|
50
|
+
@connection = Faraday.new(faraday_options) do |builder|
|
|
51
|
+
builder.request(:authorization, :token, token) if token
|
|
52
|
+
builder.request(:basic_auth, username, password) if username && password
|
|
53
|
+
builder.request(:retry)
|
|
54
|
+
builder.adapter(:typhoeus)
|
|
55
|
+
builder.response(:raise_error)
|
|
56
|
+
builder.use :instrumentation if defined? FaradayMiddleware::Instrumentation
|
|
57
|
+
builder.response(:logger, nil, formatter: GH.const_get(options[:formatter].camelize)) if options[:formatter]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Public: ...
|
|
62
|
+
def inspect
|
|
63
|
+
"#<#{self.class}: #{api_host}>"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Internal: ...
|
|
67
|
+
def fetch_resource(key)
|
|
68
|
+
frontend.http(:get, frontend.path_for(key), headers)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Internal: ...
|
|
72
|
+
def generate_response(key, response)
|
|
73
|
+
body = response.body
|
|
74
|
+
headers = response.headers
|
|
75
|
+
url = response.env[:url] if response.respond_to?(:env) && response.env
|
|
76
|
+
url = response.url if response.respond_to?(:url)
|
|
77
|
+
url = frontend.full_url(key) if url.to_s.empty?
|
|
78
|
+
modify(body, headers, url)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Internal: ...
|
|
82
|
+
def http(verb, url, headers = {}, &block)
|
|
83
|
+
body = headers.delete :body
|
|
84
|
+
connection.run_request(verb, url, body, headers, &block)
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
raise Error.new(e, nil, verb:, url:, headers:)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Internal: ...
|
|
90
|
+
def request(verb, key, body = nil)
|
|
91
|
+
response = frontend.http(verb, path_for(key), headers) do |req|
|
|
92
|
+
req.body = Response.new(body).to_s if body
|
|
93
|
+
end
|
|
94
|
+
frontend.generate_response(key, response)
|
|
95
|
+
rescue GH::Error => e
|
|
96
|
+
e.info[:payload] = Response.new(body).to_s if body
|
|
97
|
+
raise e
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Public: ...
|
|
101
|
+
def post(key, body)
|
|
102
|
+
frontend.request(:post, key, body)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Public: ...
|
|
106
|
+
def delete(key, body = nil)
|
|
107
|
+
frontend.request(:delete, key, body)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Public: ...
|
|
111
|
+
def head(key)
|
|
112
|
+
frontend.request(:head, key)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Public: ...
|
|
116
|
+
def patch(key, body)
|
|
117
|
+
frontend.request(:patch, key, body)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Public: ...
|
|
121
|
+
def put(key, body)
|
|
122
|
+
frontend.request(:put, key, body)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Public: ...
|
|
126
|
+
def reset; end
|
|
127
|
+
|
|
128
|
+
# Public: ...
|
|
129
|
+
def load(data)
|
|
130
|
+
modify(data)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Public: ...
|
|
134
|
+
def in_parallel
|
|
135
|
+
raise 'use GH::Parallel middleware for #in_parallel support'
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def full_url(key)
|
|
139
|
+
uri = Addressable::URI.parse(key)
|
|
140
|
+
uri.path = File.join(api_host.path, uri.path) unless uri.absolute? || uri.path.start_with?(api_host.path)
|
|
141
|
+
uri = api_host + uri
|
|
142
|
+
raise ArgumentError, "URI out of scope: #{key}" if uri.host != api_host.host
|
|
143
|
+
|
|
144
|
+
uri
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def path_for(key)
|
|
148
|
+
frontend.full_url(key).request_uri
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def identifier(key)
|
|
154
|
+
path_for(key)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def modify(body, headers = {}, url = nil)
|
|
158
|
+
return body if body.is_a? Response
|
|
159
|
+
|
|
160
|
+
Response.new(body, headers, url)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
data/lib/gh/response.rb
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
require 'gh'
|
|
4
|
+
require 'multi_json'
|
|
5
|
+
|
|
6
|
+
module GH
|
|
7
|
+
# Public: Class wrapping low level Github responses.
|
|
8
|
+
#
|
|
9
|
+
# Delegates safe methods to the parsed body (expected to be an Array or Hash).
|
|
10
|
+
class Response
|
|
11
|
+
include Enumerable
|
|
12
|
+
include GH::Case
|
|
13
|
+
attr_accessor :headers, :data, :body, :url
|
|
14
|
+
|
|
15
|
+
# subset of safe methods that both Array and Hash implement
|
|
16
|
+
extend Forwardable
|
|
17
|
+
def_delegators(:@data, :[], :assoc, :each, :empty?, :flatten, :include?, :index, :inspect, :length,
|
|
18
|
+
:pretty_print, :pretty_print_cycle, :rassoc, :select, :size, :to_a, :values_at)
|
|
19
|
+
|
|
20
|
+
# Internal: Initializes a new instance.
|
|
21
|
+
#
|
|
22
|
+
# headers - HTTP headers as a Hash
|
|
23
|
+
# body - HTTP body as a String
|
|
24
|
+
def initialize(body = '{}', headers = {}, url = nil)
|
|
25
|
+
@url = url
|
|
26
|
+
@headers = headers.transform_keys { |k| k.downcase }
|
|
27
|
+
|
|
28
|
+
case body
|
|
29
|
+
when nil, '' then @data = {}
|
|
30
|
+
when respond_to(:to_str) then @body = body.to_str
|
|
31
|
+
when respond_to(:to_hash) then @data = body.to_hash
|
|
32
|
+
when respond_to(:to_ary) then @data = body.to_ary
|
|
33
|
+
else raise ArgumentError, "cannot parse #{body.inspect}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
@body.force_encoding('utf-8') if @body.respond_to? :force_encoding
|
|
37
|
+
@body ||= MultiJson.encode(@data)
|
|
38
|
+
@data ||= MultiJson.decode(@body)
|
|
39
|
+
rescue EncodingError
|
|
40
|
+
raise "Invalid encoding in #{url}, please contact github."
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Public: Duplicates the instance. Will also duplicate some instance variables to behave as expected.
|
|
44
|
+
#
|
|
45
|
+
# Returns new Response instance.
|
|
46
|
+
def dup
|
|
47
|
+
super.dup_ivars
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Public: Returns the response body as a String.
|
|
51
|
+
def to_s
|
|
52
|
+
@body.dup
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Public: Returns true or false indicating whether it supports method.
|
|
56
|
+
def respond_to?(method, *)
|
|
57
|
+
return super unless (method.to_s == 'to_hash') || (method.to_s == 'to_ary')
|
|
58
|
+
|
|
59
|
+
data.respond_to? method
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Public: Implements to_hash conventions, please check respond_to?(:to_hash).
|
|
63
|
+
def to_hash
|
|
64
|
+
return method_missing(__method__) unless respond_to? __method__
|
|
65
|
+
|
|
66
|
+
@data.dup.to_hash
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Public: Implements to_ary conventions, please check respond_to?(:to_hash).
|
|
70
|
+
def to_ary
|
|
71
|
+
return method_missing(__method__) unless respond_to? __method__
|
|
72
|
+
|
|
73
|
+
@data.dup.to_ary
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Public: ...
|
|
77
|
+
def to_gh
|
|
78
|
+
self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Public: ...
|
|
82
|
+
def ==(other)
|
|
83
|
+
super or @data == other
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
protected
|
|
87
|
+
|
|
88
|
+
def dup_ivars
|
|
89
|
+
@headers = @headers.dup
|
|
90
|
+
@data = @data.dup
|
|
91
|
+
@body = @body.dup
|
|
92
|
+
self
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def content_type
|
|
98
|
+
headers['content-type']
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'faraday/logging/formatter'
|
|
4
|
+
|
|
5
|
+
module GH
|
|
6
|
+
class ResponseXHeaderFormatter < Faraday::Logging::Formatter
|
|
7
|
+
def request(env); end
|
|
8
|
+
|
|
9
|
+
def response(env)
|
|
10
|
+
info('Response') { env.response_headers.select { |k, _v| k =~ /^x-/ }.sort.to_h }
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/gh/stack.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'gh'
|
|
4
|
+
|
|
5
|
+
module GH
|
|
6
|
+
# Public: Exposes DSL for stacking wrappers.
|
|
7
|
+
#
|
|
8
|
+
# Examples
|
|
9
|
+
#
|
|
10
|
+
# api = GH::Stack.build do
|
|
11
|
+
# use GH::Cache, cache: Rails.cache
|
|
12
|
+
# use GH::Normalizer
|
|
13
|
+
# use GH::Remote, username: "admin", password: "admin"
|
|
14
|
+
# end
|
|
15
|
+
class Stack
|
|
16
|
+
attr_reader :options
|
|
17
|
+
|
|
18
|
+
# Public: Generates a new wrapper stack from the given block.
|
|
19
|
+
#
|
|
20
|
+
# options - Hash of options that will be passed to all layers upon initialization.
|
|
21
|
+
#
|
|
22
|
+
# Returns top most Wrapper instance.
|
|
23
|
+
def self.build(options = {}, &block)
|
|
24
|
+
new(&block).build(options)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Public: Generates a new Stack instance.
|
|
28
|
+
#
|
|
29
|
+
# options - Hash of options that will be passed to all layers upon initialization.
|
|
30
|
+
#
|
|
31
|
+
# Can be used for easly stacking layers.
|
|
32
|
+
def initialize(_options = {}, &block)
|
|
33
|
+
@options = {}
|
|
34
|
+
@stack = []
|
|
35
|
+
instance_eval(&block) if block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Public: Adds a new layer to the stack.
|
|
39
|
+
#
|
|
40
|
+
# Layer will be wrapped by layers already on the stack.
|
|
41
|
+
def use(klass, options = {})
|
|
42
|
+
@stack << [klass, options]
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Public: Generates wrapper instances for stack configuration.
|
|
47
|
+
#
|
|
48
|
+
# options - Hash of options that will be passed to all layers upon initialization.
|
|
49
|
+
#
|
|
50
|
+
# Returns top most Wrapper instance.
|
|
51
|
+
def build(options = {})
|
|
52
|
+
@stack.reverse.inject(nil) do |backend, (klass, opts)|
|
|
53
|
+
klass.new backend, @options.merge(opts).merge(options)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Public: ...
|
|
58
|
+
def replace(old_class, new_class)
|
|
59
|
+
@stack.map! { |klass, options| [old_class == klass ? new_class : klass, options] }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
alias new build
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'gh/error'
|
|
4
|
+
require 'base64'
|
|
5
|
+
|
|
6
|
+
module GH
|
|
7
|
+
class TokenCheck < Wrapper
|
|
8
|
+
attr_accessor :client_id, :client_secret, :token
|
|
9
|
+
|
|
10
|
+
def setup(backend, options)
|
|
11
|
+
@client_secret = options[:client_secret]
|
|
12
|
+
@client_id = options[:client_id]
|
|
13
|
+
@token = options[:token]
|
|
14
|
+
@check_token = true
|
|
15
|
+
super
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def check_token
|
|
19
|
+
return unless @check_token && client_id && client_secret && token
|
|
20
|
+
|
|
21
|
+
@check_token = false
|
|
22
|
+
|
|
23
|
+
auth_header = 'Basic %s' % Base64.strict_encode64("#{client_id}:#{client_secret}")
|
|
24
|
+
http :post, path_for("/applications/#{client_id}/token"), body: "{\"access_token\": \"#{token}\"}",
|
|
25
|
+
'Authorization' => auth_header
|
|
26
|
+
rescue GH::Error(response_status: 404) => e
|
|
27
|
+
raise GH::TokenInvalid, e
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def http(*)
|
|
31
|
+
check_token
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/lib/gh/version.rb
ADDED
data/lib/gh/wrapper.rb
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'gh'
|
|
4
|
+
require 'addressable/uri'
|
|
5
|
+
|
|
6
|
+
module GH
|
|
7
|
+
# Public: Simple base class for low level layers.
|
|
8
|
+
# Handy if you want to manipulate resources coming in from Github.
|
|
9
|
+
#
|
|
10
|
+
# Examples
|
|
11
|
+
#
|
|
12
|
+
# class IndifferentAccess
|
|
13
|
+
# def [](key) super.tap { |r| r.data.with_indifferent_access! } end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# gh = IndifferentAccess.new
|
|
17
|
+
# gh['users/rkh'][:name] # => "Konstantin Haase"
|
|
18
|
+
#
|
|
19
|
+
# # easy to use in the low level stack
|
|
20
|
+
# gh = Github.build do
|
|
21
|
+
# use GH::Cache
|
|
22
|
+
# use IndifferentAccess
|
|
23
|
+
# use GH::Normalizer
|
|
24
|
+
# end
|
|
25
|
+
class Wrapper
|
|
26
|
+
extend Forwardable
|
|
27
|
+
include Case
|
|
28
|
+
|
|
29
|
+
# Public: Get wrapped layer.
|
|
30
|
+
attr_reader :backend
|
|
31
|
+
|
|
32
|
+
# Public: ...
|
|
33
|
+
attr_reader :options
|
|
34
|
+
|
|
35
|
+
# Public: Returns the URI used for sending out web request.
|
|
36
|
+
def_delegator :backend, :api_host
|
|
37
|
+
|
|
38
|
+
# Internal: ...
|
|
39
|
+
def_delegator :backend, :http
|
|
40
|
+
|
|
41
|
+
# Internal: ...
|
|
42
|
+
def_delegator :backend, :request
|
|
43
|
+
|
|
44
|
+
# Public: ...
|
|
45
|
+
def_delegator :backend, :post
|
|
46
|
+
|
|
47
|
+
# Public: ...
|
|
48
|
+
def_delegator :backend, :delete
|
|
49
|
+
|
|
50
|
+
# Public: ...
|
|
51
|
+
def_delegator :backend, :head
|
|
52
|
+
|
|
53
|
+
# Public: ...
|
|
54
|
+
def_delegator :backend, :patch
|
|
55
|
+
|
|
56
|
+
# Public: ...
|
|
57
|
+
def_delegator :backend, :put
|
|
58
|
+
|
|
59
|
+
# Public: ...
|
|
60
|
+
def_delegator :backend, :fetch_resource
|
|
61
|
+
|
|
62
|
+
# Public: ...
|
|
63
|
+
def_delegator :backend, :in_parallel
|
|
64
|
+
|
|
65
|
+
# Public: ...
|
|
66
|
+
def_delegator :backend, :in_parallel?
|
|
67
|
+
|
|
68
|
+
# Public: ...
|
|
69
|
+
def_delegator :backend, :full_url
|
|
70
|
+
|
|
71
|
+
# Public: ...
|
|
72
|
+
def_delegator :backend, :path_for
|
|
73
|
+
|
|
74
|
+
def self.double_dispatch
|
|
75
|
+
define_method(:modify) { |data| double_dispatch(data) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Public: Retrieves resources from Github.
|
|
79
|
+
def self.[](key)
|
|
80
|
+
new[key]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Public: Retrieves resources from Github.
|
|
84
|
+
#
|
|
85
|
+
# By default, this method is delegated to the next layer on the stack
|
|
86
|
+
# and modify is called.
|
|
87
|
+
def [](key)
|
|
88
|
+
generate_response key, fetch_resource(key)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Internal: ...
|
|
92
|
+
def generate_response(key, resource)
|
|
93
|
+
modify backend.generate_response(key, resource)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Internal: Get/set default layer to wrap when creating a new instance.
|
|
97
|
+
def self.wraps(klass = nil)
|
|
98
|
+
@wraps = klass if klass
|
|
99
|
+
@wraps ||= Remote
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Public: Initialize a new Wrapper.
|
|
103
|
+
#
|
|
104
|
+
# backend - layer to be wrapped
|
|
105
|
+
# options - config options
|
|
106
|
+
def initialize(backend = nil, options = {})
|
|
107
|
+
backend, @options = normalize_options(backend, options)
|
|
108
|
+
@options.each_pair { |key, value| public_send("#{key}=", value) if respond_to? "#{key}=" }
|
|
109
|
+
setup(backend, @options)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Public: Set wrapped layer.
|
|
113
|
+
def backend=(layer)
|
|
114
|
+
reset if backend
|
|
115
|
+
layer.frontend = self
|
|
116
|
+
@backend = layer
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Internal: ...
|
|
120
|
+
attr_writer :frontend
|
|
121
|
+
|
|
122
|
+
# Internal: ...
|
|
123
|
+
def frontend
|
|
124
|
+
@frontend ? @frontend.frontend : self
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Public: ...
|
|
128
|
+
def inspect
|
|
129
|
+
"#<#{self.class}: #{backend.inspect}>"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Internal: ...
|
|
133
|
+
def prefixed(key)
|
|
134
|
+
"#{prefix}##{identifier(key)}"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Public: ...
|
|
138
|
+
def reset
|
|
139
|
+
backend&.reset
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Public: ...
|
|
143
|
+
def load(data)
|
|
144
|
+
modify backend.load(data)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def identifier(key)
|
|
150
|
+
backend.prefixed(key)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def prefix
|
|
154
|
+
self.class.name
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def double_dispatch(data)
|
|
158
|
+
case data
|
|
159
|
+
when respond_to(:to_gh) then modify_response(data)
|
|
160
|
+
when respond_to(:to_hash) then modify_hash(data)
|
|
161
|
+
when respond_to(:to_ary) then modify_array(data)
|
|
162
|
+
when respond_to(:to_str) then modify_string(data)
|
|
163
|
+
when respond_to(:to_int) then modify_integer(data)
|
|
164
|
+
else modify_unknown data
|
|
165
|
+
end
|
|
166
|
+
rescue StandardError => e
|
|
167
|
+
raise Error.new(e, data)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def modify_response(response)
|
|
171
|
+
result = double_dispatch response.data
|
|
172
|
+
result.respond_to?(:to_gh) ? result.to_gh : Response.new(result, response.headers, response.url)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def modify(data, *)
|
|
176
|
+
data
|
|
177
|
+
rescue StandardError => e
|
|
178
|
+
raise Error.new(e, data)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def modify_array(array)
|
|
182
|
+
array.map { |e| modify(e) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def modify_hash(hash)
|
|
186
|
+
corrected = {}
|
|
187
|
+
hash.each_pair { |k, v| corrected[k] = modify(v) }
|
|
188
|
+
corrected.default_proc = hash.default_proc if hash.default_proc
|
|
189
|
+
corrected
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
alias modify_string modify
|
|
193
|
+
alias modify_integer modify
|
|
194
|
+
alias modify_unknown modify
|
|
195
|
+
|
|
196
|
+
def setup(backend, options)
|
|
197
|
+
self.backend = backend.is_a?(Wrapper) ? backend : self.class.wraps.new(backend, options)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def normalize_options(backend, options)
|
|
201
|
+
if backend.is_a?(Hash)
|
|
202
|
+
options = backend
|
|
203
|
+
backend = nil
|
|
204
|
+
end
|
|
205
|
+
options ||= {}
|
|
206
|
+
backend ||= options[:backend] || options[:api_url] || 'https://api.github.com'
|
|
207
|
+
[backend, options]
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def setup_default_proc(hash, &block)
|
|
211
|
+
old_proc = hash.default_proc
|
|
212
|
+
hash.default_proc = proc do |h, key|
|
|
213
|
+
value = old_proc.call(h, key) if old_proc
|
|
214
|
+
value = block[h, key] if value.nil?
|
|
215
|
+
value
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def setup_lazy_loading(hash, *args)
|
|
220
|
+
loaded = false
|
|
221
|
+
setup_default_proc(hash) do |h, key|
|
|
222
|
+
next if loaded
|
|
223
|
+
|
|
224
|
+
fields = lazy_load(h, key, *args)
|
|
225
|
+
if fields
|
|
226
|
+
modify_hash(fields)
|
|
227
|
+
h.merge!(fields)
|
|
228
|
+
loaded = true
|
|
229
|
+
fields[key]
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
hash
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|