derelict 0.3.4.travis.108 → 0.3.4.travis.115

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YzY0YmUwMjBkNThkOTFiMGRkOTgxNWY0MzU1MGUzMDUzYzM2YmNkNw==
4
+ NjFhN2Q1ZGVhMThhODA1NjZlMGFjNzI3MWY0Mzg5Njg0M2EwM2QzZQ==
5
5
  data.tar.gz: !binary |-
6
- ZTRlYzI3NTVjN2ZmNDk1OGNkNDQ5ZGI3YmEwNjMxMzFmNmJjYjI5Mg==
6
+ Y2JkYjNiNjRmNGFjMzNmNTM1ODFmMTQ5ODczNWU0YzYwMjI4NmQzOQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- MzRmOGY5ZGIxOTEwNzc4Y2FlNTE4MTEyNDRkNzQyY2MwNmU3N2Q5MWY5ZGFk
10
- MTg1ZDJkNGU4NzE3YzA1ZGZhYmMwNzNhZWYyZWJjNjU1MjBkMjgzZGEzMmFh
11
- ZWU4Zjc4NzgyN2M5MTUzNmRkMTk4Y2JkYjc3OTM4MWJlMzA2YWQ=
9
+ ZWNiOGVlYmQ4YjBkN2ZkOTFhMmJiNTgxNmIyYTc3ZWI3NjlkYjIxZTNhZGMx
10
+ NmUxZDQ5YWNiYmZhNjVlM2NjMTUxNjgwMDFlMjQzNzk5ODFkMzViNWNkNDA4
11
+ ODE3OTljYTkyZDIyMDVjODM1NzhjMDU3OWRiZGM0ZWQ4YWE1YjY=
12
12
  data.tar.gz: !binary |-
13
- ZDQ4MTVmYTZkYjNkZGNhZTIyMjFhODQzMzc1MzIxZGVkOWQxYWNhODRhNmYx
14
- NmIxMTIyODg5MmU3YTZlMjhiZmQzNTVkZTBhNjMxNzEwOTQ3ZTYwZjgxYWRj
15
- Y2FkZTJiZTQ5NWI0MDU2YWIyNzI3YTJhZDk4NTU4NjMzNTQwMjc=
13
+ ZmZmNWI1MzRlNTgzZDZiNmUxZmMwNGFmZDQ2N2ZiY2UwYmUzMGYxZGUzNTkw
14
+ MmQ1NzdjZDVmM2U3NGU2MWQxYjg2NzM2MWE5NDZhZjhjZjZhNTBkNWI5N2Vk
15
+ NjM2ZWU1ODUzMjYyZmM1NGZkYmMyOGZlYjUzZGRjNWM0OGQ5MjM=
data/README.md CHANGED
@@ -64,7 +64,7 @@ instance = Derelict.instance("/path/to/vagrant")
64
64
  instance = Derelict.instance # Defaults to /Applications/Vagrant
65
65
 
66
66
  # Issue commands to the instance directly (not usually necessary)
67
- result = instance.execute('--version') # Shell::Executer object
67
+ result = instance.execute('--version') # Derelict::Executer object
68
68
  print "success" if result.success? # if Vagrant's exit status was 0
69
69
  print result.stdout # "Vagrant 1.3.3\n"
70
70
 
@@ -73,7 +73,7 @@ connection = instance.connect("/path/to/project")
73
73
 
74
74
  # Issue commands to the connection directly (runs from the project dir)
75
75
  result = connection.execute(:up) # runs "vagrant up" in project dir
76
- result.success? # it's a Shell::Executer object again
76
+ result.success? # Derelict::Executer object again
77
77
 
78
78
  # Retrieve a particular VM from a connection (multi-machine support)
79
79
  vm = connection.vm(:web) # "vm" is a Derelict::VirtualMachine
data/derelict.gemspec CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
 
35
35
  spec.add_runtime_dependency "log4r"
36
36
  spec.add_runtime_dependency "memoist"
37
- spec.add_runtime_dependency "shell-executer"
37
+ spec.add_runtime_dependency "open4"
38
38
 
