lib/keep_up/bundle.rb in keep_up-0.6.3 vs lib/keep_up/bundle.rb in keep_up-0.7.0

- old
+ new

@@ -1,132 +1,121 @@ -require 'bundler' +# frozen_string_literal: true + require_relative 'gemfile_filter' require_relative 'gemspec_filter' require_relative 'dependency' -require_relative 'one' module KeepUp # A Gemfile with its current set of locked dependencies. class Bundle - def initialize(definition_builder:) - @definition_builder = definition_builder + OUTDATED_MATCHER = + /([^ ]*) \(newest ([^,]*), installed ([^,]*)(?:, requested (.*))?\)/.freeze + UPDATE_MATCHER = + /(?:Using|Installing|Fetching) ([^ ]*) ([^ ]*)(?: \(was (.*))?\)/.freeze + + def initialize(runner:, local:) + @runner = runner + @local = local end def dependencies - gemspec_dependencies + gemfile_dependencies + transitive_dependencies + @dependencies ||= + begin + command = "bundle outdated --parseable#{' --local' if @local}" + lines = run_filtered command, OUTDATED_MATCHER + lines.map do |name, newest, version, requirement| + requirement_list = requirement&.split(/,\s*/) + requirement_list ||= fetch_gemspec_dependency_requirements(name) + version = version.split(' ').first + newest = newest.split(' ').first + Dependency.new(name: name, + locked_version: version, + newest_version: newest, + requirement_list: requirement_list) + end + end end - def apply_updated_dependency(dependency) - report_intent dependency - update_gemfile_contents(dependency) - update_gemspec_contents(dependency) - result = update_lockfile(dependency) - report_result dependency, result - result - end - def check? - bundler_definition.to_lock == File.read('Gemfile.lock') + _, status = @runner.run2 'bundle check' + status == 0 end - private + def update_gemfile_contents(update) + update = find_specification_update(dependencies, update) + return unless update - attr_reader :definition_builder - - def report_intent(dependency) - print "Updating #{dependency.name}" + update_specification_contents(update, 'Gemfile', GemfileFilter) end - def report_result(dependency, result) - if result - puts " to #{result.version}" - else - puts " to #{dependency.version}" - puts 'Update failed' - end - end + def update_gemspec_contents(update) + return unless gemspec_name - def gemfile_dependencies - raw = if Bundler::VERSION >= '1.15.' - bundler_lockfile.dependencies.values - else - bundler_lockfile.dependencies - end - build_dependencies raw - end + update = find_specification_update(dependencies, update) + return unless update - def gemspec_dependencies - gemspec_source = bundler_lockfile.sources. - find { |it| it.is_a? Bundler::Source::Gemspec } - return [] unless gemspec_source - build_dependencies gemspec_source.gemspec.dependencies + update_specification_contents(update, gemspec_name, GemspecFilter) end - def transitive_dependencies - build_dependencies bundler_lockfile.specs.flat_map(&:dependencies).uniq - end + # Update lockfile and return resulting spec, or false in case of failure + def update_lockfile(update) + update_name = update.name + command = "bundle update#{' --local' if @local} --conservative #{update_name}" + lines = run_filtered command, UPDATE_MATCHER + lines.each do |name, version, old_version| + next unless name == update_name && old_version - def build_dependencies(deps) - deps.map { |dep| build_dependency dep }.compact + current = Gem::Specification.new(name, old_version) + result = Gem::Specification.new(name, version) + return result if result.version > current.version + end + nil end - def build_dependency(dep) - spec = locked_spec dep - return unless spec - Dependency.new(name: dep.name, - requirement_list: dep.requirement.as_list, - locked_version: spec.version) - end + private - def locked_spec(dep) - bundler_lockfile.specs.find { |it| it.name == dep.name } + def gemspec + @gemspec ||= eval File.read(gemspec_name) if gemspec_name end - def bundler_lockfile - @bundler_lockfile ||= bundler_definition.locked_gems + def gemspec_dependencies + @gemspec_dependencies ||= if gemspec + gemspec.dependencies + else + [] + end end - def bundler_definition - @bundler_definition ||= definition_builder.build(false) + def fetch_gemspec_dependency_requirements(name) + dep = gemspec_dependencies.find { |it| it.name == name } + return unless dep + + dep.requirements_list end - def update_gemfile_contents(update) - current_dependency = gemfile_dependencies.find { |it| it.name == update.name } - return unless current_dependency - return if current_dependency.matches_spec?(update) + def find_specification_update(current_dependencies, update) + current_dependency = current_dependencies.find { |it| it.name == update.name } + return if !current_dependency || current_dependency.matches_spec?(update) - update = current_dependency.generalize_specification(update) - - contents = File.read 'Gemfile' - updated_contents = GemfileFilter.apply(contents, update) - File.write 'Gemfile', updated_contents + current_dependency.generalize_specification(update) end - def update_gemspec_contents(update) - current_dependency = gemspec_dependencies.find { |it| it.name == update.name } - return unless current_dependency - return if current_dependency.matches_spec?(update) - - update = current_dependency.generalize_specification(update) - - contents = File.read gemspec_name - updated_contents = GemspecFilter.apply(contents, update) - File.write gemspec_name, updated_contents + def update_specification_contents(update, file, filter) + File.write file, filter.apply(File.read(file), update) end def gemspec_name - @gemspec_name ||= One.fetch(Dir.glob('*.gemspec')) + @gemspec_name ||= Dir.glob('*.gemspec').first end - # Update lockfile and return resulting spec, or false in case of failure - def update_lockfile(update) - Bundler.clear_gemspec_cache - definition = definition_builder.build(gems: [update.name]) - definition.lock('Gemfile.lock') - current = locked_spec(update) - result = definition.specs.find { |it| it.name == update.name } - result if result.version > current.version - rescue Bundler::VersionConflict - false + def run_filtered(command, regexp) + result = @runner.run command + lines = result.split("\n").reject(&:empty?) + lines.map do |line| + matchdata = regexp.match line + next unless matchdata + + matchdata.to_a[1..-1] + end.compact end end end