terrapin 0.6.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +6 -0
  4. data/GOALS +8 -0
  5. data/Gemfile +7 -0
  6. data/LICENSE +26 -0
  7. data/NEWS.md +85 -0
  8. data/README.md +215 -0
  9. data/Rakefile +13 -0
  10. data/lib/terrapin.rb +12 -0
  11. data/lib/terrapin/command_line.rb +197 -0
  12. data/lib/terrapin/command_line/multi_pipe.rb +50 -0
  13. data/lib/terrapin/command_line/output.rb +12 -0
  14. data/lib/terrapin/command_line/runners.rb +7 -0
  15. data/lib/terrapin/command_line/runners/backticks_runner.rb +30 -0
  16. data/lib/terrapin/command_line/runners/fake_runner.rb +30 -0
  17. data/lib/terrapin/command_line/runners/popen_runner.rb +29 -0
  18. data/lib/terrapin/command_line/runners/posix_runner.rb +49 -0
  19. data/lib/terrapin/command_line/runners/process_runner.rb +41 -0
  20. data/lib/terrapin/exceptions.rb +8 -0
  21. data/lib/terrapin/os_detector.rb +27 -0
  22. data/lib/terrapin/version.rb +4 -0
  23. data/spec/spec_helper.rb +31 -0
  24. data/spec/support/fake_logger.rb +18 -0
  25. data/spec/support/have_output.rb +11 -0
  26. data/spec/support/nonblocking_examples.rb +14 -0
  27. data/spec/support/stub_os.rb +25 -0
  28. data/spec/support/unsetting_exitstatus.rb +7 -0
  29. data/spec/support/with_exitstatus.rb +12 -0
  30. data/spec/terrapin/command_line/output_spec.rb +14 -0
  31. data/spec/terrapin/command_line/runners/backticks_runner_spec.rb +24 -0
  32. data/spec/terrapin/command_line/runners/fake_runner_spec.rb +22 -0
  33. data/spec/terrapin/command_line/runners/popen_runner_spec.rb +24 -0
  34. data/spec/terrapin/command_line/runners/posix_runner_spec.rb +40 -0
  35. data/spec/terrapin/command_line/runners/process_runner_spec.rb +40 -0
  36. data/spec/terrapin/command_line_spec.rb +195 -0
  37. data/spec/terrapin/errors_spec.rb +62 -0
  38. data/spec/terrapin/os_detector_spec.rb +23 -0
  39. data/spec/terrapin/runners_spec.rb +97 -0
  40. data/terrapin.gemspec +28 -0
  41. metadata +209 -0
