myprecious 0.0.5 → 0.2.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 +5 -5
- data/bin/myprecious +5 -1
- data/lib/myprecious.rb +619 -95
- data/lib/myprecious/cves.rb +239 -0
- data/lib/myprecious/data_caches.rb +71 -0
- data/lib/myprecious/python_packages.rb +1190 -0
- data/lib/myprecious/ruby_gems.rb +291 -0
- metadata +141 -7
@@ -0,0 +1,291 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'gems'
|
3
|
+
require 'myprecious/cves'
|
4
|
+
require 'myprecious/data_caches'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
module MyPrecious
|
8
|
+
class RubyGemInfo
|
9
|
+
include DataCaching
|
10
|
+
|
11
|
+
MIN_RELEASED_DAYS = 90
|
12
|
+
MIN_STABLE_DAYS = 14
|
13
|
+
|
14
|
+
INFO_CACHE_DIR = MyPrecious.data_cache(DATA_DIR / 'rb-info-cache')
|
15
|
+
VERSIONS_CACHE_DIR = MyPrecious.data_cache(DATA_DIR / 'rb-versions-cache')
|
16
|
+
|
17
|
+
SOURCE_CODE_URI_ENTRIES = %w[github_repo source_code_uri]
|
18
|
+
|
19
|
+
##
|
20
|
+
# Enumerate Ruby gems used in a project
|
21
|
+
#
|
22
|
+
# The project at +fpath+ must have a "Gemfile.lock" file as used by the
|
23
|
+
# +bundler+ gem.
|
24
|
+
#
|
25
|
+
# The block receives an Array with three values:
|
26
|
+
# - Either +:current+ or +:reqs+, indicating the meaning of the element at
|
27
|
+
# index 2,
|
28
|
+
# - The name of the gem, and
|
29
|
+
# - Either a Gem::Version (if the index-0 element is :current) or a
|
30
|
+
# Gem::Requirement (if the index-0 element is :reqs)
|
31
|
+
#
|
32
|
+
# Iterations yielding +:current+ given the version of the gem currently
|
33
|
+
# specified by the Gemfile.lock in the project. Iterations yielding
|
34
|
+
# +:reqs+ give requirements on the specified gem dictated by other gems
|
35
|
+
# used by the project. Each gem name will appear in only one +:current+
|
36
|
+
# iteration, but may occur in multiple +:reqs+ iterations.
|
37
|
+
#
|
38
|
+
def self.each_gem_used(fpath, gemfile: 'Gemfile')
|
39
|
+
return enum_for(:each_gem_used, fpath) unless block_given?
|
40
|
+
|
41
|
+
gemlock = Pathname(fpath).join(gemfile + '.lock')
|
42
|
+
raise "No #{gemfile}.lock in #{fpath}" unless gemlock.exist?
|
43
|
+
|
44
|
+
section = nil
|
45
|
+
gemlock.each_line do |l|
|
46
|
+
break if l.upcase == l && section == 'GEM'
|
47
|
+
|
48
|
+
case l
|
49
|
+
when /^[A-Z]*\s*$/
|
50
|
+
section = l.strip
|
51
|
+
when /^\s*(?<gem>\S+)\s+\(\s*(?<gemver>\d[^)]*)\)/
|
52
|
+
yield [:current, $~[:gem], Gem::Version.new($~[:gemver])] if section == 'GEM'
|
53
|
+
when /^\s*(?<gem>\S+)\s+\(\s*(?<verreqs>[^)]*)\)/
|
54
|
+
yield [:reqs, $~[:gem], Gem::Requirement.new(*$~[:verreqs].split(/,\s*/))] if section == 'GEM'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Build a Hash mapping names of gems used by a project to RubyGemInfo about them
|
61
|
+
#
|
62
|
+
# The project at +fpath+ must have a "Gemfile.lock" file as used by the
|
63
|
+
# +bundler+ gem.
|
64
|
+
#
|
65
|
+
# The accumulated RubyGemInfo instances should have non-+nil+
|
66
|
+
# #current_version values and meaningful information in #version_reqs,
|
67
|
+
# as indicated in the Gemfile.lock for +fpath+.
|
68
|
+
#
|
69
|
+
def self.accum_gem_lock_info(fpath, **opts)
|
70
|
+
{}.tap do |gems|
|
71
|
+
each_gem_used(fpath, **opts) do |entry_type, name, verreq|
|
72
|
+
g = (gems[name] ||= RubyGemInfo.new(name))
|
73
|
+
|
74
|
+
case entry_type
|
75
|
+
when :current
|
76
|
+
g.current_version = verreq
|
77
|
+
when :reqs
|
78
|
+
g.version_reqs.concat verreq.as_list
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Get an appropriate, human friendly column title for an attribute
|
86
|
+
#
|
87
|
+
def self.col_title(attr)
|
88
|
+
case attr
|
89
|
+
when :name then 'Gem'
|
90
|
+
else Reporting.common_col_title(attr)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def initialize(name)
|
95
|
+
super()
|
96
|
+
@name = name
|
97
|
+
@version_reqs = Gem::Requirement.new
|
98
|
+
end
|
99
|
+
attr_reader :name, :version_reqs
|
100
|
+
attr_accessor :current_version
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
%Q{#<#{self.class.name}:#{'%#.8x' % (object_id << 1)} "#{name}">}
|
104
|
+
end
|
105
|
+
|
106
|
+
def homepage_uri
|
107
|
+
get_gems_info['homepage_uri']
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# An Array of Arrays containing version (Gem::Version) and release date (Time)
|
112
|
+
#
|
113
|
+
# The returned Array is sorted in order of descending version number.
|
114
|
+
#
|
115
|
+
def versions_with_release
|
116
|
+
@versions ||= get_gems_versions.map do |ver|
|
117
|
+
[
|
118
|
+
Gem::Version.new(ver['number']),
|
119
|
+
Time.parse(ver['created_at']).freeze
|
120
|
+
].freeze
|
121
|
+
end.reject {|vn, rd| vn.prerelease?}.sort.reverse.freeze
|
122
|
+
end
|
123
|
+
|
124
|
+
##
|
125
|
+
# Version number recommended based on stability criteria
|
126
|
+
#
|
127
|
+
# May return +nil+ if no version meets the established criteria
|
128
|
+
#
|
129
|
+
def recommended_version
|
130
|
+
return nil if versions_with_release.empty?
|
131
|
+
return @recommended_version if defined? @recommended_version
|
132
|
+
|
133
|
+
orig_time_horizon = time_horizon = \
|
134
|
+
Time.now - (MIN_RELEASED_DAYS * ONE_DAY)
|
135
|
+
horizon_versegs = nonpatch_versegs(versions_with_release[0][0])
|
136
|
+
|
137
|
+
versions_with_release.each do |ver, released|
|
138
|
+
next if ver.prerelease?
|
139
|
+
return (@recommended_version = current_version) if current_version && current_version >= ver
|
140
|
+
|
141
|
+
# Reset the time-horizon clock if moving back into previous patch-series
|
142
|
+
if (nonpatch_versegs(ver) <=> horizon_versegs) < 0
|
143
|
+
time_horizon = orig_time_horizon
|
144
|
+
end
|
145
|
+
|
146
|
+
if released < time_horizon && version_reqs.satisfied_by?(ver)
|
147
|
+
return (@recommended_version = ver)
|
148
|
+
end
|
149
|
+
time_horizon = [time_horizon, released - (MIN_STABLE_DAYS * ONE_DAY)].min
|
150
|
+
end
|
151
|
+
return (@recommended_version = nil)
|
152
|
+
end
|
153
|
+
|
154
|
+
def latest_version
|
155
|
+
return nil if versions_with_release.empty?
|
156
|
+
versions_with_release[0][0]
|
157
|
+
end
|
158
|
+
|
159
|
+
def latest_released
|
160
|
+
return nil if versions_with_release.empty?
|
161
|
+
Date.parse(versions_with_release[0][1].to_s).to_s
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Age in days of the current version
|
166
|
+
#
|
167
|
+
def age
|
168
|
+
return @age if defined? @age
|
169
|
+
@age = get_age
|
170
|
+
end
|
171
|
+
|
172
|
+
def license
|
173
|
+
gv_data = get_gems_versions
|
174
|
+
|
175
|
+
curver_data = gv_data.find {|v| Gem::Version.new(v['number']) == current_version}
|
176
|
+
current_licenses = curver_data && curver_data['licenses'] || []
|
177
|
+
|
178
|
+
rcmdd_data = gv_data.find {|v| Gem::Version.new(v['number']) == recommended_version}
|
179
|
+
rcmdd_licenses = rcmdd_data && rcmdd_data['licenses'] || current_licenses
|
180
|
+
|
181
|
+
now_included = rcmdd_licenses - current_licenses
|
182
|
+
now_excluded = current_licenses - rcmdd_licenses
|
183
|
+
|
184
|
+
case
|
185
|
+
when now_included.empty? && now_excluded.empty?
|
186
|
+
LicenseDescription.new(current_licenses.join(' or '))
|
187
|
+
when !now_excluded.empty?
|
188
|
+
# "#{current_licenses.join(' or ')} (but rec'd ver. doesn't allow #{now_excluded.join(' or ')})"
|
189
|
+
LicenseDescription.new(current_licenses.join(' or ')).tap do |desc|
|
190
|
+
desc.update_info = "rec'd ver. doesn't allow #{now_excluded.join(' or ')}"
|
191
|
+
end
|
192
|
+
when current_licenses.empty? && !now_included.empty?
|
193
|
+
LicenseDescription.new("Rec'd ver.: #{now_included.join(' or ')}")
|
194
|
+
when !now_included.empty?
|
195
|
+
# "#{current_licenses.join(' or ')} (or #{now_included.join(' or ')} on upgrade to rec'd ver.)"
|
196
|
+
LicenseDescription.new(current_licenses.join(' or ')).tap do |desc|
|
197
|
+
desc.update_info = "or #{now_included.join(' or ')} on upgrade to rec'd ver."
|
198
|
+
end
|
199
|
+
else
|
200
|
+
# "#{current_licenses.join(' or ')} (rec'd ver.: #{rcmdd_licenses.join(' or ')})"
|
201
|
+
LicenseDescription.new(current_licenses.join(' or ')).tap do |desc|
|
202
|
+
desc.update_info = "rec'd ver.: #{rcmdd_licenses.join(' or ')}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def cves
|
208
|
+
CVEs.get_for(name, current_version && current_version.to_s).map do |cve, appl|
|
209
|
+
cve
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def changelogs
|
214
|
+
gv_data = get_gems_versions.sort_by {|v| Gem::Version.new(v['number'])}.reverse
|
215
|
+
if current_version
|
216
|
+
gv_data = gv_data.take_while {|v| Gem::Version.new(v['number']) > current_version}
|
217
|
+
end
|
218
|
+
gv_data.collect {|v| (v['metadata'] || {})['changelog_uri']}.compact.uniq
|
219
|
+
end
|
220
|
+
|
221
|
+
def changelog
|
222
|
+
changelogs[0]
|
223
|
+
end
|
224
|
+
|
225
|
+
def release_history_url
|
226
|
+
"https://rubygems.org/gems/#{name}/versions" if (
|
227
|
+
begin
|
228
|
+
get_gems_versions
|
229
|
+
rescue StandardError
|
230
|
+
nil
|
231
|
+
end
|
232
|
+
)
|
233
|
+
end
|
234
|
+
|
235
|
+
def days_between_current_and_recommended
|
236
|
+
v, cv_rel = versions_with_release.find {|v, r| v == current_version} || []
|
237
|
+
v, rv_rel = versions_with_release.find {|v, r| v == recommended_version} || []
|
238
|
+
return nil if cv_rel.nil? || rv_rel.nil?
|
239
|
+
|
240
|
+
return ((rv_rel - cv_rel) / ONE_DAY).to_i
|
241
|
+
end
|
242
|
+
|
243
|
+
def obsolescence
|
244
|
+
cv_major = current_version && current_version.segments[0]
|
245
|
+
rv_major = recommended_version && recommended_version.segments[0]
|
246
|
+
at_least_moderate = false
|
247
|
+
case
|
248
|
+
when cv_major.nil? || rv_major.nil?
|
249
|
+
# Can't compare
|
250
|
+
when cv_major + 1 < rv_major
|
251
|
+
# More than a single major version difference is severe
|
252
|
+
return :severe
|
253
|
+
when cv_major < rv_major
|
254
|
+
# Moderate obsolescence if we're a major version behind
|
255
|
+
at_least_moderate = true
|
256
|
+
end
|
257
|
+
|
258
|
+
days_between = days_between_current_and_recommended
|
259
|
+
|
260
|
+
return Reporting.obsolescence_by_age(days_between, at_least_moderate: at_least_moderate)
|
261
|
+
end
|
262
|
+
|
263
|
+
def source_code_uri
|
264
|
+
metadata = get_gems_info['metadata']
|
265
|
+
SOURCE_CODE_URI_ENTRIES.each {|k| return metadata[k] if metadata[k]}
|
266
|
+
return nil
|
267
|
+
end
|
268
|
+
|
269
|
+
private
|
270
|
+
def get_gems_info
|
271
|
+
cache = INFO_CACHE_DIR.join("#{name}.json")
|
272
|
+
apply_cache(cache) {Gems.info(name)}
|
273
|
+
end
|
274
|
+
|
275
|
+
def get_gems_versions
|
276
|
+
cache = VERSIONS_CACHE_DIR.join("#{name}.json")
|
277
|
+
apply_cache(cache) {Gems.versions(name)}
|
278
|
+
end
|
279
|
+
|
280
|
+
def get_age
|
281
|
+
versions_with_release.each do |ver, released|
|
282
|
+
return ((Time.now - released) / ONE_DAY).to_i if ver == current_version
|
283
|
+
end
|
284
|
+
return nil
|
285
|
+
end
|
286
|
+
|
287
|
+
def nonpatch_versegs(v)
|
288
|
+
v.segments[0..-2]
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
metadata
CHANGED
@@ -1,17 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: myprecious
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Balki Kodarapu
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gems
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.0.0
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '1.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.0.0
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: git
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.5.0
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '1.5'
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 1.5.0
|
50
|
+
- - "~>"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '1.5'
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rake-toolkit_program
|
15
55
|
requirement: !ruby/object:Gem::Requirement
|
16
56
|
requirements:
|
17
57
|
- - ">="
|
@@ -25,20 +65,111 @@ dependencies:
|
|
25
65
|
- !ruby/object:Gem::Version
|
26
66
|
version: '0'
|
27
67
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
68
|
+
name: rest-client
|
29
69
|
requirement: !ruby/object:Gem::Requirement
|
30
70
|
requirements:
|
31
71
|
- - ">="
|
32
72
|
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
73
|
+
version: '2.0'
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.0.2
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.0'
|
84
|
+
- - "~>"
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 2.0.2
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: parslet
|
89
|
+
requirement: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '2.0'
|
94
|
+
type: :runtime
|
95
|
+
prerelease: false
|
96
|
+
version_requirements: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '2.0'
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: rubyzip
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '2.3'
|
34
108
|
type: :runtime
|
35
109
|
prerelease: false
|
110
|
+
version_requirements: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - "~>"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '2.3'
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: bundler
|
117
|
+
requirement: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - "~>"
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '1.13'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - "~>"
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: '1.13'
|
129
|
+
- !ruby/object:Gem::Dependency
|
130
|
+
name: pry
|
131
|
+
requirement: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - "~>"
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0.13'
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - "~>"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0.13'
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: rb-readline
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - "~>"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0.5'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - "~>"
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0.5'
|
157
|
+
- !ruby/object:Gem::Dependency
|
158
|
+
name: byebug
|
159
|
+
requirement: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
type: :development
|
165
|
+
prerelease: false
|
36
166
|
version_requirements: !ruby/object:Gem::Requirement
|
37
167
|
requirements:
|
38
168
|
- - ">="
|
39
169
|
- !ruby/object:Gem::Version
|
40
170
|
version: '0'
|
41
|
-
description: A simple, markdown generated with information about your gems
|
171
|
+
description: A simple, markdown generated with information about your gems and python
|
172
|
+
packages
|
42
173
|
email: balki.kodarapu@gmail.com
|
43
174
|
executables:
|
44
175
|
- myprecious
|
@@ -47,6 +178,10 @@ extra_rdoc_files: []
|
|
47
178
|
files:
|
48
179
|
- bin/myprecious
|
49
180
|
- lib/myprecious.rb
|
181
|
+
- lib/myprecious/cves.rb
|
182
|
+
- lib/myprecious/data_caches.rb
|
183
|
+
- lib/myprecious/python_packages.rb
|
184
|
+
- lib/myprecious/ruby_gems.rb
|
50
185
|
homepage: http://rubygems.org/gems/myprecious
|
51
186
|
licenses:
|
52
187
|
- MIT
|
@@ -66,8 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
201
|
- !ruby/object:Gem::Version
|
67
202
|
version: '0'
|
68
203
|
requirements: []
|
69
|
-
|
70
|
-
rubygems_version: 2.4.8
|
204
|
+
rubygems_version: 3.0.3
|
71
205
|
signing_key:
|
72
206
|
specification_version: 4
|
73
207
|
summary: Your precious dependencies!
|