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