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.
- 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 [](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
|
+

|
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
|