Skip to content

Commit

Permalink
Allow commands with spaces to be constructed as new Commands
Browse files Browse the repository at this point in the history
If you were using the `appraisal` executable and passed an argument that
contained spaces, those spaces would not be preserved when Appraisal
went to execute the full command. For instance, this command...

    appraisal 4.2 rspec spec/some/file_spec.rb -e "some example group"

*Should* be expanded as follows:

    BUNDLE_GEMFILE=$PWD/gemfiles/4.2.gemfile rspec spec/some/file_spec.rb -e "some example group"

...but before this commit would be incorrectly expanded as follows:

    BUNDLE_GEMFILE=$PWD/gemfiles/4.2.gemfile rspec spec/some/file_spec.rb -e some example group

Close thoughtbot#86
  • Loading branch information
mcmire authored and Prem Sichanugrist committed Mar 4, 2015
1 parent 1d3885c commit 3bb7825
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 28 deletions.
20 changes: 14 additions & 6 deletions lib/appraisal/appraisal.rb
Expand Up @@ -54,11 +54,18 @@ def write_gemfile
end

def install(job_size = 1)
Command.new(check_command + ' || ' + install_command(job_size)).run
command = [
check_command,
"||",
install_command(job_size)
].flatten.join(" ")

Command.new(command).run
end

def update(gems = [])
Command.new(update_command(gems)).run
command, env = update_command(gems)
Command.new(command, env: env).run
end

def gemfile_path
Expand All @@ -83,17 +90,18 @@ def relativize

def check_command
gemfile_option = "--gemfile='#{gemfile_path}'"
['bundle', 'check', gemfile_option].join(' ')
['bundle', 'check', gemfile_option]
end

def install_command(job_size)
gemfile_option = "--gemfile='#{gemfile_path}'"
['bundle', 'install', gemfile_option, bundle_parallel_option(job_size)].compact.join(' ')
['bundle', 'install', gemfile_option, bundle_parallel_option(job_size)].compact
end

def update_command(gems)
gemfile_config = "BUNDLE_GEMFILE='#{gemfile_path}'"
[gemfile_config, 'bundle', 'update', *gems].compact.join(' ')
env = { "BUNDLE_GEMFILE" => gemfile_path }
command = ['bundle', 'update', *gems].compact
[command, env]
end

def gemfile_root
Expand Down
4 changes: 2 additions & 2 deletions lib/appraisal/cli.rb
Expand Up @@ -86,10 +86,10 @@ def method_missing(name, *args, &block)
matching_appraisal = File.new.appraisals.detect { |appraisal| appraisal.name == name.to_s }

if matching_appraisal
Command.new(args.join(' '), matching_appraisal.gemfile_path).run
Command.new(args, gemfile: matching_appraisal.gemfile_path).run
else
File.each do |appraisal|
Command.new(ARGV.join(' '), appraisal.gemfile_path).run
Command.new(ARGV, gemfile: appraisal.gemfile_path).run
end
end
end
Expand Down
59 changes: 39 additions & 20 deletions lib/appraisal/command.rb
@@ -1,66 +1,85 @@
require "shellwords"

module Appraisal
# Executes commands with a clean environment
class Command
BUNDLER_ENV_VARS = %w(RUBYOPT BUNDLE_PATH BUNDLE_BIN_PATH BUNDLE_GEMFILE).freeze

def self.from_args(gemfile)
ARGV.shift
command = ([$0] + ARGV).join(' ')
new(command, gemfile)
end
attr_reader :command, :env, :gemfile, :original_env

def initialize(command, gemfile = nil)
def initialize(command, options = {})
@gemfile = options[:gemfile]
@env = options.fetch(:env, {})
@command = command_starting_with_bundle(command)
@original_env = {}
@gemfile = gemfile
if command =~ /^(bundle|BUNDLE_GEMFILE)/
@command = command
else
@command = "bundle exec #{command}"
end
end

def run
announce
with_clean_env do
unless Kernel.system(@command)
unless Kernel.system(env, command_as_string)
exit(1)
end
end
end

def exec
announce
with_clean_env { Kernel.exec(@command) }
with_clean_env { Kernel.exec(env, command_as_string) }
end

private

def with_clean_env
unset_bundler_env_vars
ENV['BUNDLE_GEMFILE'] = @gemfile
ENV['BUNDLE_GEMFILE'] = gemfile
ENV['APPRAISAL_INITIALIZED'] = '1'
yield
ensure
restore_env
end

def announce
if @gemfile
puts ">> BUNDLE_GEMFILE=#{@gemfile} #{@command}"
if gemfile
puts ">> BUNDLE_GEMFILE=#{gemfile} #{command_as_string}"
else
puts ">> #{@command}"
puts ">> #{command_as_string}"
end
end

def unset_bundler_env_vars
BUNDLER_ENV_VARS.each do |key|
@original_env[key] = ENV[key]
original_env[key] = ENV[key]
ENV[key] = nil
end
end

def restore_env
@original_env.each { |key, value| ENV[key] = value }
original_env.each { |key, value| ENV[key] = value }
end

def command_starts_with_bundle?(original_command)
if original_command.is_a?(Array)
original_command.first =~ /^bundle/
else
original_command =~ /^bundle/
end
end

def command_starting_with_bundle(original_command)
if command_starts_with_bundle?(original_command)
original_command
else
%w(bundle exec) + original_command
end
end

def command_as_string
if command.is_a?(Array)
Shellwords.join(command)
else
command
end
end
end
end
9 changes: 9 additions & 0 deletions spec/acceptance/cli/run_spec.rb
Expand Up @@ -14,6 +14,7 @@

run 'appraisal install'
write_file 'test.rb', 'puts "Running: #{$dummy_version}"'
write_file 'test with spaces.rb', 'puts "Running: #{$dummy_version}"'
end

it 'sets APPRAISAL_INITIALIZED environment variable' do
Expand Down Expand Up @@ -44,4 +45,12 @@
expect(output).to include 'Running: 1.1.0'
end
end

context 'when one of the arguments contains spaces' do
it 'preserves those spaces' do
command = 'appraisal 1.0.0 ruby -rbundler/setup -rdummy "test with spaces.rb"'
output = run(command)
expect(output).to include 'Running: 1.0.0'
end
end
end

0 comments on commit 3bb7825

Please sign in to comment.