Skip to content

Commit

Permalink
introduce BundleOutdatedParser to handle bundler’s output
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuapaling committed Oct 17, 2016
1 parent 52428c7 commit 50738d2
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 96 deletions.
1 change: 1 addition & 0 deletions lib/safe_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions lib/safe_update/bundle_outdated_parser.rb
Original file line number Diff line number Diff line change
@@ -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
55 changes: 13 additions & 42 deletions lib/safe_update/outdated_gem.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
21 changes: 1 addition & 20 deletions lib/safe_update/updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 42 additions & 0 deletions spec/bundle_outdated_parser_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require 'spec_helper'

sample_output = <<eot
The git source `git://github.com/ianfleeton/paypal-express.git` uses the `git` protocol, which transmits data without encryption. Disable this warning with `bundle config git.allow_insecure true`, or switch to the `https` protocol to keep your data secure.
some other example line that should be discarded
Fetching git://github.com/activerecord-hackery/ransack.git
Fetching gem metadata from https://rubygems.org/..........
Fetching version metadata from https://rubygems.org/...
Fetching dependency metadata from https://rubygems.org/..
Resolving dependencies...................................
Outdated gems included in the bundle:
* rails-footnotes (newest 4.1.8 4e6f69f, installed 4.1.8 a179ce0)
* rspec-rails (newest 3.4.2, installed 3.4.0, requested ~> 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
36 changes: 3 additions & 33 deletions spec/outdated_gem_spec.rb
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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')
Expand Down
9 changes: 8 additions & 1 deletion spec/updater_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 50738d2

Please sign in to comment.