39
39
 
40
40
  version_major = RbConfig::CONFIG["MAJOR"].to_i
@@ -48,7 +48,7 @@ module Derelict
48
48
  #
49
49
  # * subcommand: Vagrant subcommand to run (:up, :status, etc.)
50
50
  # * arguments: Arguments to pass to the subcommand (optional)
51
- # * block: Passed through to Shell.execute (shell-executer)
51
+ # * block: Passed through to Derelict::Executer.execute
52
52
  #
53
53
  # Raises +Derelict::Instance::CommandFailed+ if the command fails.
54
54
  def execute!(subcommand, *arguments, &block)
@@ -0,0 +1,173 @@
1
+ module Derelict
2
+ # Executes an external (shell) command "safely"
3
+ #
4
+ # The safety involved is mainly ensuring that the command is
5
+ # gracefully terminated if this process is about to terminate.
6
+ class Executer
7
+ attr_reader :stdout, :stderr, :success
8
+
9
+ # Executes <tt>command</tt> and returns after execution
10
+ #
11
+ # * command: A string containing the command to run
12
+ # * options: A hash of options, with the following (symbol) keys:
13
+ # * :mode: Controls how the process' output is given to
14
+ # the block, one of :chars (pass each character
15
+ # one by one, retrieved with getc), or :lines
16
+ # (pass only whole lines, retrieved with gets).
17
+ # (optional, defaults to :lines)
18
+ # * :no_buffer: If true, the process' stdout and stderr won't
19
+ # be collected in the stdout and stderr
20
+ # properties, and will only be passed to the
21
+ # block (optional, defaults to false)
22
+ # * block: Gets passed stdout and stderr every time the process
23
+ # outputs to each stream (first parameter is stdout,
24
+ # second parameter is stderr; only one will contain
25
+ # data, the other will be nil)
26
+ def self.execute(command, options = {}, &block)
27
+ self.new(options).execute(command, &block)
28
+ end
29
+
30
+
31
+ # Initializes an Executer instance with particular options
32
+ #
33
+ # * options: A hash of options, with the following (symbol) keys:
34
+ # * :mode: Controls how the process' output is given to
35
+ # the block, one of :chars (pass each character
36
+ # one by one, retrieved with getc), or :lines
37
+ # (pass only whole lines, retrieved with gets).
38
+ # (optional, defaults to :lines)
39
+ # * :no_buffer: If true, the process' stdout and stderr won't
40
+ # be collected in the stdout and stderr
41
+ # properties, and will only be passed to the
42
+ # block (optional, defaults to false)
43
+ def initialize(options = {})
44
+ @options = {:mode => :lines, :no_buffer => false}.merge(options)
45
+
46
+ if @options[:mode] == :chars
47
+ @reader = proc {|s| s.getc }
48
+ else
49
+ @reader = proc {|s| s.gets }
50
+ end
51
+
52
+ @mutex = Mutex.new
53
+ reset
54
+ end
55
+
56
+ # Executes <tt>command</tt> and returns after execution
57
+ #
58
+ # * command: A string containing the command to run
59
+ # * block: Gets passed stdout and stderr every time the process
60
+ # outputs to each stream (first parameter is stdout,
61
+ # second parameter is stderr; only one will contain
62
+ # data, the other will be nil)
63
+ def execute(command, &block)
64
+ reset
65
+ pid, stdin, stdout, stderr = Open4::popen4(command)
66
+
67
+ save_exit_status(pid)
68
+ forward_signals_to(pid) { handle_streams stdout, stderr, &block }
69
+ self
70
+ end
71
+
72
+
73
+ private
74
+ # Clears the variables relating to a particular command execution
75
+ #
76
+ # This is done when first initialising, and just before a command
77
+ # is run, to get rid of the previous command's data.
78
+ def reset
79
+ @stdout = ''
80
+ @stderr = ''
81
+ @success = nil
82
+ end
83
+
84
+ # Waits for the exit status of a process (in a thread) saving it
85
+ #
86
+ # This will set the @status instance variable to true if the exit
87
+ # status was 0, or false if the exit status was anything else.
88
+ def save_exit_status(pid)
89
+ Thread.start do
90
+ @success = nil
91
+ @success = (Process.waitpid2(pid).last.exitstatus == 0)
92
+ end
93
+ end
94
+
95
+ # Forward signals to a process while running the given block
96
+ #
97
+ # * pid: The process ID to forward signals to
98
+ # * signals: The names of the signals to handle (optional,
99
+ # defaults to SIGINT only)
100
+ def forward_signals_to(pid, signals = %w[INT])
101
+ # Set up signal handlers
102
+ signals.each do |signal|
103
+ Signal.trap(signal) { Process.kill signal, pid }
104
+ end
105
+
106
+ # Run the block now that the signals are being forwarded
107
+ yield
108
+
109
+ # Reset signal handlers
110
+ signals.each do |signal|
111
+ Signal.trap signal, "DEFAULT"
112
+ end
113
+ end
114
+
115
+ # Manages reading from the stdout and stderr streams
116
+ #
117
+ # * stdout: The process' stdout stream
118
+ # * stderr: The process' stderr stream
119
+ # * block: The block to pass any read data to (optional)
120
+ def handle_streams(stdout, stderr, &block)
121
+ streams = [stdout, stderr]
122
+ until streams.empty?
123
+ # Find which streams are ready for reading, timeout 0.1s
124
+ selected, = select(streams, nil, nil, 0.1)
125
+
126
+ # Try again if none were ready
127
+ next if selected.nil? or selected.empty?
128
+
129
+ selected.each do |stream|
130
+ if stream.eof?
131
+ streams.delete(stream) unless @success.nil?
132
+ next
133
+ end
134
+
135
+ while data = @reader.call(stream)
136
+ data = ((@options[:mode] == :chars) ? data.chr : data)
137
+ stream_name = (stream == stdout) ? :stdout : :stderr
138
+ output data, stream_name, &block unless block.nil?
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ # Outputs data to the block
145
+ #
146
+ # * data: The data that needs to be passed to the block
147
+ # * stream_name: The stream data came from (:stdout or :stderr)
148
+ # * block: The block to pass the data to
149
+ def output(data, stream_name = :stdout, &block)
150
+ # Pass the output to the block
151
+ if block.arity == 2
152
+ args = [nil, nil]
153
+ if stream_name == :stdout
154
+ args[0] = data
155
+ else
156
+ args[1] = data
157
+ end
158
+ block.call(*args)
159
+ else
160
+ yield data if stream_name == :stdout
161
+ end
162
+
163
+ # Add to the buffers
164
+ unless @options[:no_buffer]
165
+ if stream_name == :stdout
166
+ @stdout += data
167
+ else
168
+ @stderr += data
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -2,11 +2,13 @@ module Derelict
2
2
  class Instance
