gemverse 0.0.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 +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +9 -0
- data/README.md +123 -0
- data/Rakefile +28 -0
- data/lib/gemverse/api.rb +42 -0
- data/lib/gemverse/cache.rb +100 -0
- data/lib/gemverse/gems.rb +105 -0
- data/lib/gemverse/timeline.rb +161 -0
- data/lib/gemverse.rb +95 -0
- metadata +106 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 69db10da33808ff62ca728d7ada452de1283a465d9fbbce8b9919d88f976a9b4
|
|
4
|
+
data.tar.gz: 1554b1c90f9c0e467bee55f2e98eff8ffae13e3b2090d8bd5bb21ab86b81b645
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4d640f9f26100ae1646431670fa275bf267bb9ab88727d754e0c3efffaa29c74c2ea1136da0516908de48020aead49e38aed93780a917c85c04361e84ac3b518
|
|
7
|
+
data.tar.gz: 5fbf83a842f797c19b8054aa1b86c3891650bf5a1a6e9bb85dda0371755713fe38d0321db4c653a70951f72e64c5017eea6e831158d4ac1b6c2fdfdf4346ef38
|
data/CHANGELOG.md
ADDED
data/Manifest.txt
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Gemverse - Gem Universe
|
|
2
|
+
|
|
3
|
+
gemverse gem - gem universe incl. rubygems API V1 wrapper lite; gem version cache, gem timeline reports, 'n' more
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
* home :: [github.com/rubycocos/git](https://github.com/rubycocos/git)
|
|
8
|
+
* bugs :: [github.com/rubycocos/git/issues](https://github.com/rubycocos/git/issues)
|
|
9
|
+
* gem :: [rubygems.org/gems/gemverse](https://rubygems.org/gems/gemverse)
|
|
10
|
+
* rdoc :: [rubydoc.info/gems/gemverse](http://rubydoc.info/gems/gemverse)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### RubyGems API "To The Metal" Wrapper V1 - Lite Edition
|
|
20
|
+
|
|
21
|
+
The gemverse includes a lightweight "to the metal"
|
|
22
|
+
wrapper for the rubygems API V1
|
|
23
|
+
that returns data(sets) in the JSON format:
|
|
24
|
+
|
|
25
|
+
``` ruby
|
|
26
|
+
require 'gemverse'
|
|
27
|
+
|
|
28
|
+
## get all gems owned by the author with the handle / known as gettalong
|
|
29
|
+
data = Gems::API.gems_by( 'gettalong' )
|
|
30
|
+
# same as https://rubygems.org/api/v1/owners/gettalong/gems.json
|
|
31
|
+
|
|
32
|
+
## get all versions of the hexapdf gem
|
|
33
|
+
data = Gems::API.versions( 'hexapdf' )
|
|
34
|
+
# same as https://rubygems.org/api/v1/versions/hexpdf.json"
|
|
35
|
+
|
|
36
|
+
#...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### Gem Cache 'n' Timeline Reports
|
|
41
|
+
|
|
42
|
+
Let's build a gem timeline report / what's news page.
|
|
43
|
+
Let's spotlight the work of [Thomas Leitner, Austria also known as `gettalong`](https://rubygems.org/profiles/gettalong)
|
|
44
|
+
who 24 published gems (as of 2023) in the last 10+ years.
|
|
45
|
+
|
|
46
|
+
Note: Yes, you can. Replace the `gettalong` rubygems id / login with your own to build your very own timeline.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
**Step 1 - Online - Get gems & versions via "higher-level" rubygems api calls**
|
|
50
|
+
|
|
51
|
+
``` ruby
|
|
52
|
+
cache = Gems::Cache.new( './cache' )
|
|
53
|
+
|
|
54
|
+
gems = Gems.find_by( owner: 'gettalong' )
|
|
55
|
+
puts " #{gems.size} record(s)"
|
|
56
|
+
|
|
57
|
+
## bonus: save gems in a "flat" tabular datafile using the comma-separated values (.csv) format
|
|
58
|
+
gems.export( './profile/gettalong/gems.csv' )
|
|
59
|
+
|
|
60
|
+
## fetch all gem versions and (auto-save)
|
|
61
|
+
## in a "flat" tabular datafile (e.g. <gem>/versions.csv)
|
|
62
|
+
## using the comma-spearated values (.csv) format
|
|
63
|
+
## in the cache directory
|
|
64
|
+
cache.update_versions( gems: gems )
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
**Step 2 - Offline - Read versions from cache and build reports / timeline**
|
|
69
|
+
|
|
70
|
+
``` ruby
|
|
71
|
+
cache = Gems::Cache.new( './gems' )
|
|
72
|
+
|
|
73
|
+
gems = read_csv( './profile/gettalong/gems.csv' )
|
|
74
|
+
puts " #{gems.size} record(s)"
|
|
75
|
+
|
|
76
|
+
versions = cache.read_versions( gems: gems )
|
|
77
|
+
puts " #{versions.size} record(s)"
|
|
78
|
+
|
|
79
|
+
timeline = Gems::Timeline.new( versions,
|
|
80
|
+
title: "Thomas Leitner's Timeline" )
|
|
81
|
+
timeline.save( "./profile/gettalong/README.md" )
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
That's it.
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
Tip: You can build "custom" timeline reports
|
|
90
|
+
and filter / select the gems to include as you like.
|
|
91
|
+
Let's (re)build the timeline for all ruby cocos (code commons)
|
|
92
|
+
gems.
|
|
93
|
+
|
|
94
|
+
``` ruby
|
|
95
|
+
cache = Gems::Cache.new( './cache' )
|
|
96
|
+
|
|
97
|
+
gems = read_csv( './collection/cocos.csv' )
|
|
98
|
+
puts " #{gems.size} record(s)"
|
|
99
|
+
|
|
100
|
+
versions = cache.read_versions( gems: gems )
|
|
101
|
+
puts " #{versions.size} record(s)"
|
|
102
|
+
|
|
103
|
+
timeline = Gems::Timeline.new( versions,
|
|
104
|
+
title: 'Ruby Code Commons (COCOS) Timeline' )
|
|
105
|
+
timeline.save( "./collection/cocos/README.md" )
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
That's it.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
See
|
|
112
|
+
[Thomas Leitner's Timeline](samples/gems_gettalong),
|
|
113
|
+
[Jan Lelis's Timeline](samples/gems_janlelis),
|
|
114
|
+
[Ruby Code Commons (COCOS) Timeline](samples/gems_cocos), and some more
|
|
115
|
+
for some real-world timeline samples.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
The `gemverse` scripts are dedicated to the public domain.
|
|
122
|
+
Use it as you please with no restrictions whatsoever.
|
|
123
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'hoe'
|
|
2
|
+
|
|
3
|
+
Hoe.spec 'gemverse' do
|
|
4
|
+
|
|
5
|
+
self.version = '0.0.1' # note: for now add version inline
|
|
6
|
+
|
|
7
|
+
self.summary = "gemverse gem - gem universe incl. rubygems API V1 wrapper lite; gem version cache, gem timeline reports, 'n' more"
|
|
8
|
+
self.description = summary
|
|
9
|
+
|
|
10
|
+
self.urls = { home: 'https://github.com/rubycocos/git' }
|
|
11
|
+
|
|
12
|
+
self.author = 'Gerald Bauer'
|
|
13
|
+
self.email = 'opensport@googlegroups.com'
|
|
14
|
+
|
|
15
|
+
# switch extension to .markdown for gihub formatting
|
|
16
|
+
self.readme_file = 'README.md'
|
|
17
|
+
self.history_file = 'CHANGELOG.md'
|
|
18
|
+
|
|
19
|
+
self.licenses = ['Public Domain']
|
|
20
|
+
|
|
21
|
+
self.extra_deps = [
|
|
22
|
+
['cocos'],
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
self.spec_extras = {
|
|
26
|
+
required_ruby_version: '>= 2.2.2'
|
|
27
|
+
}
|
|
28
|
+
end
|
data/lib/gemverse/api.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
module Gems
|
|
3
|
+
module API
|
|
4
|
+
|
|
5
|
+
BASE = 'https://rubygems.org/api/v1'
|
|
6
|
+
|
|
7
|
+
def self.gems_by( owner )
|
|
8
|
+
src = "#{BASE}/owners/#{owner}/gems.json"
|
|
9
|
+
call( src )
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def self.versions( name )
|
|
14
|
+
## note: will NOT include yanked versions
|
|
15
|
+
## check if there's a query parameter ???
|
|
16
|
+
src = "#{BASE}/versions/#{name}.json"
|
|
17
|
+
call( src )
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def self.call( src ) ## get response as (parsed) json (hash table)
|
|
22
|
+
|
|
23
|
+
headers = {
|
|
24
|
+
# 'User-Agent' => "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36",
|
|
25
|
+
'User-Agent' => "ruby v#{RUBY_VERSION}",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
response = Webclient.get( src, headers: headers )
|
|
29
|
+
|
|
30
|
+
if response.status.ok?
|
|
31
|
+
puts "#{response.status.code} #{response.status.message} - content_type: #{response.content_type}, content_length: #{response.content_length}"
|
|
32
|
+
|
|
33
|
+
response.json
|
|
34
|
+
else
|
|
35
|
+
puts "!! HTTP ERROR:"
|
|
36
|
+
puts "#{response.status.code} #{response.status.message}"
|
|
37
|
+
exit 1
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end # module API
|
|
42
|
+
end # module Gems
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
module Gems
|
|
4
|
+
class Cache
|
|
5
|
+
def initialize( basedir='./gems' )
|
|
6
|
+
@basedir = basedir
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def update_versions( gems: [] )
|
|
11
|
+
|
|
12
|
+
delay_in_s = 0.5
|
|
13
|
+
|
|
14
|
+
gems.each_with_index do |gem,i|
|
|
15
|
+
|
|
16
|
+
name = if gem.is_a?( String )
|
|
17
|
+
gem
|
|
18
|
+
elsif gem.is_a?( Hash )
|
|
19
|
+
gem['name']
|
|
20
|
+
else ## assume our own Gem struct/class for now
|
|
21
|
+
gem.name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
puts "==> #{i+1}/#{gems.size} #{name}..."
|
|
25
|
+
|
|
26
|
+
## update versions info
|
|
27
|
+
puts " sleeping #{delay_in_s} second(s)"
|
|
28
|
+
sleep( delay_in_s )
|
|
29
|
+
|
|
30
|
+
data = Gems::API.versions( name )
|
|
31
|
+
puts " #{data.size} version record(s) found"
|
|
32
|
+
|
|
33
|
+
headers = ['name',
|
|
34
|
+
'version',
|
|
35
|
+
'created',
|
|
36
|
+
'downloads']
|
|
37
|
+
recs = []
|
|
38
|
+
data.each do |h|
|
|
39
|
+
## only use year/month/day for now now hours etc.
|
|
40
|
+
created = Date.strptime( h['created_at'], '%Y-%m-%d' )
|
|
41
|
+
|
|
42
|
+
rec = [
|
|
43
|
+
name,
|
|
44
|
+
h['number'],
|
|
45
|
+
created.strftime( '%Y-%m-%d' ),
|
|
46
|
+
h['downloads_count'].to_s,
|
|
47
|
+
]
|
|
48
|
+
recs << rec
|
|
49
|
+
end
|
|
50
|
+
write_csv( "#{@basedir}/#{name}/versions.csv", [headers]+recs )
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def read_versions( gems: [] )
|
|
57
|
+
## read in and merge all version records
|
|
58
|
+
versions = []
|
|
59
|
+
|
|
60
|
+
## if no gems passed in - get all versions.csv datasets in basedir
|
|
61
|
+
if gems.empty?
|
|
62
|
+
paths = Dir.glob( "#{@basedir}/*/versions.csv" )
|
|
63
|
+
gems = paths.map { |path| File.basename(File.dirname(path)) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
gems.each_with_index do |gem,i|
|
|
67
|
+
|
|
68
|
+
name = if gem.is_a?( String )
|
|
69
|
+
gem
|
|
70
|
+
elsif gem.is_a?( Hash )
|
|
71
|
+
gem['name']
|
|
72
|
+
else ## assume our own Gem struct/class for now
|
|
73
|
+
gem.name
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
path = "#{@basedir}/#{name}/versions.csv"
|
|
77
|
+
puts "==> #{i+1}/#{gems.size} reading #{name}..."
|
|
78
|
+
recs = read_csv( path )
|
|
79
|
+
recs.reverse.each_with_index do |rec,n|
|
|
80
|
+
more = { 'count' => (n+1).to_s } ## auto-add version count(er) e.g. 1,2,3,...
|
|
81
|
+
versions << rec.merge( more )
|
|
82
|
+
end
|
|
83
|
+
puts " #{recs.size} record(s)"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
## sort by version created and name
|
|
87
|
+
versions = versions.sort do |l,r|
|
|
88
|
+
res = r['created'] <=> l['created']
|
|
89
|
+
res = l['name'] <=> r['name'] if res == 0
|
|
90
|
+
res = r['version'] <=> l['version'] if res == 0
|
|
91
|
+
res
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
versions
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
end ## class Cache
|
|
100
|
+
end ## module Gems
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
|
|
2
|
+
module Gems
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GemDataset ## rename to Gems or Gemset or such - why? why not?
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def initialize( gems )
|
|
10
|
+
@gems = gems
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def size() @gems.size; end
|
|
14
|
+
def each_with_index( &block ) @gems.each_with_index( &block ); end
|
|
15
|
+
def each( &block ) @gems.each( &block ); end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## todo/check: add an write_csv( path ) alias / alternate method name - why? why not?
|
|
19
|
+
def export( path )
|
|
20
|
+
recs = []
|
|
21
|
+
|
|
22
|
+
@gems.each do |gem|
|
|
23
|
+
|
|
24
|
+
if gem.yanked?
|
|
25
|
+
puts "!! ERROR - includes yanked gem"
|
|
26
|
+
pp gem
|
|
27
|
+
exit 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
rec = [gem.name,
|
|
31
|
+
gem.version,
|
|
32
|
+
gem.version_created.strftime( '%Y-%m-%d' ),
|
|
33
|
+
gem.version_downloads.to_s,
|
|
34
|
+
gem.homepage,
|
|
35
|
+
gem.runtime_dependencies.join( ' | ' ),
|
|
36
|
+
]
|
|
37
|
+
recs << rec
|
|
38
|
+
end
|
|
39
|
+
headers = ['name',
|
|
40
|
+
'version',
|
|
41
|
+
'version_created',
|
|
42
|
+
'version_downloads',
|
|
43
|
+
'homepage',
|
|
44
|
+
'dependencies',
|
|
45
|
+
]
|
|
46
|
+
write_csv( path, [headers]+recs )
|
|
47
|
+
end # method export
|
|
48
|
+
end ## class GemDataset
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Gem ## todo/check: rename or use Gem::Meta or such - why? why not?
|
|
54
|
+
|
|
55
|
+
attr_reader :name,
|
|
56
|
+
:version, :version_created, :version_downloads,
|
|
57
|
+
:homepage,
|
|
58
|
+
:runtime
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def self.create( h ) ## todo/check: rename to create_from_json or such - why? why not?
|
|
62
|
+
## optional keyword args
|
|
63
|
+
kwargs = {
|
|
64
|
+
version: h['version'],
|
|
65
|
+
## only use year/month/day for now now hours etc. - why? why not
|
|
66
|
+
version_created: h['version_created_at'] ? Date.strptime( h['version_created_at'], '%Y-%m-%d' ) : nil,
|
|
67
|
+
version_downloads: h['version_downloads'],
|
|
68
|
+
## note: (auto-)clean
|
|
69
|
+
## for example - newline seen in "http://icanhasaudio.com/\n" !!!
|
|
70
|
+
homepage: h['homepage_uri'] ? h['homepage_uri'].gsub( /[ \r\n]/, '')
|
|
71
|
+
: nil,
|
|
72
|
+
yanked: h['yanked'],
|
|
73
|
+
runtime: h['dependencies']['runtime'].map { |dep| dep['name'] }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
new( name: h['name'],
|
|
77
|
+
**kwargs )
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def initialize( name:,
|
|
82
|
+
version: nil,
|
|
83
|
+
version_created: nil,
|
|
84
|
+
version_downloads: nil,
|
|
85
|
+
homepage: nil,
|
|
86
|
+
yanked: nil,
|
|
87
|
+
runtime: [] )
|
|
88
|
+
|
|
89
|
+
@name = name
|
|
90
|
+
@version = version
|
|
91
|
+
@version_created = version_created
|
|
92
|
+
@version_downloads = version_downloads
|
|
93
|
+
@homepage = homepage
|
|
94
|
+
@yanked = yanked
|
|
95
|
+
@runtime = runtime
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
alias_method :runtime_dependencies, :runtime
|
|
99
|
+
|
|
100
|
+
## always return false if not defined - why? why not?
|
|
101
|
+
def yanked?() @yanked.nil? ? false : @yanked; end
|
|
102
|
+
end # class Gems
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
end ## module Gems
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
|
|
2
|
+
module Gems
|
|
3
|
+
class Timeline
|
|
4
|
+
|
|
5
|
+
def initialize( versions )
|
|
6
|
+
## sort by version created and name
|
|
7
|
+
@versions = versions.sort do |l,r|
|
|
8
|
+
res = r['created'] <=> l['created']
|
|
9
|
+
res = l['name'] <=> r['name'] if res == 0
|
|
10
|
+
res = r['version'] <=> l['version'] if res == 0
|
|
11
|
+
res
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def save( path )
|
|
17
|
+
buf = build
|
|
18
|
+
write_text( path, buf )
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build
|
|
23
|
+
## step 1 - build document model - versions by year & week
|
|
24
|
+
## and split into new & update
|
|
25
|
+
model = {}
|
|
26
|
+
|
|
27
|
+
@versions.each do |rec|
|
|
28
|
+
|
|
29
|
+
date = Date.strptime( rec['created'], '%Y-%m-%d' )
|
|
30
|
+
year = date.year.to_s
|
|
31
|
+
week = date.strftime('%V') ## note: return zero-padded string e.g. 01, 02, etc.
|
|
32
|
+
|
|
33
|
+
by_year = model[ year ] ||= {}
|
|
34
|
+
by_week = by_year[ week ] ||= { 'new' => [],
|
|
35
|
+
'updated' => [] }
|
|
36
|
+
|
|
37
|
+
if rec['count'].to_i == 1
|
|
38
|
+
by_week[ 'new' ] << rec
|
|
39
|
+
else
|
|
40
|
+
by_week[ 'updated' ] << rec
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
## pp model
|
|
45
|
+
|
|
46
|
+
## step 2 - build document
|
|
47
|
+
|
|
48
|
+
buf = String.new
|
|
49
|
+
buf << "# Timeline \n\n"
|
|
50
|
+
|
|
51
|
+
## add breadcrumps for years
|
|
52
|
+
buf << model.keys.map do |year|
|
|
53
|
+
"[#{year}](##{year})"
|
|
54
|
+
end.join( ' · ' )
|
|
55
|
+
buf << "\n\n"
|
|
56
|
+
|
|
57
|
+
## add new gems by year
|
|
58
|
+
buf << "## New Gems By Year\n\n"
|
|
59
|
+
|
|
60
|
+
model.each do |year, by_week|
|
|
61
|
+
## get totals for year
|
|
62
|
+
gems_new = by_week.values.reduce( [] ) do |acc,gems|
|
|
63
|
+
acc + gems['new']
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
buf << "**#{year}** - "
|
|
67
|
+
|
|
68
|
+
buf << if gems_new.size > 0
|
|
69
|
+
_build_gems_new( gems_new )
|
|
70
|
+
else
|
|
71
|
+
"Ø \n\n"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
model.each do |year, by_week|
|
|
77
|
+
buf << "## #{year}\n\n"
|
|
78
|
+
|
|
79
|
+
by_week.each do |week, gems|
|
|
80
|
+
buf << "**Week #{week}**\n\n"
|
|
81
|
+
|
|
82
|
+
buf << _build_gems_new( gems['new'] )
|
|
83
|
+
buf << _build_gems_updated( gems['updated'] )
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
buf
|
|
88
|
+
end # method build
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _build_gem( gem )
|
|
92
|
+
date = Date.strptime( gem['created'], '%Y-%m-%d' )
|
|
93
|
+
|
|
94
|
+
buf = String.new
|
|
95
|
+
buf << "[**"
|
|
96
|
+
buf << gem['name']
|
|
97
|
+
buf << "**]"
|
|
98
|
+
buf << "("
|
|
99
|
+
buf << "https://rubygems.org/gems/#{gem['name']}/versions/#{gem['version']}"
|
|
100
|
+
buf << " "
|
|
101
|
+
buf << %Q{"update no.#{gem['count']} @ #{date.strftime('%a %d %b %Y')}"}
|
|
102
|
+
buf << ") "
|
|
103
|
+
buf << gem['version']
|
|
104
|
+
buf
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _build_gems_new( gems )
|
|
109
|
+
buf = String.new
|
|
110
|
+
if gems.size > 0
|
|
111
|
+
buf << "**NEW!** - #{gems.size} Gem(s) - "
|
|
112
|
+
buf << gems.map do |gem| _build_gem( gem )
|
|
113
|
+
end.join( ', ' )
|
|
114
|
+
buf << "\n\n"
|
|
115
|
+
end
|
|
116
|
+
buf
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def _build_gems_updated( gems )
|
|
120
|
+
buf = String.new
|
|
121
|
+
if gems.size > 0
|
|
122
|
+
buf << "#{gems.size} Update(s) - "
|
|
123
|
+
buf << gems.map do |gem| _build_gem( gem )
|
|
124
|
+
end.join( ', ' )
|
|
125
|
+
buf << "\n\n"
|
|
126
|
+
end
|
|
127
|
+
buf
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def export( path )
|
|
135
|
+
|
|
136
|
+
headers = ['week',
|
|
137
|
+
'name',
|
|
138
|
+
'version',
|
|
139
|
+
'count',
|
|
140
|
+
'created',
|
|
141
|
+
'downloads']
|
|
142
|
+
recs = []
|
|
143
|
+
@versions.each_with_index do |h,i|
|
|
144
|
+
|
|
145
|
+
date = Date.strptime( h['created'], '%Y-%m-%d' )
|
|
146
|
+
|
|
147
|
+
rec = [
|
|
148
|
+
date.strftime('%Y/%V'),
|
|
149
|
+
h['name'],
|
|
150
|
+
h['version'],
|
|
151
|
+
h['count'],
|
|
152
|
+
date.strftime( '%Y-%m-%d' ), # add (%a) for (Sun), (Mon) or such - why? why not?
|
|
153
|
+
h['downloads'],
|
|
154
|
+
]
|
|
155
|
+
recs << rec
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
write_csv( path, [headers]+recs )
|
|
159
|
+
end
|
|
160
|
+
end ## class Timeline
|
|
161
|
+
end ## module Gems
|
data/lib/gemverse.rb
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'cocos'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## move to cocos - upstream - why? why not?
|
|
5
|
+
def write_csv( path, recs )
|
|
6
|
+
buf = String.new
|
|
7
|
+
buf << recs[0].join( ', ' )
|
|
8
|
+
buf << "\n"
|
|
9
|
+
recs[1..-1].each do |values|
|
|
10
|
+
buf << values.join( ', ' )
|
|
11
|
+
buf << "\n"
|
|
12
|
+
end
|
|
13
|
+
write_text( path, buf )
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
require_relative 'gemverse/api'
|
|
22
|
+
require_relative 'gemverse/gems'
|
|
23
|
+
require_relative 'gemverse/cache'
|
|
24
|
+
|
|
25
|
+
require_relative 'gemverse/timeline' ## timeline report
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
module Gems
|
|
30
|
+
|
|
31
|
+
###
|
|
32
|
+
## "high-level" finders
|
|
33
|
+
|
|
34
|
+
def self.find_by( owner: ) ## todo/check: use
|
|
35
|
+
|
|
36
|
+
rows = API.gems_by( owner )
|
|
37
|
+
# pp data
|
|
38
|
+
puts " #{rows.size} record(s) founds"
|
|
39
|
+
|
|
40
|
+
## write "raw respone" to cache for debugging
|
|
41
|
+
write_json( "./cache/gems_#{owner}.json", rows )
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
recs = []
|
|
46
|
+
rows.each do |row|
|
|
47
|
+
recs << Gem.create( row )
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
## sort by
|
|
51
|
+
## 1) (latest) version created and
|
|
52
|
+
## 2) name
|
|
53
|
+
recs = recs.sort do |l,r|
|
|
54
|
+
res = r.version_created <=> l.version_created
|
|
55
|
+
res = l.name <=> r.name if res == 0
|
|
56
|
+
res
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
GemDataset.new( recs )
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def self.read_csv( path )
|
|
65
|
+
## note: requires Kernel:: otherwise stackoverflow endlessly calling read_csv
|
|
66
|
+
rows = Kernel::read_csv( path )
|
|
67
|
+
puts " #{rows.size} record(s) founds"
|
|
68
|
+
|
|
69
|
+
recs = []
|
|
70
|
+
rows.each do |row|
|
|
71
|
+
kwargs = {
|
|
72
|
+
version: row['version'].empty? ? nil : row['version'],
|
|
73
|
+
version_downloads: row['version_downloads'].empty? ? nil : row['version_downloads'].to_i,
|
|
74
|
+
version_created: row['version_created'].empty? ? nil : Date.strptime( row['version_created'], '%Y-%m-%d' ),
|
|
75
|
+
homepage: row['homepage'].empty? ? nil : row['homepage'],
|
|
76
|
+
runtime: row['dependencies'].empty? ? [] : row['dependencies'].split('|').map {|dep| dep.strip },
|
|
77
|
+
}
|
|
78
|
+
recs << Gem.new( name: row['name'], **kwargs )
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
## sort by
|
|
82
|
+
## 1) (latest) version created and
|
|
83
|
+
## 2) name
|
|
84
|
+
recs = recs.sort do |l,r|
|
|
85
|
+
res = r.version_created <=> l.version_created
|
|
86
|
+
res = l.name <=> r.name if res == 0
|
|
87
|
+
res
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
GemDataset.new( recs )
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
end # module Gems
|
|
95
|
+
|
metadata
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gemverse
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Gerald Bauer
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-01-20 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: cocos
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rdoc
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '4.0'
|
|
34
|
+
- - "<"
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: '7'
|
|
37
|
+
type: :development
|
|
38
|
+
prerelease: false
|
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
40
|
+
requirements:
|
|
41
|
+
- - ">="
|
|
42
|
+
- !ruby/object:Gem::Version
|
|
43
|
+
version: '4.0'
|
|
44
|
+
- - "<"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '7'
|
|
47
|
+
- !ruby/object:Gem::Dependency
|
|
48
|
+
name: hoe
|
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.23'
|
|
54
|
+
type: :development
|
|
55
|
+
prerelease: false
|
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.23'
|
|
61
|
+
description: gemverse gem - gem universe incl. rubygems API V1 wrapper lite; gem version
|
|
62
|
+
cache, gem timeline reports, 'n' more
|
|
63
|
+
email: opensport@googlegroups.com
|
|
64
|
+
executables: []
|
|
65
|
+
extensions: []
|
|
66
|
+
extra_rdoc_files:
|
|
67
|
+
- CHANGELOG.md
|
|
68
|
+
- Manifest.txt
|
|
69
|
+
- README.md
|
|
70
|
+
files:
|
|
71
|
+
- CHANGELOG.md
|
|
72
|
+
- Manifest.txt
|
|
73
|
+
- README.md
|
|
74
|
+
- Rakefile
|
|
75
|
+
- lib/gemverse.rb
|
|
76
|
+
- lib/gemverse/api.rb
|
|
77
|
+
- lib/gemverse/cache.rb
|
|
78
|
+
- lib/gemverse/gems.rb
|
|
79
|
+
- lib/gemverse/timeline.rb
|
|
80
|
+
homepage: https://github.com/rubycocos/git
|
|
81
|
+
licenses:
|
|
82
|
+
- Public Domain
|
|
83
|
+
metadata: {}
|
|
84
|
+
post_install_message:
|
|
85
|
+
rdoc_options:
|
|
86
|
+
- "--main"
|
|
87
|
+
- README.md
|
|
88
|
+
require_paths:
|
|
89
|
+
- lib
|
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: 2.2.2
|
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '0'
|
|
100
|
+
requirements: []
|
|
101
|
+
rubygems_version: 3.3.7
|
|
102
|
+
signing_key:
|
|
103
|
+
specification_version: 4
|
|
104
|
+
summary: gemverse gem - gem universe incl. rubygems API V1 wrapper lite; gem version
|
|
105
|
+
cache, gem timeline reports, 'n' more
|
|
106
|
+
test_files: []
|