hubba 0.4.0 → 0.6.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 +4 -5
- data/README.md +20 -20
- data/Rakefile +4 -4
- data/lib/hubba.rb +30 -12
- data/lib/hubba/client.rb +47 -21
- data/lib/hubba/config.rb +51 -0
- data/lib/hubba/github.rb +60 -51
- data/lib/hubba/reports.rb +249 -0
- data/lib/hubba/reposet.rb +170 -0
- data/lib/hubba/stats.rb +104 -33
- data/lib/hubba/version.rb +2 -4
- data/test/helper.rb +0 -2
- data/test/stats/jekyll~minima.json +8 -4
- data/test/test_config.rb +10 -2
- data/test/test_stats.rb +44 -5
- data/test/test_stats_tmp.rb +8 -7
- metadata +22 -17
- data/lib/hubba/cache.rb +0 -62
- data/test/cache/users~geraldb~orgs.json +0 -46
- data/test/cache/users~geraldb~repos.json +0 -263
- data/test/test_cache.rb +0 -45
@@ -0,0 +1,249 @@
|
|
1
|
+
module Hubba
|
2
|
+
|
3
|
+
|
4
|
+
class Report
|
5
|
+
def initialize( stats_or_hash_or_path=Hubba.stats )
|
6
|
+
## puts "[debug] Report#initialize:"
|
7
|
+
## pp stats_or_hash_or_path if stats_or_hash_or_path.is_a?( String )
|
8
|
+
|
9
|
+
@stats = if stats_or_hash_or_path.is_a?( String ) ||
|
10
|
+
stats_or_hash_or_path.is_a?( Hash )
|
11
|
+
hash_or_path = stats_or_hash_or_path
|
12
|
+
Hubba.stats( hash_or_path )
|
13
|
+
else
|
14
|
+
stats_or_hash_or_path ## assume Summary/Stats - todo/fix: double check!!!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def save( path )
|
19
|
+
buf = build
|
20
|
+
puts "writing report >#{path}< ..."
|
21
|
+
File.open( path, "w:utf-8" ) do |f|
|
22
|
+
f.write( buf )
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end ## class Report
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
class ReportSummary < Report
|
30
|
+
|
31
|
+
def build
|
32
|
+
## create a (summary report)
|
33
|
+
##
|
34
|
+
## add stars, last_updates, etc.
|
35
|
+
## org description etc??
|
36
|
+
|
37
|
+
## note: orgs is orgs+users e.g. geraldb, yorobot etc
|
38
|
+
buf = String.new('')
|
39
|
+
buf << "# #{@stats.repos.size} repos @ #{@stats.orgs.size} orgs\n"
|
40
|
+
buf << "\n"
|
41
|
+
|
42
|
+
|
43
|
+
@stats.orgs.each do |org|
|
44
|
+
name = org[0]
|
45
|
+
repos = org[1]
|
46
|
+
buf << "### #{name} _(#{repos.size})_\n"
|
47
|
+
buf << "\n"
|
48
|
+
|
49
|
+
### add stats for repos
|
50
|
+
entries = []
|
51
|
+
repos.each do |repo|
|
52
|
+
entries << "**#{repo.name}** ★#{repo.stats.stars} (#{repo.stats.size} kb)"
|
53
|
+
end
|
54
|
+
|
55
|
+
buf << entries.join( ' · ' ) ## use interpunct? - was: • (bullet)
|
56
|
+
buf << "\n"
|
57
|
+
buf << "\n"
|
58
|
+
end
|
59
|
+
|
60
|
+
buf
|
61
|
+
end # method build
|
62
|
+
end # class ReportSummary
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
class ReportStars < Report
|
67
|
+
|
68
|
+
def build
|
69
|
+
|
70
|
+
## add stars, last_updates, etc.
|
71
|
+
## org description etc??
|
72
|
+
|
73
|
+
## note: orgs is orgs+users e.g. geraldb, yorobot etc
|
74
|
+
buf = String.new('')
|
75
|
+
buf << "# #{@stats.repos.size} repos @ #{@stats.orgs.size} orgs\n"
|
76
|
+
buf << "\n"
|
77
|
+
|
78
|
+
|
79
|
+
repos = @stats.repos.sort do |l,r|
|
80
|
+
## note: use reverse sort (right,left) - e.g. most stars first
|
81
|
+
r.stats.stars <=> l.stats.stars
|
82
|
+
end
|
83
|
+
|
84
|
+
## pp repos
|
85
|
+
|
86
|
+
repos.each_with_index do |repo,i|
|
87
|
+
buf << "#{i+1}. ★#{repo.stats.stars} **#{repo.full_name}** (#{repo.stats.size} kb)\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
buf
|
91
|
+
end # method build
|
92
|
+
end # class ReportStars
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
class ReportTimeline < Report
|
97
|
+
|
98
|
+
def build
|
99
|
+
## create a (timeline report)
|
100
|
+
|
101
|
+
## note: orgs is orgs+users e.g. geraldb, yorobot etc
|
102
|
+
buf = String.new('')
|
103
|
+
buf << "# #{@stats.repos.size} repos @ #{@stats.orgs.size} orgs\n"
|
104
|
+
buf << "\n"
|
105
|
+
|
106
|
+
|
107
|
+
repos = @stats.repos.sort do |l,r|
|
108
|
+
## note: use reverse sort (right,left) - e.g. most stars first
|
109
|
+
## r[:stars] <=> l[:stars]
|
110
|
+
|
111
|
+
## sort by created_at (use julian days)
|
112
|
+
r.stats.created.jd <=> l.stats.created.jd
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
## pp repos
|
117
|
+
|
118
|
+
|
119
|
+
last_year = -1
|
120
|
+
last_month = -1
|
121
|
+
|
122
|
+
repos.each_with_index do |repo,i|
|
123
|
+
year = repo.stats.created.year
|
124
|
+
month = repo.stats.created.month
|
125
|
+
|
126
|
+
if last_year != year
|
127
|
+
buf << "\n## #{year}\n\n"
|
128
|
+
end
|
129
|
+
|
130
|
+
if last_month != month
|
131
|
+
buf << "\n### #{month}\n\n"
|
132
|
+
end
|
133
|
+
|
134
|
+
last_year = year
|
135
|
+
last_month = month
|
136
|
+
|
137
|
+
buf << "- #{repo.stats.created_at.strftime('%Y-%m-%d')} ★#{repo.stats.stars} **#{repo.full_name}** (#{repo.stats.size} kb)\n"
|
138
|
+
end
|
139
|
+
|
140
|
+
buf
|
141
|
+
end # method build
|
142
|
+
end # class ReportTimeline
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
class ReportTrending < Report
|
147
|
+
|
148
|
+
def build
|
149
|
+
|
150
|
+
## note: orgs is orgs+users e.g. geraldb, yorobot etc
|
151
|
+
buf = String.new('')
|
152
|
+
buf << "# #{@stats.repos.size} repos @ #{@stats.orgs.size} orgs\n"
|
153
|
+
buf << "\n"
|
154
|
+
|
155
|
+
###
|
156
|
+
## todo:
|
157
|
+
## use calc per month (days: 30)
|
158
|
+
## per week is too optimistic (e.g. less than one star/week e.g. 0.6 or something)
|
159
|
+
|
160
|
+
repos = @stats.repos.sort do |l,r|
|
161
|
+
## note: use reverse sort (right,left) - e.g. most stars first
|
162
|
+
## r[:stars] <=> l[:stars]
|
163
|
+
|
164
|
+
## sort by created_at (use julian days)
|
165
|
+
## r[:created_at].to_date.jd <=> l[:created_at].to_date.jd
|
166
|
+
|
167
|
+
res = r.diff <=> l.diff
|
168
|
+
res = r.stats.stars <=> l.stats.stars if res == 0
|
169
|
+
res = r.stats.created.jd <=> l.stats.created.jd if res == 0
|
170
|
+
res
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
## pp repos
|
175
|
+
|
176
|
+
|
177
|
+
repos.each_with_index do |repo,i|
|
178
|
+
if repo.diff == 0
|
179
|
+
buf << "- -/- "
|
180
|
+
else
|
181
|
+
buf << "- #{repo.diff}/month "
|
182
|
+
end
|
183
|
+
|
184
|
+
buf << " ★#{repo.stats.stars} **#{repo.full_name}** (#{repo.stats.size} kb) - "
|
185
|
+
buf << "#{repo.stats.history_str}\n"
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
buf
|
190
|
+
end # method build
|
191
|
+
end # class ReportTrending
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
class ReportUpdates < Report
|
196
|
+
|
197
|
+
def build
|
198
|
+
|
199
|
+
## note: orgs is orgs+users e.g. geraldb, yorobot etc
|
200
|
+
buf = String.new('')
|
201
|
+
buf << "# #{@stats.repos.size} repos @ #{@stats.orgs.size} orgs\n"
|
202
|
+
buf << "\n"
|
203
|
+
|
204
|
+
repos = @stats.repos.sort do |l,r|
|
205
|
+
r.stats.committed.jd <=> l.stats.committed.jd
|
206
|
+
end
|
207
|
+
|
208
|
+
## pp repos
|
209
|
+
|
210
|
+
|
211
|
+
buf << "committed / pushed / updated / created\n\n"
|
212
|
+
|
213
|
+
today = Date.today
|
214
|
+
|
215
|
+
repos.each_with_index do |repo,i|
|
216
|
+
|
217
|
+
days_ago = today.jd - repo.stats.committed.jd
|
218
|
+
|
219
|
+
diff1 = repo.stats.committed.jd - repo.stats.pushed.jd
|
220
|
+
diff2 = repo.stats.committed.jd - repo.stats.updated.jd
|
221
|
+
diff3 = repo.stats.pushed.jd - repo.stats.updated.jd
|
222
|
+
|
223
|
+
buf << "- (#{days_ago}d) **#{repo.full_name}** ★#{repo.stats.stars} - "
|
224
|
+
buf << "#{repo.stats.committed} "
|
225
|
+
buf << "("
|
226
|
+
buf << (diff1==0 ? '=' : "#{diff1}d")
|
227
|
+
buf << "/"
|
228
|
+
buf << (diff2==0 ? '=' : "#{diff2}d")
|
229
|
+
buf << ")"
|
230
|
+
buf << " / "
|
231
|
+
buf << "#{repo.stats.pushed} "
|
232
|
+
buf << "("
|
233
|
+
buf << (diff3==0 ? '=' : "#{diff3}d")
|
234
|
+
buf << ")"
|
235
|
+
buf << " / "
|
236
|
+
buf << "#{repo.stats.updated} / "
|
237
|
+
buf << "#{repo.stats.created} - "
|
238
|
+
buf << "‹#{repo.stats.last_commit_message}›"
|
239
|
+
buf << " (#{repo.stats.size} kb)"
|
240
|
+
buf << "\n"
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
buf
|
245
|
+
end # method build
|
246
|
+
end # class ReportUpdates
|
247
|
+
|
248
|
+
|
249
|
+
end # module Hubba
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module Hubba
|
2
|
+
|
3
|
+
|
4
|
+
def self.update_stats( host_or_path='./repos.yml' ) ## move to reposet e.g. Reposet#update_status!!!!
|
5
|
+
h = if hash_or_path.is_a?( String ) ## assume it is a file path!!!
|
6
|
+
path = hash_or_path
|
7
|
+
YAML.load_file( path )
|
8
|
+
else
|
9
|
+
hash_or_path # assume its a hash / reposet already!!!
|
10
|
+
end
|
11
|
+
|
12
|
+
gh = Github.new
|
13
|
+
gh.update_stats( h )
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def self.stats( hash_or_path='./repos.yml' ) ## use read_stats or such - why? why not?
|
18
|
+
h = if hash_or_path.is_a?( String ) ## assume it is a file path!!!
|
19
|
+
path = hash_or_path
|
20
|
+
YAML.load_file( path )
|
21
|
+
else
|
22
|
+
hash_or_path # assume its a hash / reposet already!!!
|
23
|
+
end
|
24
|
+
|
25
|
+
Summary.new( h ) ## wrap in "easy-access" facade / wrapper
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
class Summary # todo/check: use a different name e.g (Data)Base, Census, Catalog, Collection, Index, Register or such???
|
31
|
+
|
32
|
+
class Repo ## (nested) class
|
33
|
+
|
34
|
+
attr_reader :owner,
|
35
|
+
:name
|
36
|
+
|
37
|
+
def initialize( owner, name )
|
38
|
+
@owner = owner ## rename to login, username - why? why not?
|
39
|
+
@name = name ## rename to reponame ??
|
40
|
+
end
|
41
|
+
|
42
|
+
def full_name() "#{owner}/#{name}"; end
|
43
|
+
|
44
|
+
def stats
|
45
|
+
## note: load stats on demand only (first access) for now - why? why not?
|
46
|
+
@stats ||= begin
|
47
|
+
stats = Stats.new( full_name )
|
48
|
+
stats.read
|
49
|
+
stats
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def diff
|
54
|
+
@diff ||= stats.calc_diff_stars( samples: 3, days: 30 )
|
55
|
+
end
|
56
|
+
end # (nested) class Repo
|
57
|
+
|
58
|
+
|
59
|
+
attr_reader :orgs, :repos
|
60
|
+
|
61
|
+
def initialize( hash )
|
62
|
+
@orgs = [] # orgs and users -todo/check: use better name - logins or owners? why? why not?
|
63
|
+
@repos = []
|
64
|
+
add( hash )
|
65
|
+
|
66
|
+
puts "#{@repos.size} repos @ #{@orgs.size} orgs"
|
67
|
+
end
|
68
|
+
|
69
|
+
#############
|
70
|
+
## private helpes
|
71
|
+
def add( hash ) ## add repos.yml set
|
72
|
+
hash.each do |org_with_counter, names|
|
73
|
+
## remove optional number from key e.g.
|
74
|
+
## mrhydescripts (3) => mrhydescripts
|
75
|
+
## footballjs (4) => footballjs
|
76
|
+
## etc.
|
77
|
+
org = org_with_counter.sub( /\([0-9]+\)/, '' ).strip
|
78
|
+
repos = []
|
79
|
+
names.each do |name|
|
80
|
+
repo = Repo.new( org, name )
|
81
|
+
repos << repo
|
82
|
+
end
|
83
|
+
@orgs << [org, repos]
|
84
|
+
@repos += repos
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end # class Summary
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
## orgs - include repos form org(anizations) too
|
92
|
+
## cache - save json response to cache_dir - change to/use debug/tmp_dir? - why? why not?
|
93
|
+
def self.reposet( *users, orgs: true,
|
94
|
+
cache: false )
|
95
|
+
# users = [users] if users.is_a?( String ) ### wrap in array if single user
|
96
|
+
|
97
|
+
gh = Hubba::Github.new
|
98
|
+
|
99
|
+
forks = []
|
100
|
+
|
101
|
+
h = {}
|
102
|
+
users.each do |user|
|
103
|
+
res = gh.user_repos( user )
|
104
|
+
save_json( "#{config.cache_dir}/users~#{user}~repos.json", res.data ) if cache
|
105
|
+
|
106
|
+
repos = []
|
107
|
+
####
|
108
|
+
# check for forked repos (auto-exclude personal by default)
|
109
|
+
# note: forked repos in orgs get NOT auto-excluded!!!
|
110
|
+
res.data.each do |repo|
|
111
|
+
fork = repo['fork']
|
112
|
+
if fork
|
113
|
+
print "FORK "
|
114
|
+
forks << "#{repo['full_name']} (AUTO-EXCLUDED)"
|
115
|
+
else
|
116
|
+
print " "
|
117
|
+
repos << repo['name']
|
118
|
+
end
|
119
|
+
print repo['full_name']
|
120
|
+
print "\n"
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
h[ "#{user} (#{repos.size})" ] = repos.sort
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
## all repos from orgs
|
129
|
+
## note: for now only use first (primary user) - why? why not?
|
130
|
+
if orgs
|
131
|
+
user = users[0]
|
132
|
+
res = gh.user_orgs( user )
|
133
|
+
save_json( "#{config.cache_dir}/users~#{user}~orgs.json", res.data ) if cache
|
134
|
+
|
135
|
+
|
136
|
+
logins = res.logins.each do |login|
|
137
|
+
## next if ['xxx'].include?( login ) ## add orgs here to skip
|
138
|
+
|
139
|
+
res = gh.org_repos( login )
|
140
|
+
save_json( "#{config.cache_dir}/orgs~#{login}~repos.json", res.data ) if cache
|
141
|
+
|
142
|
+
repos = []
|
143
|
+
res.data.each do |repo|
|
144
|
+
fork = repo['fork']
|
145
|
+
if fork
|
146
|
+
print "FORK "
|
147
|
+
forks << repo['full_name']
|
148
|
+
repos << repo['name']
|
149
|
+
else
|
150
|
+
print " "
|
151
|
+
repos << repo['name']
|
152
|
+
end
|
153
|
+
print repo['full_name']
|
154
|
+
print "\n"
|
155
|
+
end
|
156
|
+
|
157
|
+
h[ "#{login} (#{repos.size})" ] = repos.sort
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
if forks.size > 0
|
162
|
+
puts
|
163
|
+
puts "#{forks.size} fork(s):"
|
164
|
+
puts forks
|
165
|
+
end
|
166
|
+
|
167
|
+
h
|
168
|
+
end ## method reposet
|
169
|
+
end # module Hubba
|
170
|
+
|
data/lib/hubba/stats.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
1
|
module Hubba
|
4
2
|
|
5
3
|
####
|
@@ -12,39 +10,95 @@ module Hubba
|
|
12
10
|
def initialize( full_name )
|
13
11
|
@data = {}
|
14
12
|
@data['full_name'] = full_name # e.g. poole/hyde etc.
|
13
|
+
|
14
|
+
@cache = {}
|
15
15
|
end
|
16
16
|
|
17
17
|
|
18
|
-
def full_name() @
|
18
|
+
def full_name() @data['full_name']; end
|
19
|
+
|
19
20
|
|
20
21
|
## note: return datetime objects (NOT strings); if not present/available return nil/null
|
21
|
-
def created_at() @
|
22
|
-
def updated_at() @
|
23
|
-
def pushed_at() @
|
22
|
+
def created_at() @cache['created_at'] ||= parse_datetime( @data['created_at'] ); end
|
23
|
+
def updated_at() @cache['updated_at'] ||= parse_datetime( @data['updated_at'] ); end
|
24
|
+
def pushed_at() @cache['pushed_at'] ||= parse_datetime( @data['pushed_at'] ); end
|
24
25
|
|
25
|
-
|
26
|
+
## date (only) versions
|
27
|
+
def created() @cache['created'] ||= parse_date( @data['created_at'] ); end
|
28
|
+
def updated() @cache['updated'] ||= parse_date( @data['updated_at'] ); end
|
29
|
+
def pushed() @cache['pushed'] ||= parse_date( @data['pushed_at'] ); end
|
26
30
|
|
27
31
|
def size
|
28
32
|
# size of repo in kb (as reported by github api)
|
29
|
-
@
|
33
|
+
@data['size'] || 0 ## return 0 if not found - why? why not? (return nil - why? why not??)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def history
|
38
|
+
@cache['history'] ||= begin
|
39
|
+
if @data['history']
|
40
|
+
build_history( @data['history'] )
|
41
|
+
else
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
end
|
30
45
|
end
|
31
46
|
|
47
|
+
|
32
48
|
def stars
|
33
49
|
## return last stargazers_count entry (as number; 0 if not found)
|
34
|
-
@stars ||= history ? history[0].stars : 0
|
50
|
+
@cache['stars'] ||= history ? history[0].stars : 0
|
35
51
|
end
|
36
52
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
53
|
+
|
54
|
+
def commits() @data['commits']; end
|
55
|
+
|
56
|
+
def last_commit ## convenience shortcut; get first/last commit (use [0]) or nil
|
57
|
+
if @data['commits'] && @data['commits'][0]
|
58
|
+
@data['commits'][0]
|
59
|
+
else
|
60
|
+
nil
|
61
|
+
end
|
45
62
|
end
|
46
63
|
|
47
64
|
|
65
|
+
def committed ## last commit date (from author NOT committer)
|
66
|
+
@cache['committed'] ||= parse_date( last_commit_author_date )
|
67
|
+
end
|
68
|
+
|
69
|
+
def committed_at() ## last commit date (from author NOT committer)
|
70
|
+
@cache['committed_at'] ||= parse_datetime( last_commit_author_date )
|
71
|
+
end
|
72
|
+
|
73
|
+
def last_commit_author_date
|
74
|
+
h = last_commit
|
75
|
+
h ? h['author']['date'] : nil
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
def last_commit_message ## convenience shortcut; last commit message
|
80
|
+
h = last_commit
|
81
|
+
|
82
|
+
committer_name = h['committer']['name']
|
83
|
+
author_name = h['author']['name']
|
84
|
+
message = h['message']
|
85
|
+
|
86
|
+
buf = ""
|
87
|
+
buf << message
|
88
|
+
buf << " by #{author_name}"
|
89
|
+
|
90
|
+
if committer_name != author_name
|
91
|
+
buf << " w/ #{committer_name}"
|
92
|
+
end
|
93
|
+
end # method commit_message
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
###
|
98
|
+
# helpers
|
99
|
+
def parse_datetime( str ) str ? DateTime.strptime( str, '%Y-%m-%dT%H:%M:%S') : nil; end
|
100
|
+
def parse_date( str ) str ? Date.strptime( str, '%Y-%m-%d') : nil; end
|
101
|
+
|
48
102
|
########
|
49
103
|
## build history items (structs)
|
50
104
|
|
@@ -56,6 +110,7 @@ module Hubba
|
|
56
110
|
def initialize( date:, stars: )
|
57
111
|
@date = date
|
58
112
|
@stars = stars
|
113
|
+
@next = nil
|
59
114
|
end
|
60
115
|
|
61
116
|
## link items (append item at the end/tail)
|
@@ -134,12 +189,13 @@ module Hubba
|
|
134
189
|
## todo: check for better way (convert to float upfront - why? why not?)
|
135
190
|
|
136
191
|
diff = (diff_stars * days * 1000) / diff_days
|
137
|
-
puts "diff=#{diff}:#{diff.class.name}" ## check if it's a float
|
192
|
+
## puts "diff=#{diff}:#{diff.class.name}" ## check if it's a float
|
138
193
|
(diff.to_f/1000.0)
|
139
194
|
end
|
140
195
|
end
|
141
196
|
|
142
|
-
|
197
|
+
|
198
|
+
def history_str ## todo/check: rename/change to format_history or fmt_history - why? why not?
|
143
199
|
## returns "pretty printed" history as string buffer
|
144
200
|
buf = ''
|
145
201
|
buf << "[#{history.size}]: "
|
@@ -166,12 +222,13 @@ module Hubba
|
|
166
222
|
end # method history_str
|
167
223
|
|
168
224
|
|
225
|
+
|
169
226
|
###############################
|
170
227
|
## fetch / read / write methods
|
171
228
|
|
172
|
-
def
|
173
|
-
|
174
|
-
|
229
|
+
def update( repo, commits ) ## update stats / fetch data from github via api
|
230
|
+
raise ArgumentError, "Hubba::Resource expected; got #{repo.class.name}" unless repo.is_a?( Resource )
|
231
|
+
raise ArgumentError, "Hubba::Resource expected; got #{commits.class.name}" unless commits.is_a?( Resource )
|
175
232
|
|
176
233
|
## e.g. 2015-05-11T20:21:43Z
|
177
234
|
## puts Time.iso8601( repo.data['created_at'] )
|
@@ -196,7 +253,6 @@ module Hubba
|
|
196
253
|
|
197
254
|
##########################
|
198
255
|
## also check / keep track of (latest) commit
|
199
|
-
commits = gh.repo_commits( full_name )
|
200
256
|
puts "last commit/update:"
|
201
257
|
## pp commits
|
202
258
|
commit = {
|
@@ -204,6 +260,10 @@ module Hubba
|
|
204
260
|
'date' => commits.data[0]['commit']['committer']['date'],
|
205
261
|
'name' => commits.data[0]['commit']['committer']['name']
|
206
262
|
},
|
263
|
+
'author' => {
|
264
|
+
'date' => commits.data[0]['commit']['author']['date'],
|
265
|
+
'name' => commits.data[0]['commit']['author']['name']
|
266
|
+
},
|
207
267
|
'message' => commits.data[0]['commit']['message']
|
208
268
|
}
|
209
269
|
|
@@ -212,15 +272,21 @@ module Hubba
|
|
212
272
|
|
213
273
|
pp @data
|
214
274
|
|
215
|
-
|
275
|
+
## reset (invalidate) cached values from data hash
|
276
|
+
## use after reading or fetching
|
277
|
+
@cache = {}
|
278
|
+
|
216
279
|
self ## return self for (easy chaining)
|
217
280
|
end
|
218
281
|
|
219
282
|
|
220
283
|
|
221
|
-
def write
|
284
|
+
def write
|
222
285
|
basename = full_name.gsub( '/', '~' ) ## e.g. poole/hyde become poole~hyde
|
223
|
-
|
286
|
+
data_dir = Hubba.config.data_dir
|
287
|
+
puts " writing stats to #{basename} (#{data_dir})..."
|
288
|
+
|
289
|
+
## todo/fix: add FileUtils.makepath_r or such!!!
|
224
290
|
File.open( "#{data_dir}/#{basename}.json", 'w:utf-8' ) do |f|
|
225
291
|
f.write JSON.pretty_generate( data )
|
226
292
|
end
|
@@ -228,17 +294,22 @@ module Hubba
|
|
228
294
|
end
|
229
295
|
|
230
296
|
|
231
|
-
def read
|
297
|
+
def read
|
232
298
|
## note: skip reading if file not present
|
233
299
|
basename = full_name.gsub( '/', '~' ) ## e.g. poole/hyde become poole~hyde
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
300
|
+
data_dir = Hubba.config.data_dir
|
301
|
+
path = "#{data_dir}/#{basename}.json"
|
302
|
+
|
303
|
+
if File.exist?( path )
|
304
|
+
puts " reading stats from #{basename} (#{data_dir})..."
|
305
|
+
json = File.open( path, 'r:utf-8' ) { |f| f.read }
|
238
306
|
@data = JSON.parse( json )
|
239
|
-
|
307
|
+
|
308
|
+
## reset (invalidate) cached values from data hash
|
309
|
+
## use after reading or fetching
|
310
|
+
@cache = {}
|
240
311
|
else
|
241
|
-
puts "skipping reading stats from #{basename} -- file not found"
|
312
|
+
puts "!! WARN: - skipping reading stats from #{basename} -- file not found"
|
242
313
|
end
|
243
314
|
self ## return self for (easy chaining)
|
244
315
|
end
|