hubba 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63505774303ed6881c5650478eb76a1df7a3d412
4
- data.tar.gz: 517173da9e008bd5d9ea74d2e9d8d1622edf7fdf
3
+ metadata.gz: 122527cdc3cddb68e2c54a6e51fcfb77c2c91c0c
4
+ data.tar.gz: 6bb52baff0536f55ad969885fc8500b031457e44
5
5
  SHA512:
6
- metadata.gz: 8a806d4ee314cc13c1605781cbc6af7192600d2c34ec9ebccc949541a800a41d93d43def28600aa0d27e7526a47afa1e319bc2184ec4cee1c917196cc080ca91
7
- data.tar.gz: bcc42747f508c0acab55a8f652be1103afef20d9409172081bba34a530a9bbc441a2d5ec109bcd4b43e3049a3b3f74fc1c478d17e9c84a7e7ce41e7dabd11014
6
+ metadata.gz: df4e036a205be30371ec9de85e8085717235922fcc21e22f9f516cc40481a2a8cf6a9b0edd0a37cb24759059a08b5ea74957f41f89139bf9e1a860ed20029e3b
7
+ data.tar.gz: 698fc0d240aa6c6f8b125d1ae004beacf5ae97c220eb42d4fd542a49a656ece101e3a9fd9e740eaefb63e7f03c132458872f4ea5949cd30e2eceb9ccc3bb9e54
@@ -6,6 +6,8 @@ lib/hubba.rb
6
6
  lib/hubba/client.rb
7
7
  lib/hubba/config.rb
8
8
  lib/hubba/github.rb
9
+ lib/hubba/reports.rb
10
+ lib/hubba/reposet.rb
9
11
  lib/hubba/stats.rb
10
12
  lib/hubba/version.rb
11
13
  test/helper.rb
@@ -1,6 +1,24 @@
1
1
  # 3rd party (our own)
2
2
  require 'webclient'
3
3
 
4
+
5
+ ###############
6
+ ## helpers
7
+
8
+ def save_json( path, data ) ## data - hash or array
9
+ File.open( path, 'w:utf-8' ) do |f|
10
+ f.write( JSON.pretty_generate( data ))
11
+ end
12
+ end
13
+
14
+ def save_yaml( path, data )
15
+ File.open( path, 'w:utf-8' ) do |f|
16
+ f.write( data.to_yaml )
17
+ end
18
+ end
19
+
20
+
21
+
4
22
  # our own code
5
23
  require 'hubba/version' # note: let version always go first
6
24
  require 'hubba/config'
@@ -8,6 +26,11 @@ require 'hubba/client'
8
26
  require 'hubba/github'
9
27
  require 'hubba/stats'
10
28
 
29
+ ## "higher level" porcelain services / helpers for easy (re)use
30
+ require 'hubba/reposet'
31
+
32
+ require 'hubba/reports'
33
+
11
34
 
12
35
  ############
13
36
  # add convenience alias for alternate spelling - why? why not?
@@ -57,11 +57,11 @@ def get( request_uri )
57
57
  # => "text/html; charset=UTF-8"
58
58
 
59
59
  # Iterate all response headers.
60
- puts "HTTP HEADERS:"
61
- res.headers.each do |key, value|
62
- puts " #{key}: >#{value}<"
63
- end
64
- puts
60
+ # puts "HTTP HEADERS:"
61
+ # res.headers.each do |key, value|
62
+ # puts " #{key}: >#{value}<"
63
+ # end
64
+ # puts
65
65
 
66
66
  # => "location => http://www.google.com/"
67
67
  # => "content-type => text/html; charset=UTF-8"
@@ -1,8 +1,13 @@
1
1
  module Hubba
2
2
 
3
3
  class Configuration
4
- def data_dir() @data_dir || './data'; end
5
- def data_dir=( value ) @data_dir = value; end
4
+ def data_dir() @data_dir || './data'; end
5
+ def data_dir=( value ) @data_dir = value; end
6
+
7
+ ### todo/check: rename to/use tmp_dir - why? why not?
8
+ def cache_dir() @cache_dir || './cache'; end
9
+ def cache_dir=( value ) @cache_dir = value; end
10
+
6
11
 