@@ -0,0 +1,50 @@
1
+ module Terrapin
2
+ class CommandLine
3
+ class MultiPipe
4
+ def initialize
5
+ @stdout_in, @stdout_out = IO.pipe
6
+ @stderr_in, @stderr_out = IO.pipe
7
+ end
8
+
9
+ def pipe_options
10
+ { out: @stdout_out, err: @stderr_out }
11
+ end
12
+
13
+ def output
14
+ Output.new(@stdout_output, @stderr_output)
15
+ end
16
+
17
+ def read_and_then(&block)
18
+ close_write
19
+ read
20
+ block.call
21
+ close_read
22
+ end
23
+
24
+ private
25
+
26
+ def close_write
27
+ @stdout_out.close
28
+ @stderr_out.close
29
+ end
30
+
31
+ def read
32
+ @stdout_output = read_stream(@stdout_in)
33
+ @stderr_output = read_stream(@stderr_in)
34
+ end
35
+
36
+ def close_read
37
+ @stdout_in.close
38
+ @stderr_in.close
39
+ end
40
+
41
+ def read_stream(io)
42
+ result = ""
43
+ while partial_result = io.read(8192)
44
+ result << partial_result
45
+ end
46
+ result
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,12 @@
1
+ class Terrapin::CommandLine::Output
2
+ def initialize(output = nil, error_output = nil)
3
+ @output = output
4
+ @error_output = error_output
5
+ end
6
+
7
+ attr_reader :output, :error_output
8
+
9
+ def to_s
10
+ output.to_s
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ # coding: UTF-8
2
+
3
+ require 'terrapin/command_line/runners/backticks_runner'
4
+ require 'terrapin/command_line/runners/process_runner'
5
+ require 'terrapin/command_line/runners/posix_runner'
6
+ require 'terrapin/command_line/runners/popen_runner'
7
+ require 'terrapin/command_line/runners/fake_runner'
@@ -0,0 +1,30 @@
1
+ # coding: UTF-8
2
+
3
+ require 'climate_control'
4
+
5
+ module Terrapin
6
+ class CommandLine
7
+ class BackticksRunner
8
+ def self.supported?
9
+ true
10
+ end
11
+
12
+ def supported?
13
+ self.class.supported?
14
+ end
15
+
16
+ def call(command, env = {}, options = {})
17
+ with_modified_environment(env) do
18
+ Output.new(`#{command}`)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def with_modified_environment(env, &block)
25
+ ClimateControl.modify(env, &block)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # coding: UTF-8
2
+
3
+ module Terrapin
4
+ class CommandLine
5
+ class FakeRunner
6
+ def self.supported?
7
+ false
8
+ end
9
+
10
+ def supported?
11
+ self.class.supported?
12
+ end
13
+
14
+ attr_reader :commands
15
+
16
+ def initialize
17
+ @commands = []
18
+ end
19
+
20
+ def call(command, env = {}, options = {})
21
+ commands << [command, env]
22
+ Output.new("")
23
+ end
24
+
25
+ def ran?(predicate_command)
26
+ @commands.any?{|(command, _)| command =~ Regexp.new(predicate_command) }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # coding: UTF-8
2
+
3
+ module Terrapin
4
+ class CommandLine
5
+ class PopenRunner
6
+ def self.supported?
7
+ true
8
+ end
9
+
10
+ def supported?
11
+ self.class.supported?
12
+ end
13
+
14
+ def call(command, env = {}, options = {})
15
+ with_modified_environment(env) do
16
+ IO.popen(command, "r", options) do |pipe|
17
+ Output.new(pipe.read)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def with_modified_environment(env, &block)
25
+ ClimateControl.modify(env, &block)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,49 @@
1
+ # coding: UTF-8
2
+
3
+ module Terrapin
4
+ class CommandLine
5
+ class PosixRunner
6
+ def self.available?
7
+ return @available unless @available.nil?
8
+
9
+ @available = posix_spawn_gem_available?
10
+ end
11
+
12
+ def self.supported?
13
+ available? && !OS.java?
14
+ end
15
+
16
+ def supported?
17
+ self.class.supported?
18
+ end
19
+
20
+ def call(command, env = {}, options = {})
21
+ pipe = MultiPipe.new
22
+ pid = spawn(env, command, options.merge(pipe.pipe_options))
23
+ pipe.read_and_then do
24
+ waitpid(pid)
25
+ end
26
+ pipe.output
27
+ end
28
+
29
+ private
30
+
31
+ def spawn(*args)
32
+ POSIX::Spawn.spawn(*args)
33
+ end
34
+
35
+ def waitpid(pid)
36
+ Process.waitpid(pid)
37
+ end
38
+
39
+ def self.posix_spawn_gem_available?
40
+ require 'posix/spawn'
41
+ true
42
+ rescue LoadError
43
+ false
44
+ end
45
+
46
+ private_class_method :posix_spawn_gem_available?
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ # coding: UTF-8
2
+
3
+ module Terrapin
4
+ class CommandLine
5
+ class ProcessRunner
6
+ def self.available?
7
+ Process.respond_to?(:spawn)
8
+ end
9
+
10
+ def self.supported?
11
+ available? && !OS.java?
12
+ end
13
+
14
+ def supported?
15
+ self.class.supported?
16
+ end
17
+
18
+ def call(command, env = {}, options = {})
19
+ pipe = MultiPipe.new
20
+ pid = spawn(env, command, options.merge(pipe.pipe_options))
21
+ pipe.read_and_then do
22
+ waitpid(pid)
23
+ end
24
+ pipe.output
25
+ end
26
+
27
+ private
28
+
29
+ def spawn(*args)
30
+ Process.spawn(*args)
31
+ end
32
+
33
+ def waitpid(pid)
34
+ Process.waitpid(pid)
35
+ rescue Errno::ECHILD
36
+ # In JRuby, waiting on a finished pid raises.
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,8 @@
1
+ # coding: UTF-8
2
+
3
+ module Terrapin
4
+ class CommandLineError < StandardError; end
5
+ class CommandNotFoundError < CommandLineError; end
6
+ class ExitStatusError < CommandLineError; end
7
+ class InterpolationError < CommandLineError; end
8
+ end
@@ -0,0 +1,27 @@
1
+ # coding: UTF-8
2
+
3
+ module Terrapin
4
+ class OSDetector
5
+ def java?
6
+ arch =~ /java/
7
+ end
8
+
9
+ def unix?
10
+ RbConfig::CONFIG['host_os'] !~ /mswin|mingw/
11
+ end
12
+
13
+ def windows?
14
+ !unix?
15
+ end
16
+
17
+ def path_separator
18
+ File::PATH_SEPARATOR
19
+ end
20
+
21
+ def arch
22
+ RUBY_PLATFORM
23
+ end
24
+ end
25
+
26
+ OS = OSDetector.new
27
+ end
@@ -0,0 +1,4 @@
1
+ # coding: UTF-8
2
+ module Terrapin
3
+ VERSION = "0.6.0.alpha".freeze
4
+ end
@@ -0,0 +1,31 @@
1
+ require 'rspec'
2
+ require 'mocha/api'
3
+ require 'bourne'
4
+ require 'terrapin'
5
+ require 'timeout'
6
+ require 'tempfile'
7
+ require 'pry'
8
+ require 'thread'
9
+ require 'pathname'
10
+
11
+ begin; require 'active_support/logger'; rescue LoadError; end
12
+ begin; require 'active_support/buffered_logger'; rescue LoadError; end
13
+
14
+ Dir[File.dirname(__FILE__) + "/support/**.rb"].each{|support_file| require support_file }
15
+
16
+ RSpec.configure do |config|
17
+ config.mock_with :mocha
18
+ config.include WithExitstatus
19
+ config.include StubOS
20
+ config.include UnsettingExitstatus
21
+ end
22
+
23
+ def best_logger
24
+ if ActiveSupport.const_defined?("Logger")
25
+ ActiveSupport::Logger
26
+ elsif ActiveSupport.const_defined?("BufferedLogger")
27
+ ActiveSupport::BufferedLogger
28
+ else
29
+ Logger
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ class FakeLogger
2
+ def initialize(options = {})
3
+ @tty = options[:tty]
4
+ @entries = []
5
+ end
6
+
7
+ def info(text)
8
+ @entries << text
9
+ end
10
+
11
+ def entries
12
+ @entries
13
+ end
14
+
15
+ def tty?
16
+ @tty
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ RSpec::Matchers.define :have_output do |expected|
2
+ match do |actual|
3
+ actual.output == expected
4
+ end
5
+ end
6
+
7
+ RSpec::Matchers.define :have_error_output do |expected|
8
+ match do |actual|
9
+ actual.error_output == expected
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ shared_examples_for "a command that does not block" do
2
+ it 'does not block if the command outputs a lot of data' do
3
+ garbage_file = Tempfile.new("garbage")
4
+ 10.times{ garbage_file.write("A" * 1024 * 1024) }
5
+
6
+ Timeout.timeout(5) do
7
+ output = subject.call("cat '#{garbage_file.path}'")
8
+ $?.exitstatus.should == 0
9
+ output.output.length.should == 10 * 1024 * 1024
10
+ end
11
+
12
+ garbage_file.close
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ module StubOS
2
+ def on_windows!
3
+ stub_os('mswin')
4
+ Terrapin::OS.stubs(:path_separator).returns(";")
5
+ end
6
+
7
+ def on_unix!
8
+ stub_os('darwin11.0.0')
9
+ Terrapin::OS.stubs(:path_separator).returns(":")
10
+ end
11
+
12
+ def on_mingw!
13
+ stub_os('mingw')
14
+ Terrapin::OS.stubs(:path_separator).returns(";")
15
+ end
16
+
17
+ def on_java!
18
+ Terrapin::OS.stubs(:arch).returns("universal-java1.7")
19
+ end
20
+
21
+ def stub_os(host_string)
22
+ # http://blog.emptyway.com/2009/11/03/proper-way-to-detect-windows-platform-in-ruby/
23
+ RbConfig::CONFIG.stubs(:[]).with('host_os').returns(host_string)
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ module UnsettingExitstatus
2
+ def assuming_no_processes_have_been_run
3
+ class << $?
4
+ undef_method :exitstatus
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ module WithExitstatus
2
+ def with_exitstatus_returning(code)
3
+ saved_exitstatus = $?.respond_to?(:exitstatus) ? $?.exitstatus : 0
4
+ begin
5
+ `ruby -e "exit #{code.to_i}"`
6
+ yield
7
+ ensure
8
+ `ruby -e "exit #{saved_exitstatus.to_i}"`
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Terrapin::CommandLine::Output do
4
+ it 'holds an input and error stream' do
5
+ output = Terrapin::CommandLine::Output.new(:a, :b)
6
+ expect(output.output).to eq :a
7
+ expect(output.error_output).to eq :b
8
+ end
9
+
10
+ it 'calls #to_s on the output when you call #to_s on it' do
11
+ output = Terrapin::CommandLine::Output.new(:a, :b)
12
+ expect(output.to_s).to eq 'a'
13
+ end
14
+ end