myprecious 0.0.5 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.5
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: 2017-04-26 00:00:00.000000000 Z
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: date
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
- rubyforge_project:
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!