hubba 0.1.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/{HISTORY.md → CHANGELOG.md} +0 -0
- data/Manifest.txt +8 -1
- data/README.md +20 -20
- data/Rakefile +5 -5
- data/lib/hubba.rb +4 -14
- data/lib/hubba/cache.rb +4 -5
- data/lib/hubba/client.rb +43 -20
- data/lib/hubba/github.rb +26 -4
- data/lib/hubba/stats.rb +301 -0
- data/lib/hubba/version.rb +2 -4
- data/test/helper.rb +0 -2
- data/test/stats/jekyll~minima.json +25 -0
- data/test/stats/openblockchains~awesome-blockchains.json +27 -0
- data/test/stats/opendatajson~factbook.json.json +39 -0
- data/test/stats/poole~hyde.json +21 -0
- data/test/test_cache.rb +3 -6
- data/test/test_config.rb +0 -2
- data/test/test_stats.rb +122 -0
- data/test/test_stats_tmp.rb +41 -0
- metadata +29 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 84577f7d76ee6b6e2b0c1dd2c747536bede539fe
|
4
|
+
data.tar.gz: 9f42b72e4402d148eaccbfd635f316161a50a857
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 94e26559d4d814411a5537f879f58132c906ef7a3b2f7a61cc8f83d0e5f282b143d68f37a5462e5aef5e25daa86072b40e94c1b5177ab57023b80260f503b054
|
7
|
+
data.tar.gz: b9f03dba1f8861edbec94fb3411b2383dc1e12744387b9c6480b43a75ee6fa7331b57d81199a084cec742f0b35abf09167409d0328fd00a7da4fe3f15343a167
|
data/{HISTORY.md → CHANGELOG.md}
RENAMED
File without changes
|
data/Manifest.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
CHANGELOG.md
|
2
2
|
Manifest.txt
|
3
3
|
README.md
|
4
4
|
Rakefile
|
@@ -6,9 +6,16 @@ lib/hubba.rb
|
|
6
6
|
lib/hubba/cache.rb
|
7
7
|
lib/hubba/client.rb
|
8
8
|
lib/hubba/github.rb
|
9
|
+
lib/hubba/stats.rb
|
9
10
|
lib/hubba/version.rb
|
10
11
|
test/cache/users~geraldb~orgs.json
|
11
12
|
test/cache/users~geraldb~repos.json
|
12
13
|
test/helper.rb
|
14
|
+
test/stats/jekyll~minima.json
|
15
|
+
test/stats/openblockchains~awesome-blockchains.json
|
16
|
+
test/stats/opendatajson~factbook.json.json
|
17
|
+
test/stats/poole~hyde.json
|
13
18
|
test/test_cache.rb
|
14
19
|
test/test_config.rb
|
20
|
+
test/test_stats.rb
|
21
|
+
test/test_stats_tmp.rb
|
data/README.md
CHANGED
@@ -1,20 +1,20 @@
|
|
1
|
-
# hubba
|
2
|
-
|
3
|
-
hubba gem - (yet) another (lite) GitHub HTTP API client / library
|
4
|
-
|
5
|
-
* home :: [github.com/
|
6
|
-
* bugs :: [github.com/
|
7
|
-
* gem :: [rubygems.org/gems/hubba](https://rubygems.org/gems/hubba)
|
8
|
-
* rdoc :: [rubydoc.info/gems/hubba](http://rubydoc.info/gems/hubba)
|
9
|
-
|
10
|
-
|
11
|
-
## Usage
|
12
|
-
|
13
|
-
TBD
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
1
|
+
# hubba
|
2
|
+
|
3
|
+
hubba gem - (yet) another (lite) GitHub HTTP API client / library
|
4
|
+
|
5
|
+
* home :: [github.com/rubycoco/git](https://github.com/rubycoco/git)
|
6
|
+
* bugs :: [github.com/rubycoco/git/issues](https://github.com/rubycoco/git/issues)
|
7
|
+
* gem :: [rubygems.org/gems/hubba](https://rubygems.org/gems/hubba)
|
8
|
+
* rdoc :: [rubydoc.info/gems/hubba](http://rubydoc.info/gems/hubba)
|
9
|
+
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
TBD
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
## License
|
18
|
+
|
19
|
+
The `hubba` scripts are dedicated to the public domain.
|
20
|
+
Use it as you please with no restrictions whatsoever.
|
data/Rakefile
CHANGED
@@ -5,26 +5,26 @@ Hoe.spec 'hubba' do
|
|
5
5
|
|
6
6
|
self.version = Hubba::VERSION
|
7
7
|
|
8
|
-
self.summary = 'hubba -
|
8
|
+
self.summary = 'hubba - (yet) another (lite) GitHub HTTP API client / library'
|
9
9
|
self.description = summary
|
10
10
|
|
11
|
-
self.urls =
|
11
|
+
self.urls = { home: 'https://github.com/rubycoco/git' }
|
12
12
|
|
13
13
|
self.author = 'Gerald Bauer'
|
14
14
|
self.email = 'ruby-talk@ruby-lang.org'
|
15
15
|
|
16
16
|
# switch extension to .markdown for gihub formatting
|
17
17
|
self.readme_file = 'README.md'
|
18
|
-
self.history_file = '
|
18
|
+
self.history_file = 'CHANGELOG.md'
|
19
19
|
|
20
20
|
self.extra_deps = [
|
21
|
-
['
|
21
|
+
['webclient', '>= 0.1.1']
|
22
22
|
]
|
23
23
|
|
24
24
|
self.licenses = ['Public Domain']
|
25
25
|
|
26
26
|
self.spec_extras = {
|
27
|
-
required_ruby_version: '>=
|
27
|
+
required_ruby_version: '>= 2.2.2'
|
28
28
|
}
|
29
29
|
|
30
30
|
end
|
data/lib/hubba.rb
CHANGED
@@ -1,23 +1,13 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
require 'net/http'
|
4
|
-
require "net/https"
|
5
|
-
require 'uri'
|
6
|
-
|
7
|
-
require 'pp'
|
8
|
-
require 'json'
|
9
|
-
require 'yaml'
|
10
|
-
|
11
|
-
|
12
|
-
# 3rd party gems/libs
|
13
|
-
require 'logutils'
|
1
|
+
# 3rd party (our own)
|
2
|
+
require 'webclient'
|
14
3
|
|
15
4
|
# our own code
|
16
5
|
require 'hubba/version' # note: let version always go first
|
17
6
|
require 'hubba/cache'
|
18
7
|
require 'hubba/client'
|
19
8
|
require 'hubba/github'
|
9
|
+
require 'hubba/stats'
|
20
10
|
|
21
11
|
|
22
12
|
# say hello
|
23
|
-
puts Hubba.banner if defined?($
|
13
|
+
puts Hubba.banner if defined?($RUBYCOCO_DEBUG)
|
data/lib/hubba/cache.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Hubba
|
4
2
|
|
5
3
|
class Cache ## lets you work with GitHub api "offline" using just a local cache of stored json
|
@@ -14,7 +12,7 @@ class Cache ## lets you work with GitHub api "offline" using just a local ca
|
|
14
12
|
basename = request_uri_to_basename( request_uri )
|
15
13
|
path = "#{@dir}/#{basename}.json"
|
16
14
|
if File.exist?( path )
|
17
|
-
text = File.
|
15
|
+
text = File.open( path, 'r:utf-8') { |f| f.read }
|
18
16
|
json = JSON.parse( text )
|
19
17
|
json
|
20
18
|
else
|
@@ -32,8 +30,8 @@ class Cache ## lets you work with GitHub api "offline" using just a local ca
|
|
32
30
|
data = obj # assume Hash or Array -- todo: add support for String - why? why not??
|
33
31
|
end
|
34
32
|
|
35
|
-
File.open( path, 'w' ) do |f|
|
36
|
-
f.write JSON.pretty_generate( data )
|
33
|
+
File.open( path, 'w:utf-8' ) do |f|
|
34
|
+
f.write( JSON.pretty_generate( data ))
|
37
35
|
end
|
38
36
|
end
|
39
37
|
|
@@ -42,6 +40,7 @@ class Cache ## lets you work with GitHub api "offline" using just a local ca
|
|
42
40
|
## 1) cut off leading /
|
43
41
|
## 2) convert / to ~
|
44
42
|
## 3) remove (optional) query string (for now) - why? why not?
|
43
|
+
## e.g. /users/#{name}/orgs?per_page=100 or such
|
45
44
|
##
|
46
45
|
## e.g.
|
47
46
|
## '/users/geraldb' => 'users~geraldb',
|
data/lib/hubba/client.rb
CHANGED
@@ -1,53 +1,76 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Hubba
|
4
2
|
|
5
3
|
|
6
4
|
class Client
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
BASE_URL = 'https://api.github.com'
|
7
|
+
|
8
|
+
|
9
|
+
def initialize( token: nil,
|
10
|
+
user: nil, password: nil )
|
11
|
+
## add support for (personal access) token
|
12
|
+
@token = token
|
13
13
|
|
14
14
|
## add support for basic auth - defaults to no auth (nil/nil)
|
15
|
+
## remove - deprecated (use token) - why? why not?
|
15
16
|
@user = user ## use login like Oktokit - why? why not?
|
16
17
|
@password = password
|
17
18
|
end # method initialize
|
18
19
|
|
20
|
+
|
21
|
+
|
19
22
|
def get( request_uri )
|
23
|
+
###
|
24
|
+
# e.g. /users/geraldb
|
25
|
+
# /users/geraldb/repos
|
26
|
+
|
20
27
|
puts "GET #{request_uri}"
|
28
|
+
url = "#{BASE_URL}/#{request_uri}"
|
21
29
|
|
22
|
-
|
23
|
-
##
|
24
|
-
##
|
25
|
-
req["User-Agent"] = "ruby/hubba" ## required by GitHub API
|
26
|
-
req["Accept" ] = "application/vnd.github.v3+json" ## recommend by GitHub API
|
30
|
+
headers = {}
|
31
|
+
headers['User-Agent'] = 'ruby/hubba' ## required by GitHub API
|
32
|
+
headers['Accept'] = 'application/vnd.github.v3+json' ## recommend by GitHub API
|
27
33
|
|
34
|
+
auth = []
|
28
35
|
## check if credentials (user/password) present - if yes, use basic auth
|
29
|
-
if @
|
36
|
+
if @token
|
37
|
+
puts " using (personal access) token - starting with: #{@token[0..6]}**********"
|
38
|
+
headers['Authorization'] = "token #{@token}"
|
39
|
+
## token works like:
|
40
|
+
## curl -H 'Authorization: token my_access_token' https://api.github.com/user/repos
|
41
|
+
elsif @user && @password
|
30
42
|
puts " using basic auth - user: #{@user}, password: ***"
|
31
|
-
|
43
|
+
## use credential auth "tuple" (that is, array with two string items) for now
|
44
|
+
## or use Webclient::HttpBasicAuth or something - why? why not?
|
45
|
+
auth = [@user, @password]
|
46
|
+
# req.basic_auth( @user, @password )
|
47
|
+
else
|
48
|
+
puts " using no credentials (no token, no user/password)"
|
32
49
|
end
|
33
50
|
|
34
|
-
res =
|
51
|
+
res = Webclient.get( url,
|
52
|
+
headers: headers,
|
53
|
+
auth: auth )
|
35
54
|
|
36
55
|
# Get specific header
|
37
56
|
# response["content-type"]
|
38
57
|
# => "text/html; charset=UTF-8"
|
39
58
|
|
40
59
|
# Iterate all response headers.
|
41
|
-
res.
|
42
|
-
|
60
|
+
res.headers.each do |key, value|
|
61
|
+
puts "#{key} => #{value}"
|
43
62
|
end
|
44
63
|
# => "location => http://www.google.com/"
|
45
64
|
# => "content-type => text/html; charset=UTF-8"
|
46
65
|
# ...
|
47
66
|
|
48
|
-
|
49
|
-
|
50
|
-
|
67
|
+
if res.status.ok?
|
68
|
+
res.json
|
69
|
+
else
|
70
|
+
puts "!! HTTP ERROR: #{res.status.code} #{res.status.message}:"
|
71
|
+
pp res.raw
|
72
|
+
exit 1
|
73
|
+
end
|
51
74
|
end # methdo get
|
52
75
|
|
53
76
|
end ## class Client
|
data/lib/hubba/github.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Hubba
|
4
2
|
|
5
3
|
class Configuration
|
4
|
+
attr_accessor :token
|
5
|
+
|
6
6
|
attr_accessor :user
|
7
7
|
attr_accessor :password
|
8
8
|
|
9
9
|
def initialize
|
10
10
|
# try default setup via ENV variables
|
11
|
+
@token = ENV[ 'HUBBA_TOKEN' ]
|
12
|
+
|
11
13
|
@user = ENV[ 'HUBBA_USER' ] ## use HUBBA_LOGIN - why? why not?
|
12
14
|
@password = ENV[ 'HUBBA_PASSWORD' ]
|
13
15
|
end
|
@@ -15,6 +17,8 @@ end
|
|
15
17
|
|
16
18
|
## lets you use
|
17
19
|
## Hubba.configure do |config|
|
20
|
+
## config.token = 'secret'
|
21
|
+
## #-or-
|
18
22
|
## config.user = 'testdada'
|
19
23
|
## config.password = 'secret'
|
20
24
|
## end
|
@@ -61,8 +65,16 @@ class Github
|
|
61
65
|
|
62
66
|
def initialize( cache_dir: './cache' )
|
63
67
|
@cache = Cache.new( cache_dir )
|
64
|
-
|
65
|
-
|
68
|
+
|
69
|
+
@client = if Hubba.configuration.token
|
70
|
+
Client.new( token: Hubba.configuration.token )
|
71
|
+
elsif Hubba.configuration.user &&
|
72
|
+
Hubba.configuration.password
|
73
|
+
Client.new( user: Hubba.configuration.user,
|
74
|
+
password: Hubba.configuration.password )
|
75
|
+
else
|
76
|
+
Client.new
|
77
|
+
end
|
66
78
|
|
67
79
|
@offline = false
|
68
80
|
end
|
@@ -104,6 +116,16 @@ def org_repos( name )
|
|
104
116
|
end
|
105
117
|
|
106
118
|
|
119
|
+
|
120
|
+
def repo( full_name ) ## full_name (handle) e.g. henrythemes/jekyll-starter-theme
|
121
|
+
Resource.new( get "/repos/#{full_name}" )
|
122
|
+
end
|
123
|
+
|
124
|
+
def repo_commits( full_name )
|
125
|
+
Resource.new( get "/repos/#{full_name}/commits" )
|
126
|
+
end
|
127
|
+
|
128
|
+
|
107
129
|
private
|
108
130
|
def get( request_uri )
|
109
131
|
if offline?
|
data/lib/hubba/stats.rb
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
module Hubba
|
2
|
+
|
3
|
+
####
|
4
|
+
# keep track of repo stats over time (with history hash)
|
5
|
+
|
6
|
+
class Stats ## todo/check: rename to GithubRepoStats or RepoStats - why? why not?
|
7
|
+
|
8
|
+
attr_reader :data
|
9
|
+
|
10
|
+
def initialize( full_name )
|
11
|
+
@data = {}
|
12
|
+
@data['full_name'] = full_name # e.g. poole/hyde etc.
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def full_name() @full_name ||= @data['full_name']; end
|
17
|
+
|
18
|
+
## note: return datetime objects (NOT strings); if not present/available return nil/null
|
19
|
+
def created_at() @created_at ||= @data['created_at'] ? DateTime.strptime( @data['created_at'], '%Y-%m-%dT%H:%M:%S') : nil; end
|
20
|
+
def updated_at() @updated_at ||= @data['updated_at'] ? DateTime.strptime( @data['updated_at'], '%Y-%m-%dT%H:%M:%S') : nil; end
|
21
|
+
def pushed_at() @pushed_at ||= @data['pushed_at'] ? DateTime.strptime( @data['pushed_at'], '%Y-%m-%dT%H:%M:%S') : nil; end
|
22
|
+
|
23
|
+
## date (only) versions
|
24
|
+
def created() @created ||= @data['created_at'] ? Date.strptime( @data['created_at'], '%Y-%m-%d') : nil; end
|
25
|
+
def updated() @updated ||= @data['updated_at'] ? Date.strptime( @data['updated_at'], '%Y-%m-%d') : nil; end
|
26
|
+
def pushed() @pushed ||= @data['pushed_at'] ? Date.strptime( @data['pushed_at'], '%Y-%m-%d') : nil; end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
def history() @history ||= @data['history'] ? build_history( @data['history'] ) : nil; end
|
31
|
+
|
32
|
+
def size
|
33
|
+
# size of repo in kb (as reported by github api)
|
34
|
+
@size ||= @data['size'] || 0 ## return 0 if not found - why? why not? (return nil - why? why not??)
|
35
|
+
end
|
36
|
+
|
37
|
+
def stars
|
38
|
+
## return last stargazers_count entry (as number; 0 if not found)
|
39
|
+
@stars ||= history ? history[0].stars : 0
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def commits() @data['commits']; end
|
44
|
+
|
45
|
+
def last_commit() ## convenience shortcut; get first/last commit (use [0]) or nil
|
46
|
+
if @data['commits'] && @data['commits'][0]
|
47
|
+
@data['commits'][0]
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def committed() ## last commit date (from author NOT committer)
|
54
|
+
@committed ||= last_commit ? Date.strptime( last_commit['author']['date'], '%Y-%m-%d') : nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def committed_at() ## last commit date (from author NOT committer)
|
58
|
+
@committed_at ||= last_commit ? DateTime.strptime( last_commit['author']['date'], '%Y-%m-%dT%H:%M:%S') : nil
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def last_commit_message() ## convenience shortcut; last commit message
|
63
|
+
h = last_commit
|
64
|
+
|
65
|
+
committer_name = h['committer']['name']
|
66
|
+
author_name = h['author']['name']
|
67
|
+
message = h['message']
|
68
|
+
|
69
|
+
buf = ""
|
70
|
+
buf << message
|
71
|
+
buf << " by #{author_name}"
|
72
|
+
|
73
|
+
if committer_name != author_name
|
74
|
+
buf << " w/ #{committer_name}"
|
75
|
+
end
|
76
|
+
end # method commit_message
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
def reset_cache
|
81
|
+
## reset (invalidate) cached values from data hash
|
82
|
+
## use after reading or fetching
|
83
|
+
@full_name = nil
|
84
|
+
@created_at = @updated_at = @pushed_at = nil
|
85
|
+
@created = @updated = @pused = nil
|
86
|
+
@history = nil
|
87
|
+
@size = nil
|
88
|
+
@stars = nil
|
89
|
+
|
90
|
+
@committed_at = nil
|
91
|
+
@committed = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
########
|
96
|
+
## build history items (structs)
|
97
|
+
|
98
|
+
class HistoryItem
|
99
|
+
|
100
|
+
attr_reader :date, :stars ## read-only attributes
|
101
|
+
attr_accessor :prev, :next ## read/write attributes (for double linked list/nodes/items)
|
102
|
+
|
103
|
+
def initialize( date:, stars: )
|
104
|
+
@date = date
|
105
|
+
@stars = stars
|
106
|
+
@next = nil
|
107
|
+
end
|
108
|
+
|
109
|
+
## link items (append item at the end/tail)
|
110
|
+
def append( item )
|
111
|
+
@next = item
|
112
|
+
item.prev = self
|
113
|
+
end
|
114
|
+
|
115
|
+
def diff_days
|
116
|
+
if @next
|
117
|
+
## note: use jd=julian days for calculation
|
118
|
+
@date.jd - @next.date.jd
|
119
|
+
else
|
120
|
+
nil ## last item (tail)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def diff_stars
|
125
|
+
if @next
|
126
|
+
@stars - @next.stars
|
127
|
+
else
|
128
|
+
nil ## last item (tail)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end ## class HistoryItem
|
132
|
+
|
133
|
+
|
134
|
+
def build_history( timeseries )
|
135
|
+
items = []
|
136
|
+
|
137
|
+
keys = timeseries.keys.sort.reverse ## newest (latest) items first
|
138
|
+
keys.each do |key|
|
139
|
+
h = timeseries[ key ]
|
140
|
+
|
141
|
+
item = HistoryItem.new(
|
142
|
+
date: Date.strptime( key, '%Y-%m-%d' ),
|
143
|
+
stars: h['stargazers_count'] || 0 )
|
144
|
+
|
145
|
+
## link items
|
146
|
+
last_item = items[-1]
|
147
|
+
last_item.append( item ) if last_item ## if not nil? append (note first item has no prev item)
|
148
|
+
|
149
|
+
items << item
|
150
|
+
end
|
151
|
+
|
152
|
+
## todo/check: return [] for empty items array (items.empty?) - why?? why not??
|
153
|
+
if items.empty?
|
154
|
+
nil
|
155
|
+
else
|
156
|
+
items
|
157
|
+
end
|
158
|
+
end ## method build_history
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
def calc_diff_stars( samples: 3, days: 30 )
|
163
|
+
## samples: use n history item samples e.g. 3 samples
|
164
|
+
## days e.g. 7 days (per week), 30 days (per month)
|
165
|
+
|
166
|
+
if history.nil?
|
167
|
+
nil ## todo/check: return 0.0 too - why? why not?
|
168
|
+
elsif history.size == 1
|
169
|
+
## just one item; CANNOT calc diff; return zero
|
170
|
+
0.0
|
171
|
+
else
|
172
|
+
idx = [history.size, samples].min ## calc last index
|
173
|
+
last = history[idx-1]
|
174
|
+
first = history[0]
|
175
|
+
|
176
|
+
diff_days = first.date.jd - last.date.jd
|
177
|
+
diff_stars = first.stars - last.stars
|
178
|
+
|
179
|
+
## note: use factor 1000 for fixed integer division
|
180
|
+
## converts to float at the end
|
181
|
+
|
182
|
+
## todo: check for better way (convert to float upfront - why? why not?)
|
183
|
+
|
184
|
+
diff = (diff_stars * days * 1000) / diff_days
|
185
|
+
puts "diff=#{diff}:#{diff.class.name}" ## check if it's a float
|
186
|
+
(diff.to_f/1000.0)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def history_str
|
191
|
+
## returns "pretty printed" history as string buffer
|
192
|
+
buf = ''
|
193
|
+
buf << "[#{history.size}]: "
|
194
|
+
|
195
|
+
history.each do |item|
|
196
|
+
buf << "#{item.stars}"
|
197
|
+
|
198
|
+
diff_stars = item.diff_stars
|
199
|
+
diff_days = item.diff_days
|
200
|
+
if diff_stars && diff_days ## note: last item has no diffs
|
201
|
+
if diff_stars > 0 || diff_stars < 0
|
202
|
+
if diff_stars > 0
|
203
|
+
buf << " (+#{diff_stars}"
|
204
|
+
else
|
205
|
+
buf << " (#{diff_stars}"
|
206
|
+
end
|
207
|
+
buf << " in #{diff_days}d) "
|
208
|
+
else ## diff_stars == 0
|
209
|
+
buf << " (#{diff_days}d) "
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
buf
|
214
|
+
end # method history_str
|
215
|
+
|
216
|
+
|
217
|
+
###############################
|
218
|
+
## fetch / read / write methods
|
219
|
+
|
220
|
+
def fetch( gh ) ## update stats / fetch data from github via api
|
221
|
+
puts "fetching #{full_name}..."
|
222
|
+
repo = gh.repo( full_name )
|
223
|
+
|
224
|
+
## e.g. 2015-05-11T20:21:43Z
|
225
|
+
## puts Time.iso8601( repo.data['created_at'] )
|
226
|
+
@data['created_at'] = repo.data['created_at']
|
227
|
+
@data['updated_at'] = repo.data['updated_at']
|
228
|
+
@data['pushed_at'] = repo.data['pushed_at']
|
229
|
+
|
230
|
+
@data['size'] = repo.data['size'] # size in kb (kilobyte)
|
231
|
+
|
232
|
+
rec = {}
|
233
|
+
|
234
|
+
puts "stargazers_count"
|
235
|
+
puts repo.data['stargazers_count']
|
236
|
+
rec['stargazers_count'] = repo.data['stargazers_count']
|
237
|
+
|
238
|
+
today = Date.today.strftime( '%Y-%m-%d' ) ## e.g. 2016-09-27
|
239
|
+
puts "add record #{today} to history..."
|
240
|
+
pp rec # check if stargazers_count is a number (NOT a string)
|
241
|
+
|
242
|
+
@data[ 'history' ] ||= {}
|
243
|
+
@data[ 'history' ][ today ] = rec
|
244
|
+
|
245
|
+
##########################
|
246
|
+
## also check / keep track of (latest) commit
|
247
|
+
commits = gh.repo_commits( full_name )
|
248
|
+
puts "last commit/update:"
|
249
|
+
## pp commits
|
250
|
+
commit = {
|
251
|
+
'committer' => {
|
252
|
+
'date' => commits.data[0]['commit']['committer']['date'],
|
253
|
+
'name' => commits.data[0]['commit']['committer']['name']
|
254
|
+
},
|
255
|
+
'author' => {
|
256
|
+
'date' => commits.data[0]['commit']['author']['date'],
|
257
|
+
'name' => commits.data[0]['commit']['author']['name']
|
258
|
+
},
|
259
|
+
'message' => commits.data[0]['commit']['message']
|
260
|
+
}
|
261
|
+
|
262
|
+
## for now store only the latest commit (e.g. a single commit in an array)
|
263
|
+
@data[ 'commits' ] = [commit]
|
264
|
+
|
265
|
+
pp @data
|
266
|
+
|
267
|
+
reset_cache
|
268
|
+
self ## return self for (easy chaining)
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
|
273
|
+
def write( data_dir: './data' )
|
274
|
+
basename = full_name.gsub( '/', '~' ) ## e.g. poole/hyde become poole~hyde
|
275
|
+
puts "writing stats to #{basename}..."
|
276
|
+
File.open( "#{data_dir}/#{basename}.json", 'w:utf-8' ) do |f|
|
277
|
+
f.write JSON.pretty_generate( data )
|
278
|
+
end
|
279
|
+
self ## return self for (easy chaining)
|
280
|
+
end
|
281
|
+
|
282
|
+
|
283
|
+
def read( data_dir: './data' )
|
284
|
+
## note: skip reading if file not present
|
285
|
+
basename = full_name.gsub( '/', '~' ) ## e.g. poole/hyde become poole~hyde
|
286
|
+
filename = "#{data_dir}/#{basename}.json"
|
287
|
+
if File.exist?( filename )
|
288
|
+
puts "reading stats from #{basename}..."
|
289
|
+
json = File.open( filename, 'r:utf-8' ) { |file| file.read } ## todo/fix: use read_utf8
|
290
|
+
@data = JSON.parse( json )
|
291
|
+
reset_cache
|
292
|
+
else
|
293
|
+
puts "skipping reading stats from #{basename} -- file not found"
|
294
|
+
end
|
295
|
+
self ## return self for (easy chaining)
|
296
|
+
end
|
297
|
+
|
298
|
+
end # class Stats
|
299
|
+
|
300
|
+
|
301
|
+
end # module Hubba
|
data/lib/hubba/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
{
|
2
|
+
"full_name": "jekyll/minima",
|
3
|
+
"created_at": "2016-05-20T23:07:56Z",
|
4
|
+
"updated_at": "2018-02-11T16:13:33Z",
|
5
|
+
"pushed_at": "2018-02-07T22:14:11Z",
|
6
|
+
"size": 321,
|
7
|
+
"history": {
|
8
|
+
"2018-02-12": {
|
9
|
+
"stargazers_count": 717
|
10
|
+
}
|
11
|
+
},
|
12
|
+
"commits": [
|
13
|
+
{
|
14
|
+
"author": {
|
15
|
+
"name": "ashmaroli",
|
16
|
+
"date": "2018-02-21T19:35:59Z"
|
17
|
+
},
|
18
|
+
"committer": {
|
19
|
+
"name": "Frank Taillandier",
|
20
|
+
"date": "2018-02-21T19:35:59Z"
|
21
|
+
},
|
22
|
+
"message": "social icons should resolve baseurl properly (#201)"
|
23
|
+
}
|
24
|
+
]
|
25
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
{
|
2
|
+
"full_name": "openblockchains/awesome-blockchains",
|
3
|
+
"created_at": "2017-09-13T22:33:56Z",
|
4
|
+
"size": 1620,
|
5
|
+
"history": {
|
6
|
+
"2017-12-10": {
|
7
|
+
"stargazers_count": 1084
|
8
|
+
},
|
9
|
+
"2018-01-28": {
|
10
|
+
"stargazers_count": 1411
|
11
|
+
},
|
12
|
+
"2018-02-08": {
|
13
|
+
"stargazers_count": 1526
|
14
|
+
}
|
15
|
+
},
|
16
|
+
"commits": [
|
17
|
+
{
|
18
|
+
"committer": {
|
19
|
+
"date": "2018-02-08T09:33:28Z",
|
20
|
+
"name": "Gerald Bauer"
|
21
|
+
},
|
22
|
+
"message": "Update README.md\n\nJust a little typo cryto -> crypto."
|
23
|
+
}
|
24
|
+
],
|
25
|
+
"updated_at": "2018-02-08T19:26:35Z",
|
26
|
+
"pushed_at": "2018-02-08T09:33:29Z"
|
27
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
{
|
2
|
+
"full_name": "opendatajson/factbook.json",
|
3
|
+
"created_at": "2014-07-12T12:43:52Z",
|
4
|
+
"history": {
|
5
|
+
"2017-02-11": {
|
6
|
+
"stargazers_count": 457
|
7
|
+
},
|
8
|
+
"2017-02-12": {
|
9
|
+
"stargazers_count": 457
|
10
|
+
},
|
11
|
+
"2017-06-18": {
|
12
|
+
"stargazers_count": 505
|
13
|
+
},
|
14
|
+
"2017-07-28": {
|
15
|
+
"stargazers_count": 512
|
16
|
+
},
|
17
|
+
"2017-12-10": {
|
18
|
+
"stargazers_count": 533
|
19
|
+
},
|
20
|
+
"2018-01-28": {
|
21
|
+
"stargazers_count": 536
|
22
|
+
},
|
23
|
+
"2018-02-08": {
|
24
|
+
"stargazers_count": 539
|
25
|
+
}
|
26
|
+
},
|
27
|
+
"commits": [
|
28
|
+
{
|
29
|
+
"committer": {
|
30
|
+
"date": "2017-03-29T17:23:29Z",
|
31
|
+
"name": "GitHub"
|
32
|
+
},
|
33
|
+
"message": "Update MONGO.md"
|
34
|
+
}
|
35
|
+
],
|
36
|
+
"size": 7355,
|
37
|
+
"updated_at": "2018-02-01T12:35:19Z",
|
38
|
+
"pushed_at": "2017-03-29T17:23:30Z"
|
39
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
{
|
2
|
+
"full_name": "poole/hyde",
|
3
|
+
"created_at": "2013-02-07T07:01:38Z",
|
4
|
+
"updated_at": "2018-02-12T05:44:07Z",
|
5
|
+
"pushed_at": "2018-01-14T02:41:16Z",
|
6
|
+
"size": 28428,
|
7
|
+
"history": {
|
8
|
+
"2018-02-12": {
|
9
|
+
"stargazers_count": 2125
|
10
|
+
}
|
11
|
+
},
|
12
|
+
"commits": [
|
13
|
+
{
|
14
|
+
"committer": {
|
15
|
+
"date": "2015-05-11T20:21:43Z",
|
16
|
+
"name": "Mark Otto"
|
17
|
+
},
|
18
|
+
"message": "Merge pull request #91 from pborreli/patch-1\n\nFixed typo"
|
19
|
+
}
|
20
|
+
]
|
21
|
+
}
|
data/test/test_cache.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
###
|
4
2
|
# to run use
|
5
3
|
# ruby -I ./lib -I ./test test/test_cache.rb
|
@@ -32,15 +30,14 @@ class TestCache < MiniTest::Test
|
|
32
30
|
def test_cache
|
33
31
|
orgs = @cache.get( '/users/geraldb/orgs' )
|
34
32
|
assert_equal 4, orgs.size
|
35
|
-
|
33
|
+
|
36
34
|
repos = @cache.get( '/users/geraldb/repos' )
|
37
35
|
assert_equal 3, repos.size
|
38
36
|
end # method test_cache
|
39
37
|
|
40
38
|
def test_cache_miss
|
41
|
-
|
42
|
-
|
39
|
+
assert_nil @cache.get( '/test/hello' )
|
40
|
+
assert_nil @cache.get( '/test/hola' )
|
43
41
|
end # method test_cache_miss
|
44
42
|
|
45
43
|
end # class TestCache
|
46
|
-
|
data/test/test_config.rb
CHANGED
data/test/test_stats.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib -I ./test test/test_stats.rb
|
4
|
+
|
5
|
+
|
6
|
+
require 'helper'
|
7
|
+
|
8
|
+
|
9
|
+
class TestStats < MiniTest::Test
|
10
|
+
|
11
|
+
|
12
|
+
def test_jekyll_minima
|
13
|
+
|
14
|
+
stats = Hubba::Stats.new( 'jekyll/minima' )
|
15
|
+
|
16
|
+
assert_equal 0, stats.size
|
17
|
+
assert_equal 0, stats.stars
|
18
|
+
assert_nil stats.history
|
19
|
+
|
20
|
+
stats.read( data_dir: "#{Hubba.root}/test/stats" )
|
21
|
+
|
22
|
+
assert_equal 321, stats.size
|
23
|
+
assert_equal 717, stats.stars
|
24
|
+
assert_equal 717, stats.history[0].stars
|
25
|
+
assert_equal 1, stats.history.size
|
26
|
+
|
27
|
+
assert_equal Date.new(2018, 2, 12 ), stats.history[0].date
|
28
|
+
|
29
|
+
assert_nil stats.history[0].diff_days
|
30
|
+
|
31
|
+
assert_equal Date.new(2018, 2, 21 ), stats.committed
|
32
|
+
assert_equal Date.new(2016, 5, 20 ), stats.created
|
33
|
+
assert_equal Date.new(2018, 2, 11 ), stats.updated
|
34
|
+
assert_equal Date.new(2018, 2, 7 ), stats.pushed
|
35
|
+
|
36
|
+
assert_equal DateTime.new(2018, 2, 21, 19, 35, 59 ), stats.committed_at
|
37
|
+
assert_equal DateTime.new(2016, 5, 20, 23, 7, 56 ), stats.created_at
|
38
|
+
assert_equal DateTime.new(2018, 2, 11, 16, 13, 33 ), stats.updated_at
|
39
|
+
assert_equal DateTime.new(2018, 2, 7, 22, 14, 11 ), stats.pushed_at
|
40
|
+
|
41
|
+
|
42
|
+
pp stats.last_commit
|
43
|
+
pp stats.last_commit_message
|
44
|
+
pp stats.history_str ## pp history pretty printed to string (buffer)
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
def test_awesome_blockchains
|
50
|
+
|
51
|
+
stats = Hubba::Stats.new( 'openblockchains/awesome-blockchains' )
|
52
|
+
|
53
|
+
assert_equal 0, stats.size
|
54
|
+
assert_equal 0, stats.stars
|
55
|
+
assert_nil stats.history
|
56
|
+
|
57
|
+
stats.read( data_dir: "#{Hubba.root}/test/stats" )
|
58
|
+
|
59
|
+
assert_equal 1620, stats.size
|
60
|
+
assert_equal 1526, stats.stars
|
61
|
+
assert_equal 1526, stats.history[0].stars
|
62
|
+
assert_equal 1411, stats.history[1].stars
|
63
|
+
assert_equal 1084, stats.history[2].stars
|
64
|
+
assert_equal 1084, stats.history[-1].stars
|
65
|
+
assert_equal 3, stats.history.size
|
66
|
+
|
67
|
+
assert_equal Date.new(2018, 2, 8 ), stats.history[0].date
|
68
|
+
assert_equal Date.new(2018, 1, 28 ), stats.history[1].date
|
69
|
+
assert_equal Date.new(2017, 12, 10 ), stats.history[2].date
|
70
|
+
|
71
|
+
assert_equal 11, stats.history[0].diff_days
|
72
|
+
assert_equal 49, stats.history[1].diff_days
|
73
|
+
assert_nil stats.history[2].diff_days
|
74
|
+
|
75
|
+
assert_equal 115, stats.history[0].diff_stars
|
76
|
+
assert_equal 327, stats.history[1].diff_stars
|
77
|
+
assert_nil stats.history[2].diff_stars
|
78
|
+
|
79
|
+
assert_equal 221.0, stats.calc_diff_stars ## defaults to samples: 3, days: 30
|
80
|
+
assert_equal 51.566, stats.calc_diff_stars( samples: 5, days: 7 )
|
81
|
+
|
82
|
+
pp stats.history_str ## pp history pretty printed to string (buffer)
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def test_factbook_json
|
87
|
+
|
88
|
+
stats = Hubba::Stats.new( 'opendatajson/factbook.json' )
|
89
|
+
|
90
|
+
assert_equal 0, stats.size
|
91
|
+
assert_equal 0, stats.stars
|
92
|
+
assert_nil stats.history
|
93
|
+
|
94
|
+
stats.read( data_dir: "#{Hubba.root}/test/stats" )
|
95
|
+
|
96
|
+
assert_equal 7355, stats.size
|
97
|
+
assert_equal 539, stats.stars
|
98
|
+
assert_equal 539, stats.history[0].stars
|
99
|
+
assert_equal 536, stats.history[1].stars
|
100
|
+
assert_equal 533, stats.history[2].stars
|
101
|
+
assert_equal 457, stats.history[-1].stars
|
102
|
+
assert_equal 7, stats.history.size
|
103
|
+
|
104
|
+
assert_equal Date.new(2018, 2, 8 ), stats.history[0].date
|
105
|
+
assert_equal Date.new(2018, 1, 28 ), stats.history[1].date
|
106
|
+
assert_equal Date.new(2017, 12, 10 ), stats.history[2].date
|
107
|
+
|
108
|
+
assert_equal 11, stats.history[0].diff_days
|
109
|
+
assert_equal 49, stats.history[1].diff_days
|
110
|
+
assert_nil stats.history[-1].diff_days
|
111
|
+
|
112
|
+
assert_equal 3, stats.history[0].diff_stars
|
113
|
+
assert_equal 3, stats.history[1].diff_stars
|
114
|
+
assert_nil stats.history[-1].diff_stars
|
115
|
+
|
116
|
+
assert_equal 3.0, stats.calc_diff_stars ## defaults to samples: 3, days: 30
|
117
|
+
assert_equal 1.012, stats.calc_diff_stars( samples: 5, days: 7 )
|
118
|
+
|
119
|
+
pp stats.history_str ## pp history pretty printed to string (buffer)
|
120
|
+
end
|
121
|
+
|
122
|
+
end # class TestStats
|
@@ -0,0 +1,41 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib -I ./test test/test_stats_tmp.rb
|
4
|
+
|
5
|
+
|
6
|
+
require 'helper'
|
7
|
+
|
8
|
+
|
9
|
+
class TestStatsTmp < MiniTest::Test
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@gh = Hubba::Github.new( cache_dir: "#{Hubba.root}/test/cache" )
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_stats
|
16
|
+
repos = [
|
17
|
+
'poole/hyde',
|
18
|
+
'jekyll/minima'
|
19
|
+
]
|
20
|
+
|
21
|
+
repos.each do |repo|
|
22
|
+
stats = Hubba::Stats.new( repo )
|
23
|
+
# stats.read( data_dir: './tmp' )
|
24
|
+
stats.read( data_dir: "#{Hubba.root}/test/stats" )
|
25
|
+
|
26
|
+
puts "stars before fetch: #{stats.stars}"
|
27
|
+
puts "size before fetch: #{stats.size} kb"
|
28
|
+
|
29
|
+
## note/todo: enable for "live" online testing
|
30
|
+
## stats.fetch( @gh )
|
31
|
+
|
32
|
+
puts "stars after fetch: #{stats.stars}"
|
33
|
+
puts "size after fetch: #{stats.size} kb"
|
34
|
+
|
35
|
+
stats.write( data_dir: './tmp' )
|
36
|
+
end
|
37
|
+
|
38
|
+
assert true # for now everything ok if we get here
|
39
|
+
end
|
40
|
+
|
41
|
+
end # class TestStatsTmp
|
metadata
CHANGED
@@ -1,67 +1,73 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hubba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-10-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: webclient
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.1.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 0.1.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rdoc
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '4.0'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '7'
|
34
37
|
type: :development
|
35
38
|
prerelease: false
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
37
40
|
requirements:
|
38
|
-
- - "
|
41
|
+
- - ">="
|
39
42
|
- !ruby/object:Gem::Version
|
40
43
|
version: '4.0'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '7'
|
41
47
|
- !ruby/object:Gem::Dependency
|
42
48
|
name: hoe
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
44
50
|
requirements:
|
45
51
|
- - "~>"
|
46
52
|
- !ruby/object:Gem::Version
|
47
|
-
version: '3.
|
53
|
+
version: '3.22'
|
48
54
|
type: :development
|
49
55
|
prerelease: false
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
51
57
|
requirements:
|
52
58
|
- - "~>"
|
53
59
|
- !ruby/object:Gem::Version
|
54
|
-
version: '3.
|
55
|
-
description: hubba -
|
60
|
+
version: '3.22'
|
61
|
+
description: hubba - (yet) another (lite) GitHub HTTP API client / library
|
56
62
|
email: ruby-talk@ruby-lang.org
|
57
63
|
executables: []
|
58
64
|
extensions: []
|
59
65
|
extra_rdoc_files:
|
60
|
-
-
|
66
|
+
- CHANGELOG.md
|
61
67
|
- Manifest.txt
|
62
68
|
- README.md
|
63
69
|
files:
|
64
|
-
-
|
70
|
+
- CHANGELOG.md
|
65
71
|
- Manifest.txt
|
66
72
|
- README.md
|
67
73
|
- Rakefile
|
@@ -69,13 +75,20 @@ files:
|
|
69
75
|
- lib/hubba/cache.rb
|
70
76
|
- lib/hubba/client.rb
|
71
77
|
- lib/hubba/github.rb
|
78
|
+
- lib/hubba/stats.rb
|
72
79
|
- lib/hubba/version.rb
|
73
80
|
- test/cache/users~geraldb~orgs.json
|
74
81
|
- test/cache/users~geraldb~repos.json
|
75
82
|
- test/helper.rb
|
83
|
+
- test/stats/jekyll~minima.json
|
84
|
+
- test/stats/openblockchains~awesome-blockchains.json
|
85
|
+
- test/stats/opendatajson~factbook.json.json
|
86
|
+
- test/stats/poole~hyde.json
|
76
87
|
- test/test_cache.rb
|
77
88
|
- test/test_config.rb
|
78
|
-
|
89
|
+
- test/test_stats.rb
|
90
|
+
- test/test_stats_tmp.rb
|
91
|
+
homepage: https://github.com/rubycoco/git
|
79
92
|
licenses:
|
80
93
|
- Public Domain
|
81
94
|
metadata: {}
|
@@ -89,7 +102,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
102
|
requirements:
|
90
103
|
- - ">="
|
91
104
|
- !ruby/object:Gem::Version
|
92
|
-
version:
|
105
|
+
version: 2.2.2
|
93
106
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
107
|
requirements:
|
95
108
|
- - ">="
|
@@ -97,8 +110,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
110
|
version: '0'
|
98
111
|
requirements: []
|
99
112
|
rubyforge_project:
|
100
|
-
rubygems_version: 2.2
|
113
|
+
rubygems_version: 2.5.2
|
101
114
|
signing_key:
|
102
115
|
specification_version: 4
|
103
|
-
summary: hubba -
|
116
|
+
summary: hubba - (yet) another (lite) GitHub HTTP API client / library
|
104
117
|
test_files: []
|