ridiculous/drg

View on GitHub
lib/drg/tasks/active_pinner.rb

Summary

Maintainability
A
0 mins
Test Coverage
module DRG
  module Tasks
    class ActivePinner
      include Log

      attr_reader :gemfile, :type
      attr_writer :versions

      # @param [Symbol] type of pin to perform. Available options are [:available, :minor, :patch]
      def initialize(type = :patch)
        @type = type
        @gemfile = Gemfile.new
        @versions = {}
      end

      def perform(gem_name = nil)
        updated_gems = []
        if gem_name
          updated_gems << update(gem_name)
        else
          Updater.new.perform do |gems|
            load_versions gems
            gems.each do |gem|
              updated_gems << update(gem)
            end
          end
        end
        updated_gems.compact!
        log %Q(Done)
        log %Q(You may want to run: "bundle update #{updated_gems.join ' '}") if updated_gems.any?
        gemfile.write if gemfile.saved_lines.any?
      end

      # @note calls #latest_minor_version and #latest_patch_version
      # @returns [nil|String]
      def update(gem_name)
        spec = ::Bundler.locked_gems.specs.find { |spec| spec.name == gem_name }
        gem = spec && gemfile.find_by_name(spec.name)
        return nil unless gem
        latest_version = public_send("latest_#{type}_version", spec.name, spec.version)
        if latest_version
          log %Q(Updating "#{spec.name}" from #{spec.version.to_s} to #{latest_version})
          gemfile.update gem, latest_version
          spec.name
        else
          log %Q(No newer #{type} versions found for "#{spec.name}"), :grey
        end
      end

      #
      # Private
      #

      def latest_available_version(name, current_version)
        new_versions(name, current_version).first
      end

      # @param [String] name of the gem
      # @param [String] current_version of the gem
      def latest_minor_version(name, current_version)
        new_versions(name, current_version).select { |version|
          segments = version.scan(/\d+/)
          major = segments[0].to_i
          minor = segments[1].to_i
          minor > current_version.segments[1] && major == current_version.segments[0]
        }.first
      end

      # @param [String] name of the gem
      # @param [String] current_version of the gem
      def latest_patch_version(name, current_version)
        new_versions(name, current_version).select { |version|
          segments = version.scan(/\d+/)
          major = segments[0].to_i
          minor = segments[1].to_i
          patch = segments[-1].to_i
          patch > current_version.segments[-1] && minor == current_version.segments[1] && major == current_version.segments[0]
        }.first
      end

      # @param [String] name of the gem
      # @param [String] current_version of the gem
      def new_versions(name, current_version)
        versions(name).select { |version| higher?(version.scan(/\d+/), current_version.segments) }
      end

      # @param [String] name of the gem
      # @return [Array] a list of available versions (e.g. ['1.2.0', '1.1.0'])
      def versions(name)
        @versions[name] ||= begin
          log %Q(Searching for latest #{type} version of "#{name}" ...)
          `gem query -radn ^#{name}$`.scan(/([\d.]+),/).flatten.uniq
        end
      end

      def load_versions(gems)
        load_gem_versions(gems).scan(/^(#{Array(gems).join('|')})\s\(([\d.\s,\w\-]+)\)$/).each do |gem_name, versions|
          @versions[gem_name] = versions.to_s.split(', ').map { |x| x[/([\d.\w\-]+)/, 1] }.compact
        end
      end

      def load_gem_versions(gems)
        log %Q(Searching for latest #{type} version of #{gems.join(', ')} ...)
        `gem query -ra #{gems.join(' ')}`
      end

      # @param [Array] list of a gem version's segments
      # @param [Array] other_list of another gem version's segments
      def higher?(list, other_list)
        higher = false
        equal = false
        sem_ver_size = [list.size, other_list.size].max
        sem_ver_size.times do |i|
          if list[i].to_i >= other_list[i].to_i
            higher = true
            equal = list[i].to_i == other_list[i].to_i
          else
            break
          end
        end
        higher && !equal
      end
    end
  end
end