gh 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -17,6 +17,7 @@ This will by default use all the middleware that ships with GH, in the following
17
17
  * `GH::LazyLoader` - will load missing fields when accessed (handy for dealing with incomplete data without sending to many requests)
18
18
  * `GH::MergeCommit` - adds infos about merge commits to pull request payloads
19
19
  * `GH::LinkFollower` - will add content of hypermedia links as fields (lazyly), allows you to traverse relations
20
+ * `GH::Pagination` - adds support for transparent pagination
20
21
  * `GH::Instrumentation` - let's you instrument `gh`
21
22
 
22
23
  ## Main Entry Points
data/gh.gemspec CHANGED
@@ -23,4 +23,6 @@ Gem::Specification.new do |s|
23
23
  s.add_runtime_dependency 'backports', '~> 2.3'
24
24
  s.add_runtime_dependency 'multi_json', '~> 1.0'
25
25
  s.add_runtime_dependency 'addressable'
26
+ s.add_runtime_dependency 'net-http-persistent'
27
+ s.add_runtime_dependency 'net-http-pipeline'
26
28
  end
data/lib/gh.rb CHANGED
@@ -6,12 +6,14 @@ module GH
6
6
  autoload :Cache, 'gh/cache'
7
7
  autoload :Case, 'gh/case'
8
8
  autoload :Error, 'gh/error'
9
+ autoload :FaradayAdapter, 'gh/faraday_adapter'
9
10
  autoload :Instrumentation, 'gh/instrumentation'
10
11
  autoload :LazyLoader, 'gh/lazy_loader'
11
12
  autoload :LinkFollower, 'gh/link_follower'
12
13
  autoload :MergeCommit, 'gh/merge_commit'
13
14
  autoload :Normalizer, 'gh/normalizer'
14
15
  autoload :Pagination, 'gh/pagination'
16
+ autoload :Parallel, 'gh/parallel'
15
17
  autoload :Remote, 'gh/remote'
16
18
  autoload :Response, 'gh/response'
17
19
  autoload :ResponseWrapper, 'gh/response_wrapper'
@@ -35,10 +37,11 @@ module GH
35
37
  end
36
38
 
37
39
  extend SingleForwardable
38
- def_delegators :current, :api_host, :[], :reset, :load, :post, :delete, :patch, :put
40
+ def_delegators :current, :api_host, :[], :reset, :load, :post, :delete, :patch, :put, :in_parallel, :in_parallel?
39
41
 
40
42
  DefaultStack = Stack.new do
41
43
  use Instrumentation
44
+ use Parallel
42
45
  use Pagination
43
46
  use LinkFollower
44
47
  use MergeCommit
@@ -43,19 +43,12 @@ module GH
43
43
  clear_partial or clear_all
44
44
  end
45
45
 
46
- # Public: Retrieves resources from Github and caches response for future access.
47
- #
48
- # Examples
49
- #
50
- # Github::Cache.new['users/rkh'] # => { ... }
51
- #
52
- # Returns the Response.
53
- def [](key)
46
+ private
47
+
48
+ def fetch_resource(key)
54
49
  cache.fetch(prefixed(key)) { super }
55
50
  end
56
51
 
57
- private
58
-
59
52
  def clear_partial
60
53
  return false unless cache.respond_to? :delete_matched
61
54
  pattern = "^" << Regexp.escape(prefixed(""))
@@ -18,7 +18,7 @@ module GH
18
18
  private
19
19
 
20
20
  def lazy_load(hash, key, link)
21
- result = modify_hash(backend[link].data, true)
21
+ modify_hash(backend[link].data, true)
22
22
  rescue Exception => error
23
23
  raise Error.new(error, hash)
24
24
  end
@@ -0,0 +1,49 @@
1
+ require 'gh'
2
+
3
+ module GH
4
+ # Public: ...
5
+ class NestedResources < Wrapper
6
+ wraps GH::Normalizer
7
+ double_dispatch
8
+
9
+ def modify_hash(hash, loaded = false)
10
+ hash = super(hash)
11
+ link = hash['_links'].try(:[], 'self') unless loaded
12
+ set_links hash, Addressable::URI.parse(link['href']) if link
13
+ hash
14
+ end
15
+
16
+ def add(hash, link, name, path = name)
17
+ hash["_links"][name] ||= { "href" => nested(link, path) }
18
+ end
19
+
20
+ def nested(link, path)
21
+ new_link = link.dup
22
+ if path.start_with? '/'
23
+ new_link.path = path
24
+ else
25
+ new_link.path += path
26
+ end
27
+ new_link
28
+ end
29
+
30
+ def set_links(hash, link)
31
+ case link.path
32
+ when '/gists'
33
+ add hash, link, 'public'
34
+ add hash, link, 'starred'
35
+ when %r{^/repos/[^/]+/[^/]+$}
36
+ add hash, link, 'commits', 'git/commits'
37
+ add hash, link, 'refs', 'git/refs'
38
+ add hash, link, 'tags', 'git/tags'
39
+ add hash, link, 'issues'
40
+ when %r{^/repos/[^/]+/[^/]+/issues/\d+$}
41
+ add hash, link, 'comments'
42
+ add hash, link, 'events'
43
+ when '/user'
44
+ add hash, link, 'gists', '/gists'
45
+ add hash, link, 'issues', '/issues'
46
+ end
47
+ end
48
+ end
49
+ end
@@ -4,10 +4,7 @@ require 'time'
4
4
  module GH
