class MyPrecious::RubyGemInfo

Constants

INFO_CACHE_DIR
MIN_RELEASED_DAYS
MIN_STABLE_DAYS
SOURCE_CODE_URI_ENTRIES
VERSIONS_CACHE_DIR

Attributes

current_version[RW]
name[R]
version_reqs[R]

Public Class Methods

accum_gem_lock_info(fpath, **opts) click to toggle source

Build a Hash mapping names of gems used by a project to RubyGemInfo about them

The project at fpath must have a “Gemfile.lock” file as used by the bundler gem.

The accumulated RubyGemInfo instances should have non-nil current_version values and meaningful information in version_reqs, as indicated in the Gemfile.lock for fpath.

# File lib/myprecious/ruby_gems.rb, line 69
def self.accum_gem_lock_info(fpath, **opts)
  {}.tap do |gems|
    each_gem_used(fpath, **opts) do |entry_type, name, verreq|
      g = (gems[name] ||= RubyGemInfo.new(name))
      
      case entry_type
      when :current
        g.current_version = verreq
      when :reqs
        g.version_reqs.concat verreq.as_list
      end
    end
  end
end
col_title(attr) click to toggle source

Get an appropriate, human friendly column title for an attribute

# File lib/myprecious/ruby_gems.rb, line 87
def self.col_title(attr)
  case attr
  when :name then 'Gem'
  else Reporting.common_col_title(attr)
  end
end
each_gem_used(fpath, gemfile: 'Gemfile') { |:current, $~[:gem], version($~[:gemver])| ... } click to toggle source

Enumerate Ruby gems used in a project

The project at fpath must have a “Gemfile.lock” file as used by the bundler gem.

The block receives an Array with three values:

  • Either :current or :reqs, indicating the meaning of the element at index 2,

  • The name of the gem, and

  • Either a Gem::Version (if the index-0 element is :current) or a Gem::Requirement (if the index-0 element is :reqs)

Iterations yielding :current given the version of the gem currently specified by the Gemfile.lock in the project. Iterations yielding :reqs give requirements on the specified gem dictated by other gems used by the project. Each gem name will appear in only one :current iteration, but may occur in multiple :reqs iterations.

# File lib/myprecious/ruby_gems.rb, line 38
def self.each_gem_used(fpath, gemfile: 'Gemfile')
  return enum_for(:each_gem_used, fpath) unless block_given?
  
  gemlock = Pathname(fpath).join(gemfile + '.lock')
  raise "No #{gemfile}.lock in #{fpath}" unless gemlock.exist?
  
  section = nil
  gemlock.each_line do |l|
    break if l.upcase == l && section == 'GEM'
    
    case l
    when /^[A-Z]*\s*$/
      section = l.strip
    when /^\s*(?<gem>\S+)\s+\(\s*(?<gemver>\d[^)]*)\)/
      yield [:current, $~[:gem], Gem::Version.new($~[:gemver])] if section == 'GEM'
    when /^\s*(?<gem>\S+)\s+\(\s*(?<verreqs>[^)]*)\)/
      yield [:reqs, $~[:gem], Gem::Requirement.new(*$~[:verreqs].split(/,\s*/))] if section == 'GEM'
    end
  end
end
new(name) click to toggle source
Calls superclass method
# File lib/myprecious/ruby_gems.rb, line 94
def initialize(name)
  super()
  @name = name
  @version_reqs = Gem::Requirement.new
end

Public Instance Methods

age() click to toggle source

Age in days of the current version

# File lib/myprecious/ruby_gems.rb, line 167
def age
  return @age if defined? @age
  @age = get_age
end
changelog() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 221
def changelog
  changelogs[0]
end
changelogs() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 213
def changelogs
  gv_data = get_gems_versions.sort_by {|v| Gem::Version.new(v['number'])}.reverse
  if current_version
    gv_data = gv_data.take_while {|v| Gem::Version.new(v['number']) > current_version}
  end
  gv_data.collect {|v| (v['metadata'] || {})['changelog_uri']}.compact.uniq
end
cves() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 207
def cves
  CVEs.get_for(name, current_version && current_version.to_s).map do |cve, appl|
    cve
  end
end
homepage_uri() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 106
def homepage_uri
  get_gems_info['homepage_uri']
