hubba 0.6.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 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