ghee 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +1 -0
- data/.gitignore +5 -0
- data/.rspec +0 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +353 -0
- data/Rakefile +15 -0
- data/ghee.gemspec +31 -0
- data/lib/ghee.rb +49 -0
- data/lib/ghee/api/authorizations.rb +14 -0
- data/lib/ghee/api/events.rb +25 -0
- data/lib/ghee/api/gists.rb +75 -0
- data/lib/ghee/api/issues.rb +98 -0
- data/lib/ghee/api/milestones.rb +47 -0
- data/lib/ghee/api/orgs.rb +103 -0
- data/lib/ghee/api/repos.rb +96 -0
- data/lib/ghee/api/users.rb +72 -0
- data/lib/ghee/connection.rb +33 -0
- data/lib/ghee/resource_proxy.rb +111 -0
- data/lib/ghee/state_methods.rb +29 -0
- data/lib/ghee/version.rb +4 -0
- data/spec/ghee/api/authorizations_spec.rb +46 -0
- data/spec/ghee/api/events_spec.rb +47 -0
- data/spec/ghee/api/gists_spec.rb +153 -0
- data/spec/ghee/api/hooks_spec.rb +52 -0
- data/spec/ghee/api/issues_spec.rb +122 -0
- data/spec/ghee/api/milestones_spec.rb +103 -0
- data/spec/ghee/api/orgs_spec.rb +56 -0
- data/spec/ghee/api/repos_spec.rb +126 -0
- data/spec/ghee/api/teams_spec.rb +90 -0
- data/spec/ghee/api/users_spec.rb +102 -0
- data/spec/ghee/connection_spec.rb +54 -0
- data/spec/ghee/resource_proxy_spec.rb +207 -0
- data/spec/ghee_spec.rb +42 -0
- data/spec/settings.yml.sample +5 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +16 -0
- metadata +208 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
class Ghee
|
2
|
+
|
3
|
+
# API module encapsulates all of API endpoints
|
4
|
+
# implemented thus far
|
5
|
+
#
|
6
|
+
module API
|
7
|
+
|
8
|
+
# The Users module handles all of the Github User
|
9
|
+
# API endpoints
|
10
|
+
#
|
11
|
+
module Users
|
12
|
+
|
13
|
+
# Users::Proxy inherits from Ghee::Proxy and
|
14
|
+
# enables defining methods on the proxy object
|
15
|
+
#
|
16
|
+
class Proxy < ::Ghee::ResourceProxy
|
17
|
+
include Ghee::CUD
|
18
|
+
|
19
|
+
# Gists for a user
|
20
|
+
#
|
21
|
+
# Returns json
|
22
|
+
#
|
23
|
+
def gists(params={})
|
24
|
+
Ghee::API::Gists::Proxy.new(connection,"#{path_prefix}/gists",params)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Repos for a user
|
28
|
+
#
|
29
|
+
# Returns json
|
30
|
+
#
|
31
|
+
def repos(name=nil, params={})
|
32
|
+
params = name if name.is_a?Hash
|
33
|
+
prefix = name.is_a?(String) ? "/repos/#{self["login"]}/#{name}" : "#{path_prefix}/repos"
|
34
|
+
Ghee::API::Repos::Proxy.new(connection,prefix, params)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns list of the provided users organizations or
|
38
|
+
# an organization by name
|
39
|
+
#
|
40
|
+
# org - String name of the organization (optional)
|
41
|
+
#
|
42
|
+
# Returns json
|
43
|
+
#
|
44
|
+
def orgs(org=nil, params={})
|
45
|
+
params = org if org.is_a?Hash
|
46
|
+
prefix = org.is_a?(String) ? "/orgs/#{org}" : "#{path_prefix}/orgs"
|
47
|
+
Ghee::API::Orgs::Proxy.new(connection, prefix, params)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
# Get authenticated user
|
54
|
+
#
|
55
|
+
# Returns json
|
56
|
+
#
|
57
|
+
def user
|
58
|
+
Proxy.new(connection, '/user')
|
59
|
+
end
|
60
|
+
|
61
|
+
# Get a single user
|
62
|
+
#
|
63
|
+
# user - String of user login
|
64
|
+
#
|
65
|
+
# Returns json
|
66
|
+
#
|
67
|
+
def users(user)
|
68
|
+
Proxy.new(connection, "/users/#{user}")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'multi_json'
|
4
|
+
|
5
|
+
class Ghee
|
6
|
+
class Connection < Faraday::Connection
|
7
|
+
attr_reader :hash
|
8
|
+
|
9
|
+
# Instantiates connection, accepts an options hash
|
10
|
+
# for authenticated access
|
11
|
+
#
|
12
|
+
# OAuth2 expects
|
13
|
+
# :access_token => "OAuth Token"
|
14
|
+
#
|
15
|
+
# Basic auth expects
|
16
|
+
# :basic_auth => {:user_name => "octocat", :password => "secret"}
|
17
|
+
def initialize(hash={})
|
18
|
+
@hash = hash
|
19
|
+
access_token = hash[:access_token] if hash.has_key?:access_token
|
20
|
+
basic_auth = hash[:basic_auth] if hash.has_key?:basic_auth
|
21
|
+
|
22
|
+
super('https://api.github.com') do |builder|
|
23
|
+
builder.use Faraday::Request::JSON
|
24
|
+
builder.use Faraday::Response::ParseJson
|
25
|
+
builder.adapter Faraday.default_adapter
|
26
|
+
end
|
27
|
+
|
28
|
+
self.headers["Authorization"] = "token #{access_token}" if access_token
|
29
|
+
self.basic_auth(basic_auth[:user_name], basic_auth[:password]) if basic_auth
|
30
|
+
self.headers["Accept"] = 'application/json'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
class Ghee
|
2
|
+
|
3
|
+
# ResourceProxy lets us create a virtual
|
4
|
+
# proxy for any API resource, utilizing
|
5
|
+
# method_missing to handle passing
|
6
|
+
# messages to the real object
|
7
|
+
#
|
8
|
+
class ResourceProxy
|
9
|
+
|
10
|
+
# Undefine methods that might get in the way
|
11
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__|instance_eval|instance_variable_get|object_id/ }
|
12
|
+
|
13
|
+
# Make connection and path_prefix readable
|
14
|
+
attr_reader :connection, :path_prefix, :params
|
15
|
+
|
16
|
+
# Expose pagination data
|
17
|
+
attr_reader :current_page, :total, :pagination
|
18
|
+
|
19
|
+
# Instantiates proxy with the connection
|
20
|
+
# and path_prefix
|
21
|
+
#
|
22
|
+
# connection - Ghee::Connection object
|
23
|
+
# path_prefix - String
|
24
|
+
#
|
25
|
+
def initialize(connection, path_prefix, params = {})
|
26
|
+
@connection, @path_prefix, @params = connection, path_prefix, params
|
27
|
+
end
|
28
|
+
|
29
|
+
# Method_missing takes any message passed
|
30
|
+
# to the ResourceProxy and sends it to the
|
31
|
+
# real object
|
32
|
+
#
|
33
|
+
# message - Message object
|
34
|
+
# args* - Arguements passed
|
35
|
+
#
|
36
|
+
def method_missing(message, *args, &block)
|
37
|
+
subject.send(message, *args, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Subject is the response body parsed
|
41
|
+
# as json
|
42
|
+
#
|
43
|
+
# Returns json
|
44
|
+
#
|
45
|
+
def subject
|
46
|
+
@subject ||= connection.get(path_prefix){|req| req.params.merge!params }.body
|
47
|
+
end
|
48
|
+
|
49
|
+
# Paginate is a helper method to handle
|
50
|
+
# request pagination to the github api
|
51
|
+
#
|
52
|
+
# options - Hash containing pagination params
|
53
|
+
# eg;
|
54
|
+
# :per_page => 100, :page => 1
|
55
|
+
#
|
56
|
+
# Returns self
|
57
|
+
#
|
58
|
+
def paginate(options)
|
59
|
+
@current_page = options.fetch(:page) {raise ArgumentError, ":page parameter required"}
|
60
|
+
per_page = options.delete(:per_page) || 30
|
61
|
+
response = connection.get do |req|
|
62
|
+
req.url path_prefix, :per_page => per_page, :page => current_page
|
63
|
+
req.params.merge! params
|
64
|
+
end
|
65
|
+
|
66
|
+
if @subject.nil?
|
67
|
+
@subject = response.body
|
68
|
+
else
|
69
|
+
@subject = @subject.concat response.body
|
70
|
+
end
|
71
|
+
|
72
|
+
parse_link_header response.headers.delete("link")
|
73
|
+
|
74
|
+
return self
|
75
|
+
end
|
76
|
+
|
77
|
+
def all
|
78
|
+
return self if pagination && next_page.nil?
|
79
|
+
|
80
|
+
self.paginate :per_page => 100, :page => next_page || 1
|
81
|
+
|
82
|
+
self.all
|
83
|
+
end
|
84
|
+
|
85
|
+
# Generate first_page, last_page, next_page, prev_page convienence methods
|
86
|
+
%w{ next prev first last }.each do |term|
|
87
|
+
define_method "#{term}_page" do
|
88
|
+
pagination ? pagination[term.to_sym] ? pagination[term.to_sym][:page] : nil : nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def parse_link_header(header)
|
95
|
+
return @total = subject.size, @pagination = {} if header.nil?
|
96
|
+
require 'cgi'
|
97
|
+
pattern = /<(?<link>.*)>;\s+rel="(?<rel>.*)"/
|
98
|
+
matches = {}
|
99
|
+
header.split(',').each do |m|
|
100
|
+
match = pattern.match m
|
101
|
+
uri = URI.parse(match[:link])
|
102
|
+
uri_params = CGI.parse(uri.query)
|
103
|
+
page = uri_params["page"].first.to_i
|
104
|
+
per_page = uri_params["per_page"] ? uri_params["per_page"].first.to_i : 30
|
105
|
+
matches[match[:rel].to_sym] = {:link => match[:link], :page => page, :per_page => per_page}
|
106
|
+
end
|
107
|
+
@pagination = matches
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Ghee
|
2
|
+
module CUD
|
3
|
+
|
4
|
+
# Creates
|
5
|
+
#
|
6
|
+
# return json
|
7
|
+
#
|
8
|
+
def create(attributes)
|
9
|
+
connection.post(path_prefix,attributes).body
|
10
|
+
end
|
11
|
+
|
12
|
+
# Patchs
|
13
|
+
#
|
14
|
+
# return json
|
15
|
+
#
|
16
|
+
def patch(attributes)
|
17
|
+
connection.patch(path_prefix, attributes).body
|
18
|
+
end
|
19
|
+
|
20
|
+
# Destroys
|
21
|
+
#
|
22
|
+
# return boolean
|
23
|
+
#
|
24
|
+
def destroy
|
25
|
+
connection.delete(path_prefix).status == 204
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
data/lib/ghee/version.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Ghee::API::Authorizations do
|
4
|
+
subject { Ghee.new(GH_AUTH) }
|
5
|
+
|
6
|
+
describe "#authorizations" do
|
7
|
+
|
8
|
+
context "with a test authorization" do
|
9
|
+
before :all do
|
10
|
+
VCR.use_cassette "authorizations.create.test" do
|
11
|
+
@test_auth = subject.authorizations.create :scopes => ["public_repo"]
|
12
|
+
@test_auth.should_not be_nil
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:test_auth) {@test_auth}
|
17
|
+
|
18
|
+
it "should return an auth" do
|
19
|
+
VCR.use_cassette "authorizations(id)" do
|
20
|
+
auth = subject.authorizations(test_auth["id"])
|
21
|
+
auth["scopes"].should include("public_repo")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return a list of auths" do
|
26
|
+
VCR.use_cassette "authorizations" do
|
27
|
+
auth = subject.authorizations
|
28
|
+
auth.size().should > 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should patch an auth" do
|
33
|
+
VCR.use_cassette "authorization(id).patch" do
|
34
|
+
auth = subject.authorizations(test_auth["id"]).patch :add_scopes => ["repo"]
|
35
|
+
auth["scopes"].should include("repo")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
after :all do
|
40
|
+
VCR.use_cassette "authorizations(id).destroy" do
|
41
|
+
subject.authorizations(test_auth["id"]).destroy.should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Ghee::API::Events do
|
4
|
+
subject { Ghee.new(GH_AUTH) }
|
5
|
+
|
6
|
+
EventTypes = ["CommitComment","CreateEvent","DeleteEvent","DownloadEvent","FollowEvent",
|
7
|
+
"ForkEvent","ForkApplyEvent","GistEvent","GollumEvent","IssueCommentEvent",
|
8
|
+
"IssuesEvent","MemberEvent","PublicEvent","PullRequestEvent","PushEvent",
|
9
|
+
"TeamAddEvent","WatchEvent"]
|
10
|
+
|
11
|
+
def should_be_an_event(event)
|
12
|
+
EventTypes.should include(event['type'])
|
13
|
+
event['repo'].should be_instance_of(Hash)
|
14
|
+
event['actor'].should be_instance_of(Hash)
|
15
|
+
event['org'].should be_instance_of(Hash)
|
16
|
+
event['created_at'].should_not be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#events" do
|
20
|
+
it "should return public events" do
|
21
|
+
VCR.use_cassette('events') do
|
22
|
+
events = subject.events
|
23
|
+
events.size.should > 0
|
24
|
+
should_be_an_event(events.first)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe "#paginate" do
|
28
|
+
it "should return page 1" do
|
29
|
+
VCR.use_cassette "events.page1" do
|
30
|
+
events = subject.events.paginate :page => 1
|
31
|
+
events.size.should > 0
|
32
|
+
events.next_page.should == 2
|
33
|
+
should_be_an_event(events.first)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
it "should return page 2" do
|
37
|
+
VCR.use_cassette "events.page2" do
|
38
|
+
events = subject.events.paginate :page => 2
|
39
|
+
events.size.should > 0
|
40
|
+
events.next_page.should == 3
|
41
|
+
events.prev_page.should == 1
|
42
|
+
should_be_an_event(events.first)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Ghee::API::Gists do
|
4
|
+
subject { Ghee.new(GH_AUTH) }
|
5
|
+
|
6
|
+
def should_be_a_gist(gist)
|
7
|
+
gist['url'].should include('https://api.github.com/gists/')
|
8
|
+
gist['user']['url'].should include('https://api.github.com/users/')
|
9
|
+
gist['created_at'].should_not be_nil
|
10
|
+
gist['files'].should be_instance_of(Hash)
|
11
|
+
gist['files'].size.should > 0
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#users" do
|
15
|
+
describe "#gists" do
|
16
|
+
it "should return users gists" do
|
17
|
+
VCR.use_cassette('users(login).gists') do
|
18
|
+
gists = subject.users('jonmagic').gists
|
19
|
+
gists.size.should > 0
|
20
|
+
should_be_a_gist(gists.first)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#gists" do
|
27
|
+
it "should return gists for authenticated user" do
|
28
|
+
VCR.use_cassette('gists') do
|
29
|
+
gists = subject.gists
|
30
|
+
gists.size.should > 0
|
31
|
+
should_be_a_gist(gists.first)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#public" do
|
36
|
+
it "should return public gists" do
|
37
|
+
VCR.use_cassette('gists.public') do
|
38
|
+
gists = subject.gists.public
|
39
|
+
gists.size.should > 0
|
40
|
+
should_be_a_gist(gists.first)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#starred" do
|
46
|
+
it "should return starred gists" do
|
47
|
+
VCR.use_cassette('gists.starred') do
|
48
|
+
gists = subject.gists.starred
|
49
|
+
gists.size.should > 0
|
50
|
+
should_be_a_gist(gists.first)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#create" do
|
56
|
+
it "should create a gist" do
|
57
|
+
VCR.use_cassette('gists.create') do
|
58
|
+
gist = subject.gists.create({
|
59
|
+
:description => "Testing the ghee api",
|
60
|
+
:public => true,
|
61
|
+
:files => {
|
62
|
+
'ghee_test.txt' => {
|
63
|
+
:content => "Booya!"
|
64
|
+
}
|
65
|
+
}
|
66
|
+
})
|
67
|
+
should_be_a_gist(gist)
|
68
|
+
subject.gists(gist['id']).destroy
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with gist id" do
|
74
|
+
before(:all) do
|
75
|
+
VCR.use_cassette('gists.test') do
|
76
|
+
@test_gist = subject.gists.create({:public => false, :files => {'file.txt' => {:content => 'ready to destroy'}}})
|
77
|
+
end
|
78
|
+
end
|
79
|
+
let(:test_gist) { @test_gist }
|
80
|
+
|
81
|
+
it "should return single gist" do
|
82
|
+
VCR.use_cassette('gists(id)') do
|
83
|
+
gist = subject.gists(test_gist['id'])
|
84
|
+
gist['id'].should == test_gist['id']
|
85
|
+
should_be_a_gist(gist)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#patch" do
|
90
|
+
it "should patch the gist" do
|
91
|
+
VCR.use_cassette('gists(id).patch') do
|
92
|
+
gist = subject.gists(test_gist['id']).patch({
|
93
|
+
:files => {
|
94
|
+
'test.md' => {
|
95
|
+
:content => 'clarified butter'
|
96
|
+
}
|
97
|
+
}
|
98
|
+
})
|
99
|
+
should_be_a_gist(gist)
|
100
|
+
gist['files']['test.md']['content'].should == 'clarified butter'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#star" do
|
106
|
+
it "should star the gist" do
|
107
|
+
VCR.use_cassette('gists(id).star') do
|
108
|
+
subject.gists(test_gist['id']).star.should be_true
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "#unstar" do
|
114
|
+
it "should star the gist" do
|
115
|
+
VCR.use_cassette('gists(id).unstar') do
|
116
|
+
subject.gists(test_gist['id']).unstar.should be_true
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "#starred?" do
|
122
|
+
it "should return true if gist is starred" do
|
123
|
+
VCR.use_cassette('gists(id).starred? is true') do
|
124
|
+
subject.gists(test_gist['id']).star
|
125
|
+
subject.gists(test_gist['id']).starred?.should be_true
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should return false if gist is unstarred" do
|
130
|
+
VCR.use_cassette('gists(id).starred? is false') do
|
131
|
+
subject.gists(test_gist['id']).unstar
|
132
|
+
subject.gists(test_gist['id']).starred?.should be_false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "#destroy" do
|
138
|
+
it "should delete the gist and return true" do
|
139
|
+
VCR.use_cassette('gists(id).destroy true') do
|
140
|
+
gist = subject.gists.create({:public => false, :files => {'file.txt' => {:content => 'ready to destroy'}}})
|
141
|
+
subject.gists(gist['id']).destroy.should be_true
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should return false if gist doesn't exist" do
|
146
|
+
VCR.use_cassette('gists(id).destroy false') do
|
147
|
+
subject.gists("12345678901234567890").destroy.should be_false
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|