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.
@@ -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!