7
12
  # try default setup via ENV variables
8
13
  def token() @token || ENV[ 'HUBBA_TOKEN' ]; end
@@ -87,19 +87,47 @@ end
87
87
  # more
88
88
  def update( obj )
89
89
  if obj.is_a?( Stats )
90
- full_name = obj.full_name
90
+ stats = obj
91
+ full_name = stats.full_name
91
92
  puts "[update 1/2] fetching repo >#{full_name}<..."
92
93
  repo = repo( full_name )
93
94
  puts "[update 2/2] fetching repo >#{full_name}< commits ..."
94
95
  commits = repo_commits( full_name )
95
96
 
96
- obj.update( repo, commits )
97
+ stats.update( repo, commits )
97
98
  else
98
99
  raise ArgumentError, "unknown source object passed in - expected Hubba::Stats; got #{obj.class.name}"
99
100
  end
100
101
  end
101
102
 
102
103
 
104
+ def update_stats( h ) ## todo/fix: change to Reposet - why? why not???
105
+ h.each do |org_with_counter,names|
106
+
107
+ ## remove optional number from key e.g.
108
+ ## mrhydescripts (3) => mrhydescripts
109
+ ## footballjs (4) => footballjs
110
+ ## etc.
111
+
112
+ org = org_with_counter.sub( /\([0-9]+\)/, '' ).strip
113
+
114
+ ## puts " -- #{key_with_counter} [#{key}] --"
115
+
116
+ names.each do |name|
117
+ full_name = "#{org}/#{name}"
118
+
119
+ ## puts " fetching stats #{count+1}/#{repo_count} - >#{full_name}<..."
120
+ stats = Stats.new( full_name )
121
+ stats.read
122
+
123
+ update( stats ) ## fetch & update stats
124
+
125
+ stats.write
126
+ end
127
+ end
128
+ end
129
+
130
+
103
131
  private
104
132
  def get( request_uri )
105
133
  @client.get( request_uri )
@@ -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
+
@@ -189,7 +189,7 @@ module Hubba
189
189
  ## todo: check for better way (convert to float upfront - why? why not?)
190
190
 
191
191
  diff = (diff_stars * days * 1000) / diff_days
192
- 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
193
193
  (diff.to_f/1000.0)
194
194
  end
195
195
  end
@@ -284,7 +284,7 @@ module Hubba
284
284
  def write
285
285
  basename = full_name.gsub( '/', '~' ) ## e.g. poole/hyde become poole~hyde
286
286
  data_dir = Hubba.config.data_dir
287
- puts "writing stats to #{basename} (#{data_dir})..."
287
+ puts " writing stats to #{basename} (#{data_dir})..."
288
288
 
289
289
  ## todo/fix: add FileUtils.makepath_r or such!!!
290
290
  File.open( "#{data_dir}/#{basename}.json", 'w:utf-8' ) do |f|
@@ -301,7 +301,7 @@ module Hubba
301
301
  path = "#{data_dir}/#{basename}.json"
302
302
 
303
303
  if File.exist?( path )
304
- puts "reading stats from #{basename} (#{data_dir})..."
304
+ puts " reading stats from #{basename} (#{data_dir})..."
305
305
  json = File.open( path, 'r:utf-8' ) { |f| f.read }
306
306
  @data = JSON.parse( json )
307
307
 
@@ -1,7 +1,7 @@
1
1
  module Hubba
2
2
  MAJOR = 0 ## todo: namespace inside version or something - why? why not??
3
3
  MINOR = 6
4
- PATCH = 0
4
+ PATCH = 1
5
5
  VERSION = [MAJOR,MINOR,PATCH].join('.')
6
6
 
7
7
  def self.version
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hubba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.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: 2020-10-08 00:00:00.000000000 Z
11
+ date: 2020-10-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: webclient
@@ -75,6 +75,8 @@ files:
75
75
  - lib/hubba/client.rb
76
76
  - lib/hubba/config.rb
77
77
  - lib/hubba/github.rb
78
+ - lib/hubba/reports.rb
79
+ - lib/hubba/reposet.rb
78
80
  - lib/hubba/stats.rb
79
81
  - lib/hubba/version.rb
80
82
  - test/helper.rb