3
3
  # Represents an invalid instance, which can't be used with Derelict
4
4
  class CommandFailed < Derelict::Exception
5
- # Initializes a new instance of this exception, with a reason
5
+ # Initializes a new instance of this exception, for a command
6
6
  #
7
- # * reason: The result (Shell::Executer) for the command that
8
- # failed (optional, provides extra detail in the
9
- # message)
7
+ # * command: The name of the command which failed (optional,
8
+ # provides extra detail in the message)
9
+ # * result: The result (Derelict::Executer) for the command
10
+ # which failed (optional, provides extra detail in
11
+ # the message)
10
12
  def initialize(command = nil, result = nil)
11
13
  super [default_message, describe(command, result)].join
12
14
  end
@@ -62,24 +62,24 @@ module Derelict
62
62
  # as a hash of options. A list of valid options is
63
63
  # below. Any options provided that aren't in the
64
64
  # list of valid options will get passed through to
65
- # Shell.execute (from the "shell-executer" gem).
65
+ # Derelict::Executer.execute.
66
66
  # Valid option keys:
67
67
  # * sudo: Whether to run the command as root, or not
68
68
  # (defaults to false)
69
- # * block: Passed through to Shell.execute (shell-executer)
69
+ # * block: Passed through to Derelict::Executer.execute
70
70
  def execute(subcommand, *arguments, &block)
