git_hub_bub 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.travis.yml +9 -0
- data/Gemfile +3 -0
- data/README.md +214 -0
- data/Rakefile +15 -0
- data/changelog.md +3 -0
- data/git_hub_bub.gemspec +28 -0
- data/lib/git_hub_bub/request.rb +137 -0
- data/lib/git_hub_bub/response.rb +66 -0
- data/lib/git_hub_bub/version.rb +3 -0
- data/lib/git_hub_bub.rb +38 -0
- data/test/fixtures/vcr_cassettes/DELETE_milestone.yml +113 -0
- data/test/fixtures/vcr_cassettes/GET_rails/rails/issues.yml +298 -0
- data/test/fixtures/vcr_cassettes/GET_rails/rails/issues_page_2.yml +2389 -0
- data/test/fixtures/vcr_cassettes/HEAD_rails/rails/issues.yml +61 -0
- data/test/fixtures/vcr_cassettes/PATCH_user.yml +67 -0
- data/test/fixtures/vcr_cassettes/POST_/_owner/_repo/issues.yml +63 -0
- data/test/fixtures/vcr_cassettes/PUT_/repos/_owner/_repo/subscription.yml +62 -0
- data/test/git_hub_bub/request_test.rb +89 -0
- data/test/git_hub_bub/response_test.rb +50 -0
- data/test/test_helper.rb +38 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 833f07fd265f877003b7f27782bd3c0b40b63cfe
|
4
|
+
data.tar.gz: 4e63581207216c9c6147a384f7ea411c3432fd04
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 78424f9c9e28d47175c5c7ae70a9f5ac247be3bec1659cd7e33a3393e2167cc6a55675f81d14a891492564a48ddb48ff6037703a54a8c1e0bc8980a921a59583
|
7
|
+
data.tar.gz: 73fe92e332b1098962208a9ebe487855981c2cbeddea6f003208f0e5b6d128fc7f4d4b65dc8da2b08bf13d903e1c80a140905ed430837dacca8f3ea53042edd2
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# GitHubBub
|
2
|
+
|
3
|
+
A low level GitHub client that makes the disgusting issue of header based url pagination simple.
|
4
|
+
|
5
|
+
## What
|
6
|
+
|
7
|
+
Seriously? I just told you, see above.
|
8
|
+
|
9
|
+
## Why
|
10
|
+
|
11
|
+
I'm using this in a few places such as http://www.codetriage.com/. Need low level control, without sacrificing usability.
|
12
|
+
|
13
|
+
## Install
|
14
|
+
|
15
|
+
In your `Gemfile`
|
16
|
+
|
17
|
+
```
|
18
|
+
gem 'git_hub_bub'
|
19
|
+
```
|
20
|
+
|
21
|
+
Then run `$ bundle install`
|
22
|
+
|
23
|
+
## GET Whatever you Want:
|
24
|
+
|
25
|
+
To make requests to a `GET` endpoint use `GitHubBub.get`
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
response = GitHubBub.get('repos/rails/rails/issues')
|
29
|
+
```
|
30
|
+
|
31
|
+
Now you can do stuff like grab the json-ified body:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
response.json_body # => { foo: "bar" ...}
|
35
|
+
```
|
36
|
+
|
37
|
+
And get pagination (if there is any):
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
response.next_url # => "https://api.github.com/repositories/8514/issues?page=2"
|
41
|
+
response.last_url? # => false
|
42
|
+
response.pagination # => {"next_url"=>"https://api.github.com/repositories/8514/issues?page=2", "last_url"=>"https://api.github.com/repositories/8514/issues?page=18"}
|
43
|
+
```
|
44
|
+
|
45
|
+
## Passing Params
|
46
|
+
|
47
|
+
To pass parameters such as page number, or sorting or whatever, input a hash as the second argument.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
GitHubBub.get('repositories/8514/issues', page: 1, sort: 'comments', direction:'desc')
|
51
|
+
```
|
52
|
+
|
53
|
+
## Passing Anything Else
|
54
|
+
|
55
|
+
Anything else you pass in the third argument will be given to [Excon](https://github.com/geemus/excon) which powers GitHubBub. So if you want to set headers you can do it like this:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
GitHubBub.get('repositories/8514/issues', {page: 1}, {headers: { "Content-Type" => "application/x-www-form-urlencoded" }})
|
59
|
+
```
|
60
|
+
|
61
|
+
or
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
GitHubBub.get('repositories/8514/issues', {}, {headers: { "Content-Type" => "application/x-www-form-urlencoded" }})
|
65
|
+
```
|
66
|
+
|
67
|
+
See [Excon](https://github.com/geemus/excon) for documentation on more available options.
|
68
|
+
|
69
|
+
## Default Headers
|
70
|
+
|
71
|
+
Default headers are set in `GitHubBub::Request`
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
BASE_HEADERS = {'Accept' => "application/#{GITHUB_VERSION}", "User-Agent" => USER_AGENT}
|
75
|
+
```
|
76
|
+
|
77
|
+
You can change `GitHubBub::Request::GITHUB_VERSION` and `GitHubBub::Request::USER_AGENT`.
|
78
|
+
|
79
|
+
If you want any other default headers you can set them in `EXTRA_HEADERS` like so:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
GitHubBub::Request::EXTRA_HEADERS = { "Content-Type" => "application/x-www-form-urlencoded" }
|
83
|
+
```
|
84
|
+
|
85
|
+
Keep in mind this will change them for _every_ request. If you need logic behind your default headers, consider adding a `before_send_callback` to conditionally modify headers
|
86
|
+
|
87
|
+
|
88
|
+
## Authenticated Requests
|
89
|
+
|
90
|
+
Some GitHub endpoints require a user's authorization you can do that by passing in `token`:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
GitHubBub.get('/user', token: 'a38ck38ckgoldfishtoken')
|
94
|
+
```
|
95
|
+
|
96
|
+
Or you can manually set a header like so:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
GitHubBub.get('/user', {} {headers: {"Authorization" => "token a38ck38ckgoldfishtoken"}})
|
100
|
+
```
|
101
|
+
|
102
|
+
You will need to use one of these every time the GitHub api says "as an authenticated user".
|
103
|
+
|
104
|
+
## Callbacks
|
105
|
+
|
106
|
+
If you want to mess with the url or options before sending a request you can set a callback globally
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
GitHubBub::Request.set_callback do |request|
|
110
|
+
request.url = "http://schneems.com"
|
111
|
+
request.options = {do: "anything you want to _all_ the requests" }
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
## Endpoints
|
116
|
+
|
117
|
+
Check [GitHub Developer Docs](http://developer.github.com/). When you see something like
|
118
|
+
|
119
|
+
```
|
120
|
+
GET /users/:user/repos
|
121
|
+
```
|
122
|
+
|
123
|
+
It means you need to use the `GitHubBub.get` method and pass in a string like `'/users/schneems/repos'` the full request might look like this:
|
124
|
+
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
GitHubBub.get('/users/schneems/repos')
|
128
|
+
```
|
129
|
+
|
130
|
+
## Other HTTP Methods
|
131
|
+
|
132
|
+
Supports everything GitHub currently supports http://developer.github.com/v3/#http-verbs :
|
133
|
+
|
134
|
+
```
|
135
|
+
HEAD # => GitHubBub.head
|
136
|
+
GET # => GitHubBub.get
|
137
|
+
POST # => GitHubBub.post
|
138
|
+
PATCH # => GitHubBub.patch
|
139
|
+
PUT # => GitHubBub.put
|
140
|
+
DELETE # => GitHubBub.delete
|
141
|
+
```
|
142
|
+
|
143
|
+
|
144
|
+
## Configuration
|
145
|
+
|
146
|
+
You can use callbacks and there are some constants you can set, look in `GitHubBub::Request`. You will definetly want to set `GitHubBub::Request::USER_AGENT` It needs to be unique to your app: (http://developer.github.com/v3/#user-agent-required).
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
GitHubBub::Request::USER_AGENT = 'a-unique-and-permanent-agent-to-my-app'
|
150
|
+
```
|
151
|
+
|
152
|
+
## Testing
|
153
|
+
|
154
|
+
This gem is tested using the super cool request recording/stubbing framework [VCR](https://github.com/vcr/vcr). This does mean at one point and time all tests ran successfully against GitHub's servers. This also means if you want to write any tests any that are not already recorded will need to hit GitHub servers. So make sure the tests you write don't do anything really bad.
|
155
|
+
|
156
|
+
You'll also need a valid `.env` file
|
157
|
+
|
158
|
+
```sh
|
159
|
+
$ touch .env
|
160
|
+
```
|
161
|
+
|
162
|
+
Anything you put in this file will be sourced into your environment for tests. Here is an example `.env` file.
|
163
|
+
|
164
|
+
```sh
|
165
|
+
GITHUB_API_KEY=asdfe92fakeKey43ad638e35asdfd98167847248a26
|
166
|
+
OWNER=schneems
|
167
|
+
REPO=wicked
|
168
|
+
USER_NAME="Richard Schneeman"
|
169
|
+
WATCH_OWNER=emberjs
|
170
|
+
WATCH_REPO=ember.js
|
171
|
+
```
|
172
|
+
|
173
|
+
You will need to change most of these values
|
174
|
+
|
175
|
+
```
|
176
|
+
GITHUB_API_KEY=asdfe92fakeKey43ad638e35asdfd98167847248a26
|
177
|
+
```
|
178
|
+
|
179
|
+
Your github API key, you can get one from https://github.com/settings/applications.
|
180
|
+
|
181
|
+
```
|
182
|
+
OWNER=schneems
|
183
|
+
```
|
184
|
+
|
185
|
+
Your public github username
|
186
|
+
|
187
|
+
```
|
188
|
+
REPO=wicked
|
189
|
+
```
|
190
|
+
|
191
|
+
A repo that you have commit access too.
|
192
|
+
|
193
|
+
```
|
194
|
+
USER_NAME="Richard Schneeman"
|
195
|
+
```
|
196
|
+
|
197
|
+
Your real name
|
198
|
+
|
199
|
+
```
|
200
|
+
WATCH_OWNER=emberjs
|
201
|
+
```
|
202
|
+
|
203
|
+
The `:owner` of a repo you might watch or want to watch, needs to be combined with WATCH_REPO. This should be different from `OWNER`
|
204
|
+
|
205
|
+
```
|
206
|
+
WATCH_REPO=ember.js
|
207
|
+
```
|
208
|
+
|
209
|
+
A repo that the `WATCH_OWNER` owns. Should be different from `REPO`
|
210
|
+
|
211
|
+
|
212
|
+
## License
|
213
|
+
|
214
|
+
MIT
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'git_hub_bub'
|
3
|
+
|
4
|
+
task :default => [:test]
|
5
|
+
|
6
|
+
require 'rake'
|
7
|
+
require 'rake/testtask'
|
8
|
+
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
data/changelog.md
ADDED
data/git_hub_bub.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'git_hub_bub/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "git_hub_bub"
|
8
|
+
gem.version = GitHubBub::VERSION
|
9
|
+
gem.authors = ["Richard Schneeman"]
|
10
|
+
gem.email = ["richard.schneeman+rubygems@gmail.com"]
|
11
|
+
gem.description = %q{git_hub_bub makes github requests}
|
12
|
+
gem.summary = %q{git_hub_bub makes github requests}
|
13
|
+
gem.homepage = "https://github.com/schneems/git_hub_bub"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_dependency "rrrretry"
|
22
|
+
gem.add_dependency "excon"
|
23
|
+
gem.add_development_dependency "rake"
|
24
|
+
gem.add_development_dependency "vcr", '~> 2.5.0'
|
25
|
+
gem.add_development_dependency "webmock", '~> 1.11.0'
|
26
|
+
gem.add_development_dependency "dotenv"
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module GitHubBub
|
2
|
+
class RequestError < StandardError; end
|
3
|
+
|
4
|
+
class Request
|
5
|
+
attr_accessor :url, :options, :token
|
6
|
+
BASE_URI = 'https://api.github.com'
|
7
|
+
USER_AGENT ||= SecureRandom.hex(16)
|
8
|
+
GITHUB_VERSION = "vnd.github.3.raw+json"
|
9
|
+
EXTRA_HEADERS ||= {}
|
10
|
+
BASE_HEADERS = EXTRA_HEADERS.merge({'Accept' => "application/#{GITHUB_VERSION}", "User-Agent" => USER_AGENT})
|
11
|
+
RETRIES = 1
|
12
|
+
|
13
|
+
def initialize(url, query = {}, options = {})
|
14
|
+
self.url = url.include?("http") ? url : File.join(BASE_URI, url)
|
15
|
+
self.options = options || {}
|
16
|
+
self.options[:query] = query if query && !query.empty?
|
17
|
+
options[:headers] = BASE_HEADERS.merge(options[:headers]|| {})
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.head(url, query = {}, options = {})
|
21
|
+
self.new(url, query, options).head
|
22
|
+
end
|
23
|
+
|
24
|
+
def head
|
25
|
+
wrap_request do
|
26
|
+
Excon.head(url, options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.get(url, query = {}, options = {})
|
31
|
+
self.new(url, query, options).get
|
32
|
+
end
|
33
|
+
|
34
|
+
def get
|
35
|
+
wrap_request do
|
36
|
+
ex = Excon.get(url, options)
|
37
|
+
ex = Excon.get(@location, options) if @location = ex.headers["Location"]
|
38
|
+
ex
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.post(url, query = {}, options = {})
|
43
|
+
self.new(url, query, options).post
|
44
|
+
end
|
45
|
+
|
46
|
+
def post
|
47
|
+
wrap_request do
|
48
|
+
Excon.post(url, options)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.patch(url, query = {}, options = {})
|
53
|
+
self.new(url, query, options).patch
|
54
|
+
end
|
55
|
+
|
56
|
+
def patch
|
57
|
+
wrap_request do
|
58
|
+
Excon.patch(url, options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.put(url, query = {}, options = {})
|
63
|
+
self.new(url, query, options).put
|
64
|
+
end
|
65
|
+
|
66
|
+
def put
|
67
|
+
wrap_request do
|
68
|
+
Excon.put(url, options)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.delete(url, query = {}, options = {})
|
73
|
+
self.new(url, query, options).delete
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete
|
77
|
+
wrap_request do
|
78
|
+
Excon.delete(url, options)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def wrap_request(&block)
|
83
|
+
before_callbacks!
|
84
|
+
set_auth_from_token!
|
85
|
+
query_to_json_body!
|
86
|
+
response = RETRIES.times.retry do
|
87
|
+
GitHubBub::Response.create(yield)
|
88
|
+
end
|
89
|
+
raise RequestError, "message: '#{response.json_body['message']}', url: '#{url}', response: '#{response.inspect}'" unless response.status.to_s =~ /^2.*/
|
90
|
+
return response
|
91
|
+
end
|
92
|
+
|
93
|
+
# do they take query params? do they take :body?
|
94
|
+
# who cares, send them both!
|
95
|
+
def query_to_json_body!
|
96
|
+
options[:body] = options[:query].to_json if options[:query]
|
97
|
+
end
|
98
|
+
|
99
|
+
def set_auth_from_token!
|
100
|
+
return unless token
|
101
|
+
options[:headers]["Authorization"] ||= "token #{token}"
|
102
|
+
end
|
103
|
+
|
104
|
+
def token
|
105
|
+
@token ||= if options[:headers] && token_string = options[:headers]["Authorization"]
|
106
|
+
token_string.split(/\s/).last
|
107
|
+
elsif options[:query] && token = options[:query].delete(:token)
|
108
|
+
token
|
109
|
+
else
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
alias :token? :token
|
114
|
+
|
115
|
+
def self.set_before_callback(&block)
|
116
|
+
before_callbacks << block
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.before_callbacks
|
120
|
+
@before_callbacks ||=[]
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.clear_callbacks
|
124
|
+
@before_callbacks = []
|
125
|
+
end
|
126
|
+
|
127
|
+
def before_callbacks!
|
128
|
+
self.class.before_callbacks.each do |callback|
|
129
|
+
run_callback &callback
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def run_callback(&block)
|
134
|
+
yield self
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module GitHubBub
|
2
|
+
class Response < Excon::Response
|
3
|
+
|
4
|
+
def self.create(response)
|
5
|
+
self.new(response.data)
|
6
|
+
end
|
7
|
+
|
8
|
+
def json_body
|
9
|
+
::JSON.parse(self.body)
|
10
|
+
end
|
11
|
+
|
12
|
+
def pagination
|
13
|
+
@pagination ||= parse_pagination
|
14
|
+
end
|
15
|
+
|
16
|
+
def parsed_response
|
17
|
+
response.body.inspect
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_url
|
21
|
+
pagination['next_url']
|
22
|
+
end
|
23
|
+
|
24
|
+
def prev_url
|
25
|
+
pagination['prev_url']
|
26
|
+
end
|
27
|
+
alias :previous_url :prev_url
|
28
|
+
|
29
|
+
def last_url
|
30
|
+
pagination['last_url']
|
31
|
+
end
|
32
|
+
|
33
|
+
def first_url
|
34
|
+
pagination['first_url']
|
35
|
+
end
|
36
|
+
|
37
|
+
def last_page?
|
38
|
+
return true if next_url.nil?
|
39
|
+
last_page_number = page_number_from_url(last_url)
|
40
|
+
next_page_number = page_number_from_url(next_url)
|
41
|
+
return next_page_number > last_page_number
|
42
|
+
end
|
43
|
+
|
44
|
+
def first_page?
|
45
|
+
return true if first_url.nil?
|
46
|
+
return false
|
47
|
+
end
|
48
|
+
|
49
|
+
def page_number_from_url(url)
|
50
|
+
query = ::URI.parse(url).query
|
51
|
+
::CGI.parse(query)["page"].first.to_i
|
52
|
+
end
|
53
|
+
|
54
|
+
def header_links
|
55
|
+
(headers['link'] || headers['Link'] || "").split(',')
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_pagination
|
59
|
+
header_links.each_with_object({}) do |element, hash|
|
60
|
+
key = element[/rel=["'](.*)['"]/, 1]
|
61
|
+
value = element[/<(.*)>/, 1]
|
62
|
+
hash["#{key}_url"] = value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|