hubba 0.3.0 → 0.6.0

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: 990a81f63411ae5b97cd3ae1e6892cb8e8f788d3
4
- data.tar.gz: 1061e2b8963f97072cbdcedb5c6ba8c23ac3b691
3
+ metadata.gz: 63505774303ed6881c5650478eb76a1df7a3d412
4
+ data.tar.gz: 517173da9e008bd5d9ea74d2e9d8d1622edf7fdf
5
5
  SHA512:
6
- metadata.gz: 05efa27d809de02a2e5582abe3c0d9b8f70629246d6d973d640b3fbb648365d7e0c6ecf32359837b06a83b2607d1ed4d9b5799af9c0f27d4244cce4dfe235a23
7
- data.tar.gz: 9ad616f2209a6f1c566b895d0e908b5a1cc5706d85626eb0519bb74117d4a6b4dc2f71c40eac2adb3e7caa1cfe4af33cfd1f7de57d96dd2d2390126a99e58899
6
+ metadata.gz: 8a806d4ee314cc13c1605781cbc6af7192600d2c34ec9ebccc949541a800a41d93d43def28600aa0d27e7526a47afa1e319bc2184ec4cee1c917196cc080ca91
7
+ data.tar.gz: bcc42747f508c0acab55a8f652be1103afef20d9409172081bba34a530a9bbc441a2d5ec109bcd4b43e3049a3b3f74fc1c478d17e9c84a7e7ce41e7dabd11014
File without changes
@@ -1,16 +1,18 @@
1
- HISTORY.md
1
+ CHANGELOG.md
2
2
  Manifest.txt
3
3
  README.md
4
4
  Rakefile
5
5
  lib/hubba.rb
6
- lib/hubba/cache.rb
7
6
  lib/hubba/client.rb
7
+ lib/hubba/config.rb
8
8
  lib/hubba/github.rb
9
9
  lib/hubba/stats.rb
10
10
  lib/hubba/version.rb
11
- test/cache/users~geraldb~orgs.json
12
- test/cache/users~geraldb~repos.json
13
11
  test/helper.rb
14
- test/test_cache.rb
12
+ test/stats/jekyll~minima.json
13
+ test/stats/openblockchains~awesome-blockchains.json
14
+ test/stats/opendatajson~factbook.json.json
15
+ test/stats/poole~hyde.json
15
16
  test/test_config.rb
16
17
  test/test_stats.rb