end
inspect() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 102
def inspect
  %Q{#<#{self.class.name}:#{'%#.8x' % (object_id << 1)} "#{name}">}
end
latest_released() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 159
def latest_released
  return nil if versions_with_release.empty?
  Date.parse(versions_with_release[0][1].to_s).to_s
end
latest_version() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 154
def latest_version
  return nil if versions_with_release.empty?
  versions_with_release[0][0]
end
license() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 172
def license
  gv_data = get_gems_versions
  
  curver_data = gv_data.find {|v| Gem::Version.new(v['number']) == current_version}
  current_licenses = curver_data && curver_data['licenses'] || []
  
  rcmdd_data = gv_data.find {|v| Gem::Version.new(v['number']) == recommended_version}
  rcmdd_licenses = rcmdd_data && rcmdd_data['licenses'] || current_licenses
  
  now_included = rcmdd_licenses - current_licenses
  now_excluded = current_licenses - rcmdd_licenses
  
  case 
  when now_included.empty? && now_excluded.empty?
    LicenseDescription.new(current_licenses.join(' or '))
  when !now_excluded.empty?
    # "#{current_licenses.join(' or ')} (but rec'd ver. doesn't allow #{now_excluded.join(' or ')})"
    LicenseDescription.new(current_licenses.join(' or ')).tap do |desc|
      desc.update_info = "rec'd ver. doesn't allow #{now_excluded.join(' or ')}"
    end
  when current_licenses.empty? && !now_included.empty?
    LicenseDescription.new("Rec'd ver.: #{now_included.join(' or ')}")
  when !now_included.empty?
    # "#{current_licenses.join(' or ')} (or #{now_included.join(' or ')} on upgrade to rec'd ver.)"
    LicenseDescription.new(current_licenses.join(' or ')).tap do |desc|
      desc.update_info = "or #{now_included.join(' or ')} on upgrade to rec'd ver."
    end
  else
    # "#{current_licenses.join(' or ')} (rec'd ver.: #{rcmdd_licenses.join(' or ')})"
    LicenseDescription.new(current_licenses.join(' or ')).tap do |desc|
      desc.update_info = "rec'd ver.: #{rcmdd_licenses.join(' or ')}"
    end
  end
end
obsolescence() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 243
def obsolescence
  cv_major = current_version && current_version.segments[0]
  rv_major = recommended_version && recommended_version.segments[0]
  at_least_moderate = false
  case 
  when cv_major.nil? || rv_major.nil?
    # Can't compare
  when cv_major + 1 < rv_major
    # More than a single major version difference is severe
    return :severe
  when cv_major < rv_major
    # Moderate obsolescence if we're a major version behind
    at_least_moderate = true
  end
  
  days_between = days_between_current_and_recommended
  
  return Reporting.obsolescence_by_age(days_between, at_least_moderate: at_least_moderate)
end
release_history_url() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 225
def release_history_url
  "https://rubygems.org/gems/#{name}/versions" if (
    begin
      get_gems_versions
    rescue StandardError
      nil
    end
  )
end
source_code_uri() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 263
def source_code_uri
  metadata = get_gems_info['metadata']
  SOURCE_CODE_URI_ENTRIES.each {|k| return metadata[k] if metadata[k]}
  return nil
end
versions_with_release() click to toggle source

An Array of Arrays containing version (Gem::Version) and release date (Time)

The returned Array is sorted in order of descending version number.

# File lib/myprecious/ruby_gems.rb, line 115
def versions_with_release
  @versions ||= get_gems_versions.map do |ver|
    [
      Gem::Version.new(ver['number']),
      Time.parse(ver['created_at']).freeze
    ].freeze
  end.reject {|vn, rd| vn.prerelease?}.sort.reverse.freeze
end

Private Instance Methods

get_age() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 280
def get_age
  versions_with_release.each do |ver, released|
    return ((Time.now - released) / ONE_DAY).to_i if ver == current_version
  end
  return nil
end
get_gems_info() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 270
def get_gems_info
  cache = INFO_CACHE_DIR.join("#{name}.json")
  apply_cache(cache) {Gems.info(name)}
end
get_gems_versions() click to toggle source
# File lib/myprecious/ruby_gems.rb, line 275
def get_gems_versions
  cache = VERSIONS_CACHE_DIR.join("#{name}.json")
  apply_cache(cache) {Gems.versions(name)}
end
nonpatch_versegs(v) click to toggle source
# File lib/myprecious/ruby_gems.rb, line 287
def nonpatch_versegs(v)
  v.segments[0..-2]
end