5
5
  # Public: A Wrapper class that deals with normalizing Github responses.
6
6
  class Normalizer < Wrapper
7
- # Public: Fetches and normalizes a github entity.
8
- #
9
- # Returns normalized Response.
10
- def [](key)
7
+ def generate_response(key, response)
11
8
  result = super
12
9
  links(result)['self'] ||= { 'href' => full_url(key).to_s } if result.respond_to? :to_hash
13
10
  result
@@ -18,6 +18,7 @@ module GH
18
18
  end
19
19
 
20
20
  def [](value)
21
+ raise TypeError, "index has to be an Integer, got #{value.class}" unless value.is_a? Integer
21
22
  return @page[value] if value < @page.size
22
23
  next_page[value - @page.size]
23
24
  end
@@ -32,8 +33,16 @@ module GH
32
33
  wraps GH::Normalizer
33
34
  double_dispatch
34
35
 
36
+ def fetch_resource(key)
37
+ url = full_url(key)
38
+ params = url.query_values || {}
39
+ params['per_page'] ||= 100
40
+ url.query_values = params
41
+ super url.request_uri
42
+ end
43
+
35
44
  def modify_response(response)
36
- return response unless response.headers['link'] =~ /<([^>]+)>;\s*rel=\"next\"/
45
+ return response unless response.respond_to? :to_ary and response.headers['link'] =~ /<([^>]+)>;\s*rel=\"next\"/
37
46
  Paginated.new(response, $1, self)
38
47
  end
39
48
  end
@@ -0,0 +1,55 @@
1
+ require 'gh'
2
+ require 'thread'
3
+ require 'backports/basic_object' unless defined? BasicObject
4
+
5
+ module GH
6
+ # Public: ...
7
+ class Parallel < Wrapper
8
+ class Dummy < BasicObject
9
+ attr_accessor :__delegate__
10
+ def method_missing(*args)
11
+ ::Kernel.raise ::RuntimeError, "response not yet loaded" if __delegate__.nil?
12
+ __delegate__.__send__(*args)
13
+ end
14
+ end
15
+
16
+ def setup(*)
17
+ @in_parallel = false
18
+ @mutex = Mutex.new
19
+ @queue = []
20
+ super
21
+ end
22
+
23
+ def generate_response(key, response)
24
+ return super unless in_parallel?
25
+ dummy = Dummy.new
26
+ @mutex.synchronize { @queue << [dummy, key, response] }
27
+ dummy
28
+ end
29
+
30
+ def in_parallel(&block)
31
+ return yield if in_parallel?
32
+ was, @in_parallel = @in_parallel, true
33
+ result = connection.in_parallel(&block)
34
+ @mutex.synchronize do
35
+ @queue.each { |dummy, key, response| dummy.__delegate__ = backend.generate_response(key, response) }
36
+ @queue.clear
37
+ end
38
+ result
39
+ ensure
40
+ @in_parallel = was unless was.nil?
41
+ end
42
+
43
+ def in_parallel?
44
+ @in_parallel
45
+ end
46
+
47
+ def connection
48
+ @connection ||= begin
49
+ layer = backend
50
+ layer = layer.backend until layer.respond_to? :connection
51
+ layer.connection
52
+ end
53
+ end
54
+ end
55
+ end
@@ -47,7 +47,7 @@ module GH
47
47
  builder.request(:basic_auth, username, password) if username and password
48
48
  builder.request(:retry)
49
49
  builder.response(:raise_error)
50
- builder.adapter(options[:adapter] || :net_http)
50
+ builder.use(options[:adapter] || GH::FaradayAdapter)
51
51
  end
52
52
  end
53
53
 
@@ -56,17 +56,13 @@ module GH
56
56
  "#<#{self.class}: #{api_host}>"
57
57
  end
58
58
 