18
+ 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/gittiscripts/hubba](https://github.com/gittiscripts/hubba)
6
- * bugs :: [github.com/gittiscripts/hubba/issues](https://github.com/gittiscripts/hubba/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.
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 - (yet) another (lite) GitHub HTTP API client / library'
8
+ self.summary = 'hubba - (yet) another (lite) GitHub HTTP API client / library'
9
9
  self.description = summary
10
10
 
11
- self.urls = ['https://github.com/gittiscripts/hubba']
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 = 'HISTORY.md'
18
+ self.history_file = 'CHANGELOG.md'
19
19
 
20
20
  self.extra_deps = [
21
- ['logutils' ],
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: '>= 2.3'
27
+ required_ruby_version: '>= 2.2.2'
28
28
  }
29
29
 
30
30
  end
@@ -1,25 +1,20 @@
1
- # encoding: utf-8
2
-
3
- require 'net/http'
4
- require "net/https"
5
- require 'uri'
6
-
7
- require 'pp'
8
- require 'json'
9
- require 'yaml'
10
- require 'time'
11
-
12
-
13
- # 3rd party gems/libs
14
- require 'logutils'
1
+ # 3rd party (our own)
2
+ require 'webclient'
15
3
 
16
4
  # our own code
17
5
  require 'hubba/version' # note: let version always go first
18
- require 'hubba/cache'
6
+ require 'hubba/config'
19
7
  require 'hubba/client'
20
8
  require 'hubba/github'
21
9
  require 'hubba/stats'
22
10
 
23
11
 
12
+ ############
13
+ # add convenience alias for alternate spelling - why? why not?
14
+ module Hubba
15
+ GitHub = Github
16
+ end
17
+
18
+
24
19
  # say hello
25
- puts Hubba.banner if defined?($RUBYLIBS_DEBUG)
20
+ puts Hubba.banner if defined?($RUBYCOCO_DEBUG)
@@ -1,53 +1,79 @@
1
- # encoding: utf-8
2
-
3
1
  module Hubba
4
2
 
5
3
 
6
4
  class Client
7
5
 
8
- def initialize( user: nil, password: nil )
9
- uri = URI.parse( "https://api.github.com" )
10
- @http = Net::HTTP.new(uri.host, uri.port)
11
- @http.use_ssl = true
12
- @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
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 )
20
23
  puts "GET #{request_uri}"
21
24
 
22
- req = Net::HTTP::Get.new( request_uri )
23
- ## req = Net::HTTP::Get.new( "/users/geraldb" )
24
- ## req = Net::HTTP::Get.new( "/users/geraldb/repos" )
25
- req["User-Agent"] = "ruby/hubba" ## required by GitHub API
26
- req["Accept" ] = "application/vnd.github.v3+json" ## recommend by GitHub API
25
+ ## note: request_uri ALWAYS starts with leading /, thus use + for now!!!
26
+ # e.g. /users/geraldb
27
+ # /users/geraldb/repos
28
+ url = BASE_URL + request_uri
29
+
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 @user && @password
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
- req.basic_auth( @user, @password )
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 = @http.request(req)
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.each_header do |key, value|
42
- p "#{key} => #{value}"
60
+ puts "HTTP HEADERS:"
61
+ res.headers.each do |key, value|
62
+ puts " #{key}: >#{value}<"
43
63
  end
64
+ puts
65
+
44
66
  # => "location => http://www.google.com/"
45
67
  # => "content-type => text/html; charset=UTF-8"
46
68
  # ...
47
69
 
48
- json = JSON.parse( res.body )
49
- ## pp json
50
- json
70
+ if res.status.ok?
71
+ res.json
72
+ else
73
+ puts "!! HTTP ERROR: #{res.status.code} #{res.status.message}:"
74
+ pp res.raw
75
+ exit 1
76
+ end
51
77
  end # methdo get
52
78
 
53
79
  end ## class Client
@@ -0,0 +1,46 @@
1
+ module Hubba
2
+
3
+ class Configuration
4
+ def data_dir() @data_dir || './data'; end
5
+ def data_dir=( value ) @data_dir = value; end
6
+
7
+ # try default setup via ENV variables
8
+ def token() @token || ENV[ 'HUBBA_TOKEN' ]; end
9
+ def token=( value ) @token = value; end
10
+
11
+ # todo/check: use HUBBA_LOGIN - why? why not?
12
+ def user() @user || ENV[ 'HUBBA_USER' ]; end
13
+ def password() @password || ENV[ 'HUBBA_PASSWORD' ]; end
14
+ def user=( value ) @user = value; end
15
+ def password=( value ) @password = value; end
16
+
17
+ end # class Configuration
18
+
19
+
20
+ ## lets you use
21
+ ## Hubba.configure do |config|
22
+ ## config.token = 'secret'
23
+ ## -or-
24
+ ## config.user = 'testdada'
25
+ ## config.password = 'secret'
26
+ ## end
27
+ ##
28
+ ## move configure block to GitHub class - why? why not?
29
+ ## e.g. GitHub.configure do |config|
30
+ ## ...
31
+ ## end
32
+
33
+
34
+ def self.configuration
35
+ @configuration ||= Configuration.new
36
+ end
37
+ class << self
38
+ alias_method :config, :configuration
39
+ end
40
+
41
+
42
+ def self.configure
43
+ yield( configuration )
44
+ end
45
+
46
+ end # module Hubba
@@ -1,38 +1,5 @@
1
- # encoding: utf-8
2
-
3
1
  module Hubba
4
2
 
5
- class Configuration
6
- attr_accessor :user
7
- attr_accessor :password
8
-
9
- def initialize
10
- # try default setup via ENV variables
11
- @user = ENV[ 'HUBBA_USER' ] ## use HUBBA_LOGIN - why? why not?
12
- @password = ENV[ 'HUBBA_PASSWORD' ]
13
- end
14
- end
15
-
16
- ## lets you use
17
- ## Hubba.configure do |config|
18
- ## config.user = 'testdada'
19
- ## config.password = 'secret'
20
- ## end
21
- ##
22
- ## move configure block to GitHub class - why? why not?
23
- ## e.g. GitHub.configure do |config|
24
- ## ...
25
- ## end
26
-
27
-
28
- def self.configuration
29
- @configuration ||= Configuration.new
30
- end
31
-
32
- def self.configure
33
- yield( configuration )
34
- end
35
-
36
3
 
37
4
  class Resource
38
5
  attr_reader :data
@@ -53,25 +20,25 @@ class Orgs < Resource
53
20
  ## sort by name
54
21
  data.map { |item| item['login'] }.sort
55
22
  end
23
+ alias_method :names, :logins ## add name alias - why? why not?
56
24
  end
57
25
 
58
26
 
59
27
 
60
28
  class Github
61
29
 
62
- def initialize( cache_dir: './cache' )
63
- @cache = Cache.new( cache_dir )
64
- @client = Client.new( user: Hubba.configuration.user,
65
- password: Hubba.configuration.password )
66
-
67
- @offline = false
30
+ def initialize
31
+ @client = if Hubba.configuration.token
32
+ Client.new( token: Hubba.configuration.token )
33
+ elsif Hubba.configuration.user &&
34
+ Hubba.configuration.password
35
+ Client.new( user: Hubba.configuration.user,
36
+ password: Hubba.configuration.password )
37
+ else
38
+ Client.new
39
+ end
68
40
  end
69
41
 
70
- def offline!() @offline = true; end ## switch to offline - todo: find a "better" way - why? why not?
71
- def online!() @offline = false; end
72
- def offline?() @offline == true; end
73
- def online?() @offline == false; end
74
-
75
42
 
76
43
  def user( name )
77
44
  Resource.new( get "/users/#{name}" )
@@ -95,6 +62,7 @@ def user_orgs( name )
95
62
  end
96
63
 
97
64
 
65
+
98
66
  def org( name )
99
67
  Resource.new( get "/orgs/#{name}" )
100
68
  end
@@ -114,15 +82,28 @@ def repo_commits( full_name )
114
82
  end
115
83
 
116
84
 
117
- private
118
- def get( request_uri )
119
- if offline?
120
- @cache.get( request_uri )
85
+
86
+ ####
87
+ # more
88
+ def update( obj )
89
+ if obj.is_a?( Stats )
90
+ full_name = obj.full_name
91
+ puts "[update 1/2] fetching repo >#{full_name}<..."
92
+ repo = repo( full_name )
93
+ puts "[update 2/2] fetching repo >#{full_name}< commits ..."
94
+ commits = repo_commits( full_name )
95
+
96
+ obj.update( repo, commits )
121
97
  else
122
- @client.get( request_uri )
98
+ raise ArgumentError, "unknown source object passed in - expected Hubba::Stats; got #{obj.class.name}"
123
99
  end
124
100
  end
125
101
 
126
- end # class Github
127
102
 
128
- end # module Hubba
103
+ private
104
+ def get( request_uri )
105
+ @client.get( request_uri )
106
+ end
107
+
108
+ end # class Github
109
+ end # module Hubba
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module Hubba
4
2
 
5
3
  ####
@@ -12,47 +10,225 @@ 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
- def full_name() @data['full_name']; end
18
17
 
19
- def created_at() @data['created_at']; end
20
- def updated_at() @data['updated_at']; end
21
- def pushed_at() @data['pushed_at']; end
18
+ def full_name() @data['full_name']; end
19
+
22
20
 
23
- def history() @data['history']; end
21
+ ## note: return datetime objects (NOT strings); if not present/available return nil/null
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
 
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
25
30
 
26
31
  def size
27
32
  # size of repo in kb (as reported by github api)
28
33
  @data['size'] || 0 ## return 0 if not found - why? why not? (return nil - why? why not??)
29
34
  end
30
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
45
+ end
46
+
47
+
31
48
  def stars
32
49
  ## return last stargazers_count entry (as number; 0 if not found)
33
- t1 = 0
50
+ @cache['stars'] ||= history ? history[0].stars : 0
51
+ end
52
+
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
62
+ end
63
+
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
+
102
+ ########
103
+ ## build history items (structs)
104
+
105
+ class HistoryItem
106
+
107
+ attr_reader :date, :stars ## read-only attributes
108
+ attr_accessor :prev, :next ## read/write attributes (for double linked list/nodes/items)
109
+
110
+ def initialize( date:, stars: )
111
+ @date = date
112
+ @stars = stars
113
+ @next = nil
114
+ end
115
+
116
+ ## link items (append item at the end/tail)
117
+ def append( item )
118
+ @next = item
119
+ item.prev = self
120
+ end
121
+
122
+ def diff_days
123
+ if @next
124
+ ## note: use jd=julian days for calculation
125
+ @date.jd - @next.date.jd
126
+ else
127
+ nil ## last item (tail)
128
+ end
129
+ end
130
+
131
+ def diff_stars
132
+ if @next
133
+ @stars - @next.stars
134
+ else
135
+ nil ## last item (tail)
136
+ end
137
+ end
138
+ end ## class HistoryItem
139
+
140
+
141
+ def build_history( timeseries )
142
+ items = []
143
+
144
+ keys = timeseries.keys.sort.reverse ## newest (latest) items first
145
+ keys.each do |key|
146
+ h = timeseries[ key ]
147
+
148
+ item = HistoryItem.new(
149
+ date: Date.strptime( key, '%Y-%m-%d' ),
150
+ stars: h['stargazers_count'] || 0 )
151
+
152
+ ## link items
153
+ last_item = items[-1]
154
+ last_item.append( item ) if last_item ## if not nil? append (note first item has no prev item)
155
+
156
+ items << item
157
+ end
158
+
159
+ ## todo/check: return [] for empty items array (items.empty?) - why?? why not??
160
+ if items.empty?
161
+ nil
162
+ else
163
+ items
164
+ end
165
+ end ## method build_history
34
166
 
35
- if history
36
- history_keys = history.keys.sort.reverse
37
- ## todo/fix: for now assumes one entry per week
38
- ## simple case [0] and [1] for a week later
39
- ## check actual date - why? why not?
40
- stats_t1 = history_keys[0] ? history[ history_keys[0] ] : nil
41
- if stats_t1
42
- t1 = stats_t1['stargazers_count'] || 0
43
- end
44
- end
45
- t1
46
- end # method stars
167
+
168
+
169
+ def calc_diff_stars( samples: 3, days: 30 )
170
+ ## samples: use n history item samples e.g. 3 samples
171
+ ## days e.g. 7 days (per week), 30 days (per month)
172
+
173
+ if history.nil?
174
+ nil ## todo/check: return 0.0 too - why? why not?
175
+ elsif history.size == 1
176
+ ## just one item; CANNOT calc diff; return zero
177
+ 0.0
178
+ else
179
+ idx = [history.size, samples].min ## calc last index
180
+ last = history[idx-1]
181
+ first = history[0]
182
+
183
+ diff_days = first.date.jd - last.date.jd
184
+ diff_stars = first.stars - last.stars
185
+
186
+ ## note: use factor 1000 for fixed integer division
187
+ ## converts to float at the end
188
+
189
+ ## todo: check for better way (convert to float upfront - why? why not?)
190
+
191
+ diff = (diff_stars * days * 1000) / diff_days
192
+ puts "diff=#{diff}:#{diff.class.name}" ## check if it's a float
193
+ (diff.to_f/1000.0)
194
+ end
195
+ end
196
+
197
+
198
+ def history_str ## todo/check: rename/change to format_history or fmt_history - why? why not?
199
+ ## returns "pretty printed" history as string buffer
200
+ buf = ''
201
+ buf << "[#{history.size}]: "
202
+
203
+ history.each do |item|
204
+ buf << "#{item.stars}"
205
+
206
+ diff_stars = item.diff_stars
207
+ diff_days = item.diff_days
208
+ if diff_stars && diff_days ## note: last item has no diffs
209
+ if diff_stars > 0 || diff_stars < 0
210
+ if diff_stars > 0
211
+ buf << " (+#{diff_stars}"
212
+ else
213
+ buf << " (#{diff_stars}"
214
+ end
215
+ buf << " in #{diff_days}d) "
216
+ else ## diff_stars == 0
217
+ buf << " (#{diff_days}d) "
218
+ end
219
+ end
220
+ end
221
+ buf
222
+ end # method history_str
47
223
 
48
224
 
49
225
 
50
226
  ###############################
51
227
  ## fetch / read / write methods
52
228
 
53
- def fetch( gh ) ## update stats / fetch data from github via api
54
- puts "fetching #{full_name}..."
55
- repo = gh.repo( full_name )
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 )
56
232
 
