terrapin 0.6.0.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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