59
- # Public: Retrieves resources from Github.
60
- #
61
- # Examples
62
- #
63
- # Github::Remote.new['users/rkh'] # => { ... }
64
- #
65
- # Raises Faraday::Error::ResourceNotFound if the resource returns status 404.
66
- # Raises Faraday::Error::ClientError if the resource returns a status between 400 and 599.
67
- # Returns the Response.
68
- def [](key)
69
- response = frontend.http(:get, path_for(key), headers)
59
+ # Internal: ...
60
+ def fetch_resource(key)
61
+ frontend.http(:get, path_for(key), headers)
62
+ end
63
+
64
+ # Internal: ...
65
+ def generate_response(key, response)
70
66
  modify(response.body, response.headers)
71
67
  end
72
68
 
@@ -80,27 +76,27 @@ module GH
80
76
  response = frontend.http(verb, path_for(key), headers) do |req|
81
77
  req.body = Response.new({}, body).to_s if body
82
78
  end
83
- modify(response.body, response.headers)
79
+ frontend.generate_response(key, response)
84
80
  end
85
81
 
86
82
  # Public: ...
87
83
  def post(key, body)
88
- request(:post, key, body)
84
+ frontend.request(:post, key, body)
89
85
  end
90
86
 
91
87
  # Public: ...
92
88
  def delete(key)
93
- request(:delete, key)
89
+ frontend.request(:delete, key)
94
90
  end
95
91
 
96
92
  # Public: ...
97
93
  def patch(key, body)
98
- request(:patch, key, body)
94
+ frontend.request(:patch, key, body)
99
95
  end
100
96
 
101
97
  # Public: ...
102
98
  def put(key, body)
103
- request(:put, key, body)
99
+ frontend.request(:put, key, body)
104
100
  end
105
101
 
106
102
  # Public: ...
@@ -112,6 +108,11 @@ module GH
112
108
  modify(data)
113
109
  end
114
110
 
111
+ # Public: ...
112
+ def in_parallel
113
+ raise RuntimeError, "use GH::Parallel middleware for #in_parallel support"
114
+ end
115
+
115
116
  private
116
117
 
117
118
  def identifier(key)
@@ -1,4 +1,4 @@
1
1
  module GH
2
2
  # Public: Library version.
3
- VERSION = "0.4.2"
3
+ VERSION = "0.5.0"
4
4
  end
@@ -33,6 +33,9 @@ module GH
33
33
  # Internal: ...
34
34
  def_delegator :backend, :http
35
35
 
36
+ # Internal: ...
37
+ def_delegator :backend, :request
38
+
36
39
  # Public: ...
37
40
  def_delegator :backend, :post
38
41
 
@@ -43,7 +46,16 @@ module GH
43
46
  def_delegator :backend, :patch
44
47
 
45
48
  # Public: ...
46
- def_delegator :backendt, :put
49
+ def_delegator :backend, :put
50
+
51
+ # Public: ...
52
+ def_delegator :backend, :fetch_resource
53
+
54
+ # Public: ...
55
+ def_delegator :backend, :in_parallel
56
+
57
+ # Public: ...
58
+ def_delegator :backend, :in_parallel?
47
59
 
48
60
  # Public: Retrieves resources from Github.
49
61
  def self.[](key)
@@ -55,7 +67,12 @@ module GH
55
67
  # By default, this method is delegated to the next layer on the stack
56
68
  # and modify is called.
57
69
  def [](key)
58
- modify backend[key]
70
+ generate_response key, fetch_resource(key)
71
+ end
72
+
73
+ # Internal: ...
74
+ def generate_response(key, resource)
75
+ modify backend.generate_response(key, resource)
59
76
  end
60
77
 
61
78
  # Internal: Get/set default layer to wrap when creating a new instance.
@@ -185,7 +185,6 @@ describe GH::Normalizer do
185
185
  normalized.should include('author')
186
186
  normalized.should include('committer')
187
187
  normalized['author'].should be == 'me'
188
- normalized['author'].should be_equal(normalized['committer'])
189
188
  end
190
189
 
191
190
  it 'copies committer to author' do
@@ -193,7 +192,6 @@ describe GH::Normalizer do
193
192
  normalized.should include('author')
194
193
  normalized.should include('committer')
195
194
  normalized['author'].should be == 'me'
196
- normalized['author'].should be_equal(normalized['committer'])
197
195
  end
198
196
 
199
197
  it 'does not override committer or author if both exist' do
@@ -25,4 +25,8 @@ describe GH::Pagination do
25
25
  data[index].should be == value
26
26
  end
27
27
  end
28
+
29
+ it 'does not wrap hash responses' do
30
+ subject['users/rkh'].should_not be_a(GH::Pagination::Paginated)
31
+ end
28
32
  end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe GH::Parallel do
