gemverse 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ ### 0.0.1 / 2023-01-20
2
+
3
+ * Everything is new. First release.
data/Manifest.txt ADDED
@@ -0,0 +1,9 @@
1
+ CHANGELOG.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ lib/gemverse.rb
6
+ lib/gemverse/api.rb
7
+ lib/gemverse/cache.rb
8
+ lib/gemverse/gems.rb
9
+ lib/gemverse/timeline.rb
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
@@ -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: []