71
71
  options = arguments.last.is_a?(Hash) ? arguments.pop : Hash.new
72
72
  command = command(subcommand, *arguments)
73
73
  command = "sudo -- #{command}" if options.delete(:sudo)
74
74
  logger.debug "Executing #{command} using #{description}"
75
- Shell.execute command, options, &block
75
+ Executer.execute command, options, &block
76
76
  end
77
77
 
78
78
  # Executes a Vagrant subcommand, raising an exception on failure
79
79
  #
80
80
  # * subcommand: Vagrant subcommand to run (:up, :status, etc.)
81
81
  # * arguments: Arguments to pass to the subcommand (optional)
82
- # * block: Passed through to Shell.execute (shell-executer)
82
+ # * block: Passed through to Derelict::Executer.execute
83
83
  #
84
84
  # Raises +Derelict::Instance::CommandFailed+ if the command fails.
85
85
  def execute!(subcommand, *arguments, &block)
data/lib/derelict.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  require "derelict/version"
2
2
  require "log4r"
3
3
  require "memoist"
4
+ require "open4"
4
5
  require "set"
5
- require "shell/executer"
6
6
  require "shellwords"
7
7
 
8
8
  Log4r::Logger["root"] # creates the level constants (INFO, etc).
@@ -12,6 +12,7 @@ module Derelict
12
12
  autoload :Box, "derelict/box"
13
13
  autoload :Connection, "derelict/connection"
14
14
  autoload :Exception, "derelict/exception"
15
+ autoload :Executer, "derelict/executer"
15
16
  autoload :Instance, "derelict/instance"
16
17
  autoload :Parser, "derelict/parser"
17
18
  autoload :Plugin, "derelict/plugin"
@@ -0,0 +1,99 @@
1
+ require "spec_helper"
2
+
3
+ describe Derelict::Executer do
4
+ let(:executer) { described_class.new }
5
+ let(:command) { double("command") }
6
+ let(:options) { Hash.new }
7
+ let(:block) { Proc.new do end }
8
+ let(:result) { double("result") }
9
+
10
+ describe ".execute" do
11
+ before do
12
+ expect(described_class).to receive(:new).with(options).and_return(executer)
13
+ expect(executer).to receive(:execute).with(command, &block).and_return(result)
14
+ end
15
+
16
+ subject { described_class.execute command, options, &block }
17
+ it { should be result }
18
+ end
19
+
20
+ describe "#initialize" do
21
+ subject { described_class.new options }
22
+ it { should be_a described_class }
23
+ its(:stdout) { should eq "" }
24
+ its(:stderr) { should eq "" }
25
+ its(:success) { should be_nil }
26
+
27
+ context "with :chars mode specified" do
28
+ let(:options) { {:mode => :chars} }
29
+ it { should be_a described_class }
30
+ end
31
+ end
32
+
33
+ # The way #execute is tested is much slower than a unit test
34
+ # (obviously), but I ran into some difficulty implementing a proper
35
+ # unit test for this method.
36
+ #
37
+ # TODO: rewrite as a unit test
38
+ describe "#execute" do
39
+ subject { executer.execute command, &block }
40
+
41
+ context "without a block" do
42
+ let(:command) { "echo 'test 1'" }
43
+ its(:stdout) { should eq "test 1\n" }
44
+ its(:stderr) { should eq "" }
45
+ its(:success) { should be_true }
46
+
47
+ context "with non-existent command" do
48
+ let(:command) { "not_actually_a_command" }
49
+ it "should raise ENOENT" do
50
+ expect { subject }.to raise_error(Errno::ENOENT)
51
+ end
52
+ end
53
+
54
+ context "with unsuccessful command" do
55
+ let(:command) { "false" }
56
+ its(:success) { should be_false }
57
+ end
58
+ end
59
+
60
+ context "with a block" do
61
+ context "with one argument" do
62
+ let(:command) { "echo 'test 2'" }
63
+ let(:block) do
64
+ @buffer = ""
65
+ Proc.new do |stdout|
66
+ @buffer << stdout
67
+ end
68
+ end
69
+
70
+ its(:stdout) { should eq "test 2\n" }
71
+ its(:stderr) { should eq "" }
72
+ its(:success) { should be_true }
73
+ specify "the block should get called" do
74
+ subject; expect(@buffer).to eq executer.stdout
75
+ end
76
+ end
77
+
78
+ context "with two arguments" do
79
+ let(:command) { "echo 'test 3'; echo 'test 4' 1>&2" }
80
+ let(:block) do
81
+ @stdout = ""; @stderr = ""
82
+ Proc.new do |stdout, stderr|
83
+ @stdout << stdout unless stdout.nil?
84
+ @stderr << stderr unless stderr.nil?
85
+ end
86
+ end
87
+
88
+ its(:stdout) { should eq "test 3\n" }
89
+ its(:stderr) { should eq "test 4\n" }
90
+ its(:success) { should be_true }
91
+ specify "the block should get called" do
92
+ subject
93
+ expect(@stdout).to eq executer.stdout
94
+ expect(@stderr).to eq executer.stderr
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -165,10 +165,10 @@ describe Derelict::Instance do
165
165
  ]}
