gh-akerl 0.15.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|