hubba-reports 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ module Hubba
2
+
3
+ class ReportTrending < Report
4
+
5
+ def build
6
+
7
+ ## note: orgs is orgs+users e.g. geraldb, yorobot etc
8
+ buf = String.new('')
9
+ buf << "# Trending"
10
+ buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs"
11
+ buf << "\n\n"
12
+
13
+ ###
14
+ ## todo:
15
+ ## use calc per month (days: 30)
16
+ ## per week is too optimistic (e.g. less than one star/week e.g. 0.6 or something)
17
+
18
+ repos = @stats.repos.sort do |l,r|
19
+ ## note: use reverse sort (right,left) - e.g. most stars first
20
+ ## r[:stars] <=> l[:stars]
21
+
22
+ ## sort by created_at (use julian days)
23
+ ## r[:created_at].to_date.jd <=> l[:created_at].to_date.jd
24
+
25
+ res = r.diff <=> l.diff
26
+ res = r.stats.stars <=> l.stats.stars if res == 0
27
+ res = r.stats.created.jd <=> l.stats.created.jd if res == 0
28
+ res
29
+ end
30
+
31
+
32
+ ## pp repos
33
+
34
+
35
+ repos.each_with_index do |repo,i|
36
+ if repo.diff == 0
37
+ buf << "- -/- "
38
+ else
39
+ buf << "- #{repo.diff}/month "
40
+ end
41
+
42
+ buf << " ★#{repo.stats.stars} **#{repo.full_name}** (#{repo.stats.size} kb) - "
43
+ buf << "#{repo.stats.history_str}\n"
44
+ end
45
+
46
+
47
+ buf
48
+ end # method build
49
+ end # class ReportTrending
50
+
51
+ end # module Hubba
@@ -0,0 +1,60 @@
1
+ module Hubba
2
+
3
+
4
+ class ReportUpdates < Report
5
+
6
+ def build
7
+
8
+ ## note: orgs is orgs+users e.g. geraldb, yorobot etc
9
+ buf = String.new('')
10
+ buf << "# Updates"
11
+ buf << " - #{@stats.repos.size} Repos @ #{@stats.orgs.size} Orgs\n"
12
+ buf << "\n\n"
13
+
14
+ repos = @stats.repos.sort do |l,r|
15
+ r.stats.committed.jd <=> l.stats.committed.jd
16
+ end
17
+
18
+ ## pp repos
19
+
20
+
21
+ buf << "committed / pushed / updated / created\n\n"
22
+
23
+ today = Date.today
24
+
25
+ repos.each_with_index do |repo,i|
26
+
27
+ days_ago = today.jd - repo.stats.committed.jd
28
+
29
+ diff1 = repo.stats.committed.jd - repo.stats.pushed.jd
30
+ diff2 = repo.stats.committed.jd - repo.stats.updated.jd
31
+ diff3 = repo.stats.pushed.jd - repo.stats.updated.jd
32
+
33
+ buf << "- (#{days_ago}d) **#{repo.full_name}** ★#{repo.stats.stars} - "
34
+ buf << "#{repo.stats.committed} "
35
+ buf << "("
36
+ buf << (diff1==0 ? '=' : "#{diff1}d")
37
+ buf << "/"
38
+ buf << (diff2==0 ? '=' : "#{diff2}d")
39
+ buf << ")"
40
+ buf << " / "
41
+ buf << "#{repo.stats.pushed} "
42
+ buf << "("
43
+ buf << (diff3==0 ? '=' : "#{diff3}d")
44
+ buf << ")"
45
+ buf << " / "
46
+ buf << "#{repo.stats.updated} / "
47
+ buf << "#{repo.stats.created} - "
48
+ buf << "‹#{repo.stats.last_commit_message}›"
49
+ buf << " (#{repo.stats.size} kb)"
50
+ buf << "\n"
51
+ end
52
+ buf << "<!-- break -->\n" ## markdown hack: add a list end marker
53
+ buf << "\n\n"
54
+
55
+
56
+ buf
57
+ end # method build
58
+ end # class ReportUpdates
59
+
60
+ end # module Hubba
@@ -0,0 +1,230 @@
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 - needed - why? why not?
9
+
10
+ def full_name() @data['full_name']; end
11
+ def description() @data['description'] || ''; end ## todo/check: return nil if not found - why? why not?
12
+ alias_method :descr, :description
13
+ alias_method :desc, :description
14
+
15
+ def topics() @data['topics'] || []; end ## todo/check: return nil if not found - why? why not?
16
+
17
+
18
+ ## note: return datetime objects (NOT strings); if not present/available return nil/null
19
+ def created_at() @cache['created_at'] ||= parse_datetime( @data['created_at'] ); end
20
+ def updated_at() @cache['updated_at'] ||= parse_datetime( @data['updated_at'] ); end
21
+ def pushed_at() @cache['pushed_at'] ||= parse_datetime( @data['pushed_at'] ); end
22
+
23
+ ## date (only) versions
24
+ def created() @cache['created'] ||= parse_date( @data['created_at'] ); end
25
+ def updated() @cache['updated'] ||= parse_date( @data['updated_at'] ); end
26
+ def pushed() @cache['pushed'] ||= parse_date( @data['pushed_at'] ); end
27
+
28
+ def size
29
+ # size of repo in kb (as reported by github api)
30
+ @data['size'] || 0 ## return 0 if not found - why? why not? (return nil - why? why not??)
31
+ end
32
+
33
+
34
+ def history
35
+ @cache['history'] ||= begin
36
+ if @data['history']
37
+ build_history( @data['history'] )
38
+ else
39
+ nil
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+ def stars
46
+ ## return last stargazers_count entry (as number; 0 if not found)
47
+ @cache['stars'] ||= history ? history[0].stars : 0
48
+ end
49
+
50
+ ###################
51
+ # traffic
52
+ def traffic() @data['traffic']; end
53
+
54
+
55
+ ###########
56
+ # commits
57
+ def commits() @data['commits']; end
58
+
59
+ def last_commit ## convenience shortcut; get first/last commit (use [0]) or nil
60
+ if @data['commits'] && @data['commits'][0]
61
+ @data['commits'][0]
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+
68
+ def committed ## last commit date (from author NOT committer)
69
+ @cache['committed'] ||= parse_date( last_commit_author_date )
70
+ end
71
+
72
+ def committed_at() ## last commit date (from author NOT committer)
73
+ @cache['committed_at'] ||= parse_datetime( last_commit_author_date )
74
+ end
75
+
76
+ def last_commit_author_date
77
+ h = last_commit
78
+ h ? h['author']['date'] : nil
79
+ end
80
+
81
+
82
+ def last_commit_message ## convenience shortcut; last commit message
83
+ h = last_commit
84
+
85
+ committer_name = h['committer']['name']
86
+ author_name = h['author']['name']
87
+ message = h['message']
88
+
89
+ buf = ""
90
+ buf << message
91
+ buf << " by #{author_name}"
92
+
93
+ if committer_name != author_name
94
+ buf << " w/ #{committer_name}"
95
+ end
96
+ end # method commit_message
97
+
98
+
99
+
100
+ ###
101
+ # helpers
102
+ def parse_datetime( str ) str ? DateTime.strptime( str, '%Y-%m-%dT%H:%M:%S') : nil; end
103
+ def parse_date( str ) str ? Date.strptime( str, '%Y-%m-%d') : nil; end
104
+
105
+ ########
106
+ ## build history items (structs)
107
+
108
+ class HistoryItem
109
+
110
+ attr_reader :date, :stars, :forks ## read-only attributes
111
+ attr_accessor :prev, :next ## read/write attributes (for double linked list/nodes/items)
112
+
113
+ def initialize( date:, stars:, forks: )
114
+ @date = date
115
+ @stars = stars
116
+ @forks = forks
117
+ @next = nil
118
+ end
119
+
120
+ ## link items (append item at the end/tail)
121
+ def append( item )
122
+ @next = item
123
+ item.prev = self
124
+ end
125
+
126
+ def diff_days
127
+ if @next
128
+ ## note: use jd=julian days for calculation
129
+ @date.jd - @next.date.jd
130
+ else
131
+ nil ## last item (tail)
132
+ end
133
+ end
134
+
135
+ def diff_stars
136
+ if @next
137
+ @stars - @next.stars
138
+ else
139
+ nil ## last item (tail)
140
+ end
141
+ end
142
+ end ## class HistoryItem
143
+
144
+
145
+ def build_history( timeseries )
146
+ items = []
147
+
148
+ keys = timeseries.keys.sort.reverse ## newest (latest) items first
149
+ keys.each do |key|
150
+ h = timeseries[ key ]
151
+
152
+ item = HistoryItem.new(
153
+ date: Date.strptime( key, '%Y-%m-%d' ),
154
+ stars: h['stargazers_count'] || 0,
155
+ forks: h['forks_count'] || 0 )
156
+
157
+ ## link items
158
+ last_item = items[-1]
159
+ last_item.append( item ) if last_item ## if not nil? append (note first item has no prev item)
160
+
161
+ items << item
162
+ end
163
+
164
+ ## todo/check: return [] for empty items array (items.empty?) - why?? why not??
165
+ if items.empty?
166
+ nil
167
+ else
168
+ items
169
+ end
170
+ end ## method build_history
171
+
172
+
173
+
174
+ def calc_diff_stars( samples: 3, days: 30 )
175
+ ## samples: use n history item samples e.g. 3 samples
176
+ ## days e.g. 7 days (per week), 30 days (per month)
177
+
178
+ if history.nil?
179
+ nil ## todo/check: return 0.0 too - why? why not?
180
+ elsif history.size == 1
181
+ ## just one item; CANNOT calc diff; return zero
182
+ 0.0
183
+ else
184
+ idx = [history.size, samples].min ## calc last index
185
+ last = history[idx-1]
186
+ first = history[0]
187
+
188
+ diff_days = first.date.jd - last.date.jd
189
+ diff_stars = first.stars - last.stars
190
+
191
+ ## note: use factor 1000 for fixed integer division
192
+ ## converts to float at the end
193
+
194
+ ## todo: check for better way (convert to float upfront - why? why not?)
195
+
196
+ diff = (diff_stars * days * 1000) / diff_days
197
+ ## puts "diff=#{diff}:#{diff.class.name}" ## check if it's a float
198
+ (diff.to_f/1000.0)
199
+ end
200
+ end
201
+
202
+
203
+ def history_str ## todo/check: rename/change to format_history or fmt_history - why? why not?
204
+ ## returns "pretty printed" history as string buffer
205
+ buf = ''
206
+ buf << "[#{history.size}]: "
207
+
208
+ history.each do |item|
209
+ buf << "#{item.stars}"
210
+
211
+ diff_stars = item.diff_stars
212
+ diff_days = item.diff_days
213
+ if diff_stars && diff_days ## note: last item has no diffs
214
+ if diff_stars > 0 || diff_stars < 0
215
+ if diff_stars > 0
216
+ buf << " (+#{diff_stars}"
217
+ else
218
+ buf << " (#{diff_stars}"
219
+ end
220
+ buf << " in #{diff_days}d) "
221
+ else ## diff_stars == 0
222
+ buf << " (#{diff_days}d) "
223
+ end
224
+ end
225
+ end
226
+ buf
227
+ end # method history_str
228
+ end # class Stats
229
+
230
+ end # module Hubba
@@ -0,0 +1,18 @@
1
+ module HubbaReports
2
+ MAJOR = 0 ## todo: namespace inside version or something - why? why not??
3
+ MINOR = 1
4
+ PATCH = 0
5
+ VERSION = [MAJOR,MINOR,PATCH].join('.')
6
+
7
+ def self.version
8
+ VERSION
9
+ end
10
+
11
+ def self.banner
12
+ "hubba-reports/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
13
+ end
14
+
15
+ def self.root
16
+ "#{File.expand_path( File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) )}"
17
+ end
18
+ end # module HubbaReports
@@ -0,0 +1,11 @@
1
+ # minitest setup
2
+ require 'minitest/autorun'
3
+
4
+
5
+ ## note: also use local version of hubba!!!
6
+ $LOAD_PATH.unshift( "../hubba/lib" )
7
+
8
+
9
+ ## our own code
10
+ require 'hubba/reports'
11
+
@@ -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
+ }