From 50738d298294c3454d0338eba50b2a6d634a0e1d Mon Sep 17 00:00:00 2001 From: Joshua Paling Date: Mon, 17 Oct 2016 18:12:23 +1100 Subject: [PATCH] =?UTF-8?q?introduce=20BundleOutdatedParser=20to=20handle?= =?UTF-8?q?=20bundler=E2=80=99s=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/safe_update.rb | 1 + lib/safe_update/bundle_outdated_parser.rb | 60 +++++++++++++++++++++++ lib/safe_update/outdated_gem.rb | 55 +++++---------------- lib/safe_update/updater.rb | 21 +------- spec/bundle_outdated_parser_spec.rb | 42 ++++++++++++++++ spec/outdated_gem_spec.rb | 36 ++------------ spec/updater_spec.rb | 9 +++- 7 files changed, 128 insertions(+), 96 deletions(-) create mode 100644 lib/safe_update/bundle_outdated_parser.rb create mode 100644 spec/bundle_outdated_parser_spec.rb diff --git a/lib/safe_update.rb b/lib/safe_update.rb index 2a250a1..1e889bf 100644 --- a/lib/safe_update.rb +++ b/lib/safe_update.rb @@ -8,6 +8,7 @@ require 'safe_update/version' require 'safe_update/updater' require 'safe_update/outdated_gem' +require 'safe_update/bundle_outdated_parser' require 'safe_update/git_repo' module SafeUpdate diff --git a/lib/safe_update/bundle_outdated_parser.rb b/lib/safe_update/bundle_outdated_parser.rb new file mode 100644 index 0000000..319bde1 --- /dev/null +++ b/lib/safe_update/bundle_outdated_parser.rb @@ -0,0 +1,60 @@ +# This class runs `bundle outdated` and parses the output +# into a workable data structure in Ruby. +module SafeUpdate + class BundleOutdatedParser + def call + @outdated_gems = [] + # Yes, I know about `bundle outdated --parseable` but old versions + # don't support it and it's really not THAT much more parseable anyway + # and parseable still sometimes has lines that aren't relevant + @output = `bundle outdated` + @output.split(/\n+/).each do |line| + process_single_line(line) + end + return @outdated_gems + end + + private + + def process_single_line(line) + # guard clause for output that's not an outdated gem + return if !line.include?(' (newest') + # get rid of leading *, eg in ' * poltergeist (newest 1.9.0, installed 1.8.1)' + line.strip! + line.gsub!(/^\*/, '') + line.strip! + + @outdated_gems << OutdatedGem.new( + gem_name: gem_name(line), + newest: newest(line), + installed: installed(line), + requested: requested(line) + ) + end + + def gem_name(line) + string_between(line, '', ' (newest') + end + + def newest(line) + string_between(line, ' (newest ', ', installed') + end + + def requested(line) + string_between(line, ', requested ', ')') + end + + def installed(line) + if line.index('requested') + string_between(line, ', installed ', ', requested') + else + string_between(line, ', installed ', ')') + end + end + + # returns the section of string that resides between marker1 and marker2 + def string_between(string, marker1, marker2) + string[/#{Regexp.escape(marker1)}(.*?)#{Regexp.escape(marker2)}/m, 1] + end + end +end diff --git a/lib/safe_update/outdated_gem.rb b/lib/safe_update/outdated_gem.rb index 4c62e8f..31f45ab 100644 --- a/lib/safe_update/outdated_gem.rb +++ b/lib/safe_update/outdated_gem.rb @@ -1,26 +1,22 @@ module SafeUpdate class OutdatedGem - attr_reader :newest, :installed, :requested - - # line is a line from bundle outdated --parseable - # eg. react-rails (newest 1.6.0, installed 1.5.0, requested ~> 1.0) - # or. react-rails (newest 1.6.0, installed 1.5.0) - def initialize(line, git_repo = nil) - @line = line - @git_repo = git_repo || GitRepo.new - if name.to_s.empty? - fail "Unexpected output from `bundle outdated --parseable`: #{@line}" - end + attr_reader :gem_name, :newest, :installed, :requested + def initialize(opts = {}) + @gem_name = opts[:gem_name] + @newest = opts[:newest] + @installed = opts[:installed] + @requested = opts[:requested] + @git_repo = opts[:git_repo] || GitRepo.new end def attempt_update(test_command = nil) puts '-------------' - puts "OUTDATED GEM: #{name}" - puts " Newest: #{newest}. " - puts "Installed: #{installed}." - puts "Running `bundle update #{name}`..." + puts "OUTDATED GEM: #{@gem_name}" + puts " Newest: #{@newest}. " + puts "Installed: #{@installed}." + puts "Running `bundle update #{@gem_name}`..." - `bundle update #{name}` + `bundle update #{@gem_name}` # sometimes the gem may be outdated, but it's matching the # version required by the gemfile, so bundle update does nothing @@ -43,35 +39,10 @@ def attempt_update(test_command = nil) @git_repo.commit_gemfile_lock(commit_message) end - def name - string_between(@line, '', ' (newest') - end - - def newest - string_between(@line, ' (newest ', ', installed') - end - - def requested - string_between(@line, ', requested ', ')') - end - - def installed - if @line.index('requested') - string_between(@line, ', installed ', ', requested') - else - string_between(@line, ', installed ', ')') - end - end - private def commit_message - "update gem: #{name}" - end - - # returns the section of string that resides between marker1 and marker2 - def string_between(string, marker1, marker2) - string[/#{Regexp.escape(marker1)}(.*?)#{Regexp.escape(marker2)}/m, 1] + "update gem: #{@gem_name}" end end end diff --git a/lib/safe_update/updater.rb b/lib/safe_update/updater.rb index dd6fbdc..d81b3cd 100644 --- a/lib/safe_update/updater.rb +++ b/lib/safe_update/updater.rb @@ -29,26 +29,7 @@ def run(push: nil, test_command: nil) private def outdated_gems - return @outdated_gems if @outdated_gems - - @outdated_gems = [] - bundle_outdated_parseable.split(/\n+/).each do |line| - @outdated_gems << OutdatedGem.new(line, @git_repo) - end - return @outdated_gems - end - - def bundle_outdated_parseable - output = `bundle outdated --parseable` - if output.strip == "Unknown switches '--parseable'" - # pre-1.12.0 version of bundler - output = `bundle outdated` - output.gsub!(/(\n|.)*Outdated gems included in the bundle:/, '') - output.gsub!(/ \* /, '') - output.gsub!(/ in group.*/, '') - end - - output.strip + BundleOutdatedParser.new.call end def display_finished_message diff --git a/spec/bundle_outdated_parser_spec.rb b/spec/bundle_outdated_parser_spec.rb new file mode 100644 index 0000000..080ba1f --- /dev/null +++ b/spec/bundle_outdated_parser_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +sample_output = < 3.4) + * poltergeist (newest 1.9.0, installed 1.8.1) + * unf_ext (newest 0.0.7.2, installed 0.0.7.1) +eot + +describe SafeUpdate::GitRepo do + it 'Gets rid of irrelevant lines' do + parser = SafeUpdate::BundleOutdatedParser.new + allow(parser).to( + receive(:`).with('bundle outdated') + .and_return(sample_output) + ) + + outdated_gems = parser.call + # Just check two results... if it works for those two, + # safe to assume it works for the others with slightly different formats + + expect(outdated_gems[0].gem_name).to eq('rails-footnotes') + expect(outdated_gems[0].newest).to eq('4.1.8 4e6f69f') + expect(outdated_gems[0].installed).to eq('4.1.8 a179ce0') + expect(outdated_gems[0].requested).to eq(nil) + + expect(outdated_gems[1].gem_name).to eq('rspec-rails') + expect(outdated_gems[1].newest).to eq('3.4.2') + expect(outdated_gems[1].installed).to eq('3.4.0') + expect(outdated_gems[1].requested).to eq('~> 3.4') + end +end diff --git a/spec/outdated_gem_spec.rb b/spec/outdated_gem_spec.rb index dc7b127..8ad63b8 100644 --- a/spec/outdated_gem_spec.rb +++ b/spec/outdated_gem_spec.rb @@ -1,45 +1,16 @@ require 'spec_helper' describe SafeUpdate::OutdatedGem do - it 'parses gem name correctly' do - line = 'poltergeist (newest 1.9.0, installed 1.8.1)' - the_gem = SafeUpdate::OutdatedGem.new(line) - expect(the_gem.name).to eq('poltergeist') - expect(the_gem.newest).to eq('1.9.0') - expect(the_gem.installed).to eq('1.8.1') - expect(the_gem.requested).to eq(nil) - end - - it 'parses gem name correctly with no requested' do - line = 'rspec-rails (newest 3.4.2, installed 3.4.0, requested ~> 3.4)' - the_gem = SafeUpdate::OutdatedGem.new(line) - expect(the_gem.name).to eq('rspec-rails') - expect(the_gem.newest).to eq('3.4.2') - expect(the_gem.installed).to eq('3.4.0') - expect(the_gem.requested).to eq('~> 3.4') - end - - it 'raises error on unexpected lines' do - # Unknown switches '--parseable' - # is what you'll get for `bundle update --parseable` - # on earlier versions - - expect { SafeUpdate::OutdatedGem.new('bundle update --parseable') } - .to raise_error(RuntimeError) - end - describe '#attempt_update' do it 'does not run tests if not asked' do - line = 'rspec-rails (newest 3.4.2, installed 3.4.0, requested ~> 3.4)' - the_gem = SafeUpdate::OutdatedGem.new(line) + the_gem = SafeUpdate::OutdatedGem.new(gem_name: 'rspec-rails') expect(the_gem).not_to receive(:system).with('rspec') the_gem.attempt_update end it 'runs tests if asked and commits if tests pass' do - line = 'rspec-rails (newest 3.4.2, installed 3.4.0, requested ~> 3.4)' git_repo = SafeUpdate::GitRepo.new - the_gem = SafeUpdate::OutdatedGem.new(line, git_repo) + the_gem = SafeUpdate::OutdatedGem.new(gem_name: 'rspec-rails', git_repo: git_repo) expect(the_gem).to receive(:`).with('bundle update rspec-rails').exactly(1).times expect(the_gem).to receive(:system).with('rspec').exactly(1).times.and_return(true) expect(git_repo).to receive(:commit_gemfile_lock).with('update gem: rspec-rails') @@ -48,9 +19,8 @@ end it 'runs tests if asked and discards changes if tests fail' do - line = 'rspec-rails (newest 3.4.2, installed 3.4.0, requested ~> 3.4)' git_repo = SafeUpdate::GitRepo.new - the_gem = SafeUpdate::OutdatedGem.new(line, git_repo) + the_gem = SafeUpdate::OutdatedGem.new(gem_name: 'rspec-rails', git_repo: git_repo) expect(the_gem).to receive(:`).with('bundle update rspec-rails').exactly(1).times expect(the_gem).to receive(:system).with('rspec').exactly(1).times.and_return(false) expect(git_repo).not_to receive(:commit_gemfile_lock).with('update gem: rspec-rails') diff --git a/spec/updater_spec.rb b/spec/updater_spec.rb index 324e5de..97445e9 100644 --- a/spec/updater_spec.rb +++ b/spec/updater_spec.rb @@ -16,9 +16,16 @@ # but I'm not sure what alternative approach there is, given # all the methods run a bunch of shell commands that we don't # want to run in the tests themselves. - allow(updater).to receive(:bundle_outdated_parseable).and_return("1\n2\n3\n4\n5\n") SafeUpdate::OutdatedGem.any_instance.stub(:initialize).and_return(true) SafeUpdate::OutdatedGem.any_instance.stub(:attempt_update).and_return(true) + + allow(updater).to receive(:outdated_gems).and_return([ + SafeUpdate::OutdatedGem.new(name: '1'), + SafeUpdate::OutdatedGem.new(name: '2'), + SafeUpdate::OutdatedGem.new(name: '3'), + SafeUpdate::OutdatedGem.new(name: '4'), + SafeUpdate::OutdatedGem.new(name: '5') + ]) # expect git push 3 times - twice at lines 2 and 4 (based on telling # it to push every 2 commits), then once after # all lines are finished, at the very end