57
233
  ## e.g. 2015-05-11T20:21:43Z
58
234
  ## puts Time.iso8601( repo.data['created_at'] )
@@ -77,7 +253,6 @@ module Hubba
77
253
 
78
254
  ##########################
79
255
  ## also check / keep track of (latest) commit
80
- commits = gh.repo_commits( full_name )
81
256
  puts "last commit/update:"
82
257
  ## pp commits
83
258
  commit = {
@@ -85,36 +260,58 @@ module Hubba
85
260
  'date' => commits.data[0]['commit']['committer']['date'],
86
261
  'name' => commits.data[0]['commit']['committer']['name']
87
262
  },
263
+ 'author' => {
264
+ 'date' => commits.data[0]['commit']['author']['date'],
265
+ 'name' => commits.data[0]['commit']['author']['name']
266
+ },
88
267
  'message' => commits.data[0]['commit']['message']
89
268
  }
90
269
 
91
270
  ## for now store only the latest commit (e.g. a single commit in an array)
92
- @data[ 'commits'] = [commit]
271
+ @data[ 'commits' ] = [commit]
93
272
 
94
273
  pp @data
274
+
275
+ ## reset (invalidate) cached values from data hash
276
+ ## use after reading or fetching
277
+ @cache = {}
278
+
279
+ self ## return self for (easy chaining)
95
280
  end