4
+ before do
5
+ stub_request(:get, "https://api.github.com/users/rkh").to_return(:status => 200, :body => '{"name": "Konstantin Haase"}')
6
+ stub_request(:get, "https://api.github.com/users/svenfuchs").to_return(:status => 200, :body => '{"name": "Sven Fuchs"}')
7
+ stub_request(:get, "https://api.github.com/users/rkh?per_page=100").to_return(:status => 200, :body => '{"name": "Konstantin Haase"}')
8
+ stub_request(:get, "https://api.github.com/users/svenfuchs?per_page=100").to_return(:status => 200, :body => '{"name": "Sven Fuchs"}')
9
+ end
10
+
11
+ it 'allows normal requests' do
12
+ GH['users/rkh']['name'].should be == 'Konstantin Haase'
13
+ end
14
+
15
+ it 'sets in_parallel?' do
16
+ GH.should_not be_in_parallel
17
+ GH.in_parallel { GH.should be_in_parallel }
18
+ GH.should_not be_in_parallel
19
+ end
20
+
21
+ it 'runs requests in parallel' do
22
+ GH::DefaultStack.replace GH::MockBackend, GH::Remote
23
+ GH.current = nil
24
+ GH.should_not be_in_parallel
25
+
26
+ a = b = nil
27
+ GH.in_parallel do
28
+ GH.should be_in_parallel
29
+
30
+ a = GH['users/rkh']
31
+ b = GH['users/svenfuchs']
32
+
33
+ expect { a['name'] }.to raise_error(RuntimeError)
34
+ expect { b['name'] }.to raise_error(RuntimeError)
35
+ end
36
+
37
+ a['name'].should be == "Konstantin Haase"
38
+ b['name'].should be == "Sven Fuchs"
39
+
40
+ a.should respond_to('to_hash')
41
+ b.should respond_to('to_hash')
42
+
43
+ GH.should_not be_in_parallel
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ ---
2
+ - !binary "c2VydmVy": !binary |-
3
+ bmdpbngvMS4wLjEz
4
+ !binary "ZGF0ZQ==": !binary |-
5
+ V2VkLCAyMyBNYXkgMjAxMiAxMzo1OTozNiBHTVQ=
6
+ !binary "Y29udGVudC10eXBl": !binary |-
7
+ YXBwbGljYXRpb24vanNvbjsgY2hhcnNldD11dGYtOA==
8
+ !binary "dHJhbnNmZXItZW5jb2Rpbmc=": !binary |-
9
+ Y2h1bmtlZA==
10
+ !binary "Y29ubmVjdGlvbg==": !binary |-
11
+ a2VlcC1hbGl2ZQ==
12
+ !binary "c3RhdHVz": !binary |-
13
+ MjAwIE9L
14
+ !binary "eC1yYXRlbGltaXQtbGltaXQ=": !binary |-
15
+ NTAwMA==
16
+ !binary "ZXRhZw==": !binary |-
17
+ IjNhMWFkMzc4N2NiZDkwNGQ2NDk4YzllODA5NWZlODY0Ig==
18
+ !binary "bGluaw==": !binary |-
19
+ PGh0dHBzOi8vYXBpLmdpdGh1Yi5jb20vdXNlcnMvcmtoL3JlcG9zP3BhZ2U9
20
+ MSZwZXJfcGFnZT0xMDA+OyByZWw9ImZpcnN0IiwgPGh0dHBzOi8vYXBpLmdp
21
+ dGh1Yi5jb20vdXNlcnMvcmtoL3JlcG9zP3BhZ2U9MSZwZXJfcGFnZT0xMDA+
22
+ OyByZWw9InByZXYi
23
+ !binary "eC1yYXRlbGltaXQtcmVtYWluaW5n": !binary |-
24
+ NDk5Nw==
25
+ - ! '[{"html_url":"https://github.com/rkh/hpi","pushed_at":"2011-10-28T21:56:35Z","updated_at":"2012-04-10T11:37:21Z","homepage":null,"url":"https://api.github.com/repos/rkh/hpi","has_downloads":true,"watchers":10,"fork":false,"svn_url":"https://github.com/rkh/hpi","has_wiki":true,"has_issues":true,"size":168,"private":false,"mirror_url":null,"forks":1,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"hpi","language":"Ruby","description":null,"clone_url":"https://github.com/rkh/hpi.git","ssh_url":"git@github.com:rkh/hpi.git","git_url":"git://github.com/rkh/hpi.git","created_at":"2011-10-07T23:55:05Z","id":2536005,"open_issues":0},{"html_url":"https://github.com/rkh/tool","pushed_at":"2011-10-27T02:01:35Z","updated_at":"2011-10-27T02:01:35Z","homepage":null,"url":"https://api.github.com/repos/rkh/tool","has_downloads":true,"watchers":1,"fork":false,"svn_url":"https://github.com/rkh/tool","has_wiki":true,"has_issues":true,"size":140,"private":false,"mirror_url":null,"forks":1,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"tool","language":"Ruby","description":null,"clone_url":"https://github.com/rkh/tool.git","ssh_url":"git@github.com:rkh/tool.git","git_url":"git://github.com/rkh/tool.git","created_at":"2011-10-27T01:50:56Z","id":2655239,"open_issues":0},{"html_url":"https://github.com/rkh/twp","pushed_at":"2011-12-15T09:10:41Z","updated_at":"2012-02-11T19:56:48Z","homepage":"http://www.dcl.hpi.uni-potsdam.de/teaching/mds/","url":"https://api.github.com/repos/rkh/twp","has_downloads":true,"watchers":5,"fork":false,"svn_url":"https://github.com/rkh/twp","has_wiki":true,"has_issues":true,"size":256,"private":false,"mirror_url":null,"forks":1,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"twp","language":"Ruby","description":"TWP3
26
+ in Ruby","clone_url":"https://github.com/rkh/twp.git","ssh_url":"git@github.com:rkh/twp.git","git_url":"git://github.com/rkh/twp.git","created_at":"2011-12-11T23:01:21Z","id":2960627,"open_issues":0},{"html_url":"https://github.com/rkh/fibur","pushed_at":"2011-12-19T19:07:32Z","updated_at":"2011-12-19T19:07:33Z","homepage":"","url":"https://api.github.com/repos/rkh/fibur","has_downloads":true,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/fibur","has_wiki":true,"has_issues":false,"size":112,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"fibur","language":"Ruby","description":"Concurrent
27
+ execution during Ruby I/O","clone_url":"https://github.com/rkh/fibur.git","ssh_url":"git@github.com:rkh/fibur.git","git_url":"git://github.com/rkh/fibur.git","created_at":"2011-12-19T19:06:39Z","id":3014283,"open_issues":0},{"html_url":"https://github.com/rkh/rkelly","pushed_at":"2012-01-09T20:27:16Z","updated_at":"2012-01-09T20:27:16Z","homepage":"","url":"https://api.github.com/repos/rkh/rkelly","has_downloads":true,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/rkelly","has_wiki":true,"has_issues":false,"size":336,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"rkelly","language":"Ruby","description":"Pure
28
+ ruby javascript parser and interpreter.","clone_url":"https://github.com/rkh/rkelly.git","ssh_url":"git@github.com:rkh/rkelly.git","git_url":"git://github.com/rkh/rkelly.git","created_at":"2012-01-09T20:25:53Z","id":3139761,"open_issues":0},{"html_url":"https://github.com/rkh/curriculum","pushed_at":"2012-01-10T16:30:53Z","updated_at":"2012-01-14T12:17:49Z","homepage":"","url":"https://api.github.com/repos/rkh/curriculum","has_downloads":true,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/curriculum","has_wiki":true,"has_issues":false,"size":132,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"curriculum","language":"JavaScript","description":"","clone_url":"https://github.com/rkh/curriculum.git","ssh_url":"git@github.com:rkh/curriculum.git","git_url":"git://github.com/rkh/curriculum.git","created_at":"2012-01-10T16:28:11Z","id":3146605,"open_issues":0},{"html_url":"https://github.com/rkh/fog","pushed_at":"2012-01-12T15:40:22Z","updated_at":"2012-02-26T06:24:48Z","homepage":"http://fog.io","url":"https://api.github.com/repos/rkh/fog","has_downloads":true,"watchers":3,"fork":true,"svn_url":"https://github.com/rkh/fog","has_wiki":true,"has_issues":false,"size":180,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"fog","language":"Ruby","description":"The
29
+ Ruby cloud services library.","clone_url":"https://github.com/rkh/fog.git","ssh_url":"git@github.com:rkh/fog.git","git_url":"git://github.com/rkh/fog.git","created_at":"2012-01-12T15:32:01Z","id":3163178,"open_issues":0},{"html_url":"https://github.com/rkh/test-project-1","pushed_at":"2012-05-16T12:08:44Z","updated_at":"2012-05-16T12:08:44Z","homepage":"http://travis-ci.org","url":"https://api.github.com/repos/rkh/test-project-1","has_downloads":true,"watchers":0,"fork":true,"svn_url":"https://github.com/rkh/test-project-1","has_wiki":true,"has_issues":false,"size":152,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"test-project-1","language":"Ruby","description":"Test
30
+ dummy repository for testing Travis CI","clone_url":"https://github.com/rkh/test-project-1.git","ssh_url":"git@github.com:rkh/test-project-1.git","git_url":"git://github.com/rkh/test-project-1.git","created_at":"2012-02-13T15:17:57Z","id":3431064,"open_issues":1},{"html_url":"https://github.com/rkh/oh-my-zsh","pushed_at":"2012-02-22T13:48:52Z","updated_at":"2012-03-07T21:11:13Z","homepage":"http://twitter.com/ohmyzsh","url":"https://api.github.com/repos/rkh/oh-my-zsh","has_downloads":true,"watchers":2,"fork":true,"svn_url":"https://github.com/rkh/oh-my-zsh","has_wiki":true,"has_issues":false,"size":7224,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"oh-my-zsh","language":"Shell","description":"A
31
+ community-driven framework for managing your zsh configuration. Includes 40+ optional
32
+ plugins (rails, git, OSX, hub, capistrano, brew, ant, macports, etc), over 80 terminal
33
+ themes to spice up your morning, and an auto-update tool so that makes it easy
34
+ to keep up with the latest updates from the community.","clone_url":"https://github.com/rkh/oh-my-zsh.git","ssh_url":"git@github.com:rkh/oh-my-zsh.git","git_url":"git://github.com/rkh/oh-my-zsh.git","created_at":"2012-02-22T13:47:54Z","id":3514933,"open_issues":0},{"html_url":"https://github.com/rkh/rbenv-use","pushed_at":"2012-02-22T16:53:26Z","updated_at":"2012-03-27T15:54:20Z","homepage":"","url":"https://api.github.com/repos/rkh/rbenv-use","has_downloads":true,"watchers":10,"fork":false,"svn_url":"https://github.com/rkh/rbenv-use","has_wiki":true,"has_issues":true,"size":108,"private":false,"mirror_url":null,"forks":1,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"rbenv-use","language":"Shell","description":"rbenv
35
+ use rbx","clone_url":"https://github.com/rkh/rbenv-use.git","ssh_url":"git@github.com:rkh/rbenv-use.git","git_url":"git://github.com/rkh/rbenv-use.git","created_at":"2012-02-22T13:51:59Z","id":3514966,"open_issues":0},{"html_url":"https://github.com/rkh/rbenv-whatis","pushed_at":"2012-02-22T13:52:54Z","updated_at":"2012-04-24T20:50:23Z","homepage":null,"url":"https://api.github.com/repos/rkh/rbenv-whatis","has_downloads":true,"watchers":5,"fork":false,"svn_url":"https://github.com/rkh/rbenv-whatis","has_wiki":true,"has_issues":true,"size":84,"private":false,"mirror_url":null,"forks":2,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"rbenv-whatis","language":"Shell","description":null,"clone_url":"https://github.com/rkh/rbenv-whatis.git","ssh_url":"git@github.com:rkh/rbenv-whatis.git","git_url":"git://github.com/rkh/rbenv-whatis.git","created_at":"2012-02-22T13:52:44Z","id":3514978,"open_issues":0},{"html_url":"https://github.com/rkh/sinatra-template","pushed_at":"2012-03-14T15:07:40Z","updated_at":"2012-03-14T15:07:41Z","homepage":"","url":"https://api.github.com/repos/rkh/sinatra-template","has_downloads":true,"watchers":6,"fork":false,"svn_url":"https://github.com/rkh/sinatra-template","has_wiki":true,"has_issues":true,"size":124,"private":false,"mirror_url":null,"forks":5,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"sinatra-template","language":"Ruby","description":"base
36
+ template for classic sinatra apps","clone_url":"https://github.com/rkh/sinatra-template.git","ssh_url":"git@github.com:rkh/sinatra-template.git","git_url":"git://github.com/rkh/sinatra-template.git","created_at":"2012-02-25T13:39:12Z","id":3544708,"open_issues":0},{"html_url":"https://github.com/rkh/rbenv-update","pushed_at":"2012-02-25T14:17:43Z","updated_at":"2012-05-17T00:03:11Z","homepage":"","url":"https://api.github.com/repos/rkh/rbenv-update","has_downloads":true,"watchers":3,"fork":false,"svn_url":"https://github.com/rkh/rbenv-update","has_wiki":true,"has_issues":true,"size":84,"private":false,"mirror_url":null,"forks":2,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"rbenv-update","language":"Shell","description":"update
37
+ rbenv and plugins","clone_url":"https://github.com/rkh/rbenv-update.git","ssh_url":"git@github.com:rkh/rbenv-update.git","git_url":"git://github.com/rkh/rbenv-update.git","created_at":"2012-02-25T14:17:01Z","id":3544886,"open_issues":1},{"html_url":"https://github.com/rkh/call-for-proposals","pushed_at":"2012-03-06T09:42:48Z","updated_at":"2012-03-06T09:42:48Z","homepage":"","url":"https://api.github.com/repos/rkh/call-for-proposals","has_downloads":true,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/call-for-proposals","has_wiki":true,"has_issues":false,"size":160,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"call-for-proposals","language":null,"description":"Want
38
+ to make a talk proposal for EuRuKo 2012? This is the place to be!","clone_url":"https://github.com/rkh/call-for-proposals.git","ssh_url":"git@github.com:rkh/call-for-proposals.git","git_url":"git://github.com/rkh/call-for-proposals.git","created_at":"2012-02-29T14:04:31Z","id":3582162,"open_issues":0},{"html_url":"https://github.com/rkh/socialshareprivacy","pushed_at":"2012-03-01T11:20:47Z","updated_at":"2012-03-01T11:21:49Z","homepage":"","url":"https://api.github.com/repos/rkh/socialshareprivacy","has_downloads":true,"watchers":1,"fork":false,"svn_url":"https://github.com/rkh/socialshareprivacy","has_wiki":false,"has_issues":false,"size":216,"private":false,"mirror_url":null,"forks":1,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"socialshareprivacy","language":"JavaScript","description":"this
39
+ is just a mirror","clone_url":"https://github.com/rkh/socialshareprivacy.git","ssh_url":"git@github.com:rkh/socialshareprivacy.git","git_url":"git://github.com/rkh/socialshareprivacy.git","created_at":"2012-03-01T11:20:28Z","id":3591314,"open_issues":0},{"html_url":"https://github.com/rkh/Smallest-Federated-Wiki","pushed_at":"2012-03-01T21:36:10Z","updated_at":"2012-03-01T21:36:11Z","homepage":"http://wardcunningham.github.com/","url":"https://api.github.com/repos/rkh/Smallest-Federated-Wiki","has_downloads":true,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/Smallest-Federated-Wiki","has_wiki":true,"has_issues":false,"size":112,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"Smallest-Federated-Wiki","language":"JavaScript","description":"Our
40
+ new wiki innovates three ways. It shares through federation, composes by refactoring
41
+ and wraps data with visualization.","clone_url":"https://github.com/rkh/Smallest-Federated-Wiki.git","ssh_url":"git@github.com:rkh/Smallest-Federated-Wiki.git","git_url":"git://github.com/rkh/Smallest-Federated-Wiki.git","created_at":"2012-03-01T21:34:49Z","id":3596336,"open_issues":0},{"html_url":"https://github.com/rkh/gh","pushed_at":"2012-05-23T13:30:33Z","updated_at":"2012-05-23T13:30:35Z","homepage":"http://gh.rkh.im/","url":"https://api.github.com/repos/rkh/gh","has_downloads":true,"watchers":11,"fork":false,"svn_url":"https://github.com/rkh/gh","has_wiki":true,"has_issues":true,"size":244,"private":false,"mirror_url":null,"forks":3,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"gh","language":"Ruby","description":"Layered
42
+ GitHub API client","clone_url":"https://github.com/rkh/gh.git","ssh_url":"git@github.com:rkh/gh.git","git_url":"git://github.com/rkh/gh.git","created_at":"2012-03-05T13:07:41Z","id":3627076,"open_issues":0},{"html_url":"https://github.com/rkh/trinidad","pushed_at":"2012-03-10T15:56:37Z","updated_at":"2012-03-10T15:56:37Z","homepage":"","url":"https://api.github.com/repos/rkh/trinidad","has_downloads":true,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/trinidad","has_wiki":true,"has_issues":false,"size":152,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"trinidad","language":"Ruby","description":"Simple
43
+ library to run rails and rackup applications into an embedded Apache Tomcat","clone_url":"https://github.com/rkh/trinidad.git","ssh_url":"git@github.com:rkh/trinidad.git","git_url":"git://github.com/rkh/trinidad.git","created_at":"2012-03-10T15:55:36Z","id":3680426,"open_issues":0},{"html_url":"https://github.com/rkh/prefix","pushed_at":"2012-03-20T15:01:24Z","updated_at":"2012-03-20T15:21:14Z","homepage":null,"url":"https://api.github.com/repos/rkh/prefix","has_downloads":true,"watchers":2,"fork":false,"svn_url":"https://github.com/rkh/prefix","has_wiki":true,"has_issues":true,"size":92,"private":false,"mirror_url":null,"forks":1,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"prefix","language":"Ruby","description":null,"clone_url":"https://github.com/rkh/prefix.git","ssh_url":"git@github.com:rkh/prefix.git","git_url":"git://github.com/rkh/prefix.git","created_at":"2012-03-20T14:47:08Z","id":3776230,"open_issues":0},{"html_url":"https://github.com/rkh/hearts","pushed_at":"2012-03-25T16:45:47Z","updated_at":"2012-03-25T16:45:48Z","homepage":"","url":"https://api.github.com/repos/rkh/hearts","has_downloads":true,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/hearts","has_wiki":true,"has_issues":false,"size":108,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"hearts","language":"Ruby","description":"I
44
+ love hearts!","clone_url":"https://github.com/rkh/hearts.git","ssh_url":"git@github.com:rkh/hearts.git","git_url":"git://github.com/rkh/hearts.git","created_at":"2012-03-25T16:45:16Z","id":3825575,"open_issues":0},{"html_url":"https://github.com/rkh/test-project-matrix-1","pushed_at":"2012-04-15T16:21:18Z","updated_at":"2012-04-15T16:21:18Z","homepage":"","url":"https://api.github.com/repos/rkh/test-project-matrix-1","has_downloads":true,"watchers":1,"master_branch":"master","fork":true,"svn_url":"https://github.com/rkh/test-project-matrix-1","has_wiki":true,"has_issues":false,"size":112,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"test-project-matrix-1","language":"Ruby","description":"Test
45
+ dummy repository for testing Travis CI","clone_url":"https://github.com/rkh/test-project-matrix-1.git","ssh_url":"git@github.com:rkh/test-project-matrix-1.git","git_url":"git://github.com/rkh/test-project-matrix-1.git","created_at":"2012-04-15T16:20:55Z","id":4033173,"open_issues":0},{"html_url":"https://github.com/rkh/euruko-golf","pushed_at":"2012-05-04T11:54:45Z","updated_at":"2012-05-10T10:03:45Z","homepage":"","url":"https://api.github.com/repos/rkh/euruko-golf","has_downloads":true,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/euruko-golf","has_wiki":true,"has_issues":false,"size":108,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"euruko-golf","language":"Ruby","description":"Tweetable
46
+ ruby programs that output EuRuKo ASCII art.","clone_url":"https://github.com/rkh/euruko-golf.git","ssh_url":"git@github.com:rkh/euruko-golf.git","git_url":"git://github.com/rkh/euruko-golf.git","created_at":"2012-05-04T11:51:10Z","id":4224272,"open_issues":0},{"html_url":"https://github.com/rkh/github-services","pushed_at":"2012-05-15T12:00:57Z","updated_at":"2012-05-15T12:00:57Z","homepage":"http://github.com/blog/53-github-services-ipo","url":"https://api.github.com/repos/rkh/github-services","has_downloads":false,"watchers":1,"fork":true,"svn_url":"https://github.com/rkh/github-services","has_wiki":false,"has_issues":false,"size":120,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"github-services","language":"Ruby","description":"Official
47
+ GitHub Services Integration - You can set these up in your repo admin screen under
48
+ Service Hooks","clone_url":"https://github.com/rkh/github-services.git","ssh_url":"git@github.com:rkh/github-services.git","git_url":"git://github.com/rkh/github-services.git","created_at":"2012-05-15T11:35:46Z","id":4335034,"open_issues":0},{"html_url":"https://github.com/rkh/bundler","pushed_at":"2012-05-22T13:26:25Z","updated_at":"2012-05-22T13:26:25Z","homepage":"http://gembundler.com","url":"https://api.github.com/repos/rkh/bundler","has_downloads":false,"watchers":1,"master_branch":"master","fork":true,"svn_url":"https://github.com/rkh/bundler","has_wiki":true,"has_issues":false,"size":140,"private":false,"mirror_url":null,"forks":0,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"bundler","language":"Ruby","description":"Manage
49
+ your application''s gem dependencies with less pain","clone_url":"https://github.com/rkh/bundler.git","ssh_url":"git@github.com:rkh/bundler.git","git_url":"git://github.com/rkh/bundler.git","created_at":"2012-05-21T14:05:57Z","id":4394235,"open_issues":0},{"html_url":"https://github.com/rkh/gh-store","pushed_at":"2012-05-22T12:50:46Z","updated_at":"2012-05-22T13:11:14Z","homepage":null,"url":"https://api.github.com/repos/rkh/gh-store","has_downloads":true,"watchers":2,"fork":false,"svn_url":"https://github.com/rkh/gh-store","has_wiki":true,"has_issues":true,"size":92,"private":false,"mirror_url":null,"forks":1,"owner":{"url":"https://api.github.com/users/rkh","login":"rkh","avatar_url":"https://secure.gravatar.com/avatar/5c2b452f6eea4a6d84c105ebd971d2a4?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"5c2b452f6eea4a6d84c105ebd971d2a4","id":30442},"name":"gh-store","language":"Ruby","description":null,"clone_url":"https://github.com/rkh/gh-store.git","ssh_url":"git@github.com:rkh/gh-store.git","git_url":"git://github.com/rkh/gh-store.git","created_at":"2012-05-22T12:50:34Z","id":4406633,"open_issues":0}]'