terrapin 0.6.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +6 -0
- data/GOALS +8 -0
- data/Gemfile +7 -0
- data/LICENSE +26 -0
- data/NEWS.md +85 -0
- data/README.md +215 -0
- data/Rakefile +13 -0
- data/lib/terrapin.rb +12 -0
- data/lib/terrapin/command_line.rb +197 -0
- data/lib/terrapin/command_line/multi_pipe.rb +50 -0
- data/lib/terrapin/command_line/output.rb +12 -0
- data/lib/terrapin/command_line/runners.rb +7 -0
- data/lib/terrapin/command_line/runners/backticks_runner.rb +30 -0
- data/lib/terrapin/command_line/runners/fake_runner.rb +30 -0
- data/lib/terrapin/command_line/runners/popen_runner.rb +29 -0
- data/lib/terrapin/command_line/runners/posix_runner.rb +49 -0
- data/lib/terrapin/command_line/runners/process_runner.rb +41 -0
- data/lib/terrapin/exceptions.rb +8 -0
- data/lib/terrapin/os_detector.rb +27 -0
- data/lib/terrapin/version.rb +4 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/fake_logger.rb +18 -0
- data/spec/support/have_output.rb +11 -0
- data/spec/support/nonblocking_examples.rb +14 -0
- data/spec/support/stub_os.rb +25 -0
- data/spec/support/unsetting_exitstatus.rb +7 -0
- data/spec/support/with_exitstatus.rb +12 -0
- data/spec/terrapin/command_line/output_spec.rb +14 -0
- data/spec/terrapin/command_line/runners/backticks_runner_spec.rb +24 -0
- data/spec/terrapin/command_line/runners/fake_runner_spec.rb +22 -0
- data/spec/terrapin/command_line/runners/popen_runner_spec.rb +24 -0
- data/spec/terrapin/command_line/runners/posix_runner_spec.rb +40 -0
- data/spec/terrapin/command_line/runners/process_runner_spec.rb +40 -0
- data/spec/terrapin/command_line_spec.rb +195 -0
- data/spec/terrapin/errors_spec.rb +62 -0
- data/spec/terrapin/os_detector_spec.rb +23 -0
- data/spec/terrapin/runners_spec.rb +97 -0
- data/terrapin.gemspec +28 -0
- metadata +209 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2f5aede86af5c29288c8f251af1dbf9bd1a179b90a416dd638ea02c3e29930a3
|
4
|
+
data.tar.gz: 4dd46fea24d0a8a78c0c39959d5c08dcfeec5a4429a1f18acfc87323811c18bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 28e367c3deb7c6b6064a6dd2cfd1600646261302d135805ba47e3fbba18a2ee2256dbb2b9a379378c20bc1a770bcfdfc5b95fe668e5d499d3f2eda2bf1c49677
|
7
|
+
data.tar.gz: 873cb7a8046310942c2533003ae09a865fde8288fe64e97484c69d588d042b818e24ffd3b2e06e38308cffee1de99dbc86490231daf24b1cd8bade289e64f1ea
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/GOALS
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
Terrapin hits 1.0.0 when:
|
2
|
+
|
3
|
+
[*] It can run command lines across all major unix platforms.
|
4
|
+
[*] It can run command lines on Windows.
|
5
|
+
[*] It handles quoting.
|
6
|
+
[*] It takes advantage of OS-specific functionality to be thread-safe,
|
7
|
+
when possible.
|
8
|
+
[ ] It has a consistent and functioning API that pleases us.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
LICENSE
|
3
|
+
|
4
|
+
The MIT License
|
5
|
+
|
6
|
+
Copyright (c) 2011-2014 Jon Yurek and thoughtbot, inc.
|
7
|
+
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
10
|
+
in the Software without restriction, including without limitation the rights
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
13
|
+
furnished to do so, subject to the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be included in
|
16
|
+
all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
24
|
+
THE SOFTWARE.
|
25
|
+
|
26
|
+
|
data/NEWS.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
New for 0.5.7:
|
2
|
+
|
3
|
+
* Feature: Allow collection of both STDOUT and STDERR.
|
4
|
+
* Improvement: Convert arguments to strings when possible
|
5
|
+
|
6
|
+
New for 0.5.6:
|
7
|
+
|
8
|
+
* Bug Fix: Java does not need to run commands with `env`
|
9
|
+
* Bug Fix: Found out we were rescuing the wrong error
|
10
|
+
|
11
|
+
New for 0.5.5:
|
12
|
+
|
13
|
+
* Bug Fix: Posix- and ProcessRunner respect paths *and* are thread safe!
|
14
|
+
* Bug Fix: `exitstatus` should always be set, even if command doesn't run.
|
15
|
+
* Test Fix: Do not try to test Runners if they don't run on this system.
|
16
|
+
* Improvement: Pass the Errno::ENOENT message through to the exception.
|
17
|
+
* Improvement: Improve documentation
|
18
|
+
|
19
|
+
New for 0.5.4:
|
20
|
+
|
21
|
+
* Bug Fix: PosixRunner and ProcessRunner respect supplemental paths now.
|
22
|
+
|
23
|
+
New for 0.5.3:
|
24
|
+
|
25
|
+
* SECURITY: Fix exploitable bug that could allow arbitrary command execution.
|
26
|
+
See CVE-2013-4457 for more details. Thanks to Holger Just for report and fix!
|
27
|
+
* Bug fix: Sub-word interpolations can be confused for the longer version
|
28
|
+
|
29
|
+
New for 0.5.2:
|
30
|
+
|
31
|
+
* Improvement: Close all the IO objects!
|
32
|
+
* Feature: Add an Runner that uses IO.popen, so JRuby can play
|
33
|
+
* Improvement: Officially drop Ruby 1.8 support, add Ruby 2.0 support
|
34
|
+
* Bug fix: Prevent a crash if no command was actually run
|
35
|
+
* Improvement: Add security cautions to the README
|
36
|
+
|
37
|
+
New for 0.5.1:
|
38
|
+
|
39
|
+
* Fixed a bug preventing running on 1.8.7 for no good reason.
|
40
|
+
|
41
|
+
New for 0.5.0:
|
42
|
+
|
43
|
+
* Updated the copyrights to 2013
|
44
|
+
* Added UTF encoding markers on code files to ensure they're interpreted as
|
45
|
+
UTF-8 instead of ASCII.
|
46
|
+
* Swapped the ordering of the PATH and supplemental path. A binary in the
|
47
|
+
supplemental path will take precedence, now.
|
48
|
+
* Errors contain the output of the erroring command, for inspection.
|
49
|
+
* Use climate_control instead for environment management.
|
50
|
+
|
51
|
+
New for 0.4.2:
|
52
|
+
|
53
|
+
* Loggers that don't understand `tty?`, like `ActiveSupport::BufferedLogger`
|
54
|
+
will still work.
|
55
|
+
|
56
|
+
New for 0.4.1:
|
57
|
+
|
58
|
+
* Introduce FakeRunner for testing, so you don't really run commands.
|
59
|
+
* Fix logging: output the actual command, not the un-interpolated pattern.
|
60
|
+
* Prevent color codes from being output if log destination isn't a TTY.
|
61
|
+
|
62
|
+
New for 0.4.0:
|
63
|
+
|
64
|
+
* Moved interpolation to the `run` method, instead of interpolating on `new`.
|
65
|
+
* Remove official support for REE.
|
66
|
+
|
67
|
+
New for 0.3.2:
|
68
|
+
|
69
|
+
* Fix a hang when processes wait for IO.
|
70
|
+
|
71
|
+
New for 0.3.1:
|
72
|
+
|
73
|
+
* Made the `Runner` manually swappable, in case `ProcessRunner` doesn't work
|
74
|
+
for some reason.
|
75
|
+
* Fixed copyright years.
|
76
|
+
|
77
|
+
New for 0.3.0:
|
78
|
+
|
79
|
+
* Support blank arguments.
|
80
|
+
* Add `CommandLine#unix?`.
|
81
|
+
* Add `CommandLine#exit_status`.
|
82
|
+
* Automatically use `POSIX::Spawn` if available.
|
83
|
+
* Add `CommandLine#environment` as a hash of extra `ENV` data..
|
84
|
+
* Add `CommandLine#runner` which produces an object that responds to `#call`.
|
85
|
+
* Fix a race condition but only on Ruby 1.9.
|
data/README.md
ADDED
@@ -0,0 +1,215 @@
|
|
1
|
+
# Terrapin [![Build Status](https://secure.travis-ci.org/thoughtbot/terrapin.png?branch=master)](http://travis-ci.org/thoughtbot/terrapin)
|
2
|
+
|
3
|
+
A small library for doing (command) lines.
|
4
|
+
|
5
|
+
[API reference](http://rubydoc.info/gems/terrapin/)
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
The basic, normal stuff:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
line = Terrapin::CommandLine.new("echo", "hello 'world'")
|
13
|
+
line.command # => "echo hello 'world'"
|
14
|
+
line.run # => "hello world\n"
|
15
|
+
```
|
16
|
+
|
17
|
+
Interpolated arguments:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
line = Terrapin::CommandLine.new("convert", ":in -scale :resolution :out")
|
21
|
+
line.command(in: "omg.jpg",
|
22
|
+
resolution: "32x32",
|
23
|
+
out: "omg_thumb.jpg")
|
24
|
+
# => "convert 'omg.jpg' -scale '32x32' 'omg_thumb.jpg'"
|
25
|
+
```
|
26
|
+
|
27
|
+
It prevents attempts at being bad:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
line = Terrapin::CommandLine.new("cat", ":file")
|
31
|
+
line.command(file: "haha`rm -rf /`.txt") # => "cat 'haha`rm -rf /`.txt'"
|
32
|
+
|
33
|
+
line = Terrapin::CommandLine.new("cat", ":file")
|
34
|
+
line.command(file: "ohyeah?'`rm -rf /`.ha!") # => "cat 'ohyeah?'\\''`rm -rf /`.ha!'"
|
35
|
+
```
|
36
|
+
|
37
|
+
NOTE: It only does that for arguments interpolated via `run`, NOT arguments
|
38
|
+
passed into `new` (see 'Security' below):
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
line = Terrapin::CommandLine.new("echo", "haha`whoami`")
|
42
|
+
line.command # => "echo haha`whoami`"
|
43
|
+
line.run # => "hahawebserver"
|
44
|
+
```
|
45
|
+
|
46
|
+
You can ignore the result:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
line = Terrapin::CommandLine.new("noisy", "--extra-verbose", swallow_stderr: true)
|
50
|
+
line.command # => "noisy --extra-verbose 2>/dev/null"
|
51
|
+
|
52
|
+
# ... and on Windows...
|
53
|
+
line.command # => "noisy --extra-verbose 2>NUL"
|
54
|
+
```
|
55
|
+
|
56
|
+
If your command errors, you get an exception:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
line = Terrapin::CommandLine.new("git", "commit")
|
60
|
+
begin
|
61
|
+
line.run
|
62
|
+
rescue Terrapin::ExitStatusError => e
|
63
|
+
e.message # => "Command 'git commit' returned 1. Expected 0"
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
If your command might return something non-zero, and you expect that, it's cool:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
line = Terrapin::CommandLine.new("/usr/bin/false", "", expected_outcodes: [0, 1])
|
71
|
+
begin
|
72
|
+
line.run
|
73
|
+
rescue Terrapin::ExitStatusError => e
|
74
|
+
# => You never get here!
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
You don't have the command? You get an exception:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
line = Terrapin::CommandLine.new("lolwut")
|
82
|
+
begin
|
83
|
+
line.run
|
84
|
+
rescue Terrapin::CommandNotFoundError => e
|
85
|
+
e # => the command isn't in the $PATH for this process.
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
But don't fear, you can specify where to look for the command:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
Terrapin::CommandLine.path = "/opt/bin"
|
93
|
+
line = Terrapin::CommandLine.new("lolwut")
|
94
|
+
line.command # => "lolwut", but it looks in /opt/bin for it.
|
95
|
+
```
|
96
|
+
|
97
|
+
You can even give it a bunch of places to look:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
FileUtils.rm("/opt/bin/lolwut")
|
101
|
+
File.open('/usr/local/bin/lolwut') {|f| f.write('echo Hello') }
|
102
|
+
Terrapin::CommandLine.path = ["/opt/bin", "/usr/local/bin"]
|
103
|
+
line = Terrapin::CommandLine.new("lolwut")
|
104
|
+
line.run # => prints 'Hello', because it searches the path
|
105
|
+
```
|
106
|
+
|
107
|
+
Or just put it in the command:
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
line = Terrapin::CommandLine.new("/opt/bin/lolwut")
|
111
|
+
line.command # => "/opt/bin/lolwut"
|
112
|
+
```
|
113
|
+
|
114
|
+
You can see what's getting run. The 'Command' part it logs is in green for visibility!
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
line = Terrapin::CommandLine.new("echo", ":var", logger: Logger.new(STDOUT))
|
118
|
+
line.run(var: "LOL!") # => Logs this with #info -> Command :: echo 'LOL!'
|
119
|
+
```
|
120
|
+
|
121
|
+
Or log every command:
|
122
|
+
|
123
|
+
```ruby
|
124
|
+
Terrapin::CommandLine.logger = Logger.new(STDOUT)
|
125
|
+
Terrapin::CommandLine.new("date").run # => Logs this -> Command :: date
|
126
|
+
```
|
127
|
+
|
128
|
+
## Security
|
129
|
+
|
130
|
+
Short version: Only pass user-generated data into the `run` method and NOT
|
131
|
+
`new`.
|
132
|
+
|
133
|
+
As shown in examples above, Terrapin will only shell-escape what is passed in as
|
134
|
+
interpolations to the `run` method. It WILL NOT escape what is passed in to the
|
135
|
+
second argument of `new`. Terrapin assumes that you will not be manually
|
136
|
+
passing user-generated data to that argument and will be using it as a template
|
137
|
+
for your command line's structure.
|
138
|
+
|
139
|
+
## POSIX Spawn
|
140
|
+
|
141
|
+
You can potentially increase performance by installing [the posix-spawn
|
142
|
+
gem](https://rubygems.org/gems/posix-spawn). This gem can keep your
|
143
|
+
application's heap from being copied when forking command line
|
144
|
+
processes. For applications with large heaps the gain can be
|
145
|
+
significant. To include `posix-spawn`, simply add it to your `Gemfile` or,
|
146
|
+
if you don't use bundler, install the gem.
|
147
|
+
|
148
|
+
## Runners
|
149
|
+
|
150
|
+
Terrapin will attempt to choose from among 3 different ways of running commands.
|
151
|
+
The simplest is using backticks, and is the default in 1.8. In Ruby 1.9, it
|
152
|
+
will attempt to use `Process.spawn`. And, as mentioned above, if the
|
153
|
+
`posix-spawn` gem is installed, it will attempt to use that. If for some reason
|
154
|
+
one of the `.spawn` runners don't work for you, you can override them manually
|
155
|
+
by setting a new runner, like so:
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
Terrapin::CommandLine.runner = Terrapin::CommandLine::BackticksRunner.new
|
159
|
+
```
|
160
|
+
|
161
|
+
And if you really want to, you can define your own Runner, though I can't
|
162
|
+
imagine why you would.
|
163
|
+
|
164
|
+
### JRuby issues
|
165
|
+
|
166
|
+
#### Caveat
|
167
|
+
|
168
|
+
If you get `Error::ECHILD` errors and are using JRuby, there is a very good
|
169
|
+
chance that the error is actually in JRuby. This was brought to our attention
|
170
|
+
in https://github.com/thoughtbot/terrapin/issues/24 and probably fixed in
|
171
|
+
http://jira.codehaus.org/browse/JRUBY-6162. You *will* want to use the
|
172
|
+
`BackticksRunner` if you are unable to update JRuby.
|
173
|
+
|
174
|
+
#### Spawn warning
|
175
|
+
|
176
|
+
If you get `unsupported spawn option: out` warning (like in [issue 38](https://github.com/thoughtbot/terrapin/issues/38)),
|
177
|
+
try to use `PopenRunner`:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
Terrapin::CommandLine.runner = Terrapin::CommandLine::PopenRunner.new
|
181
|
+
```
|
182
|
+
|
183
|
+
## Thread Safety
|
184
|
+
|
185
|
+
Terrapin should be thread safe. As discussed [here, in this climate_control
|
186
|
+
thread](https://github.com/thoughtbot/climate_control/pull/11), climate_control,
|
187
|
+
which modifies the environment under which commands are run for the
|
188
|
+
BackticksRunner and PopenRunner, is thread-safe but not reentrant. Please let us
|
189
|
+
know if you find this is ever not the case.
|
190
|
+
|
191
|
+
## Feedback
|
192
|
+
|
193
|
+
*Security* concerns must be privately emailed to
|
194
|
+
[security@thoughtbot.com](security@thoughtbot.com).
|
195
|
+
|
196
|
+
Question? Idea? Problem? Bug? Comment? Concern? Like using question marks?
|
197
|
+
|
198
|
+
[GitHub Issues For All!](https://github.com/thoughtbot/terrapin/issues)
|
199
|
+
|
200
|
+
## Credits
|
201
|
+
|
202
|
+
Thank you to all [the contributors](https://github.com/thoughtbot/terrapin/graphs/contributors)!
|
203
|
+
|
204
|
+
![thoughtbot](http://thoughtbot.com/logo.png)
|
205
|
+
|
206
|
+
Terrapin is maintained and funded by [thoughtbot, inc](http://thoughtbot.com/community)
|
207
|
+
|
208
|
+
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
|
209
|
+
|
210
|
+
## License
|
211
|
+
|
212
|
+
Copyright 2011-2014 Jon Yurek and thoughtbot, inc. This is free software, and
|
213
|
+
may be redistributed under the terms specified in the
|
214
|
+
[LICENSE](https://github.com/thoughtbot/terrapin/blob/master/LICENSE)
|
215
|
+
file.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
desc 'Default: Run specs.'
|
6
|
+
task :default => 'spec:unit'
|
7
|
+
|
8
|
+
namespace :spec do
|
9
|
+
desc "Run unit specs"
|
10
|
+
RSpec::Core::RakeTask.new('unit') do |t|
|
11
|
+
t.pattern = 'spec/terrapin/**/*_spec.rb'
|
12
|
+
end
|
13
|
+
end
|
data/lib/terrapin.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
require 'terrapin/os_detector'
|
5
|
+
require 'terrapin/command_line'
|
6
|
+
require 'terrapin/command_line/output'
|
7
|
+
require 'terrapin/command_line/multi_pipe'
|
8
|
+
require 'terrapin/command_line/runners'
|
9
|
+
require 'terrapin/exceptions'
|
10
|
+
|
11
|
+
module Terrapin
|
12
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Terrapin
|
4
|
+
class CommandLine
|
5
|
+
class << self
|
6
|
+
attr_accessor :logger, :runner
|
7
|
+
|
8
|
+
def path
|
9
|
+
@supplemental_path
|
10
|
+
end
|
11
|
+
|
12
|
+
def path=(supplemental_path)
|
13
|
+
@supplemental_path = Array(supplemental_path).
|
14
|
+
flatten.
|
15
|
+
join(OS.path_separator)
|
16
|
+
end
|
17
|
+
|
18
|
+
def environment
|
19
|
+
@supplemental_environment ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def runner
|
23
|
+
@runner || best_runner
|
24
|
+
end
|
25
|
+
|
26
|
+
def runner_options
|
27
|
+
@default_runner_options ||= {}
|
28
|
+
end
|
29
|
+
|
30
|
+
def fake!
|
31
|
+
@runner = FakeRunner.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def unfake!
|
35
|
+
@runner = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def best_runner
|
41
|
+
[PosixRunner, ProcessRunner, BackticksRunner].detect do |runner|
|
42
|
+
runner.supported?
|
43
|
+
end.new
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@environment = {}
|
48
|
+
|
49
|
+
attr_reader :exit_status, :runner
|
50
|
+
|
51
|
+
def initialize(binary, params = "", options = {})
|
52
|
+
@binary = binary.dup
|
53
|
+
@params = params.dup
|
54
|
+
@options = options.dup
|
55
|
+
@runner = @options.delete(:runner) || self.class.runner
|
56
|
+
@logger = @options.delete(:logger) || self.class.logger
|
57
|
+
@swallow_stderr = @options.delete(:swallow_stderr)
|
58
|
+
@expected_outcodes = @options.delete(:expected_outcodes) || [0]
|
59
|
+
@environment = @options.delete(:environment) || {}
|
60
|
+
@runner_options = @options.delete(:runner_options) || {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def command(interpolations = {})
|
64
|
+
cmd = [path_prefix, @binary, interpolate(@params, interpolations)]
|
65
|
+
cmd << bit_bucket if @swallow_stderr
|
66
|
+
cmd.join(" ").strip
|
67
|
+
end
|
68
|
+
|
69
|
+
def run(interpolations = {})
|
70
|
+
@exit_status = nil
|
71
|
+
begin
|
72
|
+
full_command = command(interpolations)
|
73
|
+
log("#{colored("Command")} :: #{full_command}")
|
74
|
+
@output = execute(full_command)
|
75
|
+
rescue Errno::ENOENT => e
|
76
|
+
raise Terrapin::CommandNotFoundError, e.message
|
77
|
+
ensure
|
78
|
+
@exit_status = $?.respond_to?(:exitstatus) ? $?.exitstatus : 0
|
79
|
+
end
|
80
|
+
|
81
|
+
if @exit_status == 127
|
82
|
+
raise Terrapin::CommandNotFoundError
|
83
|
+
end
|
84
|
+
|
85
|
+
unless @expected_outcodes.include?(@exit_status)
|
86
|
+
message = [
|
87
|
+
"Command '#{full_command}' returned #{@exit_status}. Expected #{@expected_outcodes.join(", ")}",
|
88
|
+
"Here is the command output: STDOUT:\n", command_output,
|
89
|
+
"\nSTDERR:\n", command_error_output
|
90
|
+
].join("\n")
|
91
|
+
raise Terrapin::ExitStatusError, message
|
92
|
+
end
|
93
|
+
command_output
|
94
|
+
end
|
95
|
+
|
96
|
+
def command_output
|
97
|
+
output.output
|
98
|
+
end
|
99
|
+
|
100
|
+
def command_error_output
|
101
|
+
output.error_output
|
102
|
+
end
|
103
|
+
|
104
|
+
def output
|
105
|
+
@output || Output.new
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def colored(text, ansi_color = "\e[32m")
|
111
|
+
if @logger && @logger.respond_to?(:tty?) && @logger.tty?
|
112
|
+
"#{ansi_color}#{text}\e[0m"
|
113
|
+
else
|
114
|
+
text
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def log(text)
|
119
|
+
if @logger
|
120
|
+
@logger.info(text)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def path_prefix
|
125
|
+
if !self.class.path.nil? && !self.class.path.empty?
|
126
|
+
os_path_prefix
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def os_path_prefix
|
131
|
+
if OS.unix?
|
132
|
+
unix_path_prefix
|
133
|
+
else
|
134
|
+
windows_path_prefix
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def unix_path_prefix
|
139
|
+
"PATH=#{self.class.path}#{OS.path_separator}$PATH;"
|
140
|
+
end
|
141
|
+
|
142
|
+
def windows_path_prefix
|
143
|
+
"SET PATH=#{self.class.path}#{OS.path_separator}%PATH% &"
|
144
|
+
end
|
145
|
+
|
146
|
+
def execute(command)
|
147
|
+
runner.call(command, environment, runner_options)
|
148
|
+
end
|
149
|
+
|
150
|
+
def environment
|
151
|
+
self.class.environment.merge(@environment)
|
152
|
+
end
|
153
|
+
|
154
|
+
def runner_options
|
155
|
+
self.class.runner_options.merge(@runner_options)
|
156
|
+
end
|
157
|
+
|
158
|
+
def interpolate(pattern, interpolations)
|
159
|
+
interpolations = stringify_keys(interpolations)
|
160
|
+
pattern.gsub(/:\{?(\w+)\b\}?/) do |match|
|
161
|
+
key = match.tr(":{}", "")
|
162
|
+
if interpolations.key?(key)
|
163
|
+
shell_quote_all_values(interpolations[key])
|
164
|
+
else
|
165
|
+
match
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def stringify_keys(hash)
|
171
|
+
Hash[hash.map{ |k, v| [k.to_s, v] }]
|
172
|
+
end
|
173
|
+
|
174
|
+
def shell_quote_all_values(values)
|
175
|
+
Array(values).map(&method(:shell_quote)).join(" ")
|
176
|
+
end
|
177
|
+
|
178
|
+
def shell_quote(string)
|
179
|
+
return "" if string.nil?
|
180
|
+
string = string.to_s if string.respond_to? :to_s
|
181
|
+
|
182
|
+
if OS.unix?
|
183
|
+
if string.empty?
|
184
|
+
"''"
|
185
|
+
else
|
186
|
+
string.split("'", -1).map{|m| "'#{m}'" }.join("\\'")
|
187
|
+
end
|
188
|
+
else
|
189
|
+
%{"#{string}"}
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def bit_bucket
|
194
|
+
OS.unix? ? "2>/dev/null" : "2>NUL"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|