166
166
  end
167
167
 
168
- context "with mock Shell" do
168
+ context "with mock Executer" do
169
169
  let(:expected_command) { "/foo/bar/bin/vagrant test arg\\ 1" }
170
170
  before do
171
- expect(Shell).to receive(:execute).with(expected_command, options).and_return(result)
171
+ expect(Derelict::Executer).to receive(:execute).with(expected_command, options).and_return(result)
172
172
  end
173
173
 
174
174
  let(:options) { Hash.new }
@@ -207,7 +207,7 @@ describe Derelict::Instance do
207
207
 
208
208
  context "with :sudo option enabled" do
209
209
  let(:options_argument) { {:sudo => true} }
210
- let(:options) { Hash.new } # Don't pass sudo opt to Shell.execute
210
+ let(:options) { Hash.new } # Don't pass sudo opt to Executer.execute
211
211
  let(:expected_command) { "sudo -- /foo/bar/bin/vagrant test arg\\ 1" }
212
212
  subject { instance.execute :test, "arg 1", options_argument }
213
213
  its(:stdout) { should eq "stdout\n" }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: derelict
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4.travis.108
4
+ version: 0.3.4.travis.115
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Feehan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-16 00:00:00.000000000 Z
11
+ date: 2013-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: log4r
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: shell-executer
42
+ name: open4
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ! '>='
@@ -198,6 +198,7 @@ files:
198
198
  - lib/derelict/connection/not_found.rb
199
199
  - lib/derelict/exception.rb
200
200
  - lib/derelict/exception/optional_reason.rb
201
+ - lib/derelict/executer.rb
201
202
  - lib/derelict/instance.rb
202
203
  - lib/derelict/instance/command_failed.rb
203
204
  - lib/derelict/instance/invalid.rb
@@ -234,6 +235,7 @@ files:
234
235
  - spec/derelict/connection_spec.rb
235
236
  - spec/derelict/exception/optional_reason_spec.rb
236
237
  - spec/derelict/exception_spec.rb
238
+ - spec/derelict/executer_spec.rb
237
239
  - spec/derelict/instance/command_failed_spec.rb
238
240
  - spec/derelict/instance/invalid_spec.rb
239
241
  - spec/derelict/instance/missing_binary_spec.rb
@@ -296,6 +298,7 @@ test_files:
296
298
  - spec/derelict/connection_spec.rb
297
299
  - spec/derelict/exception/optional_reason_spec.rb
298
300
  - spec/derelict/exception_spec.rb
301
+ - spec/derelict/executer_spec.rb
299
302
  - spec/derelict/instance/command_failed_spec.rb
300
303
  - spec/derelict/instance/invalid_spec.rb
301
304
  - spec/derelict/instance/missing_binary_spec.rb