96
281
 
97
282
 
98
283
 
99
- def write( data_dir: './data' )
284
+ def write
100
285
  basename = full_name.gsub( '/', '~' ) ## e.g. poole/hyde become poole~hyde
101
- puts "writing (saving) to #{basename}..."
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!!!
102
290
  File.open( "#{data_dir}/#{basename}.json", 'w:utf-8' ) do |f|
103
291
  f.write JSON.pretty_generate( data )
104
292
  end
293
+ self ## return self for (easy chaining)
105
294
  end
106
295
 
107
- def read( data_dir: './data' ) ## note: use read instead of load (load is kind of keyword for loading code)
296
+
297
+ def read
108
298
  ## note: skip reading if file not present
109
299
  basename = full_name.gsub( '/', '~' ) ## e.g. poole/hyde become poole~hyde
110
- filename = "#{data_dir}/#{basename}.json"
111
- if File.exist?( filename )
112
- puts "reading (loading) from #{basename}..."
113
- json = File.open( filename, 'r:utf-8' ) { |file| file.read } ## todo/fix: use read_utf8
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 }
114
306
  @data = JSON.parse( json )
307
+
308
+ ## reset (invalidate) cached values from data hash
309
+ ## use after reading or fetching
310
+ @cache = {}
115
311
  else
116
- puts "skipping reading (loading) from #{basename} -- file not found"
312
+ puts "!! WARN: - skipping reading stats from #{basename} -- file not found"
117
313
  end
314
+ self ## return self for (easy chaining)
118
315
  end
119
316
 
120
317
  end # class Stats